plantuml_parser/dsl/
line.rs

1mod block_comment_close;
2mod block_comment_open;
3mod empty;
4mod end;
5mod footer;
6mod header;
7mod include;
8mod shared;
9mod start;
10mod title;
11
12pub use block_comment_close::BlockCommentCloseLine;
13pub use block_comment_open::BlockCommentOpenLine;
14pub use empty::EmptyLine;
15pub use end::EndLine;
16pub use footer::FooterLine;
17pub use header::HeaderLine;
18pub use include::IncludeLine;
19pub use start::StartLine;
20pub use title::TitleLine;
21
22use crate::{ParseContainer, ParseResult, wr};
23use nom::branch::alt;
24use nom::bytes::complete::take_till;
25use nom::character::complete::line_ending;
26use nom::combinator::eof;
27use nom::{IResult, Parser};
28use shared::LineWithComment;
29
30/// A line of PlantUML
31///
32/// # Examples
33///
34/// ```
35/// use plantuml_parser::PlantUmlLine;
36///
37/// # fn main() -> anyhow::Result<()> {
38/// // StartLine
39/// let input = "@startuml\n";
40/// let (rest, (_, parsed)) = PlantUmlLine::parse(input.into())?;
41/// assert_eq!(rest, "");
42/// assert_eq!(parsed.raw_str(), input);
43/// assert!(parsed.start().is_some());
44/// assert!(parsed.diagram_kind().is_some()); // Returns only `StartLine`
45///
46/// // EndLine
47/// let input = "@enduml\n";
48/// let (rest, (_, parsed)) = PlantUmlLine::parse(input.into())?;
49/// assert_eq!(rest, "");
50/// assert_eq!(parsed.raw_str(), input);
51/// assert!(parsed.end().is_some());
52///
53/// // IncludeLine
54/// let input = "!include foo.puml \n";
55/// let (rest, (_, parsed)) = PlantUmlLine::parse(input.into())?;
56/// assert_eq!(rest, "");
57/// assert_eq!(parsed.raw_str(), input);
58/// assert!(parsed.include().is_some());
59///
60/// // TitleLine
61/// let input = " title EXAMPLE TITLE\n";
62/// let (rest, (_, parsed)) = PlantUmlLine::parse(input.into())?;
63/// assert_eq!(rest, "");
64/// assert_eq!(parsed.raw_str(), input);
65/// assert!(parsed.title().is_some());
66///
67/// // HeaderLine
68/// let input = " header EXAMPLE HEADER\n";
69/// let (rest, (_, parsed)) = PlantUmlLine::parse(input.into())?;
70/// assert_eq!(rest, "");
71/// assert_eq!(parsed.raw_str(), input);
72/// assert!(parsed.header().is_some());
73///
74/// // FooterLine
75/// let input = " footer EXAMPLE FOOTER\n";
76/// let (rest, (_, parsed)) = PlantUmlLine::parse(input.into())?;
77/// assert_eq!(rest, "");
78/// assert_eq!(parsed.raw_str(), input);
79/// assert!(parsed.footer().is_some());
80///
81/// // EmptyLine
82/// let input = "  ' comment \n";
83/// let (rest, (_, parsed)) = PlantUmlLine::parse(input.into())?;
84/// assert_eq!(rest, "");
85/// assert_eq!(parsed.raw_str(), input);
86/// assert!(parsed.empty().is_some());
87/// # Ok(())
88/// # }
89/// ```
90#[derive(Clone, Debug)]
91pub struct PlantUmlLine {
92    content: PlantUmlLineKind,
93    raw: ParseContainer,
94}
95
96/// A kind of PlantUML lines to be handled by `plantuml-parser`. A line that cannot be handled is set to [`Others`][`PlantUmlLineKind::Others`].
97#[derive(Clone, Debug)]
98pub enum PlantUmlLineKind {
99    /// `@start[a-z]*(id=MY_OWN_ID)` | `@start[a-z]* MY_OWN_ID` (e.g. `@startuml`)
100    Start(StartLine),
101
102    /// `@end[a-z]*` (e.g. `@enduml`)
103    End(EndLine),
104
105    /// * `"/' begin\n"`
106    BlockCommentOpen(BlockCommentOpenLine),
107
108    /// * `"end '/\n"`
109    BlockCommentClose(BlockCommentCloseLine),
110
111    /// `!include <filepath>` | `!include <filepath>!<id>` (e.g. `!include foo.puml!1`)
112    Include(IncludeLine),
113
114    /// `title <text>` (e.g. `title TITLE`)
115    Title(TitleLine),
116
117    /// `header <text>` (e.g. `header HEADER`)
118    Header(HeaderLine),
119
120    /// `footer <text>` (e.g. `footer FOOTER`)
121    Footer(FooterLine),
122
123    /// empty line
124    Empty,
125
126    /// in comment
127    InComment(Box<PlantUmlLineKind>),
128
129    /// others
130    Others,
131}
132
133impl PlantUmlLine {
134    /// Tries to parse [`PlantUmlLine`].
135    pub fn parse(input: ParseContainer) -> ParseResult<Self> {
136        let (rest, (raw, content)) = PlantUmlLineKind::parse(input.clone())?;
137        let ret = (raw.clone(), Self { content, raw });
138        Ok((rest, ret))
139    }
140
141    /// Returns the strings.
142    pub fn raw_str(&self) -> &str {
143        self.raw.as_str()
144    }
145
146    /// Returns the [`PlantUmlLineKind`].
147    pub fn kind(&self) -> &PlantUmlLineKind {
148        &self.content
149    }
150
151    /// Returns the [`StartLine`] if it is [`StartLine`].
152    pub fn start(&self) -> Option<&StartLine> {
153        if let PlantUmlLineKind::Start(x) = &self.content {
154            Some(x)
155        } else {
156            None
157        }
158    }
159
160    /// Returns the [`EndLine`] if it is [`EndLine`].
161    pub fn end(&self) -> Option<&EndLine> {
162        if let PlantUmlLineKind::End(x) = &self.content {
163            Some(x)
164        } else {
165            None
166        }
167    }
168
169    /// Returns the [`BlockCommentOpenLine`] if it is [`BlockCommentOpenLine`].
170    pub fn block_comment_open(&self) -> Option<&BlockCommentOpenLine> {
171        if let PlantUmlLineKind::BlockCommentOpen(x) = &self.content {
172            Some(x)
173        } else {
174            None
175        }
176    }
177
178    /// Returns the [`BlockCommentCloseLine`] if it is [`BlockCommentCloseLine`].
179    pub fn block_comment_close(&self) -> Option<&BlockCommentCloseLine> {
180        if let PlantUmlLineKind::BlockCommentClose(x) = &self.content {
181            Some(x)
182        } else {
183            None
184        }
185    }
186
187    /// Returns the [`IncludeLine`] if it is [`IncludeLine`].
188    pub fn include(&self) -> Option<&IncludeLine> {
189        if let PlantUmlLineKind::Include(x) = &self.content {
190            Some(x)
191        } else {
192            None
193        }
194    }
195
196    /// Returns the [`TitleLine`] if it is [`TitleLine`].
197    pub fn title(&self) -> Option<&TitleLine> {
198        if let PlantUmlLineKind::Title(x) = &self.content {
199            Some(x)
200        } else {
201            None
202        }
203    }
204
205    /// Returns the [`HeaderLine`] if it is [`HeaderLine`].
206    pub fn header(&self) -> Option<&HeaderLine> {
207        if let PlantUmlLineKind::Header(x) = &self.content {
208            Some(x)
209        } else {
210            None
211        }
212    }
213
214    /// Returns the [`FooterLine`] if it is [`FooterLine`].
215    pub fn footer(&self) -> Option<&FooterLine> {
216        if let PlantUmlLineKind::Footer(x) = &self.content {
217            Some(x)
218        } else {
219            None
220        }
221    }
222
223    /// Returns the kind of the diagram if it is [`StartLine`].
224    pub fn diagram_kind(&self) -> Option<&str> {
225        self.start().map(|x| x.diagram_kind())
226    }
227
228    /// Returns `Some(())` if it is [`EmptyLine`].
229    pub fn empty(&self) -> Option<()> {
230        if let PlantUmlLineKind::Empty = &self.content {
231            Some(())
232        } else {
233            None
234        }
235    }
236
237    /// Convert into [`PlantUmlLineKind::InComment`].
238    pub fn into_in_comment(self) -> Self {
239        Self {
240            content: PlantUmlLineKind::InComment(Box::new(self.content)),
241            raw: self.raw,
242        }
243    }
244}
245
246impl PlantUmlLineKind {
247    fn parse(input: ParseContainer) -> ParseResult<Self> {
248        if let Ok((rest, (raw, content))) = StartLine::parse(input.clone()) {
249            let content = PlantUmlLineKind::Start(content);
250            return Ok((rest, (raw, content)));
251        }
252
253        if let Ok((rest, (raw, content))) = EndLine::parse(input.clone()) {
254            let content = PlantUmlLineKind::End(content);
255            return Ok((rest, (raw, content)));
256        }
257
258        if let Ok((rest, (raw, content))) = BlockCommentOpenLine::parse(input.clone()) {
259            let content = PlantUmlLineKind::BlockCommentOpen(content);
260            return Ok((rest, (raw, content)));
261        }
262
263        if let Ok((rest, (raw, content))) = BlockCommentCloseLine::parse(input.clone()) {
264            let content = PlantUmlLineKind::BlockCommentClose(content);
265            return Ok((rest, (raw, content)));
266        }
267
268        if let Ok((rest, (raw, content))) = IncludeLine::parse(input.clone()) {
269            let content = PlantUmlLineKind::Include(content);
270            return Ok((rest, (raw, content)));
271        }
272
273        if let Ok((rest, (raw, content))) = TitleLine::parse(input.clone()) {
274            let content = PlantUmlLineKind::Title(content);
275            return Ok((rest, (raw, content)));
276        }
277
278        if let Ok((rest, (raw, content))) = HeaderLine::parse(input.clone()) {
279            let content = PlantUmlLineKind::Header(content);
280            return Ok((rest, (raw, content)));
281        }
282
283        if let Ok((rest, (raw, content))) = FooterLine::parse(input.clone()) {
284            let content = PlantUmlLineKind::Footer(content);
285            return Ok((rest, (raw, content)));
286        }
287
288        if let Ok((rest, (raw, _))) = EmptyLine::parse(input.clone()) {
289            let content = PlantUmlLineKind::Empty;
290            return Ok((rest, (raw, content)));
291        }
292
293        let (rest, raws) = Self::parse_others(input.clone())?;
294        let content = PlantUmlLineKind::Others;
295        Ok((rest, (raws.into(), content)))
296    }
297
298    fn parse_others(input: ParseContainer) -> IResult<ParseContainer, Vec<ParseContainer>> {
299        let (rest, (not_end, end)) = (
300            wr!(take_till(|c| c == '\n' || c == '\r')),
301            alt((wr!(eof), wr!(line_ending))),
302        )
303            .parse(input)?;
304        Ok((rest, vec![not_end, end]))
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311
312    #[test]
313    fn test_parse_start_line() -> anyhow::Result<()> {
314        let testdata = "@startuml\n";
315        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
316        assert_eq!(rest, "");
317        assert_eq!(testdata, parsed.raw);
318        assert!(parsed.start().is_some());
319
320        let testdata = " \t@startuml\n";
321        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
322        assert_eq!(rest, "");
323        assert_eq!(testdata, parsed.raw);
324        assert!(parsed.start().is_some());
325
326        let testdata = "@startuml \t\n";
327        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
328        assert_eq!(rest, "");
329        assert_eq!(testdata, parsed.raw);
330        assert!(parsed.start().is_some());
331
332        let testdata = " \t@startuml \t\n";
333        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
334        assert_eq!(rest, "");
335        assert_eq!(testdata, parsed.raw);
336        assert!(parsed.start().is_some());
337
338        let testdata = " \t@startuml id_foo\t\n";
339        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
340        assert_eq!(rest, "");
341        assert_eq!(testdata, parsed.raw);
342        assert!(parsed.start().is_some());
343
344        let testdata = " \t@startuml(id=id_bar)\t\n";
345        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
346        assert_eq!(rest, "");
347        assert_eq!(testdata, parsed.raw);
348        assert!(parsed.start().is_some());
349
350        Ok(())
351    }
352
353    #[test]
354    fn test_parse_end_line() -> anyhow::Result<()> {
355        let testdata = "@enduml\n";
356        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
357        assert_eq!(rest, "");
358        assert_eq!(testdata, parsed.raw);
359        assert!(parsed.end().is_some());
360
361        let testdata = " \t@enduml\n";
362        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
363        assert_eq!(rest, "");
364        assert_eq!(testdata, parsed.raw);
365        assert!(parsed.end().is_some());
366
367        let testdata = "@enduml \t\n";
368        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
369        assert_eq!(rest, "");
370        assert_eq!(testdata, parsed.raw);
371        assert!(parsed.end().is_some());
372
373        let testdata = " \t@enduml \t\n";
374        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
375        assert_eq!(rest, "");
376        assert_eq!(testdata, parsed.raw);
377        assert!(parsed.end().is_some());
378
379        Ok(())
380    }
381
382    #[test]
383    fn test_parse_include_line() -> anyhow::Result<()> {
384        let testdata = "!include foo.puml\n";
385        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
386        assert_eq!(rest, "");
387        assert_eq!(testdata, parsed.raw);
388        assert!(parsed.include().is_some());
389
390        let testdata = " !include foo.puml!1\n";
391        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
392        assert_eq!(rest, "");
393        assert_eq!(testdata, parsed.raw);
394        assert!(parsed.include().is_some());
395
396        let testdata = "\t!include foo.puml!bar \n";
397        let (rest, (_, parsed)) = PlantUmlLine::parse(testdata.into())?;
398        assert_eq!(rest, "");
399        assert_eq!(testdata, parsed.raw);
400        assert!(parsed.include().is_some());
401
402        Ok(())
403    }
404}