findutils/*: move usage and applet bits to *.c files
[oweals/busybox.git] / libbb / parse_config.c
index 5f6dbbde165f19b639b7a10582f3b4091140872f..c511d97fbd60aa913000d6a42501100ceadf5f95 100644 (file)
  * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
  *
  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Also for use in uClibc (http://uclibc.org/) licensed under LGPLv2.1 or later.
  */
 
 #include "libbb.h"
 
+#if defined ENABLE_PARSE && ENABLE_PARSE
+int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int parse_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *delims = "# \t";
+       unsigned flags = PARSE_NORMAL;
+       int mintokens = 0, ntokens = 128;
+
+       opt_complementary = "-1:n+:m+:f+";
+       getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
+       //argc -= optind;
+       argv += optind;
+       while (*argv) {
+               parser_t *p = config_open(*argv);
+               if (p) {
+                       int n;
+                       char **t = xmalloc(sizeof(char *) * ntokens);
+                       while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
+                               for (int i = 0; i < n; ++i)
+                                       printf("[%s]", t[i]);
+                               puts("");
+                       }
+                       config_close(p);
+               }
+               argv++;
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
 /*
 
 Typical usage:
 
 ----- CUT -----
        char *t[3];     // tokens placeholder
-       parser_t p;     // parser structure
-       // open file
-       if (config_open(filename, &p)) {
+       parser_t *p = config_open(filename);
+       if (p) {
                // parse line-by-line
-               while (*config_read(&p, t, 3, 0, delimiters, comment_char) >= 0) { // 0..3 tokens
+               while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
                        // use tokens
                        bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
                }
                ...
                // free parser
-               config_close(&p);
+               config_close(p);
        }
 ----- CUT -----
 
 */
 
+parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
+{
+       FILE* fp;
+       parser_t *parser;
+
+       fp = fopen_func(filename);
+       if (!fp)
+               return NULL;
+       parser = xzalloc(sizeof(*parser));
+       parser->fp = fp;
+       return parser;
+}
+
 parser_t* FAST_FUNC config_open(const char *filename)
 {
-       parser_t *parser = xzalloc(sizeof(parser_t));
-       /* empty file configures nothing */
-       parser->fp = fopen_or_warn(filename, "r");
-       if (parser->fp)
-               return parser;
-       config_close (parser);
-       if (ENABLE_FEATURE_CLEAN_UP)
-         free(parser);
-       return NULL;
+       return config_open2(filename, fopen_or_warn_stdin);
 }
 
-static void config_free_data(parser_t *const parser)
+static void config_free_data(parser_t *parser)
 {
        free(parser->line);
-       free(parser->data);
-       parser->line = parser->data = NULL;
+       parser->line = NULL;
+       if (PARSE_KEEP_COPY) { /* compile-time constant */
+               free(parser->data);
+               parser->data = NULL;
+       }
 }
+
 void FAST_FUNC config_close(parser_t *parser)
 {
-       config_free_data(parser);
-       fclose(parser->fp);
+       if (parser) {
+               config_free_data(parser);
+               fclose(parser->fp);
+               free(parser);
+       }
 }
 
