Mercurial > code
comparison modules/ini/ini.hpp @ 521:b604d3dd45b7
Ini: resurrection
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 01 Jun 2016 16:44:55 +0200 |
parents | |
children | f48bb09bccc7 |
comparison
equal
deleted
inserted
replaced
520:b698e591b43a | 521:b604d3dd45b7 |
---|---|
1 /* | |
2 * ini.hpp -- extended .ini file parser | |
3 * | |
4 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> | |
5 * | |
6 * Permission to use, copy, modify, and/or distribute this software for any | |
7 * purpose with or without fee is hereby granted, provided that the above | |
8 * copyright notice and this permission notice appear in all copies. | |
9 * | |
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
17 */ | |
18 | |
19 #ifndef INI_HPP | |
20 #define INI_HPP | |
21 | |
22 /** | |
23 * \file ini.hpp | |
24 * \brief Extended .ini file parser. | |
25 * \author David Demelier <markand@malikania.fr> | |
26 */ | |
27 | |
28 /** | |
29 * \page Ini Ini | |
30 * \brief Extended .ini file parser. | |
31 * | |
32 * - \subpage ini-syntax | |
33 */ | |
34 | |
35 /** | |
36 * \page ini-syntax Syntax | |
37 * \brief File syntax. | |
38 * | |
39 * The syntax is similar to most of `.ini` implementations as: | |
40 * | |
41 * - a section is delimited by `[name]` can be redefined multiple times, | |
42 * - an option **must** always be defined in a section, | |
43 * - empty options must be surrounded by quotes, | |
44 * - lists can not includes trailing commas, | |
45 * - include statement must always be at the beginning of files (in no sections), | |
46 * - comments starts with # until the end of line, | |
47 * - options with spaces **must** use quotes. | |
48 * | |
49 * # Basic file | |
50 * | |
51 * ````ini | |
52 * # This is a comment. | |
53 * [section] | |
54 * option1 = value1 | |
55 * option2 = "value 2 with spaces" # comment is also allowed here | |
56 * ```` | |
57 * | |
58 * # Redefinition | |
59 * | |
60 * Sections can be redefined multiple times and are kept the order they are seen. | |
61 * | |
62 * ````ini | |
63 * [section] | |
64 * value = "1" | |
65 * | |
66 * [section] | |
67 * value = "2" | |
68 * ```` | |
69 * | |
70 * The ini::Document object will contains two ini::Section. | |
71 * | |
72 * # Lists | |
73 * | |
74 * Lists are defined using `()` and commas, like values, they may have quotes. | |
75 * | |
76 * ````ini | |
77 * [section] | |
78 * names = ( "x1", "x2" ) | |
79 * | |
80 * # This is also allowed | |
81 * biglist = ( | |
82 * "abc", | |
83 * "def" | |
84 * ) | |
85 * ```` | |
86 * | |
87 * # Include statement | |
88 * | |
89 * You can split a file into several pieces, if the include statement contains a relative path, the path will be relative | |
90 * to the current file being parsed. | |
91 * | |
92 * You **must** use the include statement before any section. | |
93 * | |
94 * If the file contains spaces, use quotes. | |
95 * | |
96 * ````ini | |
97 * # main.conf | |
98 * @include "foo.conf" | |
99 * | |
100 * # foo.conf | |
101 * [section] | |
102 * option1 = value1 | |
103 * ```` | |
104 */ | |
105 | |
106 #include <algorithm> | |
107 #include <cassert> | |
108 #include <exception> | |
109 #include <stdexcept> | |
110 #include <string> | |
111 #include <vector> | |
112 | |
113 /** | |
114 * Namespace for ini related classes. | |
115 */ | |
116 namespace ini { | |
117 | |
118 class Document; | |
119 | |
120 /** | |
121 * \class Error | |
122 * \brief Error in a file. | |
123 */ | |
124 class Error : public std::exception { | |
125 private: | |
126 int m_line; //!< line number | |
127 int m_column; //!< line column | |
128 std::string m_message; //!< error message | |
129 | |
130 public: | |
131 /** | |
132 * Constructor. | |
133 * | |
134 * \param line the line | |
135 * \param column the column | |
136 * \param msg the message | |
137 */ | |
138 inline Error(int line, int column, std::string msg) noexcept | |
139 : m_line(line) | |
140 , m_column(column) | |
141 , m_message(std::move(msg)) | |
142 { | |
143 } | |
144 | |
145 /** | |
146 * Get the line number. | |
147 * | |
148 * \return the line | |
149 */ | |
150 inline int line() const noexcept | |
151 { | |
152 return m_line; | |
153 } | |
154 | |
155 /** | |
156 * Get the column number. | |
157 * | |
158 * \return the column | |
159 */ | |
160 inline int column() const noexcept | |
161 { | |
162 return m_column; | |
163 } | |
164 | |
165 /** | |
166 * Return the raw error message (no line and column shown). | |
167 * | |
168 * \return the error message | |
169 */ | |
170 const char *what() const noexcept override | |
171 { | |
172 return m_message.c_str(); | |
173 } | |
174 }; | |
175 | |
176 /** | |
177 * \class Token | |
178 * \brief Describe a token read in the .ini source. | |
179 * | |
180 * This class can be used when you want to parse a .ini file yourself. | |
181 * | |
182 * \see analyze | |
183 */ | |
184 class Token { | |
185 public: | |
186 /** | |
187 * \brief Token type. | |
188 */ | |
189 enum Type { | |
190 Include, //!< include statement | |
191 Section, //!< [section] | |
192 Word, //!< word without quotes | |
193 QuotedWord, //!< word with quotes | |
194 Assign, //!< = assignment | |
195 ListBegin, //!< begin of list ( | |
196 ListEnd, //!< end of list ) | |
197 Comma //!< list separation | |
198 }; | |
199 | |
200 private: | |
201 Type m_type; | |
202 int m_line; | |
203 int m_column; | |
204 std::string m_value; | |
205 | |
206 public: | |
207 /** | |
208 * Construct a token. | |
209 * | |
210 * \param type the type | |
211 * \param line the line | |
212 * \param column the column | |
213 * \param value the value | |
214 */ | |
215 Token(Type type, int line, int column, std::string value = "") noexcept | |
216 : m_type(type) | |
217 , m_line(line) | |
218 , m_column(column) | |
219 { | |
220 switch (type) { | |
221 case Include: | |
222 m_value = "@include"; | |
223 break; | |
224 case Section: | |
225 case Word: | |
226 case QuotedWord: | |
227 m_value = value; | |
228 break; | |
229 case Assign: | |
230 m_value = "="; | |
231 break; | |
232 case ListBegin: | |
233 m_value = "("; | |
234 break; | |
235 case ListEnd: | |
236 m_value = ")"; | |
237 break; | |
238 case Comma: | |
239 m_value = ","; | |
240 break; | |
241 default: | |
242 break; | |
243 } | |
244 } | |
245 | |
246 /** | |
247 * Get the type. | |
248 * | |
249 * \return the type | |
250 */ | |
251 inline Type type() const noexcept | |
252 { | |
253 return m_type; | |
254 } | |
255 | |
256 /** | |
257 * Get the line. | |
258 * | |
259 * \return the line | |
260 */ | |
261 inline int line() const noexcept | |
262 { | |
263 return m_line; | |
264 } | |
265 | |
266 /** | |
267 * Get the column. | |
268 * | |
269 * \return the column | |
270 */ | |
271 inline int column() const noexcept | |
272 { | |
273 return m_column; | |
274 } | |
275 | |
276 /** | |
277 * Get the value. For words, quoted words and section, the value is the content. Otherwise it's the | |
278 * characters parsed. | |
279 * | |
280 * \return the value | |
281 */ | |
282 inline const std::string &value() const noexcept | |
283 { | |
284 return m_value; | |
285 } | |
286 }; | |
287 | |
288 /** | |
289 * List of tokens in order they are analyzed. | |
290 */ | |
291 using Tokens = std::vector<Token>; | |
292 | |
293 /** | |
294 * \class Option | |
295 * \brief Option definition. | |
296 */ | |
297 class Option : public std::vector<std::string> { | |
298 private: | |
299 std::string m_key; | |
300 | |
301 public: | |
302 /** | |
303 * Construct an empty option. | |
304 * | |
305 * \pre key must not be empty | |
306 * \param key the key | |
307 */ | |
308 inline Option(std::string key) noexcept | |
309 : std::vector<std::string>() | |
310 , m_key(std::move(key)) | |
311 { | |
312 assert(!m_key.empty()); | |
313 } | |
314 | |
315 /** | |
316 * Construct a single option. | |
317 * | |
318 * \pre key must not be empty | |
319 * \param key the key | |
320 * \param value the value | |
321 */ | |
322 inline Option(std::string key, std::string value) noexcept | |
323 : m_key(std::move(key)) | |
324 { | |
325 assert(!m_key.empty()); | |
326 | |
327 push_back(std::move(value)); | |
328 } | |
329 | |
330 /** | |
331 * Construct a list option. | |
332 * | |
333 * \pre key must not be empty | |
334 * \param key the key | |
335 * \param values the values | |
336 */ | |
337 inline Option(std::string key, std::vector<std::string> values) noexcept | |
338 : std::vector<std::string>(std::move(values)) | |
339 , m_key(std::move(key)) | |
340 { | |
341 assert(!m_key.empty()); | |
342 } | |
343 | |
344 /** | |
345 * Get the option key. | |
346 * | |
347 * \return the key | |
348 */ | |
349 inline const std::string &key() const noexcept | |
350 { | |
351 return m_key; | |
352 } | |
353 | |
354 /** | |
355 * Get the option value. | |
356 * | |
357 * \return the value | |
358 */ | |
359 inline const std::string &value() const noexcept | |
360 { | |
361 static std::string dummy; | |
362 | |
363 return empty() ? dummy : (*this)[0]; | |
364 } | |
365 }; | |
366 | |
367 /** | |
368 * \class Section | |
369 * \brief Section that contains one or more options. | |
370 */ | |
371 class Section : public std::vector<Option> { | |
372 private: | |
373 std::string m_key; | |
374 | |
375 public: | |
376 /** | |
377 * Construct a section with its name. | |
378 * | |
379 * \pre key must not be empty | |
380 * \param key the key | |
381 */ | |
382 inline Section(std::string key) noexcept | |
383 : m_key(std::move(key)) | |
384 { | |
385 assert(!m_key.empty()); | |
386 } | |
387 | |
388 /** | |
389 * Get the section key. | |
390 * | |
391 * \return the key | |
392 */ | |
393 inline const std::string &key() const noexcept | |
394 { | |
395 return m_key; | |
396 } | |
397 | |
398 /** | |
399 * Check if the section contains a specific option. | |
400 * | |
401 * \param key the option key | |
402 * \return true if the option exists | |
403 */ | |
404 inline bool contains(const std::string &key) const noexcept | |
405 { | |
406 return find(key) != end(); | |
407 } | |
408 | |
409 /** | |
410 * Find an option by key and return an iterator. | |
411 * | |
412 * \param key the key | |
413 * \return the iterator or end() if not found | |
414 */ | |
415 inline iterator find(const std::string &key) noexcept | |
416 { | |
417 return std::find_if(begin(), end(), [&] (const auto &o) { | |
418 return o.key() == key; | |
419 }); | |
420 } | |
421 | |
422 /** | |
423 * Find an option by key and return an iterator. | |
424 * | |
425 * \param key the key | |
426 * \return the iterator or end() if not found | |
427 */ | |
428 inline const_iterator find(const std::string &key) const noexcept | |
429 { | |
430 return std::find_if(cbegin(), cend(), [&] (const auto &o) { | |
431 return o.key() == key; | |
432 }); | |
433 } | |
434 | |
435 /** | |
436 * Access an option at the specified key. | |
437 * | |
438 * \param key the key | |
439 * \return the option | |
440 * \pre contains(key) must return true | |
441 */ | |
442 inline Option &operator[](const std::string &key) | |
443 { | |
444 assert(contains(key)); | |
445 | |
446 return *find(key); | |
447 } | |
448 | |
449 /** | |
450 * Overloaded function. | |
451 * | |
452 * \param key the key | |
453 * \return the option | |
454 * \pre contains(key) must return true | |
455 */ | |
456 inline const Option &operator[](const std::string &key) const | |
457 { | |
458 assert(contains(key)); | |
459 | |
460 return *find(key); | |
461 } | |
462 | |
463 /** | |
464 * Inherited operators. | |
465 */ | |
466 using std::vector<Option>::operator[]; | |
467 }; | |
468 | |
469 /** | |
470 * \class Document | |
471 * \brief Ini document description. | |
472 * \see readFile | |
473 * \see readString | |
474 */ | |
475 class Document : public std::vector<Section> { | |
476 public: | |
477 /** | |
478 * Check if a document has a specific section. | |
479 * | |
480 * \param key the key | |
481 * \return true if the document contains the section | |
482 */ | |
483 inline bool contains(const std::string &key) const noexcept | |
484 { | |
485 return std::find_if(begin(), end(), [&] (const auto &sc) { return sc.key() == key; }) != end(); | |
486 } | |
487 | |
488 /** | |
489 * Find a section by key and return an iterator. | |
490 * | |
491 * \param key the key | |
492 * \return the iterator or end() if not found | |
493 */ | |
494 inline iterator find(const std::string &key) noexcept | |
495 { | |
496 return std::find_if(begin(), end(), [&] (const auto &o) { | |
497 return o.key() == key; | |
498 }); | |
499 } | |
500 | |
501 /** | |
502 * Find a section by key and return an iterator. | |
503 * | |
504 * \param key the key | |
505 * \return the iterator or end() if not found | |
506 */ | |
507 inline const_iterator find(const std::string &key) const noexcept | |
508 { | |
509 return std::find_if(cbegin(), cend(), [&] (const auto &o) { | |
510 return o.key() == key; | |
511 }); | |
512 } | |
513 | |
514 /** | |
515 * Access a section at the specified key. | |
516 * | |
517 * \param key the key | |
518 * \return the section | |
519 * \pre contains(key) must return true | |
520 */ | |
521 inline Section &operator[](const std::string &key) | |
522 { | |
523 assert(contains(key)); | |
524 | |
525 return *find(key); | |
526 } | |
527 | |
528 /** | |
529 * Overloaded function. | |
530 * | |
531 * \param key the key | |
532 * \return the section | |
533 * \pre contains(key) must return true | |
534 */ | |
535 inline const Section &operator[](const std::string &key) const | |
536 { | |
537 assert(contains(key)); | |
538 | |
539 return *find(key); | |
540 } | |
541 | |
542 /** | |
543 * Inherited operators. | |
544 */ | |
545 using std::vector<Section>::operator[]; | |
546 }; | |
547 | |
548 /** | |
549 * Analyse a stream and detect potential syntax errors. This does not parse the file like including other | |
550 * files in include statement. | |
551 * | |
552 * It does only analysis, for example if an option is defined under no section, this does not trigger an | |
553 * error while it's invalid. | |
554 * | |
555 * \param it the iterator | |
556 * \param end where to stop | |
557 * \return the list of tokens | |
558 * \throws Error on errors | |
559 */ | |
560 Tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end); | |
561 | |
562 /** | |
563 * Overloaded function for stream. | |
564 * | |
565 * \param stream the stream | |
566 * \return the list of tokens | |
567 * \throws Error on errors | |
568 */ | |
569 Tokens analyse(std::istream &stream); | |
570 | |
571 /** | |
572 * Parse the produced tokens. | |
573 * | |
574 * \param tokens the tokens | |
575 * \param path the parent path | |
576 * \return the document | |
577 * \throw Error on errors | |
578 */ | |
579 Document parse(const Tokens &tokens, const std::string &path = "."); | |
580 | |
581 /** | |
582 * Parse a file. | |
583 * | |
584 * \param filename the file name | |
585 * \return the document | |
586 * \throw Error on errors | |
587 */ | |
588 Document readFile(const std::string &filename); | |
589 | |
590 /** | |
591 * Parse a string. | |
592 * | |
593 * If the string contains include statements, they are relative to the current working directory. | |
594 * | |
595 * \param buffer the buffer | |
596 * \return the document | |
597 * \throw Error on errors | |
598 */ | |
599 Document readString(const std::string &buffer); | |
600 | |
601 /** | |
602 * Show all tokens and their description. | |
603 * | |
604 * \param tokens the tokens | |
605 */ | |
606 void dump(const Tokens &tokens); | |
607 | |
608 } // !ini | |
609 | |
610 #endif // !INI_HPP |