]> git.armaanb.net Git - gen-shell.git/blob - src/libshared/src/Configuration.cpp
added install instructions
[gen-shell.git] / src / libshared / src / Configuration.cpp
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez.
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22 //
23 // http://www.opensource.org/licenses/mit-license.php
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26
27 #include <cmake.h>
28 #include <Configuration.h>
29 #include <inttypes.h>
30 #include <stdlib.h>
31 #include <FS.h>
32 #include <JSON.h>
33 #include <shared.h>
34 #include <format.h>
35
36 ////////////////////////////////////////////////////////////////////////////////
37 bool setVariableInFile (
38   const std::string& file,
39   const std::string& name,
40   const std::string& value)
41 {
42   // Read the file contents.
43   std::vector <std::string> contents;
44   File::read (file, contents);
45
46   bool found = false;
47   bool change = false;
48
49   for (auto& line : contents)
50   {
51     // If there is a comment on the line, it must follow the pattern.
52     auto comment = line.find ('#');
53     auto pos     = line.find (name + '=');
54
55     if (pos != std::string::npos &&
56         (comment == std::string::npos ||
57          comment > pos))
58     {
59       found = true;
60       if (comment != std::string::npos)
61         line = name + '=' + value + ' ' + line.substr (comment);
62       else
63         line = name + '=' + value;
64
65       change = true;
66     }
67   }
68
69   // Not found, so append instead.
70   if (! found)
71   {
72     contents.push_back (name + '=' + value);
73     change = true;
74   }
75
76   if (change)
77     File::write (file, contents);
78
79   return change;
80 }
81
82 ////////////////////////////////////////////////////////////////////////////////
83 bool unsetVariableInFile (
84   const std::string& file,
85   const std::string& name)
86 {
87   // Read configuration file.
88   std::vector <std::string> contents;
89   File::read (file, contents);
90
91   bool change = false;
92
93   for (auto line = contents.begin (); line != contents.end (); )
94   {
95     bool lineDeleted = false;
96
97     // If there is a comment on the line, it must follow the pattern.
98     auto comment = line->find ('#');
99     auto pos     = line->find (name + '=');
100
101     if (pos != std::string::npos &&
102         (comment == std::string::npos ||
103          comment > pos))
104     {
105       // vector::erase method returns a valid iterator to the next object
106       line = contents.erase (line);
107       lineDeleted = true;
108       change = true;
109     }
110
111     if (! lineDeleted)
112       line++;
113   }
114
115   if (change)
116     File::write (file, contents);
117
118   return change;
119 }
120
121 ////////////////////////////////////////////////////////////////////////////////
122 // Read the Configuration file and populate the *this map.  The file format is
123 // simply lines with name=value pairs.  Whitespace between name, = and value is
124 // not tolerated, but blank lines and comments starting with # are allowed.
125 //
126 // Nested files are now supported, with the following construct:
127 //   include /absolute/path/to/file
128 //
129 void Configuration::load (const std::string& file, int nest /* = 1 */)
130 {
131   if (nest > 10)
132     throw std::string ("Configuration files may only be nested to 10 levels.");
133
134   // Read the file, then parse the contents.
135   File config (file);
136
137   if (nest == 1)
138     _original_file = config;
139
140   if (config.exists () &&
141       config.readable ())
142   {
143     std::string contents;
144     if (File::read (file, contents) && contents.length ())
145       parse (contents, nest);
146   }
147 }
148
149 ////////////////////////////////////////////////////////////////////////////////
150 // Write the Configuration file.
151 void Configuration::save ()
152 {
153   std::string contents;
154   for (const auto& i : *this)
155     contents += i.first + "=" + i.second + '\n';
156
157   File::write (_original_file, contents);
158   _dirty = false;
159 }
160
161 ////////////////////////////////////////////////////////////////////////////////
162 void Configuration::parse (const std::string& input, int nest /* = 1 */)
163 {
164   // Shortcut case for default constructor.
165   if (input.length () == 0)
166     return;
167
168   // Parse each line.
169   for (auto& line : split (input, '\n'))
170   {
171     // Remove comments.
172     auto pound = line.find ('#');
173     if (pound != std::string::npos)
174       line = line.substr (0, pound);
175
176     // Skip empty lines.
177     line = trim (line);
178     if (line.length () > 0)
179     {
180       auto equal = line.find ('=');
181       if (equal != std::string::npos)
182       {
183         std::string key   = trim (line.substr (0, equal));
184         std::string value = trim (line.substr (equal+1, line.length () - equal));
185
186         (*this)[key] = json::decode (value);
187       }
188       else
189       {
190         auto include = line.find ("include");
191         if (include != std::string::npos)
192         {
193           Path included (trim (line.substr (include + 7)));
194           if (included.is_absolute ())
195           {
196             if (included.readable ())
197               load (included, nest + 1);
198             else
199               throw format ("Could not read include file '{1}'.", included._data);
200           }
201           else
202             throw format ("Can only include files with absolute paths, not '{1}'", included._data);
203         }
204         else
205           throw format ("Malformed entry '{1}' in config file.", line);
206       }
207     }
208   }
209
210   _dirty = true;
211 }
212
213 ////////////////////////////////////////////////////////////////////////////////
214 bool Configuration::has (const std::string& key) const
215 {
216   return (*this).find (key) != (*this).end ();
217 }
218
219 ////////////////////////////////////////////////////////////////////////////////
220 // Return the configuration value given the specified key.
221 std::string Configuration::get (const std::string& key) const
222 {
223   auto found = find (key);
224   if (found != end ())
225     return found->second;
226
227   return "";
228 }
229
230 ////////////////////////////////////////////////////////////////////////////////
231 int Configuration::getInteger (const std::string& key) const
232 {
233   auto found = find (key);
234   if (found != end ())
235     return strtoimax (found->second.c_str (), nullptr, 10);
236
237   return 0;
238 }
239
240 ////////////////////////////////////////////////////////////////////////////////
241 double Configuration::getReal (const std::string& key) const
242 {
243   auto found = find (key);
244   if (found != end ())
245     return strtod (found->second.c_str (), nullptr);
246
247   return 0.0;
248 }
249
250 ////////////////////////////////////////////////////////////////////////////////
251 bool Configuration::getBoolean (const std::string& key) const
252 {
253   auto found = find (key);
254   if (found != end ())
255   {
256     auto value = lowerCase (found->second);
257     if (value == "true"   ||
258         value == "1"      ||
259         value == "y"      ||
260         value == "yes"    ||
261         value == "on")
262       return true;
263   }
264
265   return false;
266 }
267
268 ////////////////////////////////////////////////////////////////////////////////
269 void Configuration::set (const std::string& key, const int value)
270 {
271   (*this)[key] = format (value);
272   _dirty = true;
273 }
274
275 ////////////////////////////////////////////////////////////////////////////////
276 void Configuration::set (const std::string& key, const double value)
277 {
278   (*this)[key] = format (value, 1, 8);
279   _dirty = true;
280 }
281
282 ////////////////////////////////////////////////////////////////////////////////
283 void Configuration::set (const std::string& key, const std::string& value)
284 {
285   (*this)[key] = value;
286   _dirty = true;
287 }
288
289 ////////////////////////////////////////////////////////////////////////////////
290 // Autovivification is ok here.
291 void Configuration::setIfBlank (const std::string& key, const std::string& value)
292 {
293   if ((*this)[key] == "")
294   {
295     (*this)[key] = value;
296     _dirty = true;
297   }
298 }
299
300 ////////////////////////////////////////////////////////////////////////////////
301 // Provide a vector of all configuration keys.
302 std::vector <std::string> Configuration::all () const
303 {
304   std::vector <std::string> items;
305   for (const auto& it : *this)
306     items.push_back (it.first);
307
308   return items;
309 }
310
311 ////////////////////////////////////////////////////////////////////////////////
312 std::string Configuration::file () const
313 {
314   return _original_file._data;
315 }
316
317 ////////////////////////////////////////////////////////////////////////////////
318 bool Configuration::dirty ()
319 {
320   return _dirty;
321 }
322
323 ////////////////////////////////////////////////////////////////////////////////