plantuml_parser/dsl/token/
include_specifier.rs

1use crate::DiagramIdToken;
2use crate::{ParseContainer, ParseResult, wr, wr2};
3use nom::branch::alt;
4use nom::bytes::complete::tag;
5use nom::bytes::complete::take_while1;
6use nom::{IResult, Parser};
7
8/// A token sequence that is the include specifier ([`DiagramIdToken`]) around the include keyword. (like `"foo.puml"` or `"bar.iuml!buz"`.)
9///
10/// * `[relative filepath]`
11/// * `[relative filepath]![index]`
12/// * `[relative filepath]![id]`
13///
14/// # Examples
15///
16/// ```
17/// use plantuml_parser::{IncludeSpecifierToken, ParseContainer};
18///
19/// # fn main() -> anyhow::Result<()> {
20/// let input = "filepath_0 rest";
21/// let (rest, (raws, token)) = IncludeSpecifierToken::parse(input.into())?;
22/// let combined_raw: ParseContainer = raws.into();
23/// assert_eq!(rest, " rest");
24/// assert_eq!(combined_raw, "filepath_0");
25/// assert_eq!(token.filepath(), "filepath_0");
26/// assert_eq!(token.index(), None);
27/// assert_eq!(token.id(), None);
28///
29/// let input = "file_path_1!diagram_2 rest";
30/// let (rest, (raws, token)) = IncludeSpecifierToken::parse(input.into())?;
31/// let combined_raw: ParseContainer = raws.into();
32/// assert_eq!(rest, " rest");
33/// assert_eq!(combined_raw, "file_path_1!diagram_2");
34/// assert_eq!(token.filepath(), "file_path_1");
35/// assert_eq!(token.index(), None);
36/// assert_eq!(token.id(), Some("diagram_2"));
37///
38/// let input = "file_path_2!4 rest";
39/// let (rest, (raws, token)) = IncludeSpecifierToken::parse(input.into())?;
40/// let combined_raw: ParseContainer = raws.into();
41/// assert_eq!(rest, " rest");
42/// assert_eq!(combined_raw, "file_path_2!4");
43/// assert_eq!(token.filepath(), "file_path_2");
44/// assert_eq!(token.index(), Some(4));
45/// assert_eq!(token.id(), Some("4"));
46/// # Ok(())
47/// # }
48/// ```
49#[derive(Clone, Debug)]
50pub struct IncludeSpecifierToken {
51    filepath: ParseContainer,
52    id: Option<DiagramIdToken>,
53}
54
55impl IncludeSpecifierToken {
56    /// Tries to parse [`IncludeSpecifierToken`]. (e.g. `"foo.puml"`, `"bar.iuml!buz"`.)
57    pub fn parse(input: ParseContainer) -> ParseResult<Self> {
58        let ret = alt((wr2!(parse_file_with_id), wr2!(parse_only_file))).parse(input)?;
59        Ok(ret)
60    }
61
62    /// Returns the filepath specified by include keyword.
63    pub fn filepath(&self) -> &str {
64        self.filepath.as_str()
65    }
66
67    /// Returns the index specified by include keyword if existed.
68    pub fn index(&self) -> Option<usize> {
69        self.id.as_ref().and_then(|x| x.id().parse().ok())
70    }
71
72    /// Returns the ID specified by include keyword if existed.
73    pub fn id(&self) -> Option<&str> {
74        self.id.as_ref().map(|x| x.id())
75    }
76}
77
78/// parse like `"foo.puml"`
79///
80/// TODO: accept escaped space charactor: like `"foo\ bar.puml"` ("\ "?)
81fn parse_only_file(input: ParseContainer) -> ParseResult<IncludeSpecifierToken> {
82    let result: IResult<_, _> = wr!(take_while1(|c: char| {
83        !['!', ' ', '\t', '\n', '\r'].contains(&c)
84    }))(input);
85    let (rest, filepath) = result?;
86
87    let parsed_raw = filepath.clone();
88    let parsed = IncludeSpecifierToken { filepath, id: None };
89    Ok((rest, (parsed_raw, parsed)))
90}
91
92/// parse like `"bar.iuml!buz"`
93fn parse_file_with_id(input: ParseContainer) -> ParseResult<IncludeSpecifierToken> {
94    let (input, ((file_raw, file), tag, (id_raw, id))) = (
95        wr2!(parse_only_file),
96        wr!(tag("!")),
97        wr2!(DiagramIdToken::parse),
98    )
99        .parse(input)?;
100
101    let parsed_raw = ParseContainer::from(vec![file_raw, tag, id_raw]);
102    let parsed = IncludeSpecifierToken {
103        filepath: file.filepath,
104        id: Some(id),
105    };
106
107    Ok((input, (parsed_raw, parsed)))
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_parse() -> anyhow::Result<()> {
116        let testdata = "foo.puml";
117        let (rest, (parsed, include)) = IncludeSpecifierToken::parse(testdata.into())?;
118        assert_eq!(rest, "");
119        assert_eq!(parsed, testdata);
120        assert_eq!(include.filepath(), "foo.puml");
121        assert!(include.id().is_none());
122
123        let testdata = "foo.puml!bar";
124        let (rest, (parsed, include)) = IncludeSpecifierToken::parse(testdata.into())?;
125        assert_eq!(rest, "");
126        assert_eq!(parsed, testdata);
127        assert_eq!(include.filepath(), "foo.puml");
128        assert_eq!(include.id(), Some("bar"));
129
130        Ok(())
131    }
132}