java_props/
lib.rs

1#[macro_use]
2extern crate lazy_static;
3
4mod parser;
5mod iterator;
6mod utils;
7
8use iterator::Iterator;
9use std::io::{self, BufReader, prelude::*};
10use std::collections::HashMap;
11use std::fs::File;
12use std::char;
13
14#[derive(PartialEq, Debug)]
15pub enum PropertyType {
16    Property,
17    Key,
18    Value,
19    Whitespace,
20    Comment,
21    LineBreak,
22    EscapedValue,
23    Separator,
24    Raw,
25}
26
27#[derive(Debug)]
28pub struct PropertyRange {
29    start: usize,
30    end: usize,
31}
32
33#[derive(Debug)]
34pub enum PropertyData {
35    Range(PropertyRange),
36    Text(String),
37}
38
39#[derive(Debug)]
40pub struct PropertyValue {
41    data: PropertyData,
42    children: Option<Vec<PropertyValue>>,
43    type_: PropertyType,
44}
45
46#[derive(Debug)]
47pub struct Properties {
48    contents: String,
49    values: Vec<PropertyValue>,
50    value_map: HashMap<String, usize>,
51    data: HashMap<String, String>,
52}
53
54impl Properties {
55    pub fn new() -> Properties {
56        Properties {
57            contents: String::new(),
58            values: vec![],
59            value_map: HashMap::new(),
60            data: HashMap::new(),
61        }
62    }
63
64    pub fn from_str(contents: &String) -> Properties {
65        let contents = contents.clone();
66
67        let mut iter = Iterator::new(&contents);
68        let mut value_map = HashMap::new();
69
70        let values = Self::build_property_values(&mut iter);
71        let data = Self::build_properties(&values, &iter);
72
73        for (i, value) in values.iter().enumerate() {
74            if value.type_ == PropertyType::Property {
75                let key = Self::build_property_component(match &value.children {
76                    Some(children) => &children[0],
77                    None => continue,
78                }, &iter);
79
80                value_map.insert(key, i);
81            }
82        }
83
84        Properties {
85            contents,
86            values,
87            value_map,
88            data,
89        }
90    }
91
92    pub fn from_file(file: &File) -> io::Result<Properties> {
93        let mut reader = BufReader::new(file);
94        let mut contents = String::new();
95
96        reader.read_to_string(&mut contents)?;
97
98        Ok(Self::from_str(&contents))
99    }
100
101    pub fn save(&self, file: &mut File) -> io::Result<String> {
102        let contents = self.to_string();
103        file.write(contents.as_bytes())?;
104        Ok(contents)
105    }
106
107    pub fn get(&self, key: &str) -> Option<&String> {
108        self.data.get(key)
109    }
110
111    pub fn set(&mut self, key: &str, value: &str) {
112        let escaped_key = utils::escape_key(key);
113        let escaped_value = utils::escape_value(&value);
114
115        let seperator = "=";
116
117        self.data.insert(String::from(key), String::from(value));
118
119        let property_value = if self.value_map.contains_key(key) {
120            let index = *self.value_map.get(key).unwrap();
121            &mut self.values[index]
122        } else {
123            let last_value = self.values.last();
124            if last_value.is_some() && !Self::is_newline_value(last_value) {
125                self.values.push(PropertyValue {
126                    data: PropertyData::Text(String::from("\n")),
127                    children: None,
128                    type_: PropertyType::Raw,
129                });
130            }
131
132            self.values.push(PropertyValue {
133                data: PropertyData::Text(String::new()),
134                children: None,
135                type_: PropertyType::Raw,
136            });
137
138            self.value_map.insert(String::from(key), self.values.len() - 1);
139            self.values.last_mut().unwrap()
140        };
141
142        if property_value.type_ == PropertyType::Raw {
143            if let PropertyData::Text(text) = &mut property_value.data {
144                // empty existing text
145                text.clear();
146
147                // set new value
148                text.push_str(&escaped_key);
149                text.push_str(seperator);
150                text.push_str(&escaped_value);
151            }
152        } else if property_value.type_ == PropertyType::Property {
153            if let Some(children) = &mut property_value.children {
154                // adjust value piece of child
155                children[2].data = PropertyData::Text(escaped_value.clone());
156                children[2].children = None;
157                children[2].type_ = PropertyType::Raw;
158            }
159        } else {
160            panic!("Unknown property type: {:?}", property_value.type_);
161        }
162    }
163
164    pub fn unset(&mut self, key: &str) {
165        self.data.remove(key);
166
167        if let Some(index) = self.value_map.get(key) {
168            self.values.remove(*index);
169            self.value_map.remove(key);
170        }
171    }
172
173    pub fn parse_file(file: &File) -> io::Result<HashMap<String, String>> {
174        let mut reader = BufReader::new(file);
175        let mut contents = String::new();
176
177        reader.read_to_string(&mut contents)?;
178
179        Ok(Self::parse(&contents))
180    }
181
182    pub fn parse(contents: &String) -> HashMap<String, String>  {
183        let mut iter = Iterator::new(contents);
184        let entries = Self::build_property_values(&mut iter);
185
186        Self::build_properties(&entries, &iter)
187    }
188
189    fn build_property_values(iter: &mut Iterator) -> Vec<PropertyValue> {
190        let mut values = Vec::new();
191
192        loop {
193            let chr = match iter.peek() {
194                Some(chr) => chr,
195                None => break,
196            };
197
198            if chr.is_whitespace() {
199                values.push(parser::read_whitespace(iter));
200            } else if parser::is_comment_indicator(chr) {
201                values.push(parser::read_comment(iter));
202            } else {
203                values.push(parser::read_property(iter));
204            }
205        }
206
207        values
208    }
209
210    fn build_property_component(value: &PropertyValue, iter: &Iterator) -> String {
211        let mut component = String::new();
212
213        if let PropertyData::Range(range) = &value.data {
214            let mut start = range.start;
215
216            if value.children.is_some() {
217                for child in value.children.as_ref().unwrap() {
218                    let child_range = match &child.data {
219                        PropertyData::Range(range) => range,
220                        _ => continue,
221                    };
222
223                    component.push_str(&iter.get_range(start, child_range.start));
224
225                    if let PropertyType::EscapedValue = child.type_ {
226                        let chr = iter.get(child_range.start + 1).unwrap();
227
228                        component.push(match chr {
229                            't' => '\t',
230                            'r' => '\r',
231                            'n' => '\n',
232                            'f' => '\x0c',
233                            'u' => {
234                                let num = u32::from_str_radix(&iter.get_range(
235                                    child_range.start + 2,
236                                    child_range.start + 6,
237                                ), 16).unwrap_or(0);
238
239                                char::from_u32(num).unwrap()
240                            },
241                            _ => chr,
242                        });
243                    }
244
245                    start = child_range.end;
246                }
247            }
248
249            component.push_str(&iter.get_range(start, range.end));
250        }
251
252        component
253    }
254
255    fn build_properties(values: &Vec<PropertyValue>, iter: &Iterator) -> HashMap<String, String> {
256        let mut data = HashMap::new();
257
258        for value in values {
259            if let PropertyType::Property = value.type_ {
260                let children = value.children.as_ref().unwrap();
261                let key = Self::build_property_component(&children[0], iter);
262                let value = Self::build_property_component(&children[2], iter);
263
264                data.insert(key, value);
265            }
266        }
267
268        data
269    }
270
271    pub fn is_newline_value(value: Option<&PropertyValue>) -> bool {
272        if value.is_some() {
273            let value = value.unwrap();
274
275            if value.type_ == PropertyType::LineBreak {
276                return true;
277            } else if value.type_ == PropertyType::Raw {
278                return match &value.data {
279                    PropertyData::Text(s) => s.trim().is_empty() && s.contains("\n"),
280                    _ => false,
281                }
282            }
283        }
284
285        false
286    }
287}
288
289impl ToString for Properties {
290    fn to_string(&self) -> String {
291        let mut buf = String::new();
292        let mut values = vec![];
293
294        for value in self.values.iter().rev() {
295            values.push(value);
296        }
297
298        while !values.is_empty() {
299            let value = values.pop().unwrap();
300
301            match value.type_ {
302                PropertyType::Raw => match &value.data {
303                    PropertyData::Text(text) => buf.push_str(text),
304                    _ => panic!("Invalid property data for raw property type!"),
305                },
306                PropertyType::Property => {
307                    for child_value in value.children.as_ref().unwrap().iter().rev() {
308                        values.push(child_value);
309                    }
310                },
311                _ => if let PropertyData::Range(range) = &value.data {
312                    buf.push_str(&self.contents[range.start..range.end]);
313                },
314            }
315        }
316
317        buf
318    }
319}
320
321#[cfg(test)]
322mod test {
323    use super::Properties;
324    use std::fs::File;
325    use std::io;
326
327    fn get_test_props() -> io::Result<Properties> {
328        let file = File::open("test.properties")?;
329        Properties::from_file(&file)
330    }
331
332    #[test]
333    fn reads_file() {
334        let props = get_test_props();
335        assert!(props.is_ok());
336    }
337
338    #[test]
339    fn simple_parse_check() {
340        let props = get_test_props().unwrap();
341        assert_eq!(
342            props.get("language"),
343            Some(&String::from("English")),
344        );
345    }
346
347    #[test]
348    fn complex_parse_check() {
349        let props = get_test_props().unwrap();
350        assert_eq!(
351            props.get("key with spaces"),
352            Some(&String::from("This is the value that could be looked up with the key \"key with spaces\".")),
353        );
354    }
355
356    #[test]
357    fn multiline_parse_check() {
358        let props = get_test_props().unwrap();
359        assert_eq!(
360            props.get("message"),
361            Some(&String::from("Welcome to Wikipedia!")),
362        );
363    }
364
365    #[test]
366    fn empty_output_check() {
367        let props_str = String::new();
368        assert_eq!(
369            Properties::from_str(&props_str).to_string(),
370            props_str,
371        )
372    }
373
374    #[test]
375    fn basic_output_check() {
376        let props_str = String::from("simple\\ key = A fun value!\\nWith multiple lines!");
377        assert_eq!(
378            Properties::from_str(&props_str).to_string(),
379            props_str,
380        )
381    }
382}