blob: 3945501ad84333d6e664730205e0a8d180e406a1 [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);
28 while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) > 0) {
29 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
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000062parser_t* FAST_FUNC config_open(const char *filename)
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 Vlasenko2e157dd2008-07-19 09:27:19 +000066 parser->fp = fopen_or_warn_stdin(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
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000074static void config_free_data(parser_t *const parser)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000075{
Denis Vlasenkoc01340f2008-07-16 22:12:18 +000076 free(parser->line);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000077 parser->line = NULL;
78 USE_FEATURE_PARSE_COPY(
79 free(parser->data);
80 parser->data = NULL;
81 )
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000082}
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000083
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000084void FAST_FUNC config_close(parser_t *parser)
85{
86 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000087 fclose(parser->fp);
88}
89
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000090/*
911. Read a line from config file. If nothing to read then bail out returning 0.
92 Handle continuation character. Advance lineno for each physical line. Cut comments.
932. if PARSE_DONT_TRIM is not set (default) skip leading and cut trailing delimiters, if any.
943. If resulting line is empty goto 1.
954. Look for first delimiter. If PARSE_DONT_REDUCE or PARSE_DONT_TRIM is set then pin empty token.
965. Else (default) if number of seen tokens is equal to max number of tokens (token is the last one)
97 and PARSE_LAST_IS_GREEDY is set then pin the remainder of the line as the last token.
98 Else (token is not last or PARSE_LAST_IS_GREEDY is not set) just replace first delimiter with '\0'
99 thus delimiting token and pin it.
1006. Advance line pointer past the end of token. If number of seen tokens is less than required number
101 of tokens then goto 4.
1027. Control the number of seen tokens is not less the min number of tokens. Die if condition is not met.
1038. Return the number of seen tokens.
104
105mintokens > 0 make config_read() exit with error message if less than mintokens
106(but more than 0) are found. Empty lines are always skipped (not warned about).
107*/
108#undef config_read
109int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000110{
111 char *line, *q;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000112 char comment = *delims++;
113 int ii;
114 int ntokens = flags & 0xFF;
115 int mintokens = (flags & 0xFF00) >> 8;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000116
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000117 /*
118 // N.B. this could only be used in read-in-one-go version, or when tokens use xstrdup(). TODO
119 if (!parser->lineno || !(flags & PARSE_DONT_NULL))
120 */
121 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000122 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000123
124 while (1) {
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000125//TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
126 line = xmalloc_fgetline(parser->fp);
127 if (!line)
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000128 return 0;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000129
130 parser->lineno++;
131 // handle continuations. Tito's code stolen :)
132 while (1) {
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000133 ii = strlen(line);
134 if (!ii)
135 goto next_line;
136 if (line[ii - 1] != '\\')
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000137 break;
138 // multi-line object
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000139 line[--ii] = '\0';
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000140//TODO: add xmalloc_fgetline-like iface but with appending to existing str
141 q = xmalloc_fgetline(parser->fp);
142 if (q) {
143 parser->lineno++;
144 line = xasprintf("%s%s", line, q);
145 free(q);
146 }
147 }
148 // comments mean EOLs
149 if (comment) {
150 q = strchrnul(line, comment);
151 *q = '\0';
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000152 ii = q - line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000153 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000154 // skip leading and trailing delimiters
155 if (!(flags & PARSE_DONT_TRIM)) {
156 // skip leading
157 int n = strspn(line, delims);
158 if (n) {
159 ii -= n;
160 strcpy(line, line + n);
161 }
162 // cut trailing
163 if (ii) {
164 while (strchr(delims, line[--ii]))
165 continue;
166 line[++ii] = '\0';
167 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000168 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000169 // if something still remains -> return it
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000170 if (ii)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000171 break;
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000172
173 next_line:
174 /* skip empty line */
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000175 free(line);
176 }
177
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000178 // non-empty line found, parse and return the number of tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000179
180 // store line
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000181 parser->line = line = xrealloc(line, ii + 1);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000182 USE_FEATURE_PARSE_COPY(
183 parser->data = xstrdup(line);
184 )
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000185
Bernhard Reutner-Fischer54d50a02008-07-17 14:00:42 +0000186 /* now split line to tokens */
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000187 ntokens--; // now it's max allowed token no
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000188 // N.B, non-empty remainder is also a token,
189 // so if ntokens <= 1, we just return the whole line
190 // N.B. if PARSE_LAST_IS_GREEDY is set the remainder of the line is stuck to the last token
191 for (ii = 0; *line && ii <= ntokens; ) {
192 //bb_info_msg("L[%s]", line);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000193 // get next token
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000194 // at the last token and need greedy token ->
195 if ((flags & PARSE_LAST_IS_GREEDY) && (ii == ntokens)) {
196 // ... don't cut the line
197 q = line + strlen(line);
198 } else {
199 // vanilla token. cut the line at the first delim
200 q = line + strcspn(line, delims);
201 *q++ = '\0';
202 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000203 // pin token
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000204 if ((flags & (PARSE_DONT_REDUCE|PARSE_DONT_TRIM)) || *line) {
205 //bb_info_msg("N[%d] T[%s]", ii, line);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000206 tokens[ii++] = line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000207 }
208 line = q;
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000209 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000210
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000211 if (ii < mintokens)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000212 bb_error_msg_and_die("bad line %u: %d tokens found, %d needed",
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000213 parser->lineno, ii, mintokens);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000214
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000215 return ii;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000216}