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}