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 _ => () }
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 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 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