blob: 5109066d8f7d8e551059de12f69ab26d25458f43 [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
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000012#if ENABLE_PARSE
13int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
14int parse_main(int argc UNUSED_PARAM, char **argv)
15{
16 const char *delims = "# \t";
17 unsigned flags = 0;
18 int mintokens = 0, ntokens = 128;
19 opt_complementary = "-1:n+:m+:f+";
20 getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
21 //argc -= optind;
22 argv += optind;
23 while (*argv) {
24 parser_t *p = config_open(*argv);
25 if (p) {
26 int n;
27 char **t = xmalloc(sizeof(char *) * ntokens);
Denis Vlasenko4a717e02008-07-20 13:01:56 +000028 while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000029 for (int i = 0; i < n; ++i)
30 printf("[%s]", t[i]);
31 puts("");
32 }
33 config_close(p);
34 }
35 argv++;
36 }
37 return EXIT_SUCCESS;
38}
39#endif
40
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000041/*
42
43Typical usage:
44
45----- CUT -----
46 char *t[3]; // tokens placeholder
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000047 parser_t *p = config_open(filename);
48 if (p) {
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000049 // parse line-by-line
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000050 while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000051 // use tokens
52 bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
53 }
54 ...
55 // free parser
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000056 config_close(p);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000057 }
58----- CUT -----
59
60*/
61
Denis Vlasenko5415c852008-07-21 23:05:26 +000062parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000063{
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000064 parser_t *parser = xzalloc(sizeof(parser_t));
65 /* empty file configures nothing */
Denis Vlasenko5415c852008-07-21 23:05:26 +000066 parser->fp = fopen_func(filename);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000067 if (parser->fp)
68 return parser;
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000069 if (ENABLE_FEATURE_CLEAN_UP)
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000070 free(parser);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000071 return NULL;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000072}
73
Denis Vlasenko5415c852008-07-21 23:05:26 +000074parser_t* FAST_FUNC config_open(const char *filename)
75{
76 return config_open2(filename, fopen_or_warn_stdin);
77}
78
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000079static void config_free_data(parser_t *const parser)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000080{
Denis Vlasenkoc01340f2008-07-16 22:12:18 +000081 free(parser->line);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000082 parser->line = NULL;
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +000083 if (PARSE_KEEP_COPY) { /* compile-time constant */
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000084 free(parser->data);
85 parser->data = NULL;
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +000086 }
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000087}
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000088
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000089void FAST_FUNC config_close(parser_t *parser)
90{
91 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000092 fclose(parser->fp);
93}
94
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000095/*
961. Read a line from config file. If nothing to read then bail out returning 0.
97 Handle continuation character. Advance lineno for each physical line. Cut comments.
982. if PARSE_DONT_TRIM is not set (default) skip leading and cut trailing delimiters, if any.
993. If resulting line is empty goto 1.
1004. Look for first delimiter. If PARSE_DONT_REDUCE or PARSE_DONT_TRIM is set then pin empty token.
1015. Else (default) if number of seen tokens is equal to max number of tokens (token is the last one)
102 and PARSE_LAST_IS_GREEDY is set then pin the remainder of the line as the last token.
103 Else (token is not last or PARSE_LAST_IS_GREEDY is not set) just replace first delimiter with '\0'
104 thus delimiting token and pin it.
1056. Advance line pointer past the end of token. If number of seen tokens is less than required number
106 of tokens then goto 4.
1077. Control the number of seen tokens is not less the min number of tokens. Die if condition is not met.
1088. Return the number of seen tokens.
109
110mintokens > 0 make config_read() exit with error message if less than mintokens
111(but more than 0) are found. Empty lines are always skipped (not warned about).
112*/
113#undef config_read
114int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000115{
116 char *line, *q;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000117 char comment = *delims++;
118 int ii;
119 int ntokens = flags & 0xFF;
120 int mintokens = (flags & 0xFF00) >> 8;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000121
Denis Vlasenko5415c852008-07-21 23:05:26 +0000122 again:
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000123 // N.B. this could only be used in read-in-one-go version, or when tokens use xstrdup(). TODO
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000124 //if (!parser->lineno || !(flags & PARSE_DONT_NULL))
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000125 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000126 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000127
128 while (1) {
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000129//TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
130 line = xmalloc_fgetline(parser->fp);
131 if (!line)
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000132 return 0;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000133
134 parser->lineno++;
135 // handle continuations. Tito's code stolen :)
136 while (1) {
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000137 ii = strlen(line);
138 if (!ii)
139 goto next_line;
140 if (line[ii - 1] != '\\')
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000141 break;
142 // multi-line object
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000143 line[--ii] = '\0';
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000144//TODO: add xmalloc_fgetline-like iface but with appending to existing str
145 q = xmalloc_fgetline(parser->fp);
146 if (q) {
147 parser->lineno++;
148 line = xasprintf("%s%s", line, q);
149 free(q);
150 }
151 }
152 // comments mean EOLs
153 if (comment) {
154 q = strchrnul(line, comment);
155 *q = '\0';
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000156 ii = q - line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000157 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000158 // skip leading and trailing delimiters
159 if (!(flags & PARSE_DONT_TRIM)) {
160 // skip leading
161 int n = strspn(line, delims);
162 if (n) {
163 ii -= n;
164 strcpy(line, line + n);
165 }
166 // cut trailing
167 if (ii) {
168 while (strchr(delims, line[--ii]))
169 continue;
170 line[++ii] = '\0';
171 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000172 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000173 // if something still remains -> return it
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000174 if (ii)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000175 break;
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000176
177 next_line:
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000178 // skip empty line
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000179 free(line);
180 }
181
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000182 // non-empty line found, parse and return the number of tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000183
184 // store line
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000185 parser->line = line = xrealloc(line, ii + 1);
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +0000186 if (flags & PARSE_KEEP_COPY) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000187 parser->data = xstrdup(line);
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +0000188 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000189
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000190 // split line to tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000191 ntokens--; // now it's max allowed token no
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000192 // N.B. non-empty remainder is also a token,
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000193 // so if ntokens <= 1, we just return the whole line
194 // N.B. if PARSE_LAST_IS_GREEDY is set the remainder of the line is stuck to the last token
195 for (ii = 0; *line && ii <= ntokens; ) {
196 //bb_info_msg("L[%s]", line);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000197 // get next token
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000198 // at the last token and need greedy token ->
199 if ((flags & PARSE_LAST_IS_GREEDY) && (ii == ntokens)) {
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000200 // skip possible delimiters
201 if (!(flags & PARSE_DONT_REDUCE))
202 line += strspn(line, delims);
203 // don't cut the line
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000204 q = line + strlen(line);
205 } else {
206 // vanilla token. cut the line at the first delim
207 q = line + strcspn(line, delims);
Denis Vlasenko9b366f42008-07-20 17:50:58 +0000208 if (*q) // watch out: do not step past the line end!
209 *q++ = '\0';
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000210 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000211 // pin token
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000212 if ((flags & (PARSE_DONT_REDUCE|PARSE_DONT_TRIM)) || *line) {
213 //bb_info_msg("N[%d] T[%s]", ii, line);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000214 tokens[ii++] = line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000215 }
216 line = q;
Denis Vlasenko9b366f42008-07-20 17:50:58 +0000217 //bb_info_msg("A[%s]", line);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000218 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000219
Denis Vlasenko5415c852008-07-21 23:05:26 +0000220 if (ii < mintokens) {
221 bb_error_msg("bad line %u: %d tokens found, %d needed",
222 parser->lineno, ii, mintokens);
223 if (flags & PARSE_MIN_DIE)
224 xfunc_die();
225 goto again;
226 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000227
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000228 return ii;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000229}