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