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
37extern 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
50namespace {
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
95namespace varconf {
96
97Config* Config::m_instance = nullptr;
98
99Config* Config::inst()
100{
101 if (m_instance == nullptr)
102 m_instance = new Config;
103
104 return m_instance;
105}
106
107Config::Config(const Config & conf)
108 : trackable(conf) {
109 m_conf = conf.m_conf;
110 m_par_lookup = conf.m_par_lookup;
111}
112
113Config::~Config()
114{
115 if (m_instance == this)
116 m_instance = nullptr;
117}
118
119std::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
129std::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
143bool 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
148void 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
163bool 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
178bool 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
195bool Config::findSection(const std::string & section) const
196{
197 return find(section);
198}
199
200bool Config::findItem(const std::string & section, const std::string & key) const
201{
202 return find(section, key);
203}
204
205int 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
277void 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
307const 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
314Variable 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
326const conf_map& Config::getSections() const
327{
328 return m_conf;
329}
330
331
332void 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
514bool 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
541void 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
572void 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
577bool 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
593bool 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