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 
24 struct double_list {
25   struct double_list *next, *prev;
26   char *data;
27 };
28 
29 // Die unless we can allocate memory.
xmalloc(size_t size)30 void *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.
xmprintf(char * format,...)42 char *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 *.
xfopen(char * path,char * mode)65 FILE *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 
dlist_pop(void * list)75 void *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 
dlist_add_nomalloc(struct double_list ** list,struct double_list * new)88 void 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
dlist_add(struct double_list ** list,char * data)100 struct 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 
114 struct 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
skip_spaces(char * s)122 char *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
keyword(char * name,char * line)130 char *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.
dlist_zap(struct double_list ** help)144 char *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 
zap_blank_lines(struct double_list ** help)154 int 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 
grab_dashlines(struct double_list ** help,struct double_list ** from,int * len)181 char **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.
parse(char * filename)231 void 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 
charsort(void * a,void * b)276 int 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 
dashsort(char ** a,char ** b)285 int 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 
dashlinesort(char ** a,char ** b)294 int dashlinesort(char **a, char **b)
295 {
296   return strcmp(*a, *b);
297 }
298 
299 // Three stages: read data, collate entries, output results.
300 
main(int argc,char * argv[])301 int 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