systemd_parser/
items.rs

1
2use std::collections::HashMap;
3
4#[derive(PartialEq, Eq, Clone, Debug)]
5pub enum SystemdItem<'a> {
6    Comment(&'a str),
7    Category(&'a str),
8    Directive(&'a str, Option<&'a str>),
9}
10
11impl<'a> SystemdItem<'a> {
12    fn is_comment(&self) -> bool { match *self { SystemdItem::Comment(_) => true, _ => false } }
13    fn is_category(&self) -> bool { match *self { SystemdItem::Category(_) => true, _ => false } }
14    fn is_directive(&self) -> bool { match *self { SystemdItem::Directive(_, _) => true, _ => false } }
15}
16
17#[derive(PartialEq, Eq, Clone, Debug)]
18pub struct UnitDirective {
19    key: String,
20    value: Option<String>,
21    category: String,
22}
23
24impl UnitDirective {
25    pub fn value(&self) -> Option<&str> { self.value.as_ref().map(|s| &s[..]) }
26    pub fn key(&self) -> &str { &self.key }
27    pub fn category(&self) -> &str { &self.category }
28}
29
30impl UnitDirective {
31    pub fn new(category: &str, key: &str, value: Option<&str>) -> UnitDirective {
32        UnitDirective {
33            category: String::from(category),
34            value: value.map(String::from),
35            key: String::from(key),
36        }
37    }
38
39    pub fn item_list_to_unit_directive_list(unit_items: &Vec<SystemdItem>)
40        -> Result<Vec<UnitDirective>, String> {
41
42        use self::SystemdItem::*;
43
44        let directive_count = unit_items.iter().filter(|items| items.is_directive()).count();
45        if directive_count < 1 {
46            return Err(format!("No directives in the file"))
47        }
48
49        let mut cat = try!(UnitDirective::get_first_category(unit_items));
50        let mut res = vec!();
51
52        for item in unit_items {
53            match *item {
54                Category(new_cat) => cat = new_cat,
55                Directive(key, value) => res.push(UnitDirective::new(cat, key, value)),
56                _ => () // TODO: do something with comments ?
57            }
58        }
59
60        Ok(res)
61    }
62
63    fn get_first_category<'b>(unit_items: &'b Vec<SystemdItem<'b>>) -> Result<&'b str, String> {
64        use self::SystemdItem::*;
65
66        let first_non_comment = unit_items.iter().find(|&item| { match *item {
67            Comment(_) => false,
68            _ => true
69        }});
70
71        if let Some(&SystemdItem::Category(first_cat)) = first_non_comment {
72            Ok(first_cat)
73        } else {
74            return Err(format!("The first non-comment line must be a [Category] (found: {:?})", unit_items.get(0)))
75        }
76    }
77}
78
79#[derive(PartialEq, Eq, Clone, Debug)]
80pub struct SystemdUnit {
81    directives: HashMap<String, DirectiveEntry>,
82}
83
84#[derive(PartialEq, Eq, Clone, Debug)]
85pub enum DirectiveEntry {
86    Solo(UnitDirective),
87    Many(Vec<UnitDirective>)
88}
89
90impl DirectiveEntry {
91    pub fn category(&self) -> String {
92        use self::DirectiveEntry::*;
93
94        match *self {
95            Solo(ref entry) => entry.category().into(),
96            Many(ref entries) => entries.get(0).expect("len > 1").category().into()
97        }
98    }
99}
100
101impl SystemdUnit {
102
103    pub fn new(unit_items: &Vec<SystemdItem>) -> Result<SystemdUnit, String> {
104
105        let directives = try!(
106            UnitDirective::item_list_to_unit_directive_list(&unit_items)
107        );
108
109        let directives_hash = try!(
110            SystemdUnit::hash_from_directives(directives)
111        );
112
113        let res = SystemdUnit {
114            directives: directives_hash
115        };
116        Ok(res)
117    }
118
119    fn hash_from_directives(directives: Vec<UnitDirective>) -> Result<HashMap<String, DirectiveEntry>, String> {
120
121        use self::DirectiveEntry::*;
122        use std::collections::hash_map::Entry;
123
124        let mut directives_hash = HashMap::new();
125
126        for directive in directives {
127            match directives_hash.entry(directive.key.clone()) {
128                Entry::Vacant(entry) => { entry.insert(Solo(directive)); },
129                Entry::Occupied(mut entry_container) => {
130                    let mut vecs = vec!();
131                    // FIXME do not clone :(
132                    match *entry_container.get() {
133                        Solo(ref first_dir) => { vecs.push(first_dir.clone()); },
134                        Many(ref dirs) => { vecs = dirs.clone(); }
135                    }
136                    vecs.push(directive);
137                    try!(SystemdUnit::validate_many(&vecs));
138                    entry_container.insert(Many(vecs));
139                },
140            }
141        }
142
143        Ok(directives_hash)
144    }
145
146    fn validate_many(dirs: &Vec<UnitDirective>) -> Result<(), String> {
147        // contract: invariant
148        assert!(dirs.len() >= 2);
149
150        let first = dirs.first().unwrap();
151        let last = dirs.last().unwrap();
152        if first.category == last.category {
153            Ok(())
154        } else {
155            Err(format!(
156                    "The same directive is repeated many times in different categories: {:?}, {:?}",
157                    first, last
158            ))
159        }
160    }
161
162    pub fn lookup_by_key(&self, key: &str) -> Option<&DirectiveEntry> {
163        self.directives.get(key)
164    }
165
166    pub fn lookup_by_category(&self, category: &str) -> Vec<&DirectiveEntry> {
167        self.directives
168            .values()
169            .filter(|directive| directive.category() == category)
170            .collect()
171    }
172
173    pub fn has_key(&self, key: &str) -> bool {
174        self.directives.contains_key(key)
175    }
176
177    pub fn has_category(&self, category: &str) -> bool {
178        self.directives
179            .values()
180            .any(|directive| directive.category() == category)
181    }
182
183    pub fn keys(&self) -> Vec<&DirectiveEntry> {
184        self.directives.values().collect()
185    }
186
187    pub fn categories(&self) -> Vec<String> {
188        use itertools::Itertools;
189
190        self.directives
191            .values()
192            .unique_by(|entry| entry.category())
193            .map(|entry| entry.category())
194            .sorted()
195    }
196}
197