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