ftd/p1/
header.rs

1pub use ftd::p1::{Error, Result};
2
3#[derive(Debug, PartialEq, Default, Clone, serde::Serialize, serde::Deserialize)]
4pub struct Header(pub Vec<(usize, String, String)>);
5
6impl Header {
7    /// returns a copy of Header after processing comments "/" and escape "\\/" (if any)
8    ///
9    /// only used by [`Section::remove_comments()`] and [`SubSection::remove_comments()`]
10    ///
11    /// [`SubSection::remove_comments()`]: ftd::p1::sub_section::SubSection::remove_comments
12    /// [`Section::remove_comments()`]: ftd::p1::section::Section::remove_comments
13    pub fn uncommented_headers(&self) -> Header {
14        let mut headers: Vec<(usize, String, String)> = vec![];
15        for (ln, key, val) in self.0.iter() {
16            if !key.trim().starts_with('/') {
17                match key.trim().starts_with(r"\/") {
18                    true => headers.push((*ln, key.trim().replacen('\\', "", 1), val.to_string())),
19                    false => headers.push((*ln, key.to_string(), val.to_string())),
20                }
21            }
22        }
23        Header(headers)
24    }
25
26    pub fn without_line_number(&self) -> Self {
27        let mut header: Header = Default::default();
28        for (_, k, v) in self.0.iter() {
29            header.add(&0, k, v);
30        }
31        header
32    }
33
34    pub fn add(&mut self, line_number: &usize, name: &str, value: &str) {
35        self.0
36            .push((*line_number, name.to_string(), value.to_string()))
37    }
38
39    pub fn bool_with_default(
40        &self,
41        doc_id: &str,
42        line_number: usize,
43        name: &str,
44        def: bool,
45    ) -> Result<bool> {
46        match self.bool(doc_id, line_number, name) {
47            Ok(b) => Ok(b),
48            Err(Error::NotFound { .. }) => Ok(def),
49            e => e,
50        }
51    }
52
53    pub fn bool_optional(
54        &self,
55        doc_id: &str,
56        line_number: usize,
57        name: &str,
58    ) -> Result<Option<bool>> {
59        match self.bool(doc_id, line_number, name) {
60            Ok(b) => Ok(Some(b)),
61            Err(Error::NotFound { .. }) => Ok(None),
62            Err(e) => Err(e),
63        }
64    }
65
66    pub fn bool(&self, doc_id: &str, line_number: usize, name: &str) -> Result<bool> {
67        for (l, k, v) in self.0.iter() {
68            if k == name {
69                return if v == "true" || v == "false" {
70                    Ok(v == "true")
71                } else {
72                    Err(ftd::p1::Error::ParseError {
73                        message: "can't parse bool".to_string(),
74                        doc_id: doc_id.to_string(),
75                        line_number: *l,
76                    })
77                };
78            }
79        }
80        Err(Error::NotFound {
81            doc_id: doc_id.to_string(),
82            line_number,
83            key: name.to_string(),
84        })
85    }
86
87    pub fn i32_with_default(
88        &self,
89        doc_id: &str,
90        line_number: usize,
91        name: &str,
92        def: i32,
93    ) -> Result<i32> {
94        match self.i32(doc_id, line_number, name) {
95            Ok(b) => Ok(b),
96            Err(Error::NotFound { .. }) => Ok(def),
97            e => e,
98        }
99    }
100
101    pub fn i32_optional(
102        &self,
103        doc_id: &str,
104        line_number: usize,
105        name: &str,
106    ) -> Result<Option<i32>> {
107        match self.i32(doc_id, line_number, name) {
108            Ok(b) => Ok(Some(b)),
109            Err(Error::NotFound { .. }) => Ok(None),
110            Err(e) => Err(e),
111        }
112    }
113
114    pub fn i32(&self, doc_id: &str, line_number: usize, name: &str) -> Result<i32> {
115        for (l, k, v) in self.0.iter() {
116            if k == name {
117                return v.parse().map_err(|e: std::num::ParseIntError| {
118                    ftd::p1::Error::ParseError {
119                        message: format!("{:?}", e),
120                        doc_id: doc_id.to_string(),
121                        line_number: *l,
122                    }
123                });
124            }
125        }
126        Err(Error::NotFound {
127            doc_id: doc_id.to_string(),
128            line_number,
129            key: name.to_string(),
130        })
131    }
132
133    pub fn i64(&self, doc_id: &str, line_number: usize, name: &str) -> Result<i64> {
134        for (l, k, v) in self.0.iter() {
135            if k == name {
136                return v.parse().map_err(|e: std::num::ParseIntError| {
137                    ftd::p1::Error::ParseError {
138                        message: format!("{:?}", e),
139                        doc_id: doc_id.to_string(),
140                        line_number: *l,
141                    }
142                });
143            }
144        }
145        Err(Error::NotFound {
146            doc_id: doc_id.to_string(),
147            line_number,
148            key: name.to_string(),
149        })
150    }
151
152    pub fn i64_optional(
153        &self,
154        doc_id: &str,
155        line_number: usize,
156        name: &str,
157    ) -> Result<Option<i64>> {
158        match self.i64(doc_id, line_number, name) {
159            Ok(b) => Ok(Some(b)),
160            Err(Error::NotFound { .. }) => Ok(None),
161            Err(e) => Err(e),
162        }
163    }
164
165    pub fn f64(&self, doc_id: &str, line_number: usize, name: &str) -> Result<f64> {
166        for (l, k, v) in self.0.iter() {
167            if k == name {
168                return v.parse().map_err(|e: std::num::ParseFloatError| {
169                    ftd::p1::Error::ParseError {
170                        message: format!("{:?}", e),
171                        doc_id: doc_id.to_string(),
172                        line_number: *l,
173                    }
174                });
175            }
176        }
177        Err(Error::NotFound {
178            doc_id: doc_id.to_string(),
179            line_number,
180            key: name.to_string(),
181        })
182    }
183
184    pub fn f64_optional(
185        &self,
186        doc_id: &str,
187        line_number: usize,
188        name: &str,
189    ) -> Result<Option<f64>> {
190        match self.f64(doc_id, line_number, name) {
191            Ok(b) => Ok(Some(b)),
192            Err(Error::NotFound { .. }) => Ok(None),
193            Err(e) => Err(e),
194        }
195    }
196
197    pub fn str_with_default<'a>(
198        &'a self,
199        doc_id: &str,
200        line_number: usize,
201        name: &str,
202        def: &'a str,
203    ) -> Result<&'a str> {
204        match self.str(doc_id, line_number, name) {
205            Ok(b) => Ok(b),
206            Err(Error::NotFound { .. }) => Ok(def),
207            e => e,
208        }
209    }
210
211    pub fn get_events(
212        &self,
213        line_number: usize,
214        doc: &ftd::p2::TDoc,
215        arguments: &ftd::Map<ftd::p2::Kind>,
216    ) -> ftd::p1::Result<Vec<ftd::p2::Event>> {
217        let events = {
218            let mut events = vec![];
219            for (_, k, v) in self.0.iter() {
220                if k.starts_with("$on-") && k.ends_with('$') {
221                    let mut event = k.replace("$on-", "");
222                    event = event[..event.len() - 1].to_string();
223                    events.push((event, v.to_string()));
224                }
225            }
226            events
227        };
228        let mut event = vec![];
229        for (e, a) in events {
230            event.push(ftd::p2::Event::to_event(
231                line_number,
232                &e,
233                &a,
234                doc,
235                arguments,
236            )?);
237        }
238        Ok(event)
239    }
240
241    pub fn str_optional(
242        &self,
243        doc_id: &str,
244        line_number: usize,
245        name: &str,
246    ) -> Result<Option<&str>> {
247        match self.str(doc_id, line_number, name) {
248            Ok(b) => Ok(Some(b)),
249            Err(Error::NotFound { .. }) => Ok(None),
250            Err(e) => Err(e),
251        }
252    }
253
254    #[allow(clippy::type_complexity)]
255    pub fn conditional_str(
256        &self,
257        doc: &ftd::p2::TDoc,
258        line_number: usize,
259        name: &str,
260        arguments: &ftd::Map<ftd::p2::Kind>,
261    ) -> Result<Vec<(usize, String, Option<String>, bool)>> {
262        let mut conditional_vector = vec![];
263        for (idx, (_, k, v)) in self.0.iter().enumerate() {
264            let v = doc.resolve_reference_name(line_number, v, arguments)?;
265            let (k, is_referenced) = if let Some(k) = k.strip_prefix('$') {
266                (k.to_string(), true)
267            } else {
268                (k.to_string(), false)
269            };
270            if k.eq(name) {
271                conditional_vector.push((idx, v.to_string(), None, is_referenced));
272            }
273            if k.contains(" if ") {
274                let mut parts = k.splitn(2, " if ");
275                let property_name = parts.next().unwrap().trim();
276                if property_name == name {
277                    let conditional_attribute = parts.next().unwrap().trim();
278                    conditional_vector.push((
279                        idx,
280                        v.to_string(),
281                        Some(conditional_attribute.to_string()),
282                        is_referenced,
283                    ));
284                }
285            }
286        }
287        if conditional_vector.is_empty() {
288            Err(Error::NotFound {
289                doc_id: doc.name.to_string(),
290                line_number,
291                key: format!("`{}` header is missing", name),
292            })
293        } else {
294            Ok(conditional_vector)
295        }
296    }
297
298    pub fn str(&self, doc_id: &str, line_number: usize, name: &str) -> Result<&str> {
299        for (_, k, v) in self.0.iter() {
300            if k == name {
301                return Ok(v.as_str());
302            }
303        }
304
305        Err(Error::NotFound {
306            doc_id: doc_id.to_string(),
307            line_number,
308            key: format!("`{}` header is missing", name),
309        })
310    }
311
312    pub fn string(&self, doc_id: &str, line_number: usize, name: &str) -> Result<String> {
313        self.str(doc_id, line_number, name).map(ToString::to_string)
314    }
315
316    pub fn string_optional(
317        &self,
318        doc_id: &str,
319        line_number: usize,
320        name: &str,
321    ) -> Result<Option<String>> {
322        Ok(self
323            .str_optional(doc_id, line_number, name)?
324            .map(ToString::to_string))
325    }
326
327    pub fn string_with_default(
328        &self,
329        doc_id: &str,
330        line_number: usize,
331        name: &str,
332        def: &str,
333    ) -> Result<String> {
334        self.str_with_default(doc_id, line_number, name, def)
335            .map(ToString::to_string)
336    }
337}