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}