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