zenv/parser/
line.rs

1const LF: char = '\n';
2const HASH: char = '#';
3const B_SLASH: char = '\\';
4const S_QUOTE: char = '\'';
5const D_QUOTE: char = '"';
6
7/// Type of the quote
8#[derive(Debug, PartialEq)]
9pub enum Quote {
10    /// When the value is single quoted i.e. `'`
11    Single,
12
13    /// When the value is double quoted i.e. `"`
14    Double,
15
16    /// When the value is not quoted
17    No,
18}
19
20/// To collect the info about the current line
21#[derive(Debug, PartialEq)]
22pub struct KeyVal<'k> {
23    /// `key` of the variable
24    pub k: &'k str,
25
26    /// `value` of the variable
27    pub v: String,
28
29    /// Whether the value is quoted or not
30    pub q: Quote,
31}
32
33/// (Can be) Used to parse the current line
34///
35/// Example
36/// ```
37/// use zenv::{Line, KeyVal, Quote};
38///
39/// let line = Line::from("BASIC=basic");
40///
41/// let k = "BASIC";
42/// let v = "basic".to_string();
43/// assert_eq!(line, Line::KeyVal(KeyVal { k, v, q: Quote::No }));
44///
45/// // Commented line
46/// let empty = Line::from("# COMMENT=commented");
47///
48/// assert_eq!(empty, Line::Empty);
49///
50/// // With quotes
51/// let quoted = Line::from("S_QUOTED='single_quoted'");
52///
53/// let k = "S_QUOTED";
54/// let v = "single_quoted".to_string();
55/// assert_eq!(quoted, Line::KeyVal(KeyVal { k, v, q: Quote::Single }));
56/// ```
57#[derive(Debug, PartialEq)]
58pub enum Line<'l> {
59    /// When the current line is a `key=val` pair
60    KeyVal(KeyVal<'l>),
61
62    /// When the current line is empty
63    Empty,
64}
65
66impl<'l> Line<'l> {
67    fn replace_lf(line: &str) -> String {
68        let mut s = String::with_capacity(line.len());
69        let mut chars = line.chars();
70
71        loop {
72            match chars.next() {
73                // If \ is found
74                Some(x) if x == B_SLASH => match chars.next() {
75                    // "\n" -> Chars: ['\\', 'n']
76                    Some('n') => {
77                        s.push(LF);
78                    }
79                    // "\\n" -> Chars: ['\\', '\\', 'n']
80                    Some(B_SLASH) => {
81                        s.push(B_SLASH);
82                    }
83                    Some(n) => {
84                        s.push(x);
85                        s.push(n);
86                    }
87                    None => s.push(x),
88                },
89                // chars() automagically converts \n into LF
90                // no special handling of new line character
91                Some(x) => s.push(x),
92                _ => break,
93            }
94        }
95
96        s
97    }
98
99    fn escape_lf(x: char) -> String {
100        if x == LF {
101            x.escape_debug().to_string()
102        } else {
103            x.to_string()
104        }
105    }
106
107    fn retain_quote(orgnl: &str, after: String, q: Quote) -> (String, Quote) {
108        // If both strings length matches then it is not closed
109        if orgnl.len().eq(&(after.len() + 1)) {
110            let new_val: String = orgnl.chars().take_while(|c| c != &HASH).collect();
111
112            (new_val.trim().to_string(), Quote::No)
113        } else {
114            (after, q)
115        }
116    }
117}
118
119impl<'l> From<&'l str> for Line<'l> {
120    fn from(line: &'l str) -> Self {
121        if line.is_empty() || line.starts_with(HASH) {
122            return Self::Empty;
123        };
124
125        let mut parts = line.splitn(2, '=');
126
127        match (parts.next(), parts.next()) {
128            (Some(k), Some(v)) => {
129                let key = k.trim();
130                let mut chars = v.chars();
131
132                let first = chars.next();
133
134                match first {
135                    Some(D_QUOTE) => {
136                        let val = {
137                            let v: String = chars.take_while(|x| x != &D_QUOTE).collect();
138                            Self::replace_lf(&v)
139                        };
140
141                        let (v, q) = Self::retain_quote(v, val, Quote::Double);
142
143                        Line::KeyVal(KeyVal { k: key, v, q })
144                    }
145                    Some(S_QUOTE) => {
146                        let val: String = chars
147                            .take_while(|x| x != &S_QUOTE)
148                            .map(Self::escape_lf)
149                            .collect();
150
151                        let (v, q) = Self::retain_quote(v, val, Quote::Single);
152
153                        Line::KeyVal(KeyVal { k: key, v, q })
154                    }
155                    Some(a) => {
156                        let mut val = Self::escape_lf(a);
157
158                        val.push_str(
159                            &chars
160                                .take_while(|x| x != &HASH)
161                                .map(Self::escape_lf)
162                                .collect::<String>(),
163                        );
164
165                        Line::KeyVal(KeyVal {
166                            k: key,
167                            v: val.trim().to_string(),
168                            q: Quote::No,
169                        })
170                    }
171                    _ => Line::KeyVal(KeyVal {
172                        k: key,
173                        v: String::with_capacity(0),
174                        q: Quote::No,
175                    }),
176                }
177            }
178            _ => Self::Empty,
179        }
180    }
181}