varconf  1.0.3
Configuration library for the Worldforge system.
config.cpp
1 /*
2  * config.cpp - implementation of the main configuration class.
3  * Copyright (C) 2001, Stefanus Du Toit, Joseph Zupko
4  * (C) 2003-2006 Alistair Riddoch
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  *
20  * Contact: Joseph Zupko
21  * jaz147@psu.edu
22  *
23  * 189 Reese St.
24  * Old Forge, PA 18518
25  */
26 
27 #include "config.h"
28 
29 #include <cstdio>
30 #include <iostream>
31 #include <fstream>
32 #include <string>
33 #include <sstream>
34 
35 #ifndef _WIN32
36 
37 extern char** environ;
38 
39 
40 // on OS-X, the CRT doesn't expose the environ symbol. The following
41 // code (found on Google) provides a value to link against, and a
42 // further tweak in getEnv gets the actual value using _NS evil.
43 #if defined(__APPLE__)
44  #include <crt_externs.h>
45  char **environ = NULL;
46 #endif
47 
48 #endif // _WIN32
49 
50 namespace {
51  enum state_t {
52  S_EXPECT_NAME, // Expect the start of a name/section/comment
53  S_SECTION, // Parsing a section name
54  S_NAME, // Parsing an item name
55  S_COMMENT, // Parsing a comment
56  S_EXPECT_EQ, // Expect an equal sign
57  S_EXPECT_VALUE, // Expect the start of a value
58  S_VALUE, // Parsing a value
59  S_QUOTED_VALUE, // Parsing a "quoted" value
60  S_EXPECT_EOL // Expect the end of the line
61  };
62 
63  enum ctype_t {
64  C_SPACE, // Whitespace
65  C_NUMERIC, // 0-9
66  C_ALPHA, // a-z, A-Z
67  C_DASH, // '-' and '_'
68  C_EQ, // '='
69  C_QUOTE, // '"'
70  C_SQUARE_OPEN, // '['
71  C_SQUARE_CLOSE, // ']'
72  C_HASH, // '#'
73  C_ESCAPE, // '\' (an "escape")
74  C_EOL, // End of the line
75  C_OTHER // Anything else
76  };
77 
78  ctype_t ctype(char c)
79  {
80  if (c=='\n') return C_EOL;
81  if (isspace(c)) return C_SPACE;
82  if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return C_ALPHA;
83  if (isdigit(c)) return C_NUMERIC;
84  if (c == '-' || c == '_') return C_DASH;
85  if (c == '=') return C_EQ;
86  if (c == '"') return C_QUOTE;
87  if (c == '[') return C_SQUARE_OPEN;
88  if (c == ']') return C_SQUARE_CLOSE;
89  if (c == '#') return C_HASH;
90  if (c == '\\') return C_ESCAPE;
91  return C_OTHER;
92  }
93 }
94 
95 namespace varconf {
96 
97 Config* Config::m_instance = nullptr;
98 
99 Config* Config::inst()
100 {
101  if (m_instance == nullptr)
102  m_instance = new Config;
103 
104  return m_instance;
105 }
106 
107 Config::Config(const Config & conf)
108  : trackable(conf) {
109  m_conf = conf.m_conf;
110  m_par_lookup = conf.m_par_lookup;
111 }
112 
113 Config::~Config()
114 {
115  if (m_instance == this)
116  m_instance = nullptr;
117 }
118 
119 std::ostream & operator <<(std::ostream & out, Config & conf)
120 {
121  if (!conf.writeToStream(out, USER)) {
122  conf.sige.emit("\nVarconf Error: error while trying to write "
123  "configuration data to output stream.\n");
124  }
125 
126  return out;
127 }
128 
129 std::istream & operator >>(std::istream & in, Config & conf)
130 {
131  try {
132  conf.parseStream(in, USER);
133  }
134  catch (const ParseError& p) {
135  std::stringstream ss;
136  ss << "Varconf Error: parser exception throw while parsing input stream.\n" << p.what();
137  conf.sige.emit(ss.str().c_str());
138  }
139 
140  return in;
141 }
142 
143 bool operator ==(const Config & one, const Config & two)
144 {
145  return one.m_conf == two.m_conf && one.m_par_lookup == two.m_par_lookup;
146 }
147 
148 void Config::clean(std::string & str)
149 {
150  ctype_t c;
151 
152  for (char & i : str) {
153  c = ctype(i);
154 
155  if (c != C_NUMERIC && c != C_ALPHA && c != C_DASH) {
156  i = '_';
157  } else {
158  i = (char) tolower(i);
159  }
160  }
161 }
162 
163 bool Config::erase(const std::string & section, const std::string & key)
164 {
165  if (find(section)) {
166  if (key.empty()) {
167  m_conf.erase(section);
168  return true;
169  } else if (find(section, key)) {
170  m_conf[section].erase(key);
171  return true;
172  }
173  }
174 
175  return false;
176 }
177 
178 bool Config::find(const std::string & section, const std::string & key) const
179 {
180  auto I = m_conf.find(section);
181  if (I != m_conf.end()) {
182  if (key.empty()) {
183  return true;
184  }
185  const sec_map & sectionRef = I->second;
186  auto J = sectionRef.find(key);
187  if (J != sectionRef.end()) {
188  return true;
189  }
190  }
191 
192  return false;
193 }
194 
195 bool Config::findSection(const std::string & section) const
196 {
197  return find(section);
198 }
199 
200 bool Config::findItem(const std::string & section, const std::string & key) const
201 {
202  return find(section, key);
203 }
204 
205 int Config::getCmdline(int argc, char** argv, Scope scope)
206 {
207  int optind = 1;
208 
209  for (int i = 1; i < argc; i++) {
210  if (argv[i][0] != '-' ) {
211  continue;
212  }
213 
214  std::string section, name, value, arg;
215  bool fnd_sec = false, fnd_nam = false;
216  size_t mark = 2;
217  if (argv[i][1] == '-' && argv[i][2] != '\0') {
218  // long argument
219  arg = argv[i];
220 
221  for (size_t j = 2; j < arg.size(); j++) {
222  if (arg[j] == ':' && arg[j+1] != '\0' && !fnd_sec && !fnd_nam) {
223  section = arg.substr(mark, (j - mark));
224  fnd_sec = true;
225  mark = j + 1;
226  }
227  else if (arg[j] == '=' && (j - mark) > 1) {
228  name = arg.substr(mark, (j - mark));
229  fnd_nam = true;
230  value = arg.substr((j + 1), (arg.size() - (j + 1)));
231  break;
232  }
233  }
234 
235  if (!fnd_nam && arg.size() != mark) {
236  name = arg.substr(mark, (arg.size() - mark));
237  }
238 
239  } else if (argv[i][1] != '-' && argv[i][1] != '\0') {
240  // short argument
241  auto I = m_par_lookup.find(argv[i][1]);
242 
243  if (I != m_par_lookup.end()) {
244  name = ((*I).second).first;
245  bool needs_value = ((*I).second).second;
246 
247  if (needs_value && (i+1) < argc && argv[i+1][0] != 0
248  && argv[i+1][0] != '-') {
249  value = argv[++i];
250  }
251  else {
252  std::stringstream ss;
253  ss << "Varconf Warning: short argument \""<< argv[i] <<"\""
254  " given on command-line expects a value"
255  " but none was given.";
256  sige.emit(ss.str().c_str());
257  }
258  }
259  else {
260  std::stringstream ss;
261  ss << "Varconf Warning: short argument \""<<argv[i]<<"\""
262  " given on command-line does not exist in"
263  " the lookup table.";
264  sige.emit(ss.str().c_str());
265  }
266  }
267 
268  if (!name.empty()) {
269  setItem(section, name, value, scope);
270  optind = i + 1;
271  }
272  }
273  return optind;
274 }
275 
276 void Config::getEnv(const std::string & prefix, Scope scope)
277 {
278  std::string name, value, section, env;
279  size_t eq_pos = 0;
280 
281 #if defined(__APPLE__)
282  if (environ == NULL)
283  environ = *_NSGetEnviron();
284 #endif
285 
286  for (size_t i = 0; environ[i] != nullptr; i++) {
287  env = environ[i];
288 
289  if (env.substr(0, prefix.size()) == prefix) {
290  eq_pos = env.find('=');
291 
292  if (eq_pos != std::string::npos) {
293  name = env.substr(prefix.size(), (eq_pos - prefix.size()));
294  value = env.substr((eq_pos + 1), (env.size() - (eq_pos + 1)));
295  }
296  else {
297  name = env.substr(prefix.size(), (env.size() - prefix.size()));
298  value = "";
299  }
300 
301  setItem(section, name, value, scope);
302  }
303  }
304 }
305 
306 const sec_map & Config::getSection(const std::string & section)
307 {
308  // TODO: This will create a new section in the config file. Is really the
309  // desired behaviour?
310  return m_conf[section];
311 }
312 
313 Variable Config::getItem(const std::string & section, const std::string & key) const
314 {
315  auto I = m_conf.find(section);
316  if (I != m_conf.end()) {
317  auto J = I->second.find(key);
318  if (J != I->second.end()) {
319  return J->second;
320  }
321  }
322  return Variable();
323 }
324 
325 const conf_map& Config::getSections() const
326 {
327  return m_conf;
328 }
329 
330 
331 void Config::parseStream(std::istream & in, Scope scope)
332 {
333  char c;
334  bool escaped = false;
335  size_t line = 1, col = 0;
336  std::string name, value, section;
337  state_t state = S_EXPECT_NAME;
338 
339  while (in.get(c)) {
340  col++;
341  switch (state) {
342  case S_EXPECT_NAME :
343  switch (ctype(c)) {
344  case C_ALPHA:
345  case C_NUMERIC:
346  case C_DASH:
347  state = S_NAME;
348  name = c;
349  break;
350  case C_SQUARE_OPEN:
351  section = "";
352  state = S_SECTION;
353  break;
354  case C_SPACE:
355  case C_EOL:
356  break;
357  case C_HASH:
358  state = S_COMMENT;
359  break;
360  default:
361  throw ParseError("item name", (int) line, (int) col);
362  }
363  break;
364  case S_SECTION :
365  switch (ctype(c)) {
366  case C_ALPHA:
367  case C_NUMERIC:
368  case C_DASH:
369  section += c;
370  break;
371  case C_SQUARE_CLOSE:
372  state = S_EXPECT_EOL;
373  break;
374  default:
375  throw ParseError("']'", (int) line, (int) col);
376  }
377  break;
378  case S_NAME :
379  switch (ctype(c)) {
380  case C_ALPHA:
381  case C_NUMERIC:
382  case C_DASH:
383  name += c;
384  break;
385  case C_EQ:
386  state = S_EXPECT_VALUE;
387  break;
388  case C_SPACE:
389  state = S_EXPECT_EQ;
390  break;
391  default:
392  throw ParseError("'='", (int) line, (int) col);
393  }
394  break;
395  case S_COMMENT :
396  switch (ctype(c)) {
397  case C_EOL:
398  state = S_EXPECT_NAME;
399  break;
400  default:
401  break;
402  }
403  break;
404  case S_EXPECT_EQ:
405  switch (ctype(c)) {
406  case C_SPACE:
407  break;
408  case C_EQ:
409  state = S_EXPECT_VALUE;
410  break;
411  default:
412  throw ParseError("'='", (int) line, (int) col);
413  }
414  break;
415  case S_EXPECT_VALUE:
416  switch (ctype(c)) {
417  case C_ALPHA:
418  case C_NUMERIC:
419  case C_DASH:
420  state = S_VALUE;
421  value = c;
422  break;
423  case C_QUOTE:
424  value = "";
425  state = S_QUOTED_VALUE;
426  break;
427  case C_EOL:
428  value = "";
429  state = S_EXPECT_NAME;
430  setItem(section, name, value, scope);
431  break;
432  case C_SPACE:
433  break;
434  default:
435  throw ParseError("value", (int) line, (int) col);
436  }
437  break;
438  case S_VALUE:
439  switch (ctype(c)) {
440  case C_QUOTE:
441  throw ParseError("value", (int) line, (int) col);
442  case C_SPACE:
443  state = S_EXPECT_EOL;
444  setItem(section, name, value, scope);
445  break;
446  case C_EOL:
447  state = S_EXPECT_NAME;
448  setItem(section, name, value, scope);
449  break;
450  case C_HASH:
451  state = S_COMMENT;
452  setItem(section, name, value, scope);
453  break;
454  default:
455  value += c;
456  break;
457  }
458  break;
459  case S_QUOTED_VALUE:
460  if (escaped) {
461  value += c;
462  escaped = false;
463  } else {
464  switch (ctype(c)) {
465  case C_QUOTE:
466  state = S_EXPECT_EOL;
467  setItem(section, name, value, scope);
468  break;
469  case C_ESCAPE:
470  escaped = true;
471  break;
472  default:
473  value += c;
474  break;
475  }
476  }
477  break;
478  case S_EXPECT_EOL:
479  switch (ctype(c)) {
480  case C_HASH:
481  state = S_COMMENT;
482  break;
483  case C_EOL:
484  state = S_EXPECT_NAME;
485  break;
486  case C_SPACE:
487  break;
488  default:
489  throw ParseError("end of line", (int) line, (int) col);
490  break;
491  }
492  break;
493  default:
494  break;
495  }
496  if (c == '\n') {
497  line++;
498  col = 0;
499  }
500  } // while (in.get(c))
501 
502  if (state == S_QUOTED_VALUE) {
503  throw ParseError("\"", (int) line, (int) col);
504  }
505 
506  if (state == S_VALUE) {
507  setItem(section, name, value, scope);
508  } else if (state == S_EXPECT_VALUE) {
509  setItem(section, name, "", scope);
510  }
511 }
512 
513 bool Config::readFromFile(const std::string & filename, Scope scope)
514 {
515  std::ifstream fin(filename.c_str());
516 
517  if (fin.fail()) {
518  std::stringstream ss;
519  ss << "Varconf Error: could not open configuration file"
520  " \""<<filename<<"\" for input.";
521  sige.emit(ss.str().c_str());
522 
523  return false;
524  }
525 
526  try {
527  parseStream(fin, scope);
528  }
529  catch (const ParseError& p) {
530  std::stringstream ss;
531  ss << "Varconf Error: parsing exception thrown while "
532  "parsing \""<<filename<<"\".\n"<< p.what();
533  sige.emit(ss.str().c_str());
534  return false;
535  }
536 
537  return true;
538 }
539 
540 void Config::setItem(const std::string & section,
541  const std::string & key,
542  const Variable & item,
543  Scope scope)
544 {
545  if (key.empty()) {
546  std::stringstream ss;
547  ss << "Varconf Warning: blank key under section \""<<section<<"\""
548  " sent to setItem() method.";
549  sige.emit(ss.str().c_str());
550  }
551  else {
552  std::string sec_clean = section;
553  std::string key_clean = key;
554 
555  clean(sec_clean);
556  clean(key_clean);
557 
558  item->setScope(scope);
559  std::map<std::string, Variable> & section_map = m_conf[sec_clean];
560  std::map<std::string, Variable>::const_iterator I = section_map.find(key_clean);
561  if (I == section_map.end() || I->second != item) {
562  section_map[key_clean] = item;
563  }
564 
565  sig.emit();
566  sigv.emit(sec_clean, key_clean);
567  sigsv.emit(sec_clean, key_clean, *this);
568  }
569 }
570 
571 void Config::setParameterLookup(char s_name, const std::string & l_name, bool value)
572 {
573  m_par_lookup[s_name] = std::pair<std::string, bool>(l_name, value);
574 }
575 
576 bool Config::writeToFile(const std::string & filename, Scope scope_mask) const
577 {
578  std::ofstream fout(filename.c_str());
579 
580  if (fout.fail()) {
581  std::stringstream ss;
582  ss << "Varconf Error: could not open configuration file"
583  " \""<< filename <<"\" for output.";
584  sige.emit(ss.str().c_str());
585 
586  return false;
587  }
588 
589  return writeToStream(fout, scope_mask);
590 }
591 
592 bool Config::writeToStream(std::ostream & out, Scope scope_mask) const
593 {
594  conf_map::const_iterator I;
595  sec_map::const_iterator J;
596 
597  for (I = m_conf.begin(); I != m_conf.end(); I++) {
598  out << std::endl << "[" << (*I).first << "]\n\n";
599 
600  for (J = (*I).second.begin(); J != (*I).second.end(); J++) {
601  if (J->second->scope() & scope_mask) {
602  out << (*J).first << " = \"" << (*J).second << "\"\n";
603  }
604  }
605  }
606 
607  return true;
608 }
609 
610 } // namespace varconf
611