├── .github
├── assets
│ └── .condarc
└── workflows
│ └── ci.yml
├── .gitignore
├── .pre-commit-config.yaml
├── INIReader.h
├── INIReaderTest.cpp
├── LICENSE.txt
├── README.md
└── test.ini
/.github/assets/.condarc:
--------------------------------------------------------------------------------
1 | channels:
2 | - conda-forge
3 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 |
4 | # Automatically stop old builds on the same branch/PR
5 | concurrency:
6 | group: ${{ github.workflow }}-${{ github.ref }}
7 | cancel-in-progress: true
8 |
9 | defaults:
10 | run:
11 | shell: bash -el {0}
12 |
13 | jobs:
14 | pre-commit-checks:
15 | name: Pre-commit checks
16 | timeout-minutes: 30
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Checkout branch
20 | uses: actions/checkout@v4
21 | - name: Run pre-commit-conda
22 | uses: quantco/pre-commit-conda@v1
23 |
24 | tests:
25 | name: "Tests"
26 | timeout-minutes: 30
27 | runs-on: ubuntu-latest
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | CXX_COMPILER: ["g++", "clang++"]
32 | steps:
33 | - name: Checkout branch
34 | uses: actions/checkout@v4
35 | with:
36 | ref: ${{ github.head_ref }}
37 | fetch-depth: 0
38 | - name: Compile and run
39 | run: |
40 | ${{ matrix.CXX_COMPILER }} INIReaderTest.cpp -o INIReaderTest.out
41 | ./INIReaderTest.out
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.out
2 | *.o
3 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/Quantco/pre-commit-mirrors-prettier
3 | rev: 3.2.5
4 | hooks:
5 | - id: prettier-conda
6 | files: \.(md|yml|yaml)$
7 | - repo: https://github.com/Quantco/pre-commit-mirrors-pre-commit-hooks
8 | rev: 4.5.0
9 | hooks:
10 | - id: trailing-whitespace-conda
11 | - id: end-of-file-fixer-conda
12 | - id: check-merge-conflict-conda
13 | args: ["--assume-in-merge"]
14 | - repo: https://github.com/Quantco/pre-commit-mirrors-typos
15 | rev: 1.19.0
16 | hooks:
17 | - id: typos-conda
18 | - repo: https://github.com/Quantco/pre-commit-mirrors-clang-format
19 | rev: 16.0.6
20 | hooks:
21 | - id: clang-format-conda
22 |
--------------------------------------------------------------------------------
/INIReader.h:
--------------------------------------------------------------------------------
1 | // Read an INI file into easy-to-access name/value pairs.
2 |
3 | // inih and INIReader are released under the New BSD license (see LICENSE.txt).
4 | // Go to the project home page for more info:
5 | //
6 | // https://github.com/benhoyt/inih
7 | /* inih -- simple .INI file parser
8 |
9 | inih is released under the New BSD license (see LICENSE.txt). Go to the project
10 | home page for more info:
11 |
12 | https://github.com/benhoyt/inih
13 |
14 | */
15 |
16 | #ifndef __INI_H__
17 | #define __INI_H__
18 |
19 | /* Make this header file easier to include in C++ code */
20 | #ifdef __cplusplus
21 | extern "C" {
22 | #endif
23 |
24 | #include <stdio.h>
25 |
26 | /* Typedef for prototype of handler function. */
27 | typedef int (*ini_handler)(void *user, const char *section, const char *name,
28 | const char *value);
29 |
30 | /* Typedef for prototype of fgets-style reader function. */
31 | typedef char *(*ini_reader)(char *str, int num, void *stream);
32 |
33 | /* Parse given INI-style file. May have [section]s, name=value pairs
34 | (whitespace stripped), and comments starting with ';' (semicolon). Section
35 | is "" if name=value pair parsed before any section heading. name:value
36 | pairs are also supported as a concession to Python's configparser.
37 |
38 | For each name=value pair parsed, call handler function with given user
39 | pointer as well as section, name, and value (data only valid for duration
40 | of handler call). Handler should return nonzero on success, zero on error.
41 |
42 | Returns 0 on success, line number of first error on parse error (doesn't
43 | stop on first error), -1 on file open error, or -2 on memory allocation
44 | error (only when INI_USE_STACK is zero).
45 | */
46 | int ini_parse(const char *filename, ini_handler handler, void *user);
47 |
48 | /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
49 | close the file when it's finished -- the caller must do that. */
50 | int ini_parse_file(FILE *file, ini_handler handler, void *user);
51 |
52 | /* Same as ini_parse(), but takes an ini_reader function pointer instead of
53 | filename. Used for implementing custom or string-based I/O. */
54 | int ini_parse_stream(ini_reader reader, void *stream, ini_handler handler,
55 | void *user);
56 |
57 | /* Nonzero to allow multi-line value parsing, in the style of Python's
58 | configparser. If allowed, ini_parse() will call the handler with the same
59 | name for each subsequent line parsed. */
60 | #ifndef INI_ALLOW_MULTILINE
61 | #define INI_ALLOW_MULTILINE 1
62 | #endif
63 |
64 | /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
65 | the file. See http://code.google.com/p/inih/issues/detail?id=21 */
66 | #ifndef INI_ALLOW_BOM
67 | #define INI_ALLOW_BOM 1
68 | #endif
69 |
70 | /* Nonzero to allow inline comments (with valid inline comment characters
71 | specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
72 | Python 3.2+ configparser behaviour. */
73 | #ifndef INI_ALLOW_INLINE_COMMENTS
74 | #define INI_ALLOW_INLINE_COMMENTS 1
75 | #endif
76 | #ifndef INI_INLINE_COMMENT_PREFIXES
77 | #define INI_INLINE_COMMENT_PREFIXES ";"
78 | #endif
79 |
80 | /* Nonzero to use stack, zero to use heap (malloc/free). */
81 | #ifndef INI_USE_STACK
82 | #define INI_USE_STACK 1
83 | #endif
84 |
85 | /* Stop parsing on first error (default is to keep parsing). */
86 | #ifndef INI_STOP_ON_FIRST_ERROR
87 | #define INI_STOP_ON_FIRST_ERROR 0
88 | #endif
89 |
90 | /* Maximum line length for any line in INI file. */
91 | #ifndef INI_MAX_LINE
92 | #define INI_MAX_LINE 200
93 | #endif
94 |
95 | #ifdef __cplusplus
96 | }
97 | #endif
98 |
99 | /* inih -- simple .INI file parser
100 |
101 | inih is released under the New BSD license (see LICENSE.txt). Go to the project
102 | home page for more info:
103 |
104 | https://github.com/benhoyt/inih
105 |
106 | */
107 |
108 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
109 | #define _CRT_SECURE_NO_WARNINGS
110 | #endif
111 |
112 | #include <ctype.h>
113 | #include <stdio.h>
114 | #include <string.h>
115 |
116 | #if !INI_USE_STACK
117 | #include <stdlib.h>
118 | #endif
119 |
120 | #define MAX_SECTION 50
121 | #define MAX_NAME 50
122 |
123 | /* Strip whitespace chars off end of given string, in place. Return s. */
124 | inline static char *rstrip(char *s) {
125 | char *p = s + strlen(s);
126 | while (p > s && isspace((unsigned char)(*--p)))
127 | *p = '\0';
128 | return s;
129 | }
130 |
131 | /* Return pointer to first non-whitespace char in given string. */
132 | inline static char *lskip(const char *s) {
133 | while (*s && isspace((unsigned char)(*s)))
134 | s++;
135 | return (char *)s;
136 | }
137 |
138 | /* Return pointer to first char (of chars) or inline comment in given string,
139 | or pointer to null at end of string if neither found. Inline comment must
140 | be prefixed by a whitespace character to register as a comment. */
141 | inline static char *find_chars_or_comment(const char *s, const char *chars) {
142 | #if INI_ALLOW_INLINE_COMMENTS
143 | int was_space = 0;
144 | while (*s && (!chars || !strchr(chars, *s)) &&
145 | !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
146 | was_space = isspace((unsigned char)(*s));
147 | s++;
148 | }
149 | #else
150 | while (*s && (!chars || !strchr(chars, *s))) {
151 | s++;
152 | }
153 | #endif
154 | return (char *)s;
155 | }
156 |
157 | /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
158 | inline static char *strncpy0(char *dest, const char *src, size_t size) {
159 | strncpy(dest, src, size);
160 | dest[size - 1] = '\0';
161 | return dest;
162 | }
163 |
164 | /* See documentation in header file. */
165 | inline int ini_parse_stream(ini_reader reader, void *stream,
166 | ini_handler handler, void *user) {
167 | /* Uses a fair bit of stack (use heap instead if you need to) */
168 | #if INI_USE_STACK
169 | char line[INI_MAX_LINE];
170 | #else
171 | char *line;
172 | #endif
173 | char section[MAX_SECTION] = "";
174 | char prev_name[MAX_NAME] = "";
175 |
176 | char *start;
177 | char *end;
178 | char *name;
179 | char *value;
180 | int lineno = 0;
181 | int error = 0;
182 |
183 | #if !INI_USE_STACK
184 | line = (char *)malloc(INI_MAX_LINE);
185 | if (!line) {
186 | return -2;
187 | }
188 | #endif
189 |
190 | /* Scan through stream line by line */
191 | while (reader(line, INI_MAX_LINE, stream) != NULL) {
192 | lineno++;
193 |
194 | start = line;
195 | #if INI_ALLOW_BOM
196 | if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
197 | (unsigned char)start[1] == 0xBB && (unsigned char)start[2] == 0xBF) {
198 | start += 3;
199 | }
200 | #endif
201 | start = lskip(rstrip(start));
202 |
203 | if (*start == ';' || *start == '#') {
204 | /* Per Python configparser, allow both ; and # comments at the
205 | start of a line */
206 | }
207 | #if INI_ALLOW_MULTILINE
208 | else if (*prev_name && *start && start > line) {
209 |
210 | #if INI_ALLOW_INLINE_COMMENTS
211 | end = find_chars_or_comment(start, NULL);
212 | if (*end)
213 | *end = '\0';
214 | rstrip(start);
215 | #endif
216 |
217 | /* Non-blank line with leading whitespace, treat as continuation
218 | of previous name's value (as per Python configparser). */
219 | if (!handler(user, section, prev_name, start) && !error)
220 | error = lineno;
221 | }
222 | #endif
223 | else if (*start == '[') {
224 | /* A "[section]" line */
225 | end = find_chars_or_comment(start + 1, "]");
226 | if (*end == ']') {
227 | *end = '\0';
228 | strncpy0(section, start + 1, sizeof(section));
229 | *prev_name = '\0';
230 | } else if (!error) {
231 | /* No ']' found on section line */
232 | error = lineno;
233 | }
234 | } else if (*start) {
235 | /* Not a comment, must be a name[=:]value pair */
236 | end = find_chars_or_comment(start, "=:");
237 | if (*end == '=' || *end == ':') {
238 | *end = '\0';
239 | name = rstrip(start);
240 | value = lskip(end + 1);
241 | #if INI_ALLOW_INLINE_COMMENTS
242 | end = find_chars_or_comment(value, NULL);
243 | if (*end)
244 | *end = '\0';
245 | #endif
246 | rstrip(value);
247 |
248 | /* Valid name[=:]value pair found, call handler */
249 | strncpy0(prev_name, name, sizeof(prev_name));
250 | if (!handler(user, section, name, value) && !error)
251 | error = lineno;
252 | } else if (!error) {
253 | /* No '=' or ':' found on name[=:]value line */
254 | error = lineno;
255 | }
256 | }
257 |
258 | #if INI_STOP_ON_FIRST_ERROR
259 | if (error)
260 | break;
261 | #endif
262 | }
263 |
264 | #if !INI_USE_STACK
265 | free(line);
266 | #endif
267 |
268 | return error;
269 | }
270 |
271 | /* See documentation in header file. */
272 | inline int ini_parse_file(FILE *file, ini_handler handler, void *user) {
273 | return ini_parse_stream((ini_reader)fgets, file, handler, user);
274 | }
275 |
276 | /* See documentation in header file. */
277 | inline int ini_parse(const char *filename, ini_handler handler, void *user) {
278 | FILE *file;
279 | int error;
280 |
281 | file = fopen(filename, "r");
282 | if (!file)
283 | return -1;
284 | error = ini_parse_file(file, handler, user);
285 | fclose(file);
286 | return error;
287 | }
288 |
289 | #endif /* __INI_H__ */
290 |
291 | #ifndef __INIREADER_H__
292 | #define __INIREADER_H__
293 |
294 | #include <map>
295 | #include <set>
296 | #include <string>
297 |
298 | // Read an INI file into easy-to-access name/value pairs. (Note that I've gone
299 | // for simplicity here rather than speed, but it should be pretty decent.)
300 | class INIReader {
301 | public:
302 | // Empty Constructor
303 | INIReader() {};
304 |
305 | // Construct INIReader and parse given filename. See ini.h for more info
306 | // about the parsing.
307 | explicit INIReader(const std::string &filename);
308 |
309 | // Construct INIReader and parse given file. See ini.h for more info
310 | // about the parsing.
311 | explicit INIReader(FILE *file);
312 |
313 | // Return the result of ini_parse(), i.e., 0 on success, line number of
314 | // first error on parse error, or -1 on file open error.
315 | int ParseError() const;
316 |
317 | // Return the list of sections found in ini file
318 | const std::set<std::string> &Sections() const;
319 |
320 | // Get a string value from INI file, returning default_value if not found.
321 | std::string Get(const std::string §ion, const std::string &name,
322 | const std::string &default_value) const;
323 |
324 | // Get an integer (long) value from INI file, returning default_value if
325 | // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
326 | long GetInteger(const std::string §ion, const std::string &name,
327 | long default_value) const;
328 |
329 | // Get a real (floating point double) value from INI file, returning
330 | // default_value if not found or not a valid floating point value
331 | // according to strtod().
332 | double GetReal(const std::string §ion, const std::string &name,
333 | double default_value) const;
334 |
335 | // Get a single precision floating point number value from INI file, returning
336 | // default_value if not found or not a valid floating point value
337 | // according to strtof().
338 | float GetFloat(const std::string §ion, const std::string &name,
339 | float default_value) const;
340 |
341 | // Get a boolean value from INI file, returning default_value if not found or
342 | // if not a valid true/false value. Valid true values are "true", "yes", "on",
343 | // "1", and valid false values are "false", "no", "off", "0" (not case
344 | // sensitive).
345 | bool GetBoolean(const std::string §ion, const std::string &name,
346 | bool default_value) const;
347 |
348 | protected:
349 | int _error;
350 | std::map<std::string, std::string> _values;
351 | std::set<std::string> _sections;
352 | static std::string MakeKey(const std::string §ion,
353 | const std::string &name);
354 | static int ValueHandler(void *user, const char *section, const char *name,
355 | const char *value);
356 | };
357 |
358 | #endif // __INIREADER_H__
359 |
360 | #ifndef __INIREADER__
361 | #define __INIREADER__
362 |
363 | #include <algorithm>
364 | #include <cctype>
365 | #include <cstdlib>
366 |
367 | inline INIReader::INIReader(const std::string &filename) {
368 | _error = ini_parse(filename.c_str(), ValueHandler, this);
369 | }
370 |
371 | inline INIReader::INIReader(FILE *file) {
372 | _error = ini_parse_file(file, ValueHandler, this);
373 | }
374 |
375 | inline int INIReader::ParseError() const { return _error; }
376 |
377 | inline const std::set<std::string> &INIReader::Sections() const {
378 | return _sections;
379 | }
380 |
381 | inline std::string INIReader::Get(const std::string §ion,
382 | const std::string &name,
383 | const std::string &default_value) const {
384 | std::string key = MakeKey(section, name);
385 | return _values.count(key) ? _values.at(key) : default_value;
386 | }
387 |
388 | inline long INIReader::GetInteger(const std::string §ion,
389 | const std::string &name,
390 | long default_value) const {
391 | std::string valstr = Get(section, name, "");
392 | const char *value = valstr.c_str();
393 | char *end;
394 | // This parses "1234" (decimal) and also "0x4D2" (hex)
395 | long n = strtol(value, &end, 0);
396 | return end > value ? n : default_value;
397 | }
398 |
399 | inline double INIReader::GetReal(const std::string §ion,
400 | const std::string &name,
401 | double default_value) const {
402 | std::string valstr = Get(section, name, "");
403 | const char *value = valstr.c_str();
404 | char *end;
405 | double n = strtod(value, &end);
406 | return end > value ? n : default_value;
407 | }
408 |
409 | inline float INIReader::GetFloat(const std::string §ion,
410 | const std::string &name,
411 | float default_value) const {
412 | std::string valstr = Get(section, name, "");
413 | const char *value = valstr.c_str();
414 | char *end;
415 | float n = strtof(value, &end);
416 | return end > value ? n : default_value;
417 | }
418 |
419 | inline bool INIReader::GetBoolean(const std::string §ion,
420 | const std::string &name,
421 | bool default_value) const {
422 | std::string valstr = Get(section, name, "");
423 | // Convert to lower case to make string comparisons case-insensitive
424 | std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
425 | if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
426 | return true;
427 | else if (valstr == "false" || valstr == "no" || valstr == "off" ||
428 | valstr == "0")
429 | return false;
430 | else
431 | return default_value;
432 | }
433 |
434 | inline std::string INIReader::MakeKey(const std::string §ion,
435 | const std::string &name) {
436 | std::string key = section + "=" + name;
437 | // Convert to lower case to make section/name lookups case-insensitive
438 | std::transform(key.begin(), key.end(), key.begin(), ::tolower);
439 | return key;
440 | }
441 |
442 | inline int INIReader::ValueHandler(void *user, const char *section,
443 | const char *name, const char *value) {
444 | INIReader *reader = (INIReader *)user;
445 | std::string key = MakeKey(section, name);
446 | if (reader->_values[key].size() > 0)
447 | reader->_values[key] += "\n";
448 | reader->_values[key] += value;
449 | reader->_sections.insert(section);
450 | return 1;
451 | }
452 |
453 | #endif // __INIREADER__
454 |
--------------------------------------------------------------------------------
/INIReaderTest.cpp:
--------------------------------------------------------------------------------
1 | // Example that shows simple usage of the INIReader class
2 |
3 | #include "INIReader.h"
4 | #include <iostream>
5 | #include <sstream>
6 |
7 | std::string sections(INIReader &reader) {
8 | std::stringstream ss;
9 | std::set<std::string> sections = reader.Sections();
10 | for (std::set<std::string>::iterator it = sections.begin();
11 | it != sections.end(); ++it)
12 | ss << *it << ",";
13 | return ss.str();
14 | }
15 |
16 | int main() {
17 | INIReader reader("test.ini");
18 |
19 | if (reader.ParseError() < 0) {
20 | std::cout << "Can't load 'test.ini'\n";
21 | return 1;
22 | }
23 | std::cout << "Config loaded from 'test.ini': found sections="
24 | << sections(reader)
25 | << " version=" << reader.GetInteger("protocol", "version", -1)
26 | << ", name=" << reader.Get("user", "name", "UNKNOWN")
27 | << ", email=" << reader.Get("user", "email", "UNKNOWN")
28 | << ", multi=" << reader.Get("user", "multi", "UNKNOWN")
29 | << ", pi=" << reader.GetReal("user", "pi", -1)
30 | << ", active=" << reader.GetBoolean("user", "active", true) << "\n";
31 | return 0;
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | The "inih" library is distributed under the New BSD license:
3 |
4 | Copyright (c) 2009, Ben Hoyt
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 | * Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | * Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the distribution.
14 | * Neither the name of Ben Hoyt nor the names of its contributors
15 | may be used to endorse or promote products derived from this software
16 | without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY
19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY
22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # inih
2 |
3 | [](https://github.com/jtilly/inih/actions/workflows/ci.yml)
4 |
5 | This is a header only C++ version of [inih](https://github.com/benhoyt/inih).
6 |
7 | **inih (INI Not Invented Here)** is a simple [.INI file](http://en.wikipedia.org/wiki/INI_file) parser written in C. It's only a couple of pages of code, and it was designed to be _small and simple_, so it's good for embedded systems. It's also more or less compatible with Python's [ConfigParser](http://docs.python.org/library/configparser.html) style of .INI files, including RFC 822-style multi-line syntax and `name: value` entries.
8 |
9 | ## Usage
10 |
11 | All you need to do is to include `INIReader.h`. Consider the following example (`INIReaderTest.cpp`):
12 |
13 | ```cpp
14 | #include <iostream>
15 | #include "INIReader.h"
16 |
17 | int main() {
18 |
19 | INIReader reader("test.ini");
20 |
21 | if (reader.ParseError() != 0) {
22 | std::cout << "Can't load 'test.ini'\n";
23 | return 1;
24 | }
25 | std::cout << "Config loaded from 'test.ini': version="
26 | << reader.GetInteger("protocol", "version", -1) << ", name="
27 | << reader.Get("user", "name", "UNKNOWN") << ", email="
28 | << reader.Get("user", "email", "UNKNOWN") << ", pi="
29 | << reader.GetReal("user", "pi", -1) << ", active="
30 | << reader.GetBoolean("user", "active", true) << "\n";
31 | return 0;
32 |
33 | }
34 | ```
35 |
36 | To compile and run:
37 |
38 | ```sh
39 | g++ INIReaderTest.cpp -o INIReaderTest.out
40 | ./INIReaderTest.out
41 | # Config loaded from 'test.ini': version=6, name=Bob Smith, email=bob@smith.com, pi=3.14159, active=1
42 | ```
43 |
--------------------------------------------------------------------------------
/test.ini:
--------------------------------------------------------------------------------
1 | ; Test config file for ini_example.c and INIReaderTest.cpp
2 |
3 | [protocol] ; Protocol configuration
4 | version=6 ; IPv6
5 |
6 | [user]
7 | name = Bob Smith ; Spaces around '=' are stripped
8 | email = bob@smith.com ; And comments (like this) ignored
9 | active = true ; Test a boolean
10 | pi = 3.14159 ; Test a floating point number
11 | multi = this is a ; test
12 | multi-line value ; test
13 |
--------------------------------------------------------------------------------