toybox/scripts/config2help.c
<<
>>
Prefs
   1/* config2.help.c - config2hep Config.in .config > help.h
   2
   3   function parse() reads Config.in data into *sym list, then
   4   we read .config and set sym->try on each enabled symbol.
   5
   6*/
   7
   8#include <ctype.h>
   9#include <stdio.h>
  10#include <string.h>
  11#include <stdlib.h>
  12#include <sys/types.h>
  13#include <sys/stat.h>
  14#include <unistd.h>
  15#include <regex.h>
  16#include <inttypes.h>
  17#include <termios.h>
  18#include <poll.h>
  19#include <sys/socket.h>
  20
  21struct statvfs {int i;};
  22#include "lib/portability.h"
  23#include "lib/lib.h"
  24
  25// Humor toys.h (lie through our teeth, C's linker doesn't care).
  26char toys[4096], libbuf[4096], toybuf[4096];
  27void show_help(FILE *out) {;}
  28void toy_exec(char *argv[]) {;}
  29void toy_init(void *which, char *argv[]) {;}
  30
  31// Parse config files into data structures.
  32
  33struct symbol {
  34  struct symbol *next;
  35  int enabled, help_indent;
  36  char *name, *depends;
  37  struct double_list *help;
  38} *sym;
  39
  40// remove leading spaces
  41char *skip_spaces(char *s)
  42{
  43  while (isspace(*s)) s++;
  44
  45  return s;
  46}
  47
  48// if line starts with name (as whole word) return pointer after it, else NULL
  49char *keyword(char *name, char *line)
  50{
  51  int len = strlen(name);
  52
  53  line = skip_spaces(line);
  54  if (strncmp(name, line, len)) return 0;
  55  line += len;
  56  if (*line && !isspace(*line)) return 0;
  57  line = skip_spaces(line);
  58
  59  return line;
  60}
  61
  62// dlist_pop() freeing wrapper structure for you.
  63char *dlist_zap(struct double_list **help)
  64{
  65  struct double_list *dd = dlist_pop(help);
  66  char *s = dd->data;
  67
  68  free(dd);
  69  
  70  return s;
  71}
  72
  73int zap_blank_lines(struct double_list **help)
  74{
  75  int got = 0;
  76
  77  while (*help) {
  78    char *s;
  79
  80    s = skip_spaces((*help)->data);
  81
  82    if (*s) break;
  83    got++;
  84    free(dlist_zap(help));
  85  }
  86
  87  return got;
  88}
  89
  90// Collect "-a blah" description lines following a blank line (or start).
  91// Returns array of removed lines with *len entries (0 for none).
  92
  93// Moves *help to new start of text (in case dash lines were at beginning).
  94// Sets *from to where dash lines removed from (in case they weren't).
  95// Discards blank lines before and after dashlines.
  96
  97// If no prefix, *help NULL. If no postfix, *from == *help
  98// if no dashlines returned *from == *help.
  99
 100char **grab_dashlines(struct double_list **help, struct double_list **from,
 101                      int *len)
 102{
 103  struct double_list *dd;
 104  char *s, **list;
 105  int count = 0;
 106
 107  *len = 0;
 108  zap_blank_lines(help);
 109  *from = *help;
 110
 111  // Find start of dash block. Must be at start or after blank line.
 112  for (;;) {
 113    s = skip_spaces((*from)->data);
 114    if (*s == '-' && s[1] != '-' && !count) break;
 115
 116    if (!*s) count = 0;
 117    else count++;
 118
 119    *from = (*from)->next;
 120    if (*from == *help) return 0;
 121  }
 122
 123  // If there was whitespace before this, zap it. This can't take out *help
 124  // because zap_blank_lines skipped blank lines, and we had to have at least
 125  // one non-blank line (a dash line) to get this far.
 126  while (!*skip_spaces((*from)->prev->data)) {
 127    *from = (*from)->prev;
 128    free(dlist_zap(from));
 129  }
 130
 131  // Count number of dashlines, copy out to array, zap trailing whitespace
 132  // If *help was at start of dashblock, move it with *from
 133  count = 0;
 134  dd = *from;
 135  if (*help == *from) *help = 0;
 136  for (;;) {
 137   if (*skip_spaces(dd->data) != '-') break;
 138   count++;
 139   if (*from == (dd = dd->next)) break;
 140  }
 141
 142  list = xmalloc(sizeof(char *)*count);
 143  *len = count;
 144  while (count) list[--count] = dlist_zap(from);
 145
 146  return list;
 147}
 148
 149// Read Config.in (and includes) to populate global struct symbol *sym list.
 150void parse(char *filename)
 151{
 152  FILE *fp = xfopen(filename, "r");
 153  struct symbol *new = 0;
 154
 155  for (;;) {
 156    char *s, *line = NULL;
 157    size_t len;
 158
 159    // Read line, trim whitespace at right edge.
 160    if (getline(&line, &len, fp) < 1) break;
 161    s = line+strlen(line);
 162    while (--s >= line) {
 163      if (!isspace(*s)) break;
 164      *s = 0;
 165    }
 166
 167    // source or config keyword at left edge?
 168    if (*line && !isspace(*line)) {
 169      if ((s = keyword("config", line))) {
 170        new = xzalloc(sizeof(struct symbol));
 171        new->next = sym;
 172        new->name = s;
 173        sym = new;
 174      } else if ((s = keyword("source", line))) parse(s);
 175
 176      continue;
 177    }
 178    if (!new) continue;
 179
 180    if (sym && sym->help_indent) {
 181      dlist_add(&(new->help), line);
 182      if (sym->help_indent < 0) {
 183        sym->help_indent = 0;
 184        while (isspace(line[sym->help_indent])) sym->help_indent++;
 185      }
 186    }
 187    else if ((s = keyword("depends", line)) && (s = keyword("on", s)))
 188      new->depends = s;
 189    else if (keyword("help", line)) sym->help_indent = -1;
 190  }
 191
 192  fclose(fp);
 193}
 194
 195int charsort(void *a, void *b)
 196{
 197  char *aa = a, *bb = b;
 198
 199  if (*aa < *bb) return -1;
 200  if (*aa > *bb) return 1;
 201  return 0;
 202}
 203
 204int dashsort(char **a, char **b)
 205{
 206  char *aa = *a, *bb = *b;
 207
 208  if (aa[1] < bb[1]) return -1;
 209  if (aa[1] > bb[1]) return 1;
 210  return 0;
 211}
 212
 213int dashlinesort(char **a, char **b)
 214{
 215  return strcmp(*a, *b);
 216}
 217
 218// Three stages: read data, collate entries, output results.
 219
 220int main(int argc, char *argv[])
 221{
 222  FILE *fp;
 223
 224  if (argc != 3) {
 225    fprintf(stderr, "usage: config2help Config.in .config\n");
 226    exit(1);
 227  }
 228
 229  // Stage 1: read data. Read Config.in to global 'struct symbol *sym' list,
 230  // then read .config to set "enabled" member of each enabled symbol.
 231
 232  // Read Config.in
 233  parse(argv[1]);
 234
 235  // read .config
 236  fp = xfopen(argv[2], "r");
 237  for (;;) {
 238    char *line = NULL;
 239    size_t len;
 240
 241    if (getline(&line, &len, fp) < 1) break;
 242    if (!strncmp("CONFIG_", line, 7)) {
 243      struct symbol *try;
 244      char *s = line+7;
 245
 246      for (try=sym; try; try=try->next) {
 247        len = strlen(try->name);
 248        if (!strncmp(try->name, s, len) && s[len]=='=' && s[len+1]=='y') {
 249          try->enabled++;
 250          break;
 251        } 
 252      }
 253    }
 254  }
 255
 256  // Stage 2: process data.
 257
 258  // Collate help according to usage, depends, and .config
 259
 260  // Loop through each entry, finding duplicate enabled "usage:" names
 261  // This is in reverse order, so last entry gets collated with previous
 262  // entry until we run out of matching pairs.
 263  for (;;) {
 264    struct symbol *throw = 0, *catch;
 265    char *this, *that, *cusage, *tusage, *name = 0;
 266    int len;
 267
 268    // find a usage: name and collate all enabled entries with that name
 269    for (catch = sym; catch; catch = catch->next) {
 270      if (catch->enabled != 1) continue;
 271      if (catch->help && (that = keyword("usage:", catch->help->data))) {
 272        struct double_list *cfrom, *tfrom, *anchor;
 273        char *try, **cdashlines, **tdashlines, *usage;
 274        int clen, tlen;
 275
 276        // Align usage: lines, finding a matching pair so we can suck help
 277        // text out of throw into catch, copying from this to that
 278        if (!throw) usage = that;
 279        else if (strncmp(name, that, len) || !isspace(that[len])) continue;
 280        catch->enabled++;
 281        while (!isspace(*that) && *that) that++;
 282        if (!throw) len = that-usage;
 283        free(name);
 284        name = strndup(usage, len);
 285        that = skip_spaces(that);
 286        if (!throw) {
 287          throw = catch;
 288          this = that;
 289
 290          continue;
 291        }
 292
 293        // Grab option description lines to collate from catch and throw
 294        tusage = dlist_zap(&throw->help);
 295        tdashlines = grab_dashlines(&throw->help, &tfrom, &tlen);
 296        cusage = dlist_zap(&catch->help);
 297        cdashlines = grab_dashlines(&catch->help, &cfrom, &clen);
 298        anchor = catch->help;
 299
 300        // If we've got both, collate and alphebetize
 301        if (cdashlines && tdashlines) {
 302          char **new = xmalloc(sizeof(char *)*(clen+tlen));
 303
 304          memcpy(new, cdashlines, sizeof(char *)*clen);
 305          memcpy(new+clen, tdashlines, sizeof(char *)*tlen);
 306          free(cdashlines);
 307          free(tdashlines);
 308          qsort(new, clen+tlen, sizeof(char *), (void *)dashlinesort);
 309          cdashlines = new;
 310
 311        // If just one, make sure it's in catch.
 312        } else if (tdashlines) cdashlines = tdashlines;
 313
 314        // If throw had a prefix, insert it before dashlines, with a
 315        // blank line if catch had a prefix.
 316        if (tfrom && tfrom != throw->help) {
 317          if (throw->help || catch->help) dlist_add(&cfrom, strdup(""));
 318          else {
 319            dlist_add(&cfrom, 0);
 320            anchor = cfrom->prev;
 321          }
 322          while (throw->help && throw->help != tfrom)
 323            dlist_add(&cfrom, dlist_zap(&throw->help));
 324          if (cfrom && cfrom->prev->data && *skip_spaces(cfrom->prev->data))
 325            dlist_add(&cfrom, strdup(""));
 326        }
 327        if (!anchor) {
 328          dlist_add(&cfrom, 0);
 329          anchor = cfrom->prev;
 330        }
 331
 332        // Splice sorted lines back in place
 333        if (cdashlines) {
 334          tlen += clen;
 335
 336          for (clen = 0; clen < tlen; clen++) 
 337            dlist_add(&cfrom, cdashlines[clen]);
 338        }
 339
 340        // If there were no dashlines, text would be considered prefix, so
 341        // the list is definitely no longer empty, so discard placeholder.
 342        if (!anchor->data) dlist_zap(&anchor);
 343
 344        // zap whitespace at end of catch help text
 345        while (!*skip_spaces(anchor->prev->data)) {
 346          anchor = anchor->prev;
 347          free(dlist_zap(&anchor));
 348        }
 349
 350        // Append trailing lines.
 351        while (tfrom) dlist_add(&anchor, dlist_zap(&tfrom));
 352
 353        // Collate first [-abc] option block in usage: lines
 354        try = 0;
 355        if (*this == '[' && this[1] == '-' && this[2] != '-' &&
 356            *that == '[' && that[1] == '-' && that[2] != '-')
 357        {
 358          char *from = this+2, *to = that+2;
 359          int ff = strcspn(from, " ]"), tt = strcspn(to, " ]");
 360
 361          if (from[ff] == ']' && to[tt] == ']') {
 362            try = xmprintf("[-%.*s%.*s] ", ff, from, tt, to);
 363            qsort(try+2, ff+tt, 1, (void *)charsort);
 364            this = skip_spaces(this+ff+3);
 365            that = skip_spaces(that+tt+3);
 366          }
 367        }
 368
 369        // The list is definitely no longer empty, so discard placeholder.
 370        if (!anchor->data) dlist_zap(&anchor);
 371
 372        // Add new collated line (and whitespace).
 373        dlist_add(&anchor, xmprintf("%*cusage: %.*s %s%s%s%s",
 374                  catch->help_indent, ' ', len, name, try ? try : "",
 375                  this, *this ? " " : "", that));
 376        free(try);
 377        dlist_add(&anchor, strdup(""));
 378        free(cusage);
 379        free(tusage);
 380        throw->enabled = 0;
 381        throw = catch;
 382        throw->help = anchor->prev->prev;
 383
 384        throw = catch;
 385        this = throw->help->data + throw->help_indent + 8 + len;
 386      }
 387    }
 388
 389    // Did we find one?
 390
 391    if (!throw) break;
 392  }
 393
 394  // Stage 3: output results to stdout.
 395
 396  // Print out help #defines
 397  while (sym) {
 398    struct double_list *dd;
 399
 400    if (sym->help) {
 401      int i;
 402      char *s = xstrdup(sym->name);
 403
 404      for (i = 0; s[i]; i++) s[i] = tolower(s[i]);
 405      printf("#define HELP_%s \"", s);
 406      free(s);
 407
 408      dd = sym->help;
 409      for (;;) {
 410        i = sym->help_indent;
 411
 412        // Trim leading whitespace
 413        s = dd->data;
 414        while (isspace(*s) && i) {
 415          s++;
 416          i--;
 417        }
 418        for (i=0; s[i]; i++) {
 419          if (s[i] == '"' || s[i] == '\\') putchar('\\');
 420          putchar(s[i]);
 421        }
 422        putchar('\\');
 423        putchar('n');
 424        dd = dd->next;
 425        if (dd == sym->help) break;
 426      }
 427      printf("\"\n\n");
 428    }
 429    sym = sym->next;
 430  }
 431
 432  return 0;
 433}
 434