mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
ini.cpp
Go to the documentation of this file.
1 /** \file ini.cpp
2  * \author Jared R. Males
3  * \brief Implementation of the The inih ini-style, file parser modified for mxlib.
4  *
5  * \ingroup mxApp_files
6  *
7  */
8 
9 #ifndef INI_COMMENT_CHAR
10  #define INI_COMMENT_CHAR ('#')
11 #endif
12 
13 /* Nonzero to allow multi-line value parsing, in the style of Python's
14  ConfigParser. If allowed, ini_parse() will call the handler with the same
15  name for each subsequent line parsed. */
16 #ifndef INI_ALLOW_MULTILINE
17 #define INI_ALLOW_MULTILINE 1
18 #endif
19 
20 /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
21  the file. See http://code.google.com/p/inih/issues/detail?id=21 */
22 #ifndef INI_ALLOW_BOM
23 #define INI_ALLOW_BOM 1
24 #endif
25 
26 /* Nonzero to use stack, zero to use heap (malloc/free). */
27 #ifndef INI_USE_STACK
28 #define INI_USE_STACK 0
29 #endif
30 
31 /* Stop parsing on first error (default is to stop parsing). */
32 #ifndef INI_STOP_ON_FIRST_ERROR
33 #define INI_STOP_ON_FIRST_ERROR 0
34 #endif
35 
36 /* Maximum line length for any line in INI file. */
37 #ifndef INI_MAX_LINE
38 #define INI_MAX_LINE 1024
39 #endif
40 
41 #define MAX_SECTION 50
42 #define MAX_NAME 50
43 
44 #if !INI_USE_STACK
45 #include <stdlib.h>
46 #endif
47 
48 #include "app/ini.hpp"
49 
50 /* Strip whitespace chars off end of given string, in place. Return s. */
51 static char* rstrip(char* s)
52 {
53  char* p = s + strlen(s);
54  while (p > s && isspace((unsigned char)(*--p)))
55  *p = '\0';
56  return s;
57 }
58 
59 /* Return pointer to first non-whitespace char in given string. */
60 static char* lskip(const char* s)
61 {
62  while (*s && isspace((unsigned char)(*s)))
63  s++;
64  return (char*)s;
65 }
66 
67 /* Return pointer to first char c or ';' comment in given string, or pointer to
68  null at end of string if neither found. ';' must be prefixed by a whitespace
69  character to register as a comment. */
70 static char* find_char_or_comment(const char* s, char c)
71 {
72  int was_whitespace = 0;
73  while (*s && *s != c && !(was_whitespace && *s == INI_COMMENT_CHAR)) {
74  was_whitespace = isspace((unsigned char)(*s));
75  s++;
76  }
77  return (char*)s;
78 }
79 
80 /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
81 static char* strncpy0(char* dest, const char* src, size_t size)
82 {
83  strncpy(dest, src, size);
84  dest[size - 1] = '\0';
85  return dest;
86 }
87 
88 int ini_parse_file(FILE* file,
89  int (*handler)( void*, const char*, const char*, const char*),
90  void* user
91  )
92 {
93  /* Uses a fair bit of stack (use heap instead if you need to) */
94 #if INI_USE_STACK
95  char line[INI_MAX_LINE];
96 #else
97  char* line;
98 #endif
99  char section[MAX_SECTION] = "";
100  char prev_name[MAX_NAME] = "";
101 
102  char* start;
103  char* end;
104  char* name;
105  char* value;
106  int lineno = 0;
107  int error = 0;
108 
109 #if !INI_USE_STACK
110  line = (char*)malloc(INI_MAX_LINE);
111  if (!line) {
112  return -2;
113  }
114 #endif
115 
116  /* Scan through file line by line */
117  while (fgets(line, INI_MAX_LINE, file) != NULL) {
118  lineno++;
119 
120  start = line;
121 #if INI_ALLOW_BOM
122  if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
123  (unsigned char)start[1] == 0xBB &&
124  (unsigned char)start[2] == 0xBF) {
125  start += 3;
126  }
127 #endif
128  start = lskip(rstrip(start));
129 
130  if (*start == INI_COMMENT_CHAR || *start == '#') {
131  /* Per Python ConfigParser, allow '#' comments at start of line */
132  }
133 #if INI_ALLOW_MULTILINE
134  else if (*prev_name && *start && start > line) {
135  /* Non-black line with leading whitespace, treat as continuation
136  of previous name's value (as per Python ConfigParser). */
137  if (!handler(user, section, prev_name, start) && !error)
138  error = lineno;
139  }
140 #endif
141  else if (*start == '[') {
142  /* A "[section]" line */
143  end = find_char_or_comment(start + 1, ']');
144  if (*end == ']') {
145  *end = '\0';
146  strncpy0(section, start + 1, sizeof(section));
147  *prev_name = '\0';
148  }
149  else if (!error) {
150  /* No ']' found on section line */
151  error = lineno;
152  }
153  }
154  else if (*start && *start != INI_COMMENT_CHAR) {
155  /* Not a comment, must be a name[=:]value pair */
156  end = find_char_or_comment(start, '=');
157  if (*end != '=') {
158  end = find_char_or_comment(start, ':');
159  }
160  if (*end == '=' || *end == ':') {
161  *end = '\0';
162  name = rstrip(start);
163  value = lskip(end + 1);
164  end = find_char_or_comment(value, '\0');
165  if (*end == INI_COMMENT_CHAR)
166  *end = '\0';
167  rstrip(value);
168 
169  /* Valid name[=:]value pair found, call handler */
170  strncpy0(prev_name, name, sizeof(prev_name));
171  if (!handler(user, section, name, value) && !error)
172  error = lineno;
173  }
174  else if (!error) {
175  /* No '=' or ':' found on name[=:]value line */
176  error = lineno;
177  }
178  }
179 
180 #if INI_STOP_ON_FIRST_ERROR
181  if (error)
182  break;
183 #endif
184  }
185 
186 #if !INI_USE_STACK
187  free(line);
188 #endif
189 
190  return error;
191 }
192 
193 int ini_parse(const char* filename,
194  int (*handler)(void*, const char*, const char*, const char*),
195  void* user
196  )
197 {
198  FILE* file;
199  int error;
200 
201  file = fopen(filename, "r");
202  if (!file)
203  return -1;
204  error = ini_parse_file(file, handler, user);
205  fclose(file);
206  return error;
207 }
208 
constexpr units::realT c()
The speed of light.
Definition: constants.hpp:60
int ini_parse_file(FILE *file, int(*handler)(void *, const char *, const char *, const char *), void *user)
Parse ini file.
Definition: ini.cpp:88
int ini_parse(const char *filename, int(*handler)(void *, const char *, const char *, const char *), void *user)
Parse given INI-style file.
Definition: ini.cpp:193
The inih ini-style, file parser modified for mxlib.