blob: 6612db36726f2924e1a978eb1800cb25786f14c3 [file] [log] [blame]
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +00001/* vi: set sw=4 ts=4: */
2/*
3 * config file parser helper
4 *
5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
6 *
7 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
8 */
9
10#include "libbb.h"
11
12/*
13
14Typical usage:
15
16----- CUT -----
17 char *t[3]; // tokens placeholder
18 parser_t p; // parser structure
19 // open file
20 if (config_open(filename, &p)) {
21 // parse line-by-line
22 while (*config_read(&p, t, 3, 0, delimiters, comment_char)) { // 0..3 tokens
23 // use tokens
24 bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
25 }
26 ...
27 // free parser
28 config_close(&p);
29 }
30----- CUT -----
31
32*/
33
34#if !PARSER_STDIO_BASED
35
36char* FAST_FUNC config_open(parser_t *parser, const char *filename)
37{
38 // empty file configures nothing!
39 char *data = xmalloc_open_read_close(filename, NULL);
40 if (!data)
41 return data;
42
43 // convert 0x5c 0x0a (backslashes at the very end of line) to 0x20 0x20 (spaces)
44 for (char *s = data; (s = strchr(s, '\\')) != NULL; ++s)
45 if ('\n' == s[1]) {
46 s[0] = s[1] = ' ';
47 }
48
49 // init parser
50 parser->line = parser->data = data;
51 parser->lineno = 0;
52
53 return data;
54}
55
56void FAST_FUNC config_close(parser_t *parser)
57{
58 // for now just free config data
59 free(parser->data);
60}
61
62char* FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment)
63{
64 char *ret, *line;
65 int noreduce = (ntokens<0); // do not treat subsequent delimiters as one delimiter
66 if (ntokens < 0)
67 ntokens = -ntokens;
68 ret = line = parser->line;
69 // nullify tokens
70 memset(tokens, 0, sizeof(void *) * ntokens);
71 // now split to lines
72 while (*line) {
73 int token_num = 0;
74 // limit the line
75 char *ptr = strchrnul(line, '\n');
76 *ptr++ = '\0';
77 // line number
78 parser->lineno++;
79 // comments mean EOLs
80 if (comment)
81 *strchrnul(line, comment) = '\0';
82 // skip leading delimiters
83 while (*line && strchr(delims, *line))
84 line++;
85 // skip empty lines
86 if (*line) {
87 char *s;
88 // now split line to tokens
89 s = line;
90 while (s) {
91 char *p;
92 // get next token
93 if (token_num+1 >= ntokens)
94 break;
95 p = s;
96 while (*p && !strchr(delims, *p))
97 p++;
98 if (!*p)
99 break;
100 *p++ = '\0';
101 // pin token
102 if (noreduce || *s) {
103 tokens[token_num++] = s;
104//bb_error_msg("L[%d] T[%s]", token_num, s);
105 }
106 s = p;
107 }
108 // non-empty remainder is also a token. So if ntokens == 0, we just return the whole line
109 if (s && (noreduce || *s))
110 tokens[token_num++] = s;
111 // sanity check: have we got all required tokens?
112 if (token_num < mintokens)
113 bb_error_msg_and_die("bad line %u, %d tokens found, %d needed", parser->lineno, token_num, mintokens);
114 // advance data for the next call
115 line = ptr;
116 break;
117 }
118 // line didn't contain any token -> try next line
119 ret = line = ptr;
120 }
121 parser->line = line;
122
123 // return current line. caller must check *ret to determine whether to continue
124 return ret;
125}
126
127#else // stdio-based
128
129FILE* FAST_FUNC config_open(parser_t *parser, const char *filename)
130{
131 // empty file configures nothing!
132 parser->fp = fopen_or_warn(filename, "r");
133 if (!parser->fp)
134 return parser->fp;
135
136 // init parser
137 parser->line = NULL;
138 parser->lineno = 0;
139
140 return parser->fp;
141}
142
143void FAST_FUNC config_close(parser_t *parser)
144{
145 fclose(parser->fp);
146}
147
148char* FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment)
149{
150 char *line, *q;
151 int token_num, len;
152 int noreduce = (ntokens < 0); // do not treat subsequent delimiters as one delimiter
153
154 if (ntokens < 0)
155 ntokens = -ntokens;
156
157 // nullify tokens
158 memset(tokens, 0, sizeof(void *) * ntokens);
159
160 // free used line
161 free(parser->line);
162 parser->line = NULL;
163
164 while (1) {
165 int n;
166
167 // get fresh line
168//TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
169 line = xmalloc_fgetline(parser->fp);
170 if (!line)
171 return line;
172
173 parser->lineno++;
174 // handle continuations. Tito's code stolen :)
175 while (1) {
176 len = strlen(line);
177 if (!len)
178 goto free_and_cont;
179 if (line[len - 1] != '\\')
180 break;
181 // multi-line object
182 line[--len] = '\0';
183//TODO: add xmalloc_fgetline-like iface but with appending to existing str
184 q = xmalloc_fgetline(parser->fp);
185 if (q) {
186 parser->lineno++;
187 line = xasprintf("%s%s", line, q);
188 free(q);
189 }
190 }
191 // comments mean EOLs
192 if (comment) {
193 q = strchrnul(line, comment);
194 *q = '\0';
195 len = q - line;
196 }
197 // skip leading delimiters
198 n = strspn(line, delims);
199 if (n) {
200 len -= n;
201 strcpy(line, line + n);
202 }
203 if (len)
204 break;
205 // skip empty lines
206 free_and_cont:
207 free(line);
208 }
209
210 // non-empty line found, parse and return
211
212 // store line
213 parser->line = line = xrealloc(line, len + 1);
214
215 // now split line to tokens
216//TODO: discard consecutive delimiters?
217 token_num = 0;
218 ntokens--; // now it's max allowed token no
219 while (1) {
220 // get next token
221 if (token_num == ntokens)
222 break;
223 q = line + strcspn(line, delims);
224 if (!*q)
225 break;
226 // pin token
227 *q++ = '\0';
228 if (noreduce || *line) {
229 tokens[token_num++] = line;
230//bb_error_msg("L[%d] T[%s]", token_num, line);
231 }
232 line = q;
233 }
234
235 // non-empty remainder is also a token,
236 // so if ntokens <= 1, we just return the whole line
237 if (noreduce || *line)
238 tokens[token_num++] = line;
239
240 if (token_num < mintokens)
241 bb_error_msg_and_die("bad line %u: %d tokens found, %d needed",
242 parser->lineno, token_num, mintokens);
243
244 return parser->line; // maybe token_num instead?
245}
246
247#endif