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) {
248  if ((i+1) < argc && argv[i+1][0] != 0 && 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  }
260  else {
261  std::stringstream ss;
262  ss << "Varconf Warning: short argument \""<<argv[i]<<"\""
263  " given on command-line does not exist in"
264  " the lookup table.";
265  sige.emit(ss.str().c_str());
266  }
267  }
268 
269  if (!name.empty()) {
270  setItem(section, name, value, scope);
271  optind = i + 1;
272  }
273  }
274  return optind;
275 }
276 
277 void Config::getEnv(const std::string & prefix, Scope scope)
278 {
279  std::string name, value, section, env;
280  size_t eq_pos = 0;
281 
282 #if defined(__APPLE__)
283  if (environ == NULL)
284  environ = *_NSGetEnviron();
285 #endif
286 
287  for (size_t i = 0; environ[i] != nullptr; i++) {
288  env = environ[i];
289 
290  if (env.substr(0, prefix.size()) == prefix) {
291  eq_pos = env.find('=');
292 
293  if (eq_pos != std::string::npos) {
294  name = env.substr(prefix.size(), (eq_pos - prefix.size()));
295  value = env.substr((eq_pos + 1), (env.size() - (eq_pos + 1)));
296  }
297  else {
298  name = env.substr(prefix.size(), (env.size() - prefix.size()));
299  value = "";
300  }
301 
302  setItem(section, name, value, scope);
303  }
304  }
305 }
306 
307 const sec_map & Config::getSection(const std::string & section)
308 {
309  // TODO: This will create a new section in the config file. Is really the
310  // desired behaviour?
311  return m_conf[section];
312 }
313 
314 Variable Config::getItem(const std::string & section, const std::string & key) const
315 {
316  auto I = m_conf.find(section);
317  if (I != m_conf.end()) {
318  auto J = I->second.find(key);
319  if (J != I->second.end()) {
320  return J->second;
321  }
322  }
323  return Variable();
324 }
325 
326 const conf_map& Config::getSections() const
327 {
328  return m_conf;
329 }
330 
331 
332 void Config::parseStream(std::istream & in, Scope scope)
333 {
334  char c;
335  bool escaped = false;
336  size_t line = 1, col = 0;
337  std::string name, value, section;
338  state_t state = S_EXPECT_NAME;
339 
340  while (in.get(c)) {
341  col++;
342  switch (state) {
343  case S_EXPECT_NAME :
344  switch (ctype(c)) {
345  case C_ALPHA:
346  case C_NUMERIC:
347  case C_DASH:
348  state = S_NAME;
349  name = c;
350  break;
351  case C_SQUARE_OPEN:
352  section = "";
353  state = S_SECTION;
354  break;
355  case C_SPACE:
356  case C_EOL:
357  break;
358  case C_HASH:
359  state = S_COMMENT;
360  break;
361  default:
362  throw ParseError("item name", (int) line, (int) col);
363  }
364  break;
365  case S_SECTION :
366  switch (ctype(c)) {
367  case C_ALPHA:
368  case C_NUMERIC:
369  case C_DASH:
370  section += c;
371  break;
372  case C_SQUARE_CLOSE:
373  state = S_EXPECT_EOL;
374  break;
375  default:
376  throw ParseError("']'", (int) line, (int) col);
377  }
378  break;
379  case S_NAME :
380  switch (ctype(c)) {
381  case C_ALPHA:
382  case C_NUMERIC:
383  case C_DASH:
384  name += c;
385  break;
386  case C_EQ:
387  state = S_EXPECT_VALUE;
388  break;
389  case C_SPACE:
390  state = S_EXPECT_EQ;
391  break;
392  default:
393  throw ParseError("'='", (int) line, (int) col);
394  }
395  break;
396  case S_COMMENT :
397  switch (ctype(c)) {
398  case C_EOL:
399  state = S_EXPECT_NAME;
400  break;
401  default:
402  break;
403  }
404  break;
405  case S_EXPECT_EQ:
406  switch (ctype(c)) {
407  case C_SPACE:
408  break;
409  case C_EQ:
410  state = S_EXPECT_VALUE;
411  break;
412  default:
413  throw ParseError("'='", (int) line, (int) col);
414  }
415  break;
416  case S_EXPECT_VALUE:
417  switch (ctype(c)) {
418  case C_ALPHA:
419  case C_NUMERIC:
420  case C_DASH:
421  state = S_VALUE;
422  value = c;
423  break;
424  case C_QUOTE:
425  value = "";
426  state = S_QUOTED_VALUE;
427  break;
428  case C_EOL:
429  value = "";
430  state = S_EXPECT_NAME;
431  setItem(section, name, value, scope);
432  break;
433  case C_SPACE:
434  break;
435  default:
436  throw ParseError("value", (int) line, (int) col);
437  }
438  break;
439  case S_VALUE:
440  switch (ctype(c)) {
441  case C_QUOTE:
442  throw ParseError("value", (int) line, (int) col);
443  case C_SPACE:
444  state = S_EXPECT_EOL;
445  setItem(section, name, value, scope);
446  break;
447  case C_EOL:
448  state = S_EXPECT_NAME;
449  setItem(section, name, value, scope);
450  break;
451  case C_HASH:
452  state = S_COMMENT;
453  setItem(section, name, value, scope);
454  break;
455  default:
456  value += c;
457  break;
458  }
459  break;
460  case S_QUOTED_VALUE:
461  if (escaped) {
462  value += c;
463  escaped = false;
464  } else {
465  switch (ctype(c)) {
466  case C_QUOTE:
467  state = S_EXPECT_EOL;
468  setItem(section, name, value, scope);
469  break;
470  case C_ESCAPE:
471  escaped = true;
472  break;
473  default:
474  value += c;
475  break;
476  }
477  }
478  break;
479  case S_EXPECT_EOL:
480  switch (ctype(c)) {
481  case C_HASH:
482  state = S_COMMENT;
483  break;
484  case C_EOL:
485  state = S_EXPECT_NAME;
486  break;
487  case C_SPACE:
488  break;
489  default:
490  throw ParseError("end of line", (int) line, (int) col);
491  break;
492  }
493  break;
494  default:
495  break;
496  }
497  if (c == '\n') {
498  line++;
499  col = 0;
500  }
501  } // while (in.get(c))
502 
503  if (state == S_QUOTED_VALUE) {
504  throw ParseError("\"", (int) line, (int) col);
505  }
506 
507  if (state == S_VALUE) {
508  setItem(section, name, value, scope);
509  } else if (state == S_EXPECT_VALUE) {
510  setItem(section, name, "", scope);
511  }
512 }
513 
514 bool Config::readFromFile(const std::string & filename, Scope scope)
515 {
516  std::ifstream fin(filename.c_str());
517 
518  if (fin.fail()) {
519  std::stringstream ss;
520  ss << "Varconf Error: could not open configuration file"
521  " \""<<filename<<"\" for input.";
522  sige.emit(ss.str().c_str());
523 
524  return false;
525  }
526 
527  try {
528  parseStream(fin, scope);
529  }
530  catch (const ParseError& p) {
531  std::stringstream ss;
532  ss << "Varconf Error: parsing exception thrown while "
533  "parsing \""<<filename<<"\".\n"<< p.what();
534  sige.emit(ss.str().c_str());
535  return false;
536  }
537 
538  return true;
539 }
540 
541 void Config::setItem(const std::string & section,
542  const std::string & key,
543  const Variable & item,
544  Scope scope)
545 {
546  if (key.empty()) {
547  std::stringstream ss;
548  ss << "Varconf Warning: blank key under section \""<<section<<"\""
549  " sent to setItem() method.";
550  sige.emit(ss.str().c_str());
551  }
552  else {
553  std::string sec_clean = section;
554  std::string key_clean = key;
555 
556  clean(sec_clean);
557  clean(key_clean);
558 
559  item->setScope(scope);
560  std::map<std::string, Variable> & section_map = m_conf[sec_clean];
561  std::map<std::string, Variable>::const_iterator I = section_map.find(key_clean);
562  if (I == section_map.end() || I->second != item) {
563  section_map[key_clean] = item;
564  }
565 
566  sig.emit();
567  sigv.emit(sec_clean, key_clean);
568  sigsv.emit(sec_clean, key_clean, *this);
569  }
570 }
571 
572 void Config::setParameterLookup(char s_name, const std::string & l_name, bool value)
573 {
574  m_par_lookup[s_name] = std::pair<std::string, bool>(l_name, value);
575 }
576 
577 bool Config::writeToFile(const std::string & filename, Scope scope_mask) const
578 {
579  std::ofstream fout(filename.c_str());
580 
581  if (fout.fail()) {
582  std::stringstream ss;
583  ss << "Varconf Error: could not open configuration file"
584  " \""<< filename <<"\" for output.";
585  sige.emit(ss.str().c_str());
586 
587  return false;
588  }
589 
590  return writeToStream(fout, scope_mask);
591 }
592 
593 bool Config::writeToStream(std::ostream & out, Scope scope_mask) const
594 {
595  conf_map::const_iterator I;
596  sec_map::const_iterator J;
597 
598  for (I = m_conf.begin(); I != m_conf.end(); I++) {
599  out << std::endl << "[" << (*I).first << "]\n\n";
600 
601  for (J = (*I).second.begin(); J != (*I).second.end(); J++) {
602  if (J->second->scope() & scope_mask) {
603  out << (*J).first << " = \"" << (*J).second << "\"\n";
604  }
605  }
606  }
607 
608  return true;
609 }
610 
611 } // namespace varconf
612