]> Dogcows Code - chaz/yoink/blob - src/stlplus/subsystems/ini_manager.cpp
archiving support for releases
[chaz/yoink] / src / stlplus / subsystems / ini_manager.cpp
1 ////////////////////////////////////////////////////////////////////////////////
2
3 // Author: Andy Rushton
4 // Copyright: (c) Southampton University 1999-2004
5 // (c) Andy Rushton 2004-2009
6 // License: BSD License, see ../docs/license.html
7
8 ////////////////////////////////////////////////////////////////////////////////
9 #include "ini_manager.hpp"
10 #include "file_system.hpp"
11 #include <fstream>
12 #include <list>
13 #include <algorithm>
14 #include <sys/stat.h>
15 #include <ctype.h>
16 ////////////////////////////////////////////////////////////////////////////////
17
18 namespace stlplus
19 {
20
21 ////////////////////////////////////////////////////////////////////////////////
22 // local utilities
23
24 static std::string trim(const std::string& val)
25 {
26 std::string result = val;
27 while (!result.empty() && isspace(result[0]))
28 result.erase(result.begin());
29 while (!result.empty() && isspace(result[result.size()-1]))
30 result.erase(result.end()-1);
31 return result;
32 }
33
34 ////////////////////////////////////////////////////////////////////////////////
35 // internal data structure for storing a single entry (i.e. line) from an ini file
36 // lines are categorised as blanks, comments or variables
37 // TODO - do I need an error category?
38 ////////////////////////////////////////////////////////////////////////////////
39
40 class ini_entry
41 {
42 public:
43 enum kind_t {BLANK, COMMENT, VARIABLE};
44 friend std::string to_string(kind_t kind)
45 {
46 switch(kind)
47 {
48 case BLANK: return "BLANK";
49 case COMMENT: return "COMMENT";
50 case VARIABLE: return "VARIABLE";
51 }
52 return "<*unknown kind*>";
53 }
54
55 private:
56 unsigned m_line;
57 kind_t m_kind;
58 std::string m_text;
59 std::string m_name;
60 std::string m_value;
61
62 public:
63 ini_entry(unsigned line) : m_line(line), m_kind(BLANK) {}
64 ini_entry(unsigned line, const std::string& comment) : m_line(line), m_kind(COMMENT), m_text("; " + comment) {}
65 ini_entry(unsigned line, const std::string& name, const std::string& value) : m_line(line), m_kind(VARIABLE), m_text(name + " = " + value), m_name(name), m_value(value) {}
66 ~ini_entry(void) {}
67
68 unsigned line(void) const {return m_line;}
69 kind_t kind(void) const {return m_kind;}
70 bool blank(void) const {return m_kind == BLANK;}
71 bool comment(void) const {return m_kind == COMMENT;}
72 bool variable(void) const {return m_kind == VARIABLE;}
73
74 const std::string& text(void) const {return m_text;}
75 const std::string& variable_name(void) const {return m_name;}
76 const std::string& variable_value(void) const {return m_value;}
77
78 bool print(std::ostream& str) const
79 {
80 str << " " << m_line << ":" << to_string(m_kind) << ": " << m_text << std::endl;
81 return !str.fail();
82 }
83 };
84
85 ////////////////////////////////////////////////////////////////////////////////
86 // internal data structure representing an ini file section containing all the
87 // entries for that section from a single ini file
88 ////////////////////////////////////////////////////////////////////////////////
89
90 class ini_section
91 {
92 private:
93 friend class ini_file;
94 std::string m_title;
95 std::list<ini_entry> m_entries;
96
97 public:
98 ini_section(const std::string& title) :
99 m_title(title)
100 {
101 }
102
103 ~ini_section(void)
104 {
105 }
106
107 const std::string& title(void) const
108 {
109 return m_title;
110 }
111
112 bool empty(void) const
113 {
114 // a section is empty if it contains no variables
115 for (std::list<ini_entry>::const_iterator i = m_entries.begin(); i != m_entries.end(); i++)
116 {
117 if (i->variable())
118 return false;
119 }
120 return true;
121 }
122
123 void clear(void)
124 {
125 m_entries.clear();
126 }
127
128 bool variable_exists(const std::string variable) const
129 {
130 for (std::list<ini_entry>::const_iterator i = m_entries.begin(); i != m_entries.end(); i++)
131 {
132 if (i->variable() && i->variable_name() == variable)
133 return true;
134 }
135 return false;
136 }
137
138 unsigned variables_size(void) const
139 {
140 unsigned result = 0;
141 for (std::list<ini_entry>::const_iterator i = m_entries.begin(); i != m_entries.end(); i++)
142 if (i->variable())
143 result++;
144 return result;
145 }
146
147 std::string variable_name(unsigned offset) const
148 {
149 unsigned j = 0;
150 for (std::list<ini_entry>::const_iterator i = m_entries.begin(); i != m_entries.end(); i++)
151 {
152 if (i->variable())
153 {
154 if (j == offset)
155 return i->variable_name();
156 j++;
157 }
158 }
159 return std::string();
160 }
161
162 std::vector<std::string> variable_names(void) const
163 {
164 std::vector<std::string> result;
165 for (std::list<ini_entry>::const_iterator i = m_entries.begin(); i != m_entries.end(); i++)
166 if (i->variable())
167 result.push_back(i->variable_name());
168 return result;
169 }
170
171 std::string variable_value(unsigned offset) const
172 {
173 unsigned j = 0;
174 for (std::list<ini_entry>::const_iterator i = m_entries.begin(); i != m_entries.end(); i++)
175 {
176 if (i->variable())
177 {
178 if (j == offset)
179 return i->variable_value();
180 j++;
181 }
182 }
183 return std::string();
184 }
185
186 std::string variable_value(const std::string variable) const
187 {
188 for (std::list<ini_entry>::const_iterator i = m_entries.begin(); i != m_entries.end(); i++)
189 {
190 if (i->variable() && i->variable_name() == variable)
191 return i->variable_value();
192 }
193 return std::string();
194 }
195
196 unsigned variable_line(const std::string variable) const
197 {
198 for (std::list<ini_entry>::const_iterator i = m_entries.begin(); i != m_entries.end(); i++)
199 {
200 if (i->variable() && i->variable_name() == variable)
201 return i->line();
202 }
203 return 0;
204 }
205
206 bool add_variable(unsigned line, const std::string& variable, const std::string& value)
207 {
208 erase_variable(variable);
209 m_entries.push_back(ini_entry(line ? line : m_entries.size(), variable, value));
210 return true;
211 }
212
213 bool add_comment(unsigned line, const std::string& comment)
214 {
215 m_entries.push_back(ini_entry(line ? line : m_entries.size(), comment));
216 return true;
217 }
218
219 bool add_blank(unsigned line)
220 {
221 m_entries.push_back(ini_entry(line ? line : m_entries.size()));
222 return true;
223 }
224
225 bool erase_variable(const std::string& variable)
226 {
227 for (std::list<ini_entry>::iterator i = m_entries.begin(); i != m_entries.end(); i++)
228 {
229 if (i->variable() && i->variable_name() == variable)
230 {
231 m_entries.erase(i);
232 return true;
233 }
234 }
235 return false;
236 }
237
238 bool print(std::ostream& str) const
239 {
240 str << " [" << m_title << "]" << std::endl;
241 for (std::list<ini_entry>::const_iterator entry = m_entries.begin(); entry != m_entries.end(); entry++)
242 entry->print(str);
243 return !str.fail();
244 }
245 };
246
247 ////////////////////////////////////////////////////////////////////////////////
248 // internal data structure representing a single ini file
249 ////////////////////////////////////////////////////////////////////////////////
250
251 class ini_file
252 {
253 private:
254 friend class ini_section;
255 std::string m_filename;
256 bool m_changed;
257 bool m_writable;
258 std::list<ini_section> m_sections;
259
260 std::list<ini_section>::const_iterator find_section(const std::string& section) const
261 {
262 for (std::list<ini_section>::const_iterator i = m_sections.begin(); i != m_sections.end(); i++)
263 {
264 if (i->title() == section)
265 return i;
266 }
267 return m_sections.end();
268 }
269
270 std::list<ini_section>::iterator find_section(const std::string& section)
271 {
272 for (std::list<ini_section>::iterator i = m_sections.begin(); i != m_sections.end(); i++)
273 {
274 if (i->title() == section)
275 return i;
276 }
277 return m_sections.end();
278 }
279
280 public:
281
282 ini_file(void) : m_changed(false), m_writable(false)
283 {
284 }
285
286 ini_file(const std::string& filename) : m_changed(false), m_writable(false)
287 {
288 read_file(filename);
289 }
290
291 ~ini_file(void)
292 {
293 if (writable())
294 save_file();
295 }
296
297 bool initialised(void) const
298 {
299 return !m_filename.empty();
300 }
301
302 bool writable(void) const
303 {
304 return m_writable;
305 }
306
307 bool read_file(const std::string& filename)
308 {
309 m_filename = filename;
310 m_changed = false;
311 // file may not yet exist - possible to create an Ini file using this class
312 // so it is writable if the file exists and is writable or if the folder is writable
313 m_writable = file_writable(m_filename);
314 if (!file_exists(m_filename))
315 return true;
316 // create a dummy top section to hold file comments
317 std::list<ini_section>::iterator section = m_sections.insert(m_sections.end(), ini_section(""));
318 std::ifstream source(m_filename.c_str());
319 unsigned line_number = 0;
320 std::string line;
321 while(std::getline(source,line))
322 {
323 line_number++;
324 unsigned i = 0;
325 while(i < line.size() && isspace(line[i]))
326 i++;
327 if (i < line.size() && line[i] == '[')
328 {
329 // found a section title
330 // skip the [
331 i++;
332 // get the text up to the end ] or the end of the line
333 std::string title;
334 while (i < line.size() && line[i] != ']')
335 title += line[i++];
336 // create a new section and make it the current section
337 section = m_sections.insert(m_sections.end(), ini_section(title));
338 }
339 else if (i < line.size() && line[i] == ';')
340 {
341 // found a comment
342 // skip the ; because that is not part of the comment
343 i++;
344 // add the rest of the line as a comment to the current section
345 section->add_comment(line_number, line.substr(i, line.size()-1));
346 }
347 else if (i == line.size())
348 {
349 // found a blank line
350 section->add_blank(line_number);
351 }
352 else
353 {
354 // found a *possible* variable - now scan forwards for the = operator
355 std::string name;
356 while (i < line.size() && line[i] != '=')
357 name += line[i++];
358 // skip the = sign
359 // TODO - detect a missing = sign here and convert to an error
360 if (i < line.size())
361 i++;
362 // trim trailing whitespace off the name
363 name = trim(name);
364 // now get the value, minus any leading whitespace
365 std::string value;
366 while(i < line.size() && isspace(line[i]))
367 i++;
368 while (i < line.size())
369 value += line[i++];
370 // trim trailing whitespace off the value
371 value = trim(value);
372 // and finally add the name/value pair to the data structure
373 section->add_variable(line_number, name, value);
374 }
375 }
376 return true;
377 }
378
379 bool save_file(void)
380 {
381 if (!initialised()) return false;
382 if (!m_changed) return true;
383 if (!m_writable) return false;
384 std::ofstream output(m_filename.c_str());
385 for (std::list<ini_section>::iterator section = m_sections.begin(); section != m_sections.end(); section++)
386 {
387 if (!(section->title().empty()))
388 output << "[" << section->title() << "]" << std::endl;
389 for (std::list<ini_entry>::iterator entry = section->m_entries.begin(); entry != section->m_entries.end(); entry++)
390 output << entry->text() << std::endl;
391 }
392 m_changed = false;
393 return true;
394 }
395
396 std::string filename(void) const
397 {
398 return m_filename;
399 }
400
401 bool empty(void) const
402 {
403 // file is empty if it has either no sections or an empty header section
404 if (m_sections.empty()) return true;
405 if ((m_sections.begin() == --m_sections.end()) && m_sections.begin()->empty()) return true;
406 return false;
407 }
408
409 bool section_exists(const std::string& title) const
410 {
411 return find_section(title) != m_sections.end();
412 }
413
414 bool add_section(const std::string& section)
415 {
416 if (!m_writable) return false;
417 m_sections.push_back(ini_section(section));
418 m_changed = true;
419 return true;
420 }
421
422 bool empty_section(const std::string& section)
423 {
424 std::list<ini_section>::iterator found = find_section(section);
425 if (found == m_sections.end()) return false;
426 return found->empty();
427 }
428
429 bool erase_section(const std::string& section)
430 {
431 if (!m_writable) return false;
432 std::list<ini_section>::iterator found = find_section(section);
433 if (found == m_sections.end()) return false;
434 m_sections.erase(found);
435 m_changed = true;
436 return true;
437 }
438
439 bool clear_section(const std::string& section)
440 {
441 if (!m_writable) return false;
442 std::list<ini_section>::iterator found = find_section(section);
443 if (found == m_sections.end()) return false;
444 found->clear();
445 m_changed = true;
446 return true;
447 }
448
449 unsigned sections_size(void) const
450 {
451 return m_sections.size();
452 }
453
454 const std::string& section_name(unsigned i) const
455 {
456 std::list<ini_section>::const_iterator result = m_sections.begin();
457 for (unsigned j = 1; j <= i; j++)
458 result++;
459 return result->title();
460 }
461
462 std::vector<std::string> section_names(void) const
463 {
464 std::vector<std::string> result;
465 for (unsigned j = 0; j < sections_size(); j++)
466 result.push_back(section_name(j));
467 return result;
468 }
469
470 bool variable_exists(const std::string& section, const std::string variable) const
471 {
472 std::list<ini_section>::const_iterator found = find_section(section);
473 if (found == m_sections.end()) return false;
474 return found->variable_exists(variable);
475 }
476
477 std::vector<std::string> variable_names(const std::string& section) const
478 {
479 std::list<ini_section>::const_iterator found = find_section(section);
480 if (found == m_sections.end()) return std::vector<std::string>();
481 return found->variable_names();
482 }
483
484 unsigned variables_size(const std::string& section) const
485 {
486 std::list<ini_section>::const_iterator found = find_section(section);
487 if (found == m_sections.end()) return 0;
488 return found->variables_size();
489 }
490
491 unsigned variable_line(const std::string& section, const std::string variable) const
492 {
493 std::list<ini_section>::const_iterator found = find_section(section);
494 if (found == m_sections.end()) return 0;
495 return found->variable_line(variable);
496 }
497
498 std::string variable_name(const std::string& section, unsigned i) const
499 {
500 std::list<ini_section>::const_iterator found = find_section(section);
501 if (found == m_sections.end()) return std::string();
502 return found->variable_name(i);
503 }
504
505 std::string variable_value(const std::string& section, unsigned i) const
506 {
507 std::list<ini_section>::const_iterator found = find_section(section);
508 if (found == m_sections.end()) return std::string();
509 return found->variable_value(i);
510 }
511
512 std::string variable_value(const std::string& section, const std::string variable) const
513 {
514 std::list<ini_section>::const_iterator found = find_section(section);
515 if (found == m_sections.end()) return std::string();
516 return found->variable_value(variable);
517 }
518
519 bool add_variable(const std::string& section, const std::string& variable, const std::string& value)
520 {
521 if (!m_writable) return false;
522 std::list<ini_section>::iterator found = find_section(section);
523 if (found == m_sections.end()) found = m_sections.insert(m_sections.end(),ini_section(section));
524 if (found->add_variable(0,variable,value))
525 m_changed = true;
526 return true;
527 }
528
529 bool add_comment(const std::string& section, const std::string& comment)
530 {
531 if (!m_writable) return false;
532 std::list<ini_section>::iterator found = find_section(section);
533 if (found == m_sections.end()) found = m_sections.insert(m_sections.end(),ini_section(section));
534 if (found->add_comment(0,comment))
535 m_changed = true;
536 return true;
537 }
538
539 bool add_blank(const std::string& section)
540 {
541 if (!m_writable) return false;
542 std::list<ini_section>::iterator found = find_section(section);
543 if (found == m_sections.end()) found = m_sections.insert(m_sections.end(),ini_section(section));
544 if (found->add_blank(0))
545 m_changed = true;
546 return true;
547 }
548
549 bool erase_variable(const std::string& section, const std::string& variable)
550 {
551 if (!m_writable) return false;
552 std::list<ini_section>::iterator found = find_section(section);
553 if (found == m_sections.end()) return false;
554 if (found->erase_variable(variable))
555 {
556 m_changed = true;
557 return true;
558 }
559 return false;
560 }
561
562 bool print(std::ostream& str) const
563 {
564 str << "file: " << m_filename << std::endl;
565 for (std::list<ini_section>::const_iterator section = m_sections.begin(); section != m_sections.end(); section++)
566 section->print(str);
567 return !str.fail();
568 }
569 };
570
571 ////////////////////////////////////////////////////////////////////////////////
572 // body data structure contains all the data and is pointed-to by exported data structure
573
574 class ini_manager_body
575 {
576 private:
577 std::vector<ini_file> m_files;
578 unsigned m_count;
579
580 ini_manager_body(const ini_manager_body&);
581 ini_manager_body& operator= (const ini_manager_body&);
582
583 public:
584
585 ini_manager_body(void) : m_count(1)
586 {
587 }
588
589 ~ini_manager_body(void)
590 {
591 save();
592 }
593
594 void increment(void)
595 {
596 ++m_count;
597 }
598
599 bool decrement(void)
600 {
601 --m_count;
602 return m_count == 0;
603 }
604
605 //////////////////////////////////////////////////////////////////////////////
606 // file management
607
608 // add files starting with the most local file (e.g. the current project) which has depth 0
609 // and working back to the most global (e.g. the installation settings) which has a depth of size()-1
610 // This does nothing if the file has already been loaded - it is not permitted to manage the same file twice.
611 // Returns true if the file loaded okay or was already loaded (it is counted as successful if the file did
612 // not exist, only read errors cause a failure)
613 bool add_file(const std::string& filename)
614 {
615 // if this file has been loaded, don't load it again
616 // this is not an error
617 for (unsigned i = 0; i < m_files.size(); i++)
618 {
619 if (filespec_to_path(filename) == filespec_to_path(m_files[i].filename()))
620 return true;
621 }
622 // add a new file to the back of the list and then load it
623 // I do it in two steps rather than passing the filename to the constructor in order to return the load status
624 m_files.push_back(ini_file());
625 return m_files.back().read_file(filename);
626 }
627
628 // as above, returns false if *none* of the files were added
629 // filenames[0] is the local file, and so on
630 bool add_files(const std::vector<std::string>& filenames)
631 {
632 bool result = true;
633 for (unsigned i = 0; i < filenames.size(); i++)
634 result &= add_file(filenames[i]);
635 return result;
636 }
637
638 // saves modified ini files - returns true if all modified files were written successfully
639 bool save(void)
640 {
641 bool result = true;
642 for (unsigned i = 0; i < m_files.size(); i++)
643 result &= m_files[i].save_file();
644 return result;
645 }
646
647 // get the number of files being managed
648 unsigned size(void) const
649 {
650 return m_files.size();
651 }
652
653 // get the ini filename associated with a depth
654 std::string filename(unsigned depth) const
655 {
656 return m_files[depth].filename();
657 }
658
659 // test whether a file in the ini manager is writable
660 bool writable(unsigned depth) const
661 {
662 return m_files[depth].writable();
663 }
664
665 // test whether a file is empty
666 // An ini file is considered empty if it has no named sections and the header is empty or missing
667 bool empty(unsigned depth) const
668 {
669 return m_files[depth].empty();
670 }
671
672 // erase the ini file from the ini manager and from the disk
673 bool erase(unsigned depth)
674 {
675 std::string file = filename(depth);
676 remove(depth);
677 return file_delete(file);
678 }
679
680 // remove the file from the ini manager but do not erase it from the disk
681 bool remove(unsigned depth)
682 {
683 if (m_files[depth].writable())
684 m_files[depth].save_file();
685 m_files.erase(m_files.begin() + depth);
686 return true;
687 }
688
689 //////////////////////////////////////////////////////////////////////////////
690 // section management
691
692 // returns the union of all section names in all of the ini files
693 std::vector<std::string> section_names(void) const
694 {
695 std::vector<std::string> result;
696 for (unsigned i = 0; i < m_files.size(); i++)
697 {
698 std::vector<std::string> file_result = section_names(i);
699 for (unsigned j = 0; j < file_result.size(); j++)
700 {
701 if (std::find(result.begin(), result.end(), file_result[j]) == result.end())
702 result.push_back(file_result[j]);
703 }
704 }
705 return result;
706 }
707
708 // returns the section names in one of the ini files
709 std::vector<std::string> section_names(unsigned depth) const
710 {
711 return m_files[depth].section_names();
712 }
713
714 // tests whether a section is found in any of the ini files
715 bool section_exists(const std::string& title) const
716 {
717 for (unsigned i = 0; i < m_files.size(); i++)
718 {
719 if (m_files[i].section_exists(title))
720 return true;
721 }
722 return false;
723 }
724
725 // tests whether the section is found in the specific ini file
726 bool section_exists(const std::string& title, unsigned depth) const
727 {
728 return m_files[depth].section_exists(title);
729 }
730
731 // adds a section to the specified ini file - does nothing if it is already present
732 bool add_section(const std::string& section, unsigned depth)
733 {
734 return m_files[depth].add_section(section);
735 }
736
737 // test whether a section is empty
738 bool empty_section(const std::string& section, unsigned depth)
739 {
740 return m_files[depth].empty_section(section);
741 }
742
743 // removes a section from the specified ini file if it exists there but cannot remove it from any other file
744 bool erase_section(const std::string& section, unsigned depth)
745 {
746 return m_files[depth].erase_section(section);
747 }
748
749 // removes all the contents of a section from the specified ini file but keeps the empty section
750 bool clear_section(const std::string& section, unsigned depth)
751 {
752 return m_files[depth].clear_section(section);
753 }
754
755 //////////////////////////////////////////////////////////////////////////////
756 // variable management
757
758 // test whether a variable exists in any of the ini files
759 bool variable_exists(const std::string& section, const std::string variable) const
760 {
761 for (unsigned i = 0; i < m_files.size(); i++)
762 {
763 if (variable_exists(section, variable, i))
764 return true;
765 }
766 return false;
767 }
768
769 // test whether a variable exists in specified ini file
770 bool variable_exists(const std::string& section, const std::string variable, unsigned depth) const
771 {
772 return m_files[depth].variable_exists(section, variable);
773 }
774
775 // get the union of all variables declared in all ini files
776 std::vector<std::string> variable_names(const std::string& section) const
777 {
778 std::vector<std::string> result;
779 for (unsigned i = 0; i < m_files.size(); i++)
780 {
781 std::vector<std::string> file_result = variable_names(section, i);
782 for (unsigned j = 0; j < file_result.size(); j++)
783 {
784 if (std::find(result.begin(), result.end(), file_result[j]) == result.end())
785 result.push_back(file_result[j]);
786 }
787 }
788 return result;
789 }
790
791 // get the set of all varaibale names from one file
792 std::vector<std::string> variable_names(const std::string& section, unsigned depth) const
793 {
794 return m_files[depth].variable_names(section);
795 }
796
797 // get the depth of the first ini file to define a variable
798 // returns 0 if defined in the local ini file, etc. Returns (unsigned)-1 if the variable doesn't exist
799 unsigned variable_depth(const std::string& section, const std::string variable) const
800 {
801 for (unsigned i = 0; i < m_files.size(); i++)
802 {
803 if (variable_exists(section, variable, i))
804 return i;
805 }
806 return (unsigned)-1;
807 }
808
809 // get the filename that first defines the variable
810 std::string variable_filename(const std::string& section, const std::string variable) const
811 {
812 for (unsigned i = 0; i < m_files.size(); i++)
813 {
814 if (variable_exists(section, variable, i))
815 return filename(i);
816 }
817 return std::string();
818 }
819
820 unsigned variable_linenumber(const std::string& section, const std::string variable) const
821 {
822 for (unsigned i = 0; i < m_files.size(); i++)
823 {
824 if (variable_exists(section, variable, i))
825 return m_files[i].variable_line(section,variable);
826 }
827 return 0;
828 }
829
830 // get the value of a variable as a single unprocessed string
831 // if the variable does not exist the string will be empty, but beware that
832 // you also get an empty string if a variable exists but has no value
833 // you can differentiate between the two cases by using variable_exists_all above
834 std::string variable_value(const std::string& section, const std::string variable) const
835 {
836 for (unsigned i = 0; i < m_files.size(); i++)
837 {
838 if (variable_exists(section, variable, i))
839 return variable_value(section, variable, i);
840 }
841 return std::string();
842 }
843
844 // get the value from the specified file
845 std::string variable_value(const std::string& section, const std::string variable, unsigned depth) const
846 {
847 return m_files[depth].variable_value(section, variable);
848 }
849
850 // get the value of a variable as a processed string
851 // processing splits the value at commas and furthermore supports quoted
852 // strings (so that values can contain commas for example)
853 // quoted strings are dequoted before they are added to the result
854 // the result is a vector of dequoted strings, one per value in the comma-separated list
855 std::vector<std::string> variable_values(const std::string& section, const std::string variable) const
856 {
857 for (unsigned i = 0; i < m_files.size(); i++)
858 {
859 if (variable_exists(section, variable, i))
860 return variable_values(section, variable, i);
861 }
862 return std::vector<std::string>();
863 }
864
865 // get the processed variable from the specified file
866 std::vector<std::string> variable_values(const std::string& section, const std::string variable, unsigned depth) const
867 {
868 // get the unprocessed value and then do the processing into processed separate values
869 std::string value = variable_value(section, variable, depth);
870 std::vector<std::string> result;
871 if (!value.empty())
872 {
873 result.push_back(std::string());
874 unsigned i = 0;
875 // loop which is repeated for each element in the comma-separated list
876 while(i < value.size())
877 {
878 // skip leading whitespace
879 while (i < value.size() && isspace(value[i])) i++;
880 // get the value - this means all text up to the next comma or end of line
881 // also allow escaped characters
882 while (i < value.size())
883 {
884 if (value[i] == ',') break;
885 if (value[i] == '\\')
886 {
887 // found an escaped character - de-escape it by only getting the next character
888 // beware of an escape character at the end of the line which just gets ignored
889 i++;
890 if (i < value.size()) result.back() += value[i++];
891 }
892 else if (value[i] == '"')
893 {
894 // get a quoted substring
895 // first skip the opening quote
896 i++;
897 // keep getting characters until a close-quote, but allow the quote character to be escaped by itself
898 while (i < value.size())
899 {
900 if (value[i] == '"')
901 {
902 // found a quote skip it
903 i++;
904 // now establish whether its an escaped quote
905 // if it is, keep it, but de-escape it by only getting the next character
906 // it it isn't, break out and continue processing the value as a non-quoted string
907 if (i < value.size() && value[i] != '"') break;
908 if (i < value.size()) result.back() += value[i++];
909 }
910 else
911 {
912 // non-quote and non-escape so just get the character
913 result.back() += value[i++];
914 }
915 }
916 }
917 else
918 {
919 // non-escape so just get the character
920 result.back() += value[i++];
921 }
922 }
923 // trim trailing whitespace off the value
924 while (!result.back().empty() && isspace(result.back()[result.back().size()-1])) result.back().erase(result.back().size()-1,1);
925 // now check for whether there is a comma or end of line
926 if (i <= value.size() && value[i] == ',')
927 {
928 // skip the comma and add a new string to the result ready for the next iteration
929 i++;
930 result.push_back(std::string());
931 }
932 else
933 {
934 // end of line found so break out
935 break;
936 }
937 }
938 }
939 return result;
940 }
941
942 // add a variable to the specified file
943 bool add_variable(const std::string& section, const std::string& variable, const std::string& value, unsigned depth)
944 {
945 return m_files[depth].add_variable(section, variable, value);
946 }
947
948 // add a variable as a processed string
949 // processing means that the values in the string vector are converted into a comma-separated list
950 // values containing reserved characters are automatically quoted - so you should not even try to quote them yourself
951 bool add_variable(const std::string& section, const std::string& variable, const std::vector<std::string>& values, unsigned depth)
952 {
953 // convert the values vector into a comma-separated string with each value escaped so that special characters do not confuse the reader
954 // the characters escaped are: '\', ',', '"'
955 std::string value;
956 for (unsigned v = 0; v < values.size(); v++)
957 {
958 const std::string& element = values[v];
959 // add the comma between values === add a comma before all but the first value
960 if (v > 0) value += ',';
961 for (unsigned i = 0; i < element.size(); i++)
962 {
963 // add a character at a time, escaping special characters
964 if (element[i] == '"' || element[i] == ',' || element[i] == '\\')
965 {
966 // escape the character
967 value += '\\';
968 }
969 value += element[i];
970 }
971 }
972 return add_variable(section, variable, value, depth);
973 }
974
975 // erase a variable from the specified file
976 // this does not remove the variable from other ini files, so the variable may still exist
977 // to mask a global variable, set the variable to an empty string instead
978 bool erase_variable(const std::string& section, const std::string& variable, unsigned depth)
979 {
980 return m_files[depth].erase_variable(section, variable);
981 }
982
983 //////////////////////////////////////////////////////////////////////////////
984 // sundry line-entry management
985
986 // add a comment to the specified ini file
987 bool add_comment(const std::string& section, const std::string& comment, unsigned depth)
988 {
989 return m_files[depth].add_comment(section, comment);
990 }
991
992 // add a blank line to the specified ini file
993 bool add_blank(const std::string& section, unsigned depth)
994 {
995 return m_files[depth].add_blank(section);
996 }
997
998 bool print(std::ostream& str) const
999 {
1000 str << "----------------------------------------" << std::endl;
1001 for (unsigned depth = 0; depth < m_files.size(); depth++)
1002 {
1003 m_files[depth].print(str);
1004 str << "----------------------------------------" << std::endl;
1005 }
1006 return !str.fail();
1007 }
1008 };
1009
1010 ////////////////////////////////////////////////////////////////////////////////
1011 // exported data structure representing the set of all ini files and providing
1012 // the access functions exported by the class
1013 ////////////////////////////////////////////////////////////////////////////////
1014
1015 ////////////////////////////////////////////////////////////////////////////////
1016 // constructors/destructors
1017
1018 ini_manager::ini_manager(void) : m_body(new ini_manager_body)
1019 {
1020 }
1021
1022 ini_manager::ini_manager(const std::vector<std::string>& filenames) : m_body(new ini_manager_body)
1023 {
1024 add_files(filenames);
1025 }
1026
1027 ini_manager::ini_manager(const ini_manager& manager) : m_body(0)
1028 {
1029 m_body = manager.m_body;
1030 m_body->increment();
1031 }
1032
1033 ini_manager& ini_manager::operator= (const ini_manager& manager)
1034 {
1035 if (m_body == manager.m_body) return *this;
1036 if (m_body->decrement())
1037 delete m_body;
1038 m_body = manager.m_body;
1039 m_body->increment();
1040 return *this;
1041 }
1042
1043 ini_manager::~ini_manager(void)
1044 {
1045 if (m_body->decrement())
1046 delete m_body;
1047 }
1048
1049 ////////////////////////////////////////////////////////////////////////////////
1050 // file management
1051
1052 bool ini_manager::add_file(const std::string& filename)
1053 {
1054 return m_body->add_file(filename);
1055 }
1056
1057 bool ini_manager::add_files(const std::vector<std::string>& filenames)
1058 {
1059 return m_body->add_files(filenames);
1060 }
1061
1062 bool ini_manager::save(void)
1063 {
1064 return m_body->save();
1065 }
1066
1067 unsigned ini_manager::size(void) const
1068 {
1069 return m_body->size();
1070 }
1071
1072 std::string ini_manager::filename(unsigned depth) const
1073 {
1074 return m_body->filename(depth);
1075 }
1076
1077 bool ini_manager::writable(unsigned depth) const
1078 {
1079 return m_body->writable(depth);
1080 }
1081
1082 bool ini_manager::empty(unsigned depth) const
1083 {
1084 return m_body->empty(depth);
1085 }
1086
1087 bool ini_manager::erase(unsigned depth)
1088 {
1089 return m_body->erase(depth);
1090 }
1091
1092 bool ini_manager::remove(unsigned depth)
1093 {
1094 return m_body->remove(depth);
1095 }
1096
1097 ////////////////////////////////////////////////////////////////////////////////
1098 // section management
1099
1100 std::vector<std::string> ini_manager::section_names(void) const
1101 {
1102 return m_body->section_names();
1103 }
1104
1105 std::vector<std::string> ini_manager::section_names(unsigned depth) const
1106 {
1107 return m_body->section_names(depth);
1108 }
1109
1110 bool ini_manager::section_exists(const std::string& section) const
1111 {
1112 return m_body->section_exists(section);
1113 }
1114
1115 bool ini_manager::section_exists(const std::string& section, unsigned depth) const
1116 {
1117 return m_body->section_exists(section, depth);
1118 }
1119
1120 bool ini_manager::add_section(const std::string& section, unsigned depth)
1121 {
1122 return m_body->add_section(section, depth);
1123 }
1124
1125 bool ini_manager::empty_section(const std::string& section, unsigned depth)
1126 {
1127 return m_body->empty_section(section, depth);
1128 }
1129
1130 bool ini_manager::erase_section(const std::string& section, unsigned depth)
1131 {
1132 return m_body->erase_section(section, depth);
1133 }
1134
1135 bool ini_manager::clear_section(const std::string& section, unsigned depth)
1136 {
1137 return m_body->clear_section(section, depth);
1138 }
1139
1140 ////////////////////////////////////////////////////////////////////////////////
1141 // variable management
1142
1143 bool ini_manager::variable_exists(const std::string& section, const std::string variable) const
1144 {
1145 return m_body->variable_exists(section, variable);
1146 }
1147
1148 bool ini_manager::variable_exists(const std::string& section, const std::string variable, unsigned depth) const
1149 {
1150 return m_body->variable_exists(section, variable, depth);
1151 }
1152
1153 std::vector<std::string> ini_manager::variable_names(const std::string& section) const
1154 {
1155 return m_body->variable_names(section);
1156 }
1157
1158 std::vector<std::string> ini_manager::variable_names(const std::string& section, unsigned depth) const
1159 {
1160 return m_body->variable_names(section, depth);
1161 }
1162
1163 unsigned ini_manager::variable_depth(const std::string& section, const std::string variable) const
1164 {
1165 return m_body->variable_depth(section, variable);
1166 }
1167
1168 std::string ini_manager::variable_filename(const std::string& section, const std::string variable) const
1169 {
1170 return m_body->variable_filename(section, variable);
1171 }
1172
1173 unsigned ini_manager::variable_linenumber(const std::string& section, const std::string variable) const
1174 {
1175 return m_body->variable_linenumber(section, variable);
1176 }
1177
1178 std::string ini_manager::variable_value(const std::string& section, const std::string variable) const
1179 {
1180 return m_body->variable_value(section, variable);
1181 }
1182
1183 std::string ini_manager::variable_value(const std::string& section, const std::string variable, unsigned depth) const
1184 {
1185 return m_body->variable_value(section, variable, depth);
1186 }
1187
1188 std::vector<std::string> ini_manager::variable_values(const std::string& section, const std::string variable) const
1189 {
1190 return m_body->variable_values(section, variable);
1191 }
1192
1193 std::vector<std::string> ini_manager::variable_values(const std::string& section, const std::string variable, unsigned depth) const
1194 {
1195 return m_body->variable_values(section, variable, depth);
1196 }
1197
1198 bool ini_manager::add_variable(const std::string& section, const std::string& variable, const std::string& value, unsigned depth)
1199 {
1200 return m_body->add_variable(section, variable, value, depth);
1201 }
1202
1203 bool ini_manager::add_variable(const std::string& section, const std::string& variable, const std::vector<std::string>& values, unsigned depth)
1204 {
1205 return m_body->add_variable(section, variable, values, depth);
1206 }
1207
1208 bool ini_manager::erase_variable(const std::string& section, const std::string& variable, unsigned depth)
1209 {
1210 return m_body->erase_variable(section, variable, depth);
1211 }
1212
1213
1214 ////////////////////////////////////////////////////////////////////////////////
1215 // sundry entries
1216
1217 bool ini_manager::add_comment(const std::string& section, const std::string& comment, unsigned depth)
1218 {
1219 return m_body->add_comment(section, comment, depth);
1220 }
1221
1222 bool ini_manager::add_blank(const std::string& section, unsigned depth)
1223 {
1224 return m_body->add_blank(section, depth);
1225 }
1226
1227 bool ini_manager::print(std::ostream& str) const
1228 {
1229 return m_body->print(str);
1230 }
1231
1232 ////////////////////////////////////////////////////////////////////////////////
1233 // diagnostic print
1234
1235 std::ostream& operator << (std::ostream& str, const ini_manager& manager)
1236 {
1237 manager.print(str);
1238 return str;
1239 }
1240
1241 ////////////////////////////////////////////////////////////////////////////////
1242
1243 } // end namespace stlplus
This page took 0.096391 seconds and 4 git commands to generate.