plantuml_parser/
filedata.rs

1use crate::Error;
2use crate::ParseContainer;
3use crate::PlantUmlContent;
4use crate::{IncludeToken, PlantUmlLine};
5use std::sync::Arc;
6
7/// PlantUML diagrams in the file.
8///
9/// # Examples
10///
11/// ```
12/// use plantuml_parser::PlantUmlFileData;
13///
14/// # fn main() -> anyhow::Result<()> {
15/// let data = r#"
16/// @startuml diagram_0
17/// Alice -> Bob: Hello
18/// @enduml
19///
20/// @startuml
21/// Bob -> Alice: Hi
22/// @enduml
23///
24/// @startuml diagram_2
25/// Alice -> Bob: How are you?
26/// @enduml
27/// "#;
28///
29/// let filedata = PlantUmlFileData::parse_from_str(data)?;
30/// assert_eq!(filedata.is_empty(), false);
31/// assert_eq!(filedata.len(), 3);
32/// assert_eq!(
33///     filedata.iter().map(|x| x.inner()).collect::<Vec<_>>(),
34///     [
35///         "Alice -> Bob: Hello\n",
36///         "Bob -> Alice: Hi\n",
37///         "Alice -> Bob: How are you?\n",
38///     ]
39/// );
40///
41/// let content_1 = filedata.get(1).unwrap();
42/// assert_eq!(content_1.inner(), "Bob -> Alice: Hi\n");
43/// let content_0 = filedata.get_by_id("diagram_0").unwrap();
44/// assert_eq!(content_0.inner(), "Alice -> Bob: Hello\n");
45/// let content_2 = filedata.get_by_id("diagram_2").unwrap();
46/// assert_eq!(content_2.inner(), "Alice -> Bob: How are you?\n");
47/// # Ok(())
48/// # }
49/// ```
50#[derive(Clone, Debug)]
51pub struct PlantUmlFileData {
52    contents: Vec<PlantUmlContent>,
53}
54
55impl PlantUmlFileData {
56    /// Creates a new [`PlantUmlFileData`] from the string.
57    ///
58    /// * `input` - a string to be parsed.
59    pub fn parse_from_str<S>(input: S) -> Result<Self, Error>
60    where
61        S: Into<String>,
62    {
63        let input = ParseContainer::new(Arc::new(input.into()));
64        Self::parse(input)
65    }
66
67    /// Creates a new [`PlantUmlFileData`] from the inner type ([`ParseContainer`]).
68    ///
69    /// * `input` - a string to be parsed.
70    pub fn parse(input: ParseContainer) -> Result<Self, Error> {
71        let mut contents = vec![];
72        let mut input = input;
73        let (mut rest, (_, mut line)) = PlantUmlLine::parse(input.clone())?;
74
75        'outer: while !rest.is_empty() {
76            while line.empty().is_some() {
77                (rest, (_, line)) = PlantUmlLine::parse(input.clone())?;
78                if line.empty().is_none() {
79                    break;
80                }
81                if rest.is_empty() {
82                    break 'outer;
83                }
84                input = rest;
85            }
86
87            let (tmp_rest, content) = PlantUmlContent::parse(input)?;
88            input = tmp_rest;
89            contents.push(content);
90            if input.is_empty() {
91                break;
92            }
93            (rest, (_, line)) = PlantUmlLine::parse(input.clone())?;
94        }
95
96        Ok(Self { contents })
97    }
98
99    /// Returns the one PlantUML diagram specified by the token.
100    ///
101    /// * `token` - A [`IncludeToken`] to get [`PlantUmlContent`] in the [`PlantUmlFileData`].
102    pub fn get_by_token(&self, token: &IncludeToken) -> Option<&PlantUmlContent> {
103        if let Some(content) = token.id().and_then(|id| self.get_by_id(id)) {
104            Some(content)
105        } else if let Some(content) = token.index().and_then(|index| self.get(index)) {
106            Some(content)
107        } else {
108            self.get(0)
109        }
110    }
111
112    /// Returns the reference to [`PlantUmlContent`] of index.
113    pub fn get(&self, index: usize) -> Option<&PlantUmlContent> {
114        self.contents.get(index)
115    }
116
117    /// Returns the reference to [`PlantUmlContent`] of `id`.
118    pub fn get_by_id(&self, id: &str) -> Option<&PlantUmlContent> {
119        self.contents.iter().find(|x| x.id() == Some(id))
120    }
121
122    /// Returns an iterator visiting all [`PlantUmlContent`] in the [`PlantUmlFileData`].
123    pub fn iter(&self) -> std::slice::Iter<'_, PlantUmlContent> {
124        self.contents.iter()
125    }
126
127    /// Returns `true` if the [`PlantUmlFileData`] has no [`PlantUmlContent`].
128    pub fn is_empty(&self) -> bool {
129        self.contents.is_empty()
130    }
131
132    /// Returns the number of [`PlantUmlContent`] in the [`PlantUmlFileData`].
133    pub fn len(&self) -> usize {
134        self.contents.len()
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    use anyhow::Result;
143
144    #[test]
145    fn test_parse_plant_uml_file_data() -> Result<()> {
146        // OK
147        let testdata = r#"@startuml
148        @enduml
149
150        @startuml a
151            a -> b
152        @enduml
153
154
155        @startuml(id=b)
156            b -> a
157        @enduml
158
159        "#;
160        let parsed = PlantUmlFileData::parse(testdata.into())?;
161        assert_eq!(parsed.contents.len(), 3);
162
163        let content_0 = parsed.get(0).unwrap();
164        assert!(content_0.id().is_none());
165
166        let content_1 = parsed.get(1).unwrap();
167        assert_eq!(content_1.id(), Some("a"));
168        assert_eq!(content_1.inner(), "            a -> b\n");
169
170        let content_a = parsed.get_by_id("a").unwrap();
171        assert_eq!(content_a.id(), Some("a"));
172        assert_eq!(content_a.inner(), "            a -> b\n");
173
174        let content_2 = parsed.get(2).unwrap();
175        assert_eq!(content_2.id(), Some("b"));
176        assert_eq!(content_2.inner(), "            b -> a\n");
177
178        let content_b = parsed.get_by_id("b").unwrap();
179        assert_eq!(content_b.id(), Some("b"));
180        assert_eq!(content_b.inner(), "            b -> a\n");
181
182        assert!(parsed.get(3).is_none());
183
184        Ok(())
185    }
186
187    #[test]
188    fn test_unclosed() -> Result<()> {
189        // OK
190        let testdata = r#"@startuml
191        @e
192
193        "#;
194
195        let result = PlantUmlFileData::parse_from_str(testdata);
196        assert!(result.is_err());
197
198        Ok(())
199    }
200}