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