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 text.clear();
146
147 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 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}