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