plantuml_parser/dsl/token/
start_diagram.rs

1use crate::DiagramIdToken;
2use crate::{ParseContainer, ParseResult, wr, wr2};
3use nom::branch::alt;
4use nom::bytes::complete::tag;
5use nom::character::complete::{alpha1, space1};
6use nom::{IResult, Parser};
7
8/// A token sequence that is around the start keyword (`"@startXYZ"`). (like `"@startuml"` or `"@startuml ID"`, `"@startuml(id=ID)"`.)
9///
10/// * `@startuml`
11/// * `@startuml ID`
12/// * `@startuml(id=ID)`
13///
14/// The above `ID` parts is parsed by [`DiagramIdToken`].
15///
16/// # Examples
17///
18/// ```
19/// use plantuml_parser::{StartDiagramToken, ParseContainer};
20///
21/// # fn main() -> anyhow::Result<()> {
22/// let input = "@startuml   "; /// the last "   " is rest parts
23/// let (rest, (raws, token)) = StartDiagramToken::parse(input.into())?;
24/// let combined_raw: ParseContainer = raws.into();
25/// assert_eq!(rest, "   ");
26/// assert_eq!(combined_raw, "@startuml");
27/// assert_eq!(token.diagram_kind(), "uml");
28/// assert_eq!(token.id(), None);
29///
30/// let input = "@startXYZ diagram_0  rest";
31/// let (rest, (raws, token)) = StartDiagramToken::parse(input.into())?;
32/// let combined_raw: ParseContainer = raws.into();
33/// assert_eq!(rest, "  rest");
34/// assert_eq!(combined_raw, "@startXYZ diagram_0");
35/// assert_eq!(token.diagram_kind(), "XYZ");
36/// assert_eq!(token.id(), Some("diagram_0"));
37///
38/// let input = "@startsalt(id=diagram_1) rest";
39/// let (rest, (raws, token)) = StartDiagramToken::parse(input.into())?;
40/// let combined_raw: ParseContainer = raws.into();
41/// assert_eq!(rest, " rest");
42/// assert_eq!(combined_raw, "@startsalt(id=diagram_1)");
43/// assert_eq!(token.diagram_kind(), "salt");
44/// assert_eq!(token.id(), Some("diagram_1"));
45/// # Ok(())
46/// # }
47/// ```
48#[derive(Clone, Debug)]
49pub struct StartDiagramToken {
50    diagram_kind: ParseContainer,
51    id: Option<DiagramIdToken>,
52}
53
54impl StartDiagramToken {
55    /// Tries to parse [`StartDiagramToken`]. (e.g. `"@startuml"`, `"@startuml ID"`, `"@startuml(id=ID)"`.)
56    pub fn parse(input: ParseContainer) -> ParseResult<Self> {
57        let (rest, (start, diagram_kind, (id_raw, id))) =
58            (wr!(tag("@start")), wr!(alpha1), parse_id_part).parse(input)?;
59
60        let ret0 = ParseContainer::from(vec![start, diagram_kind.clone(), id_raw]);
61        let ret1 = Self { diagram_kind, id };
62
63        Ok((rest, (ret0, ret1)))
64    }
65
66    /// Returns the kind of diagrams. (e.g. `XYZ` in `@startXYZ`.)
67    pub fn diagram_kind(&self) -> &str {
68        self.diagram_kind.as_str()
69    }
70
71    /// Returns the ID of the diagram.
72    pub fn id(&self) -> Option<&str> {
73        self.id.as_ref().map(|x| x.id())
74    }
75}
76
77type ParsedResult = IResult<ParseContainer, (ParseContainer, Option<DiagramIdToken>)>;
78
79/// parse like `" ID"` or `"(id=ID)"`, `""`
80fn parse_id_part(input: ParseContainer) -> ParsedResult {
81    alt((parse_spaced_id, parse_parened_id, parse_none)).parse(input)
82}
83
84/// parse like `"@startuml ID"`
85fn parse_spaced_id(input: ParseContainer) -> ParsedResult {
86    let (input, (space, (id_raw, id))) = (wr!(space1), wr2!(DiagramIdToken::parse)).parse(input)?;
87
88    let parsed = Vec::from([space, id_raw]);
89    let parsed = ParseContainer::from(parsed);
90    Ok((input, (parsed, Some(id))))
91}
92
93/// parse like `"@startuml(id=ID)"`
94fn parse_parened_id(input: ParseContainer) -> ParsedResult {
95    let (input, (open, (id_raw, id), close)) =
96        (wr!(tag("(id=")), wr2!(DiagramIdToken::parse), wr!(tag(")"))).parse(input)?;
97
98    let parsed = Vec::from([open, id_raw, close]);
99    let parsed = ParseContainer::from(parsed);
100    Ok((input, (parsed, Some(id))))
101}
102
103/// parse like `"@startuml"`
104fn parse_none(input: ParseContainer) -> ParsedResult {
105    let (input, parsed) = input.split_at(0);
106    Ok((input, (parsed, None)))
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_parse() -> anyhow::Result<()> {
115        let testdata = "@startuml";
116        let (rest, (raws, StartDiagramToken { diagram_kind, id })) =
117            StartDiagramToken::parse(testdata.into())?;
118        assert_eq!(rest, "");
119        assert_eq!(raws, testdata);
120        assert_eq!(diagram_kind, "uml");
121        assert!(id.is_none());
122
123        let testdata = "@startjson \t\n";
124        let (rest, (parsed, StartDiagramToken { diagram_kind, id })) =
125            StartDiagramToken::parse(testdata.into())?;
126        assert_eq!(rest, " \t\n");
127        assert_eq!(parsed, "@startjson");
128        assert_eq!(format!("{parsed}{rest}"), testdata);
129        assert_eq!(diagram_kind, "json");
130        assert!(id.is_none());
131
132        let testdata = "@startuml \t id aaa\n";
133        let (rest, (parsed, StartDiagramToken { diagram_kind, id })) =
134            StartDiagramToken::parse(testdata.into())?;
135        assert_eq!(rest, " aaa\n");
136        assert_eq!(parsed, "@startuml \t id");
137        assert_eq!(format!("{parsed}{rest}"), testdata);
138        assert_eq!(diagram_kind, "uml");
139        assert_eq!(id.as_ref().map(|x| x.id()), Some("id"));
140
141        let testdata = "@startuml(id=foo_BAR) bbb\n";
142        let (rest, (parsed, StartDiagramToken { diagram_kind, id })) =
143            StartDiagramToken::parse(testdata.into())?;
144        assert_eq!(rest, " bbb\n");
145        assert_eq!(parsed, "@startuml(id=foo_BAR)");
146        assert_eq!(format!("{parsed}{rest}"), testdata);
147        assert_eq!(diagram_kind, "uml");
148        assert_eq!(id.as_ref().map(|x| x.id()), Some("foo_BAR"));
149
150        Ok(())
151    }
152
153    #[test]
154    fn test_parse_id_part() -> anyhow::Result<()> {
155        let testdata = "";
156        let (rest, (parsed, id)) = parse_id_part(testdata.into())?;
157        assert_eq!(rest, "");
158        assert_eq!(parsed, testdata);
159        assert!(id.is_none());
160
161        let testdata = " aaa  ";
162        let (rest, (parsed, id)) = parse_id_part(testdata.into())?;
163        assert_eq!(rest, "  ");
164        assert_eq!(parsed, " aaa");
165        assert_eq!(format!("{parsed}{rest}"), testdata);
166        assert_eq!(id.as_ref().map(|x| x.id()), Some("aaa"));
167
168        let testdata = "(id=FOO_bar) \txxx";
169        let (rest, (parsed, id)) = parse_id_part(testdata.into())?;
170        assert_eq!(rest, " \txxx");
171        assert_eq!(parsed, "(id=FOO_bar)");
172        assert_eq!(format!("{parsed}{rest}"), testdata);
173        assert_eq!(id.as_ref().map(|x| x.id()), Some("FOO_bar"));
174
175        Ok(())
176    }
177
178    #[test]
179    fn test_parse_spaced_id() -> anyhow::Result<()> {
180        let testdata = " aaa  ";
181        let (rest, (parsed, id)) = parse_spaced_id(testdata.into())?;
182        assert_eq!(rest, "  ");
183        assert_eq!(parsed, " aaa");
184        assert_eq!(format!("{parsed}{rest}"), testdata);
185        assert_eq!(id.as_ref().map(|x| x.id()), Some("aaa"));
186
187        Ok(())
188    }
189
190    #[test]
191    fn test_parse_parened_id() -> anyhow::Result<()> {
192        let testdata = "(id=FOO_bar) \txxx";
193        let (rest, (parsed, id)) = parse_parened_id(testdata.into())?;
194        assert_eq!(rest, " \txxx");
195        assert_eq!(parsed, "(id=FOO_bar)");
196        assert_eq!(format!("{parsed}{rest}"), testdata);
197        assert_eq!(id.as_ref().map(|x| x.id()), Some("FOO_bar"));
198
199        Ok(())
200    }
201
202    #[test]
203    fn test_parse_none() -> anyhow::Result<()> {
204        let testdata = "  ";
205        let (rest, (parsed, id)) = parse_none(testdata.into())?;
206        assert_eq!(rest, "  ");
207        assert_eq!(parsed, "");
208        assert_eq!(format!("{parsed}{rest}"), testdata);
209        assert!(id.is_none());
210
211        Ok(())
212    }
213}