/* config2.help.c - config2hep Config.in .config > help.h function parse() reads Config.in data into *sym list, then we read .config and set sym->try on each enabled symbol. */ #include #include #include #include #include #include #include #include #include #include #include #include #include //****************** functions copied from lib/*.c ******************** struct double_list { struct double_list *next, *prev; char *data; }; // Die unless we can allocate memory. void *xmalloc(size_t size) { void *ret = malloc(size); if (!ret) { fprintf(stderr, "xmalloc(%ld)", (long)size); exit(1); } return ret; } // Die unless we can allocate enough space to sprintf() into. char *xmprintf(char *format, ...) { va_list va, va2; int len; char *ret; va_start(va, format); va_copy(va2, va); // How long is it? len = vsnprintf(0, 0, format, va); len++; va_end(va); // Allocate and do the sprintf() ret = xmalloc(len); vsnprintf(ret, len, format, va2); va_end(va2); return ret; } // Die unless we can open/create a file, returning FILE *. FILE *xfopen(char *path, char *mode) { FILE *f = fopen(path, mode); if (!f) { fprintf(stderr, "No file %s", path); exit(1); } return f; } void *dlist_pop(void *list) { struct double_list **pdlist = (struct double_list **)list, *dlist = *pdlist; if (dlist->next == dlist) *pdlist = 0; else { dlist->next->prev = dlist->prev; dlist->prev->next = *pdlist = dlist->next; } return dlist; } void dlist_add_nomalloc(struct double_list **list, struct double_list *new) { if (*list) { new->next = *list; new->prev = (*list)->prev; (*list)->prev->next = new; (*list)->prev = new; } else *list = new->next = new->prev = new; } // Add an entry to the end of a doubly linked list struct double_list *dlist_add(struct double_list **list, char *data) { struct double_list *new = xmalloc(sizeof(struct double_list)); new->data = data; dlist_add_nomalloc(list, new); return new; } //****************** end copies of lib/*.c ************* // Parse config files into data structures. struct symbol { struct symbol *next; int enabled, help_indent; char *name, *depends; struct double_list *help; } *sym; // remove leading spaces char *skip_spaces(char *s) { while (isspace(*s)) s++; return s; } // if line starts with name (as whole word) return pointer after it, else NULL char *keyword(char *name, char *line) { int len = strlen(name); line = skip_spaces(line); if (strncmp(name, line, len)) return 0; line += len; if (*line && !isspace(*line)) return 0; line = skip_spaces(line); return line; } // dlist_pop() freeing wrapper structure for you. char *dlist_zap(struct double_list **help) { struct double_list *dd = dlist_pop(help); char *s = dd->data; free(dd); return s; } int zap_blank_lines(struct double_list **help) { int got = 0; while (*help) { char *s; s = skip_spaces((*help)->data); if (*s) break; got++; free(dlist_zap(help)); } return got; } // Collect "-a blah" description lines following a blank line (or start). // Returns array of removed lines with *len entries (0 for none). // Moves *help to new start of text (in case dash lines were at beginning). // Sets *from to where dash lines removed from (in case they weren't). // Discards blank lines before and after dashlines. // If no prefix, *help NULL. If no postfix, *from == *help // if no dashlines returned *from == *help. char **grab_dashlines(struct double_list **help, struct double_list **from, int *len) { struct double_list *dd; char *s, **list; int count = 0; *len = 0; zap_blank_lines(help); *from = *help; // Find start of dash block. Must be at start or after blank line. for (;;) { s = skip_spaces((*from)->data); if (*s == '-' && s[1] != '-' && !count) break; if (!*s) count = 0; else count++; *from = (*from)->next; if (*from == *help) return 0; } // If there was whitespace before this, zap it. This can't take out *help // because zap_blank_lines skipped blank lines, and we had to have at least // one non-blank line (a dash line) to get this far. while (!*skip_spaces((*from)->prev->data)) { *from = (*from)->prev; free(dlist_zap(from)); } // Count number of dashlines, copy out to array, zap trailing whitespace // If *help was at start of dashblock, move it with *from count = 0; dd = *from; if (*help == *from) *help = 0; for (;;) { if (*skip_spaces(dd->data) != '-') break; count++; if (*from == (dd = dd->next)) break; } list = xmalloc(sizeof(char *)*count); *len = count; while (count) list[--count] = dlist_zap(from); return list; } // Read Config.in (and includes) to populate global struct symbol *sym list. void parse(char *filename) { FILE *fp = xfopen(filename, "r"); struct symbol *new = 0; for (;;) { char *s, *line = NULL; size_t len; // Read line, trim whitespace at right edge. if (getline(&line, &len, fp) < 1) break; s = line+strlen(line); while (--s >= line) { if (!isspace(*s)) break; *s = 0; } // source or config keyword at left edge? if (*line && !isspace(*line)) { if ((s = keyword("config", line))) { memset(new = xmalloc(sizeof(struct symbol)), 0, sizeof(struct symbol)); new->next = sym; new->name = s; sym = new; } else if ((s = keyword("source", line))) parse(s); continue; } if (!new) continue; if (sym && sym->help_indent) { dlist_add(&(new->help), line); if (sym->help_indent < 0) { sym->help_indent = 0; while (isspace(line[sym->help_indent])) sym->help_indent++; } } else if ((s = keyword("depends", line)) && (s = keyword("on", s))) new->depends = s; else if (keyword("help", line)) sym->help_indent = -1; } fclose(fp); } int charsort(void *a, void *b) { char *aa = a, *bb = b; if (*aa < *bb) return -1; if (*aa > *bb) return 1; return 0; } int dashsort(char **a, char **b) { char *aa = *a, *bb = *b; if (aa[1] < bb[1]) return -1; if (aa[1] > bb[1]) return 1; return 0; } int dashlinesort(char **a, char **b) { return strcmp(*a, *b); } // Three stages: read data, collate entries, output results. int main(int argc, char *argv[]) { FILE *fp; if (argc != 3) { fprintf(stderr, "usage: config2help Config.in .config\n"); exit(1); } // Stage 1: read data. Read Config.in to global 'struct symbol *sym' list, // then read .config to set "enabled" member of each enabled symbol. // Read Config.in parse(argv[1]); // read .config fp = xfopen(argv[2], "r"); for (;;) { char *line = NULL; size_t len; if (getline(&line, &len, fp) < 1) break; if (!strncmp("CONFIG_", line, 7)) { struct symbol *try; char *s = line+7; for (try=sym; try; try=try->next) { len = strlen(try->name); if (!strncmp(try->name, s, len) && s[len]=='=' && s[len+1]=='y') { try->enabled++; break; } } } } // Stage 2: process data. // Collate help according to usage, depends, and .config // Loop through each entry, finding duplicate enabled "usage:" names // This is in reverse order, so last entry gets collated with previous // entry until we run out of matching pairs. for (;;) { struct symbol *throw = 0, *catch; char *this, *that, *cusage, *tusage, *name = 0; int len; // find a usage: name and collate all enabled entries with that name for (catch = sym; catch; catch = catch->next) { if (catch->enabled != 1) continue; if (catch->help && (that = keyword("usage:", catch->help->data))) { struct double_list *cfrom, *tfrom, *anchor; char *try, **cdashlines, **tdashlines, *usage; int clen, tlen; // Align usage: lines, finding a matching pair so we can suck help // text out of throw into catch, copying from this to that if (!throw) usage = that; else if (strncmp(name, that, len) || !isspace(that[len])) continue; catch->enabled++; while (!isspace(*that) && *that) that++; if (!throw) len = that-usage; free(name); name = strndup(usage, len); that = skip_spaces(that); if (!throw) { throw = catch; this = that; continue; } // Grab option description lines to collate from catch and throw tusage = dlist_zap(&throw->help); tdashlines = grab_dashlines(&throw->help, &tfrom, &tlen); cusage = dlist_zap(&catch->help); cdashlines = grab_dashlines(&catch->help, &cfrom, &clen); anchor = catch->help; // If we've got both, collate and alphebetize if (cdashlines && tdashlines) { char **new = xmalloc(sizeof(char *)*(clen+tlen)); memcpy(new, cdashlines, sizeof(char *)*clen); memcpy(new+clen, tdashlines, sizeof(char *)*tlen); free(cdashlines); free(tdashlines); qsort(new, clen+tlen, sizeof(char *), (void *)dashlinesort); cdashlines = new; // If just one, make sure it's in catch. } else if (tdashlines) cdashlines = tdashlines; // If throw had a prefix, insert it before dashlines, with a // blank line if catch had a prefix. if (tfrom && tfrom != throw->help) { if (throw->help || catch->help) dlist_add(&cfrom, strdup("")); else { dlist_add(&cfrom, 0); anchor = cfrom->prev; } while (throw->help && throw->help != tfrom) dlist_add(&cfrom, dlist_zap(&throw->help)); if (cfrom && cfrom->prev->data && *skip_spaces(cfrom->prev->data)) dlist_add(&cfrom, strdup("")); } if (!anchor) { dlist_add(&cfrom, 0); anchor = cfrom->prev; } // Splice sorted lines back in place if (cdashlines) { tlen += clen; for (clen = 0; clen < tlen; clen++) dlist_add(&cfrom, cdashlines[clen]); } // If there were no dashlines, text would be considered prefix, so // the list is definitely no longer empty, so discard placeholder. if (!anchor->data) dlist_zap(&anchor); // zap whitespace at end of catch help text while (!*skip_spaces(anchor->prev->data)) { anchor = anchor->prev; free(dlist_zap(&anchor)); } // Append trailing lines. while (tfrom) dlist_add(&anchor, dlist_zap(&tfrom)); // Collate first [-abc] option block in usage: lines try = 0; if (*this == '[' && this[1] == '-' && this[2] != '-' && *that == '[' && that[1] == '-' && that[2] != '-') { char *from = this+2, *to = that+2; int ff = strcspn(from, " ]"), tt = strcspn(to, " ]"); if (from[ff] == ']' && to[tt] == ']') { try = xmprintf("[-%.*s%.*s] ", ff, from, tt, to); qsort(try+2, ff+tt, 1, (void *)charsort); this = skip_spaces(this+ff+3); that = skip_spaces(that+tt+3); } } // The list is definitely no longer empty, so discard placeholder. if (!anchor->data) dlist_zap(&anchor); // Add new collated line (and whitespace). dlist_add(&anchor, xmprintf("%*cusage: %.*s %s%s%s%s", catch->help_indent, ' ', len, name, try ? try : "", this, *this ? " " : "", that)); free(try); dlist_add(&anchor, strdup("")); free(cusage); free(tusage); throw->enabled = 0; throw = catch; throw->help = anchor->prev->prev; throw = catch; this = throw->help->data + throw->help_indent + 8 + len; } } // Did we find one? if (!throw) break; } // Stage 3: output results to stdout. // Print out help #defines while (sym) { struct double_list *dd; if (sym->help) { int i, blank; char *s; strcpy(s = xmalloc(strlen(sym->name)+1), sym->name); for (i = 0; s[i]; i++) s[i] = tolower(s[i]); printf("#define HELP_%s \"", s); free(s); dd = sym->help; blank = 0; for (;;) { // Trim leading whitespace s = dd->data; i = sym->help_indent; while (isspace(*s) && i--) s++; // Only one blank line between nonblank lines, not at start or end. if (!*s) blank = 2; else { while (blank--) { putchar('\\'); putchar('n'); } blank = 1; } for (i=0; s[i]; i++) { if (s[i] == '"' || s[i] == '\\') putchar('\\'); putchar(s[i]); } dd = dd->next; if (dd == sym->help) break; } printf("\"\n\n"); } sym = sym->next; } return 0; }