/* vi: set sw=4 ts=4: */ /* * config file parser helper * * Copyright (C) 2008 by Vladimir Dronnikov * * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ #include "libbb.h" /* Typical usage: ----- CUT ----- char *t[3]; // tokens placeholder parser_t p; // parser structure // open file if (config_open(filename, &p)) { // parse line-by-line while (*config_read(&p, t, 3, 0, delimiters, comment_char)) { // 0..3 tokens // use tokens bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]); } ... // free parser config_close(&p); } ----- CUT ----- */ #if !PARSER_STDIO_BASED char* FAST_FUNC config_open(parser_t *parser, const char *filename) { // empty file configures nothing! char *data = xmalloc_open_read_close(filename, NULL); if (!data) return data; // convert 0x5c 0x0a (backslashes at the very end of line) to 0x20 0x20 (spaces) for (char *s = data; (s = strchr(s, '\\')) != NULL; ++s) if ('\n' == s[1]) { s[0] = s[1] = ' '; } // init parser parser->line = parser->data = data; parser->lineno = 0; return data; } void FAST_FUNC config_close(parser_t *parser) { // for now just free config data free(parser->data); } char* FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment) { char *ret, *line; int noreduce = (ntokens<0); // do not treat subsequent delimiters as one delimiter if (ntokens < 0) ntokens = -ntokens; ret = line = parser->line; // nullify tokens memset(tokens, 0, sizeof(void *) * ntokens); // now split to lines while (*line) { int token_num = 0; // limit the line char *ptr = strchrnul(line, '\n'); *ptr++ = '\0'; // line number parser->lineno++; // comments mean EOLs if (comment) *strchrnul(line, comment) = '\0'; // skip leading delimiters while (*line && strchr(delims, *line)) line++; // skip empty lines if (*line) { char *s; // now split line to tokens s = line; while (s) { char *p; // get next token if (token_num+1 >= ntokens) break; p = s; while (*p && !strchr(delims, *p)) p++; if (!*p) break; *p++ = '\0'; // pin token if (noreduce || *s) { tokens[token_num++] = s; //bb_error_msg("L[%d] T[%s]", token_num, s); } s = p; } // non-empty remainder is also a token. So if ntokens == 0, we just return the whole line if (s && (noreduce || *s)) tokens[token_num++] = s; // sanity check: have we got all required tokens? if (token_num < mintokens) bb_error_msg_and_die("bad line %u, %d tokens found, %d needed", parser->lineno, token_num, mintokens); // advance data for the next call line = ptr; break; } // line didn't contain any token -> try next line ret = line = ptr; } parser->line = line; // return current line. caller must check *ret to determine whether to continue return ret; } #else // stdio-based FILE* FAST_FUNC config_open(parser_t *parser, const char *filename) { // empty file configures nothing! parser->fp = fopen_or_warn(filename, "r"); if (!parser->fp) return parser->fp; // init parser parser->line = NULL; parser->lineno = 0; return parser->fp; } void FAST_FUNC config_close(parser_t *parser) { fclose(parser->fp); } char* FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment) { char *line, *q; int token_num, len; int noreduce = (ntokens < 0); // do not treat subsequent delimiters as one delimiter if (ntokens < 0) ntokens = -ntokens; // nullify tokens memset(tokens, 0, sizeof(void *) * ntokens); // free used line free(parser->line); parser->line = NULL; while (1) { int n; // get fresh line //TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc line = xmalloc_fgetline(parser->fp); if (!line) return line; parser->lineno++; // handle continuations. Tito's code stolen :) while (1) { len = strlen(line); if (!len) goto free_and_cont; if (line[len - 1] != '\\') break; // multi-line object line[--len] = '\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); } } // comments mean EOLs if (comment) { q = strchrnul(line, comment); *q = '\0'; len = q - line; } // skip leading delimiters n = strspn(line, delims); if (n) { len -= n; strcpy(line, line + n); } if (len) break; // skip empty lines free_and_cont: free(line); } // non-empty line found, parse and return // store line parser->line = line = xrealloc(line, len + 1); // now split line to tokens //TODO: discard consecutive delimiters? token_num = 0; ntokens--; // now it's max allowed token no while (1) { // get next token if (token_num == ntokens) break; q = line + strcspn(line, delims); if (!*q) break; // pin token *q++ = '\0'; if (noreduce || *line) { tokens[token_num++] = line; //bb_error_msg("L[%d] T[%s]", token_num, line); } line = q; } // non-empty remainder is also a token, // so if ntokens <= 1, we just return the whole line if (noreduce || *line) tokens[token_num++] = line; if (token_num < mintokens) bb_error_msg_and_die("bad line %u: %d tokens found, %d needed", parser->lineno, token_num, mintokens); return parser->line; // maybe token_num instead? } #endif