-int FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment)
+/*
+0. If parser is NULL return 0.
+1. Read a line from config file. If nothing to read then return 0.
+   Handle continuation character. Advance lineno for each physical line.
+   Discard everything past comment character.
+2. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
+3. If resulting line is empty goto 1.
+4. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
+   remember the token as empty.
+5. Else (default) if number of seen tokens is equal to max number of tokens
+   (token is the last one) and PARSE_GREEDY is set then the remainder
+   of the line is the last token.
+   Else (token is not last or PARSE_GREEDY is not set) just replace
+   first delimiter with '\0' thus delimiting the token.
+6. Advance line pointer past the end of token. If number of seen tokens
+   is less than required number of tokens then goto 4.
+7. Check the number of seen tokens is not less the min number of tokens.
+   Complain or die otherwise depending on PARSE_MIN_DIE.
+8. Return the number of seen tokens.
+
+mintokens > 0 make config_read() print error message if less than mintokens
+(but more than 0) are found. Empty lines are always skipped (not warned about).
+*/
+#undef config_read
+int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
 {
-       char *line, *q;
-       int ii, seen;
-       /* do not treat consecutive delimiters as one delimiter */
-       bool noreduce = (ntokens < 0);
-       if (noreduce)
-               ntokens = -ntokens;
+       char *line;
+       int ntokens, mintokens;
+       int t, len;
 
+       ntokens = flags & 0xFF;
+       mintokens = (flags & 0xFF00) >> 8;
+
+       if (parser == NULL)
+               return 0;
+
+again:
        memset(tokens, 0, sizeof(tokens[0]) * ntokens);
        config_free_data(parser);
 
-       while (1) {
-//TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
-               line = xmalloc_fgetline(parser->fp);
-               if (!line)
-                       return -1;
-
-               parser->lineno++;
-               // handle continuations. Tito's code stolen :)
-               while (1) {
-                       ii = strlen(line);
-                       if (!ii)
-                               goto next_line;
-                       if (line[ii - 1] != '\\')
-                               break;
-                       // multi-line object
-                       line[--ii] = '\0';
-//TODO: add xmalloc_fgetline-like iface but with appending to existing str
-                       q = xmalloc_fgetline(parser->fp);
-                       if (q) {
-                               parser->lineno++;
-                               line = xasprintf("%s%s", line, q);
-                               free(q);
+       /* Read one line (handling continuations with backslash) */
+       line = bb_get_chunk_with_continuation(parser->fp, &len, &parser->lineno);
+       if (line == NULL)
+               return 0;
+       parser->line = line;
+
+       /* Strip trailing line-feed if any */
+       if (len && line[len-1] == '\n')
+               line[len-1] = '\0';
+
+       /* Skip token in the start of line? */
+       if (flags & PARSE_TRIM)
+               line += strspn(line, delims + 1);
+
+       if (line[0] == '\0' || line[0] == delims[0])
+               goto again;
+
+       if (flags & PARSE_KEEP_COPY)
+               parser->data = xstrdup(line);
+
+       /* Tokenize the line */
+       for (t = 0; *line && *line != delims[0] && t < ntokens; t++) {
+               /* Pin token */
+               tokens[t] = line;
+
+               /* Combine remaining arguments? */
+               if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
+                       /* Vanilla token, find next delimiter */
+                       line += strcspn(line, delims[0] ? delims : delims + 1);
+               } else {
+                       /* Combining, find comment char if any */
+                       line = strchrnul(line, delims[0]);
+
+                       /* Trim any extra delimiters from the end */
+                       if (flags & PARSE_TRIM) {
+                               while (strchr(delims + 1, line[-1]) != NULL)
+                                       line--;
                        }
                }
-               // comments mean EOLs
-               if (comment) {
-                       q = strchrnul(line, comment);
-                       *q = '\0';
-                       ii = q - line;
-               }
-               // skip leading delimiters
-               seen = strspn(line, delims);
-               if (seen) {
-                       ii -= seen;
-                       strcpy(line, line + seen);
+
+               /* Token not terminated? */
+               if (line[0] == delims[0])
+                       *line = '\0';
+               else if (line[0] != '\0')
+                       *(line++) = '\0';
+
+#if 0 /* unused so far */
+               if (flags & PARSE_ESCAPE) {
+                       const char *from;
+                       char *to;
+
+                       from = to = tokens[t];
+                       while (*from) {
+                               if (*from == '\\') {
+                                       from++;
+                                       *to++ = bb_process_escape_sequence(&from);
+                               } else {
+                                       *to++ = *from++;
+                               }
+                       }
+                       *to = '\0';
                }
-               if (ii)
-                       break;
+#endif
 
- next_line:
-               /* skip empty line */
-               free(line);
+               /* Skip possible delimiters */
+               if (flags & PARSE_COLLAPSE)
+                       line += strspn(line, delims + 1);
        }
 
-       // non-empty line found, parse and return
-
-       // store line
-       parser->line = line = xrealloc(line, ii + 1);
-       parser->data = xstrdup(line);
-
-       /* now split line to tokens */
-       ii = noreduce ? seen : 0;
-       ntokens--; // now it's max allowed token no
-       while (1) {
-               // get next token
-               if (ii == ntokens)
-                       break;
-               q = line + strcspn(line, delims);
-               if (!*q)
-                       break;
-               // pin token
-               *q++ = '\0';
-               if (noreduce || *line) {
-                       tokens[ii++] = line;
-//bb_info_msg("L[%d] T[%s]\n", ii, line);
-               }
-               line = q;
+       if (t < mintokens) {
+               bb_error_msg("bad line %u: %d tokens found, %d needed",
+                               parser->lineno, t, mintokens);
+               if (flags & PARSE_MIN_DIE)
+                       xfunc_die();
+               goto again;
        }
 
-       // non-empty remainder is also a token,
-       // so if ntokens <= 1, we just return the whole line
-       if (noreduce || *line)
-               tokens[ii++] = line;
-
-       if (ii < mintokens)
-               bb_error_msg_and_die("bad line %u: %d tokens found, %d needed",
-                               parser->lineno, ii, mintokens);
-
-       return ii;
+       return t;
 }