]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/configuration.cc
add default and override handling for Cnf::FindVector
[apt.git] / apt-pkg / contrib / configuration.cc
CommitLineData
6c139d6e
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
5e14c452 3// $Id: configuration.cc,v 1.28 2004/04/30 04:00:15 mdz Exp $
6c139d6e
AL
4/* ######################################################################
5
6 Configuration Class
7
8 This class provides a configuration file and command line parser
9 for a tree-oriented configuration environment. All runtime configuration
10 is stored in here.
7da2b375
AL
11
12 This source is placed in the Public Domain, do with it what you will
13 It was originally written by Jason Gunthorpe <jgg@debian.org>.
6c139d6e
AL
14
15 ##################################################################### */
16 /*}}}*/
17// Include files /*{{{*/
ea542140
DK
18#include <config.h>
19
094a497d 20#include <apt-pkg/configuration.h>
08e8f724 21#include <apt-pkg/error.h>
cdcc6d34 22#include <apt-pkg/strutl.h>
b2e465d6 23#include <apt-pkg/fileutl.h>
a5414e56 24#include <apt-pkg/init.h>
6c139d6e 25
b2e465d6 26#include <vector>
b2e465d6 27#include <fstream>
851a45a8 28#include <iostream>
851a45a8 29
ea542140
DK
30#include <apti18n.h>
31
851a45a8 32using namespace std;
6c139d6e 33 /*}}}*/
9c14e3d6
AL
34
35Configuration *_config = new Configuration;
6c139d6e
AL
36
37// Configuration::Configuration - Constructor /*{{{*/
38// ---------------------------------------------------------------------
39/* */
b2e465d6 40Configuration::Configuration() : ToFree(true)
6c139d6e
AL
41{
42 Root = new Item;
b2e465d6
AL
43}
44Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
45{
46};
47
48 /*}}}*/
49// Configuration::~Configuration - Destructor /*{{{*/
50// ---------------------------------------------------------------------
51/* */
52Configuration::~Configuration()
53{
54 if (ToFree == false)
55 return;
56
57 Item *Top = Root;
58 for (; Top != 0;)
59 {
60 if (Top->Child != 0)
61 {
62 Top = Top->Child;
63 continue;
64 }
65
66 while (Top != 0 && Top->Next == 0)
67 {
68 Item *Parent = Top->Parent;
69 delete Top;
70 Top = Parent;
71 }
72 if (Top != 0)
73 {
74 Item *Next = Top->Next;
75 delete Top;
76 Top = Next;
77 }
78 }
6c139d6e
AL
79}
80 /*}}}*/
0a8a80e5 81// Configuration::Lookup - Lookup a single item /*{{{*/
6c139d6e
AL
82// ---------------------------------------------------------------------
83/* This will lookup a single item by name below another item. It is a
84 helper function for the main lookup function */
85Configuration::Item *Configuration::Lookup(Item *Head,const char *S,
ce857f32 86 unsigned long const &Len,bool const &Create)
6c139d6e
AL
87{
88 int Res = 1;
89 Item *I = Head->Child;
90 Item **Last = &Head->Child;
9c14e3d6 91
7f25bdff
AL
92 // Empty strings match nothing. They are used for lists.
93 if (Len != 0)
94 {
95 for (; I != 0; Last = &I->Next, I = I->Next)
851a45a8 96 if ((Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
7f25bdff
AL
97 break;
98 }
99 else
100 for (; I != 0; Last = &I->Next, I = I->Next);
101
6c139d6e
AL
102 if (Res == 0)
103 return I;
104 if (Create == false)
105 return 0;
106
107 I = new Item;
171c75f1 108 I->Tag.assign(S,Len);
6c139d6e 109 I->Next = *Last;
9c14e3d6 110 I->Parent = Head;
6c139d6e
AL
111 *Last = I;
112 return I;
113}
114 /*}}}*/
115// Configuration::Lookup - Lookup a fully scoped item /*{{{*/
116// ---------------------------------------------------------------------
117/* This performs a fully scoped lookup of a given name, possibly creating
118 new items */
ce857f32 119Configuration::Item *Configuration::Lookup(const char *Name,bool const &Create)
6c139d6e 120{
0a8a80e5
AL
121 if (Name == 0)
122 return Root->Child;
123
6c139d6e
AL
124 const char *Start = Name;
125 const char *End = Start + strlen(Name);
126 const char *TagEnd = Name;
127 Item *Itm = Root;
7f25bdff 128 for (; End - TagEnd >= 2; TagEnd++)
6c139d6e
AL
129 {
130 if (TagEnd[0] == ':' && TagEnd[1] == ':')
131 {
132 Itm = Lookup(Itm,Start,TagEnd - Start,Create);
133 if (Itm == 0)
134 return 0;
135 TagEnd = Start = TagEnd + 2;
136 }
137 }
138
7f25bdff
AL
139 // This must be a trailing ::, we create unique items in a list
140 if (End - Start == 0)
141 {
142 if (Create == false)
143 return 0;
144 }
145
6c139d6e 146 Itm = Lookup(Itm,Start,End - Start,Create);
6c139d6e
AL
147 return Itm;
148}
149 /*}}}*/
150// Configuration::Find - Find a value /*{{{*/
151// ---------------------------------------------------------------------
152/* */
b2e465d6 153string Configuration::Find(const char *Name,const char *Default) const
6c139d6e 154{
b2e465d6 155 const Item *Itm = Lookup(Name);
6c139d6e 156 if (Itm == 0 || Itm->Value.empty() == true)
9c14e3d6
AL
157 {
158 if (Default == 0)
171c75f1 159 return "";
9c14e3d6
AL
160 else
161 return Default;
162 }
163
6c139d6e
AL
164 return Itm->Value;
165}
166 /*}}}*/
3b5421b4 167// Configuration::FindFile - Find a Filename /*{{{*/
9c14e3d6
AL
168// ---------------------------------------------------------------------
169/* Directories are stored as the base dir in the Parent node and the
3b5421b4 170 sub directory in sub nodes with the final node being the end filename
9c14e3d6 171 */
b2e465d6 172string Configuration::FindFile(const char *Name,const char *Default) const
9c14e3d6 173{
db2cca11 174 const Item *RootItem = Lookup("RootDir");
ec76891f
DK
175 std::string result = (RootItem == 0) ? "" : RootItem->Value;
176 if(result.empty() == false && result[result.size() - 1] != '/')
177 result.push_back('/');
db2cca11 178
b2e465d6 179 const Item *Itm = Lookup(Name);
9c14e3d6
AL
180 if (Itm == 0 || Itm->Value.empty() == true)
181 {
ec76891f
DK
182 if (Default != 0)
183 result.append(Default);
9c14e3d6 184 }
ec76891f 185 else
017f9fd6 186 {
ec76891f
DK
187 string val = Itm->Value;
188 while (Itm->Parent != 0)
017f9fd6 189 {
ec76891f
DK
190 if (Itm->Parent->Value.empty() == true)
191 {
192 Itm = Itm->Parent;
193 continue;
194 }
017f9fd6 195
ec76891f
DK
196 // Absolute
197 if (val.length() >= 1 && val[0] == '/')
af13d143
DK
198 {
199 if (val.compare(0, 9, "/dev/null") == 0)
200 val.erase(9);
ec76891f 201 break;
af13d143 202 }
b2e465d6 203
ec76891f
DK
204 // ~/foo or ./foo
205 if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/')
206 break;
207
208 // ../foo
209 if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/')
210 break;
211
212 if (Itm->Parent->Value.end()[-1] != '/')
213 val.insert(0, "/");
214
215 val.insert(0, Itm->Parent->Value);
216 Itm = Itm->Parent;
217 }
218 result.append(val);
b2e465d6
AL
219 }
220
ec76891f
DK
221 // do some normalisation by removing // and /./ from the path
222 size_t found = string::npos;
223 while ((found = result.find("/./")) != string::npos)
224 result.replace(found, 3, "/");
225 while ((found = result.find("//")) != string::npos)
226 result.replace(found, 2, "/");
227
228 return result;
9c14e3d6
AL
229}
230 /*}}}*/
3b5421b4
AL
231// Configuration::FindDir - Find a directory name /*{{{*/
232// ---------------------------------------------------------------------
233/* This is like findfile execept the result is terminated in a / */
b2e465d6 234string Configuration::FindDir(const char *Name,const char *Default) const
3b5421b4
AL
235{
236 string Res = FindFile(Name,Default);
237 if (Res.end()[-1] != '/')
af13d143
DK
238 {
239 size_t const found = Res.rfind("/dev/null");
240 if (found != string::npos && found == Res.size() - 9)
241 return Res; // /dev/null returning
3b5421b4 242 return Res + '/';
af13d143 243 }
3b5421b4
AL
244 return Res;
245}
246 /*}}}*/
50c409c4
DK
247// Configuration::FindVector - Find a vector of values /*{{{*/
248// ---------------------------------------------------------------------
249/* Returns a vector of config values under the given item */
a5414e56
DK
250#if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR < 13)
251vector<string> Configuration::FindVector(const char *Name) const { return FindVector(Name, ""); }
252#endif
253vector<string> Configuration::FindVector(const char *Name, std::string const &Default) const
50c409c4
DK
254{
255 vector<string> Vec;
256 const Item *Top = Lookup(Name);
257 if (Top == NULL)
a5414e56
DK
258 return VectorizeString(Default, ',');
259
260 if (Top->Value.empty() == false)
261 return VectorizeString(Top->Value, ',');
50c409c4
DK
262
263 Item *I = Top->Child;
264 while(I != NULL)
265 {
266 Vec.push_back(I->Value);
267 I = I->Next;
268 }
a5414e56
DK
269 if (Vec.empty() == true)
270 return VectorizeString(Default, ',');
271
50c409c4
DK
272 return Vec;
273}
274 /*}}}*/
6c139d6e
AL
275// Configuration::FindI - Find an integer value /*{{{*/
276// ---------------------------------------------------------------------
277/* */
ce857f32 278int Configuration::FindI(const char *Name,int const &Default) const
6c139d6e 279{
b2e465d6 280 const Item *Itm = Lookup(Name);
6c139d6e
AL
281 if (Itm == 0 || Itm->Value.empty() == true)
282 return Default;
283
284 char *End;
285 int Res = strtol(Itm->Value.c_str(),&End,0);
286 if (End == Itm->Value.c_str())
287 return Default;
288
08e8f724
AL
289 return Res;
290}
291 /*}}}*/
292// Configuration::FindB - Find a boolean type /*{{{*/
293// ---------------------------------------------------------------------
294/* */
ce857f32 295bool Configuration::FindB(const char *Name,bool const &Default) const
08e8f724 296{
b2e465d6 297 const Item *Itm = Lookup(Name);
08e8f724
AL
298 if (Itm == 0 || Itm->Value.empty() == true)
299 return Default;
300
3b5421b4 301 return StringToBool(Itm->Value,Default);
6c139d6e
AL
302}
303 /*}}}*/
b2e465d6
AL
304// Configuration::FindAny - Find an arbitrary type /*{{{*/
305// ---------------------------------------------------------------------
306/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
307string Configuration::FindAny(const char *Name,const char *Default) const
308{
309 string key = Name;
310 char type = 0;
311
312 if (key.size() > 2 && key.end()[-2] == '/')
313 {
314 type = key.end()[-1];
315 key.resize(key.size() - 2);
316 }
317
318 switch (type)
319 {
320 // file
321 case 'f':
322 return FindFile(key.c_str(), Default);
323
324 // directory
325 case 'd':
326 return FindDir(key.c_str(), Default);
327
328 // bool
329 case 'b':
330 return FindB(key, Default) ? "true" : "false";
331
332 // int
333 case 'i':
334 {
335 char buf[16];
cc99a102 336 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
b2e465d6
AL
337 return buf;
338 }
339 }
340
341 // fallback
342 return Find(Name, Default);
343}
344 /*}}}*/
345// Configuration::CndSet - Conditinal Set a value /*{{{*/
346// ---------------------------------------------------------------------
347/* This will not overwrite */
171c75f1 348void Configuration::CndSet(const char *Name,const string &Value)
b2e465d6
AL
349{
350 Item *Itm = Lookup(Name,true);
351 if (Itm == 0)
352 return;
353 if (Itm->Value.empty() == true)
354 Itm->Value = Value;
355}
356 /*}}}*/
78485ab2
DK
357// Configuration::Set - Set an integer value /*{{{*/
358// ---------------------------------------------------------------------
359/* */
360void Configuration::CndSet(const char *Name,int const Value)
361{
362 Item *Itm = Lookup(Name,true);
363 if (Itm == 0 || Itm->Value.empty() == false)
364 return;
365 char S[300];
366 snprintf(S,sizeof(S),"%i",Value);
367 Itm->Value = S;
368}
369 /*}}}*/
6c139d6e
AL
370// Configuration::Set - Set a value /*{{{*/
371// ---------------------------------------------------------------------
372/* */
171c75f1 373void Configuration::Set(const char *Name,const string &Value)
6c139d6e
AL
374{
375 Item *Itm = Lookup(Name,true);
376 if (Itm == 0)
377 return;
378 Itm->Value = Value;
379}
380 /*}}}*/
381// Configuration::Set - Set an integer value /*{{{*/
382// ---------------------------------------------------------------------
383/* */
0df8c3dc 384void Configuration::Set(const char *Name,int const &Value)
6c139d6e
AL
385{
386 Item *Itm = Lookup(Name,true);
387 if (Itm == 0)
388 return;
389 char S[300];
390 snprintf(S,sizeof(S),"%i",Value);
391 Itm->Value = S;
75ef8f14
MV
392}
393 /*}}}*/
394// Configuration::Clear - Clear an single value from a list /*{{{*/
395// ---------------------------------------------------------------------
396/* */
ce857f32 397void Configuration::Clear(string const &Name, int const &Value)
75ef8f14
MV
398{
399 char S[300];
400 snprintf(S,sizeof(S),"%i",Value);
401 Clear(Name, S);
402}
403 /*}}}*/
404// Configuration::Clear - Clear an single value from a list /*{{{*/
405// ---------------------------------------------------------------------
406/* */
ce857f32 407void Configuration::Clear(string const &Name, string const &Value)
75ef8f14
MV
408{
409 Item *Top = Lookup(Name.c_str(),false);
410 if (Top == 0 || Top->Child == 0)
411 return;
412
413 Item *Tmp, *Prev, *I;
414 Prev = I = Top->Child;
415
416 while(I != NULL)
417 {
418 if(I->Value == Value)
419 {
420 Tmp = I;
421 // was first element, point parent to new first element
422 if(Top->Child == Tmp)
423 Top->Child = I->Next;
424 I = I->Next;
425 Prev->Next = I;
426 delete Tmp;
427 } else {
428 Prev = I;
429 I = I->Next;
430 }
431 }
432
d8a06f6e
MV
433}
434 /*}}}*/
435// Configuration::Clear - Clear everything /*{{{*/
436// ---------------------------------------------------------------------
437void Configuration::Clear()
438{
439 const Configuration::Item *Top = Tree(0);
440 while( Top != 0 )
441 {
442 Clear(Top->FullTag());
443 Top = Top->Next;
444 }
6c139d6e
AL
445}
446 /*}}}*/
b2e465d6
AL
447// Configuration::Clear - Clear an entire tree /*{{{*/
448// ---------------------------------------------------------------------
449/* */
ce857f32 450void Configuration::Clear(string const &Name)
b2e465d6
AL
451{
452 Item *Top = Lookup(Name.c_str(),false);
75ef8f14 453 if (Top == 0)
b2e465d6 454 return;
75ef8f14 455
171c75f1 456 Top->Value.clear();
b2e465d6
AL
457 Item *Stop = Top;
458 Top = Top->Child;
459 Stop->Child = 0;
460 for (; Top != 0;)
461 {
462 if (Top->Child != 0)
463 {
464 Top = Top->Child;
465 continue;
466 }
467
468 while (Top != 0 && Top->Next == 0)
469 {
470 Item *Tmp = Top;
471 Top = Top->Parent;
472 delete Tmp;
473
474 if (Top == Stop)
475 return;
476 }
477
478 Item *Tmp = Top;
479 if (Top != 0)
480 Top = Top->Next;
481 delete Tmp;
482 }
483}
484 /*}}}*/
08e8f724
AL
485// Configuration::Exists - Returns true if the Name exists /*{{{*/
486// ---------------------------------------------------------------------
487/* */
b2e465d6 488bool Configuration::Exists(const char *Name) const
08e8f724 489{
b2e465d6 490 const Item *Itm = Lookup(Name);
08e8f724
AL
491 if (Itm == 0)
492 return false;
493 return true;
494}
495 /*}}}*/
b2e465d6
AL
496// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
497// ---------------------------------------------------------------------
498/* qualified by /[fdbi] exists */
499bool Configuration::ExistsAny(const char *Name) const
500{
501 string key = Name;
502
8d998026 503 if (key.size() > 2 && key.end()[-2] == '/')
ff6bf1be 504 {
8d998026
AL
505 if (key.find_first_of("fdbi",key.size()-1) < key.size())
506 {
507 key.resize(key.size() - 2);
508 if (Exists(key.c_str()))
509 return true;
510 }
511 else
512 {
69a63027 513 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
8d998026 514 }
ff6bf1be 515 }
b2e465d6
AL
516 return Exists(Name);
517}
518 /*}}}*/
93bf083d
AL
519// Configuration::Dump - Dump the config /*{{{*/
520// ---------------------------------------------------------------------
521/* Dump the entire configuration space */
ff2a211a 522void Configuration::Dump(ostream& str)
93bf083d 523{
2f416270
DK
524 Dump(str, NULL, "%f \"%v\";\n", true);
525}
526void Configuration::Dump(ostream& str, char const * const root,
527 char const * const formatstr, bool const emptyValue)
528{
529 const Configuration::Item* Top = Tree(root);
530 if (Top == 0)
531 return;
532 const Configuration::Item* const Root = (root == NULL) ? NULL : Top;
533 std::vector<std::string> const format = VectorizeString(formatstr, '%');
534
535 /* Write out all of the configuration directives by walking the
93bf083d 536 configuration tree */
2f416270
DK
537 do {
538 if (emptyValue == true || Top->Value.empty() == emptyValue)
539 {
540 std::vector<std::string>::const_iterator f = format.begin();
541 str << *f;
542 for (++f; f != format.end(); ++f)
543 {
544 if (f->empty() == true)
545 {
546 ++f;
547 str << '%' << *f;
548 continue;
549 }
550 char const type = (*f)[0];
551 if (type == 'f')
552 str << Top->FullTag();
553 else if (type == 't')
554 str << Top->Tag;
555 else if (type == 'v')
556 str << Top->Value;
557 else if (type == 'F')
558 str << QuoteString(Top->FullTag(), "=\"\n");
559 else if (type == 'T')
560 str << QuoteString(Top->Tag, "=\"\n");
561 else if (type == 'V')
562 str << QuoteString(Top->Value, "=\"\n");
563 else if (type == 'n')
564 str << "\n";
565 else if (type == 'N')
566 str << "\t";
567 else
568 str << '%' << type;
569 str << f->c_str() + 1;
570 }
571 }
572
93bf083d
AL
573 if (Top->Child != 0)
574 {
575 Top = Top->Child;
576 continue;
577 }
2f416270 578
93bf083d
AL
579 while (Top != 0 && Top->Next == 0)
580 Top = Top->Parent;
581 if (Top != 0)
582 Top = Top->Next;
2f416270
DK
583
584 if (Root != NULL)
585 {
586 const Configuration::Item* I = Top;
587 while(I != 0)
588 {
589 if (I == Root)
590 break;
591 else
592 I = I->Parent;
593 }
594 if (I == 0)
595 break;
596 }
597 } while (Top != 0);
93bf083d
AL
598}
599 /*}}}*/
08e8f724 600
0a8a80e5
AL
601// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
602// ---------------------------------------------------------------------
b2e465d6
AL
603/* Stop sets an optional max recursion depth if this item is being viewed as
604 part of a sub tree. */
605string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 606{
b2e465d6 607 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 608 return Tag;
b2e465d6 609 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
610}
611 /*}}}*/
612
08e8f724
AL
613// ReadConfigFile - Read a configuration file /*{{{*/
614// ---------------------------------------------------------------------
615/* The configuration format is very much like the named.conf format
b2e465d6
AL
616 used in bind8, in fact this routine can parse most named.conf files.
617 Sectional config files are like bind's named.conf where there are
618 sections like 'zone "foo.org" { .. };' This causes each section to be
619 added in with a tag like "zone::foo.org" instead of being split
6df63aa6 620 tag/value. AsSectional enables Sectional parsing.*/
ce857f32
DK
621bool ReadConfigFile(Configuration &Conf,const string &FName,bool const &AsSectional,
622 unsigned const &Depth)
b2e465d6 623{
08e8f724 624 // Open the stream for reading
851a45a8 625 ifstream F(FName.c_str(),ios::in);
08e8f724 626 if (!F != 0)
b2e465d6 627 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
3f807f6c 628
08e8f724 629 string LineBuffer;
a4e87467
AL
630 string Stack[100];
631 unsigned int StackPos = 0;
08e8f724
AL
632
633 // Parser state
634 string ParentTag;
635
636 int CurLine = 0;
637 bool InComment = false;
638 while (F.eof() == false)
639 {
3f807f6c
DB
640 // The raw input line.
641 std::string Input;
642 // The input line with comments stripped.
643 std::string Fragment;
8a27f5da
DB
644
645 // Grab the next line of F and place it in Input.
3f807f6c
DB
646 do
647 {
648 char *Buffer = new char[1024];
649
650 F.clear();
651 F.getline(Buffer,sizeof(Buffer) / 2);
652
653 Input += Buffer;
8aea8c3f 654 delete[] Buffer;
3f807f6c
DB
655 }
656 while (F.fail() && !F.eof());
657
8a27f5da
DB
658 // Expand tabs in the input line and remove leading and trailing
659 // whitespace.
3f807f6c 660 {
3f807f6c
DB
661 const int BufferSize = Input.size() * 8 + 1;
662 char *Buffer = new char[BufferSize];
663 try
664 {
665 memcpy(Buffer, Input.c_str(), Input.size() + 1);
666
667 _strtabexpand(Buffer, BufferSize);
668 _strstrip(Buffer);
669 Input = Buffer;
670 }
671 catch(...)
672 {
673 delete[] Buffer;
674 throw;
675 }
676 delete[] Buffer;
677 }
08e8f724 678 CurLine++;
5e14c452 679
8a27f5da
DB
680 // Now strip comments; if the whole line is contained in a
681 // comment, skip this line.
682
3f807f6c
DB
683 // The first meaningful character in the current fragment; will
684 // be adjusted below as we remove bytes from the front.
685 std::string::const_iterator Start = Input.begin();
686 // The last meaningful character in the current fragment.
687 std::string::const_iterator End = Input.end();
08e8f724
AL
688
689 // Multi line comment
690 if (InComment == true)
691 {
3f807f6c
DB
692 for (std::string::const_iterator I = Start;
693 I != End; ++I)
08e8f724 694 {
3f807f6c 695 if (*I == '*' && I + 1 != End && I[1] == '/')
08e8f724 696 {
3f807f6c 697 Start = I + 2;
08e8f724
AL
698 InComment = false;
699 break;
700 }
701 }
702 if (InComment == true)
703 continue;
704 }
705
706 // Discard single line comments
bfd22fc0 707 bool InQuote = false;
3f807f6c
DB
708 for (std::string::const_iterator I = Start;
709 I != End; ++I)
08e8f724 710 {
bfd22fc0
AL
711 if (*I == '"')
712 InQuote = !InQuote;
713 if (InQuote == true)
714 continue;
c914647f
DK
715
716 if ((*I == '/' && I + 1 != End && I[1] == '/') ||
717 (*I == '#' && strcmp(string(I,I+6).c_str(),"#clear") != 0 &&
718 strcmp(string(I,I+8).c_str(),"#include") != 0))
719 {
3f807f6c 720 End = I;
08e8f724
AL
721 break;
722 }
723 }
7834cb57 724
3f807f6c
DB
725 // Look for multi line comments and build up the
726 // fragment.
727 Fragment.reserve(End - Start);
7834cb57 728 InQuote = false;
3f807f6c
DB
729 for (std::string::const_iterator I = Start;
730 I != End; ++I)
08e8f724 731 {
bfd22fc0
AL
732 if (*I == '"')
733 InQuote = !InQuote;
734 if (InQuote == true)
3f807f6c
DB
735 Fragment.push_back(*I);
736 else if (*I == '/' && I + 1 != End && I[1] == '*')
08e8f724
AL
737 {
738 InComment = true;
3f807f6c
DB
739 for (std::string::const_iterator J = I;
740 J != End; ++J)
08e8f724 741 {
3f807f6c 742 if (*J == '*' && J + 1 != End && J[1] == '/')
08e8f724 743 {
3f807f6c
DB
744 // Pretend we just finished walking over the
745 // comment, and don't add anything to the output
746 // fragment.
747 I = J + 1;
08e8f724
AL
748 InComment = false;
749 break;
750 }
751 }
752
753 if (InComment == true)
3f807f6c 754 break;
08e8f724 755 }
3f807f6c
DB
756 else
757 Fragment.push_back(*I);
08e8f724 758 }
3f807f6c
DB
759
760 // Skip blank lines.
761 if (Fragment.empty())
08e8f724
AL
762 continue;
763
8a27f5da 764 // The line has actual content; interpret what it means.
7834cb57 765 InQuote = false;
3f807f6c
DB
766 Start = Fragment.begin();
767 End = Fragment.end();
768 for (std::string::const_iterator I = Start;
769 I != End; ++I)
08e8f724 770 {
7834cb57
AL
771 if (*I == '"')
772 InQuote = !InQuote;
773
774 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 775 {
b2e465d6 776 // Put the last fragment into the buffer
a4dcbf1c
DB
777 std::string::const_iterator NonWhitespaceStart = Start;
778 std::string::const_iterator NonWhitespaceStop = I;
f7f0d6c7 779 for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; ++NonWhitespaceStart)
3f807f6c 780 ;
f7f0d6c7 781 for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; --NonWhitespaceStop)
3f807f6c 782 ;
a4dcbf1c 783 if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
08e8f724 784 LineBuffer += ' ';
a4dcbf1c 785 LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
3f807f6c
DB
786
787 // Drop this from the input string, saving the character
788 // that terminated the construct we just closed. (i.e., a
789 // brace or a semicolon)
08e8f724 790 char TermChar = *I;
3f807f6c 791 Start = I + 1;
08e8f724
AL
792
793 // Syntax Error
794 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 795 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 796
b2e465d6 797 // No string on this line
08e8f724 798 if (LineBuffer.empty() == true)
b2e465d6
AL
799 {
800 if (TermChar == '}')
801 {
802 if (StackPos == 0)
803 ParentTag = string();
804 else
805 ParentTag = Stack[--StackPos];
806 }
08e8f724 807 continue;
b2e465d6
AL
808 }
809
08e8f724 810 // Parse off the tag
7f25bdff
AL
811 string Tag;
812 const char *Pos = LineBuffer.c_str();
813 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 814 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
815
816 // Parse off the word
817 string Word;
bcae6dd4 818 bool NoWord = false;
b2e465d6
AL
819 if (ParseCWord(Pos,Word) == false &&
820 ParseQuoteWord(Pos,Word) == false)
821 {
822 if (TermChar != '{')
823 {
824 Word = Tag;
825 Tag = "";
bcae6dd4 826 }
4ae405e9
AL
827 else
828 NoWord = true;
b2e465d6
AL
829 }
830 if (strlen(Pos) != 0)
831 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
832
08e8f724
AL
833 // Go down a level
834 if (TermChar == '{')
835 {
26d5b68a 836 if (StackPos < sizeof(Stack)/sizeof(std::string))
a4e87467 837 Stack[StackPos++] = ParentTag;
b2e465d6
AL
838
839 /* Make sectional tags incorperate the section into the
840 tag string */
841 if (AsSectional == true && Word.empty() == false)
842 {
843 Tag += "::" ;
844 Tag += Word;
845 Word = "";
846 }
847
08e8f724
AL
848 if (ParentTag.empty() == true)
849 ParentTag = Tag;
850 else
851 ParentTag += string("::") + Tag;
852 Tag = string();
853 }
bfd22fc0 854
08e8f724
AL
855 // Generate the item name
856 string Item;
857 if (ParentTag.empty() == true)
858 Item = Tag;
859 else
860 {
7f25bdff 861 if (TermChar != '{' || Tag.empty() == false)
08e8f724 862 Item = ParentTag + "::" + Tag;
7f25bdff
AL
863 else
864 Item = ParentTag;
08e8f724
AL
865 }
866
b2e465d6
AL
867 // Specials
868 if (Tag.length() >= 1 && Tag[0] == '#')
869 {
870 if (ParentTag.empty() == false)
871 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
872 Tag.erase(Tag.begin());
873 if (Tag == "clear")
874 Conf.Clear(Word);
875 else if (Tag == "include")
876 {
877 if (Depth > 10)
878 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
879 if (Word.length() > 2 && Word.end()[-1] == '/')
880 {
881 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
882 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
883 }
884 else
885 {
886 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
887 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
888 }
889 }
890 else
891 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
892 }
c3a3a1b1
DK
893 else if (Tag.empty() == true && NoWord == false && Word == "#clear")
894 return _error->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName.c_str(),CurLine);
b2e465d6
AL
895 else
896 {
897 // Set the item in the configuration class
bcae6dd4
AL
898 if (NoWord == false)
899 Conf.Set(Item,Word);
b2e465d6
AL
900 }
901
08e8f724 902 // Empty the buffer
171c75f1 903 LineBuffer.clear();
b2e465d6
AL
904
905 // Move up a tag, but only if there is no bit to parse
906 if (TermChar == '}')
907 {
908 if (StackPos == 0)
171c75f1 909 ParentTag.clear();
b2e465d6
AL
910 else
911 ParentTag = Stack[--StackPos];
912 }
913
08e8f724 914 }
08e8f724
AL
915 }
916
3f807f6c
DB
917 // Store the remaining text, if any, in the current line buffer.
918
919 // NB: could change this to use string-based operations; I'm
920 // using strstrip now to ensure backwards compatibility.
921 // -- dburrows 2008-04-01
922 {
923 char *Buffer = new char[End - Start + 1];
924 try
925 {
926 std::copy(Start, End, Buffer);
927 Buffer[End - Start] = '\0';
928
929 const char *Stripd = _strstrip(Buffer);
930 if (*Stripd != 0 && LineBuffer.empty() == false)
931 LineBuffer += " ";
932 LineBuffer += Stripd;
933 }
934 catch(...)
935 {
936 delete[] Buffer;
937 throw;
938 }
939 delete[] Buffer;
940 }
08e8f724 941 }
b2e465d6
AL
942
943 if (LineBuffer.empty() == false)
944 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
945 return true;
946}
947 /*}}}*/
948// ReadConfigDir - Read a directory of config files /*{{{*/
949// ---------------------------------------------------------------------
950/* */
a74cd17a 951bool ReadConfigDir(Configuration &Conf,const string &Dir,
be4eec61 952 bool const &AsSectional, unsigned const &Depth)
52643bec 953{
e29a6bb1 954 vector<string> const List = GetListOfFilesInDir(Dir, "conf", true, true);
b2e465d6
AL
955
956 // Read the files
f7f0d6c7 957 for (vector<string>::const_iterator I = List.begin(); I != List.end(); ++I)
b2e465d6
AL
958 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
959 return false;
08e8f724
AL
960 return true;
961}
962 /*}}}*/
1f2933a8
DK
963// MatchAgainstConfig Constructor /*{{{*/
964Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config)
965{
966 std::vector<std::string> const strings = _config->FindVector(Config);
967 for (std::vector<std::string>::const_iterator s = strings.begin();
968 s != strings.end(); ++s)
969 {
970 regex_t *p = new regex_t;
971 if (regcomp(p, s->c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0)
972 patterns.push_back(p);
973 else
974 {
975 regfree(p);
976 delete p;
44edc41e
MV
977 _error->Warning("Invalid regular expression '%s' in configuration "
978 "option '%s' will be ignored.",
979 s->c_str(), Config);
980 continue;
1f2933a8 981 }
b093a199 982 }
69c2ecbd 983 if (strings.empty() == true)
b093a199 984 patterns.push_back(NULL);
1f2933a8
DK
985}
986 /*}}}*/
987// MatchAgainstConfig Destructor /*{{{*/
988Configuration::MatchAgainstConfig::~MatchAgainstConfig()
b093a199
DK
989{
990 clearPatterns();
991}
992void Configuration::MatchAgainstConfig::clearPatterns()
1f2933a8
DK
993{
994 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
995 p != patterns.end(); ++p)
996 {
b093a199 997 if (*p == NULL) continue;
1f2933a8
DK
998 regfree(*p);
999 delete *p;
1000 }
44edc41e 1001 patterns.clear();
1f2933a8
DK
1002}
1003 /*}}}*/
1004// MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1005bool Configuration::MatchAgainstConfig::Match(char const * str) const
1006{
1007 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
1008 p != patterns.end(); ++p)
b093a199 1009 if (*p != NULL && regexec(*p, str, 0, 0, 0) == 0)
1f2933a8
DK
1010 return true;
1011
1012 return false;
1013}
1014 /*}}}*/