epi_iter_new(): Use xrealloc().
[tfortune.git] / tfortune.c
1 /* SPDX-License-Identifier: GPL-3.0-only */
2
3 #include <stdlib.h>
4 #include <inttypes.h>
5 #include <stdio.h>
6 #include <fcntl.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10 #include <assert.h>
11 #include <sys/mman.h>
12 #include <stdbool.h>
13 #include <string.h>
14 #include <time.h>
15 #include <limits.h>
16 #include <sys/time.h>
17 #include <lopsub.h>
18
19 #include "tf.h"
20 #include "tfortune.lsg.h"
21
22 #define TF_SEP "---- "
23
24 static struct lls_parse_result *lpr, *sublpr;
25 #define CMD_PTR(_cname) lls_cmd(LSG_TFORTUNE_CMD_ ## _cname, tfortune_suite)
26 #define OPT_RESULT(_cname, _oname) (lls_opt_result(\
27         LSG_TFORTUNE_ ## _cname ## _OPT_ ## _oname, \
28         (CMD_PTR(_cname) == CMD_PTR(TFORTUNE))? lpr : sublpr))
29 #define OPT_GIVEN(_cname, _oname) (lls_opt_given(OPT_RESULT(_cname, _oname)))
30 #define OPT_STRING_VAL(_cname, _oname) (lls_string_val(0, \
31         OPT_RESULT(_cname, _oname)))
32 #define OPT_UINT32_VAL(_cname, _oname) (lls_uint32_val(0, \
33                 OPT_RESULT(_cname, _oname)))
34
35 int loglevel_arg_val;
36
37 struct tf_user_data {int (*handler)(void);};
38 #define EXPORT_CMD_HANDLER(_cmd) const struct tf_user_data \
39         lsg_tfortune_com_ ## _cmd ## _user_data = { \
40                 .handler = com_ ## _cmd \
41         };
42
43 struct map_chunk {
44         const char *base;
45         size_t len;
46 };
47
48 struct epigram {
49         struct map_chunk epi, tags;
50 };
51
52 static int lopsub_error(int lopsub_ret, char **errctx)
53 {
54         const char *msg = lls_strerror(-lopsub_ret);
55         if (*errctx)
56                 ERROR_LOG("%s: %s\n", *errctx, msg);
57         else
58                 ERROR_LOG("%s\n", msg);
59         free(*errctx);
60         *errctx = NULL;
61         return -E_LOPSUB;
62 }
63
64 /* per epigram context for the tag expression parser */
65 struct epi_properties {
66         const struct map_chunk *chunk; /* only the epigram */
67         struct linhash_table *tagtab;
68         unsigned num_tags;
69 };
70
71 unsigned epi_len(const struct epi_properties *props)
72 {
73         return props->chunk->len;
74 }
75
76 char *epi_text(const struct epi_properties *props)
77 {
78         const char *txt = props->chunk->base;
79         char *result = malloc(props->chunk->len + 1);
80         memcpy(result, txt, props->chunk->len);
81         result[props->chunk->len] = '\0';
82         return result;
83 }
84
85 bool epi_has_tag(const char *tag, const struct epi_properties *props)
86 {
87         return linhash_lookup(tag, props->tagtab);
88 }
89
90 struct tag_iter {
91         char *str;
92         char *saveptr; /* for strtok_r(3) */
93         char *token;
94 };
95
96 static struct tag_iter *tag_iter_new(const struct map_chunk *tags)
97 {
98         struct tag_iter *titer = xmalloc(sizeof(*titer));
99
100         titer->str = xmalloc(tags->len + 1);
101         memcpy(titer->str, tags->base, tags->len);
102         titer->str[tags->len] = '\0';
103         titer->token = strtok_r(titer->str, ",", &titer->saveptr);
104         return titer;
105 }
106
107 static const char *tag_iter_get(const struct tag_iter *titer)
108 {
109         return titer->token;
110 }
111
112 static void tag_iter_next(struct tag_iter *titer)
113 {
114         titer->token = strtok_r(NULL, ",", &titer->saveptr);
115 }
116
117 static void tag_iter_free(struct tag_iter *titer)
118 {
119         if (!titer)
120                 return;
121         free(titer->str);
122         free(titer);
123 }
124
125 static bool epi_admissible(const struct epigram *epi,
126                 const struct txp_context *ast)
127 {
128         bool admissible;
129         const char *p;
130         struct epi_properties props = {
131                 .chunk = &epi->epi,
132                 .tagtab = linhash_new(3),
133                 .num_tags = 0,
134         };
135         struct tag_iter *titer;
136
137
138         for (
139                 titer = tag_iter_new(&epi->tags);
140                 (p = tag_iter_get(titer));
141                 tag_iter_next(titer)
142         ) {
143                 struct linhash_item item = {.key = p};
144                 linhash_insert(&item, props.tagtab, NULL);
145                 props.num_tags++;
146         }
147
148         admissible = txp_eval_ast(ast, &props);
149         linhash_free(props.tagtab);
150         tag_iter_free(titer);
151         return admissible;
152 }
153
154 static void print_epigram(const struct epigram *epi, bool print_tags)
155 {
156         printf("%.*s", (int)epi->epi.len, epi->epi.base);
157         if (print_tags)
158                 printf(TF_SEP "%.*s\n", (int)epi->tags.len, epi->tags.base);
159 }
160
161 static void print_admissible_epigrams(const struct epigram *epis,
162                 unsigned num_epis)
163 {
164         unsigned n;
165
166         for (n = 0; n < num_epis; n++)
167                 print_epigram(epis + n, OPT_GIVEN(PRINT, TAGS));
168 }
169
170 static void print_random_epigram(const struct epigram *epis, unsigned num_epis)
171 {
172         long unsigned r;
173         const struct epigram *epi;
174         struct timeval tv;
175
176         if (num_epis == 0) {
177                 ERROR_LOG("no matching epigram\n");
178                 return;
179         }
180         gettimeofday(&tv, NULL);
181         srandom((unsigned)tv.tv_usec);
182         r = (num_epis + 0.0) * (random() / (RAND_MAX + 1.0));
183         assert(r < num_epis);
184         epi = epis + r;
185         print_epigram(epi, OPT_GIVEN(PRINT, TAGS));
186 }
187
188 static int read_tag_expression(struct iovec *result)
189 {
190         const char *tx = OPT_STRING_VAL(PRINT, EXPRESSION);
191         int ret, fd;
192
193         assert(tx);
194         if (strcmp(tx, "-")) {
195                 char *filename;
196                 if (tx[0] != '/') {
197                         char *home = get_homedir();
198                         xasprintf(&filename, "%s/.tfortune/expressions/%s",
199                                 home, tx);
200                         free(home);
201                 } else
202                         filename = xstrdup(tx);
203                 ret = open(filename, O_RDONLY);
204                 if (ret < 0) {
205                         ret = -ERRNO_TO_TF_ERROR(errno);
206                         ERROR_LOG("could not open %s\n", filename);
207                 }
208                 free(filename);
209                 if (ret < 0)
210                         return ret;
211                 fd = ret;
212         } else
213                 fd = STDIN_FILENO;
214         ret = fd2buf(fd, result);
215         close(fd);
216         return ret;
217 }
218
219 static int tx2ast(const struct iovec *tx, struct txp_context **ast)
220 {
221         int ret;
222         char *errmsg;
223
224         ret = txp_init(tx, ast, &errmsg);
225         if (ret < 0) {
226                 ERROR_LOG("could not parse tag expression: %s\n", errmsg);
227                 free(errmsg);
228                 return ret;
229         }
230         return 1;
231 }
232
233 struct epi_iter {
234         struct iovec *maps;
235         unsigned num_maps;
236         unsigned map_num;
237         struct epigram epi;
238         unsigned num_epis;
239 };
240
241 static bool get_next_epi(struct epi_iter *eiter)
242 {
243         const char *epi_start = NULL;
244
245         for (; eiter->map_num < eiter->num_maps; eiter->map_num++) {
246                 struct iovec *iov = eiter->maps + eiter->map_num;
247                 const char *buf, *end = iov->iov_base + iov->iov_len;
248
249                 if (!epi_start && eiter->epi.tags.base)
250                         epi_start = eiter->epi.tags.base
251                                 + eiter->epi.tags.len + 1;
252                 else
253                         epi_start = iov->iov_base;
254                 buf = epi_start;
255                 while (buf < end) {
256                         const size_t sep_len = strlen(TF_SEP);
257                         const char *p, *cr, *tags;
258                         size_t tag_len;
259
260                         cr = memchr(buf, '\n', end - buf);
261                         if (!cr)
262                                 break;
263                         p = cr + 1;
264                         if (p + sep_len >= end)
265                                 break;
266                         if (strncmp(p, TF_SEP, sep_len) != 0) {
267                                 buf = p;
268                                 continue;
269                         }
270                         tags = p + sep_len;
271                         cr = memchr(tags, '\n', end - tags);
272                         if (cr)
273                                 tag_len = cr - tags;
274                         else
275                                 tag_len = end - tags;
276                         eiter->epi.epi.base = epi_start;
277                         eiter->epi.epi.len = p - epi_start;
278                         eiter->epi.tags.base = tags;
279                         eiter->epi.tags.len = tag_len;
280                         eiter->num_epis++;
281                         return true;
282                 }
283         }
284         eiter->epi.epi.base = NULL;
285         eiter->epi.epi.len = 0;
286         eiter->epi.tags.base = NULL;
287         eiter->epi.tags.len = 0;
288         return false;
289 }
290
291 static char *get_basedir(void)
292 {
293         char *home, *basedir;
294         if (OPT_GIVEN(TFORTUNE, BASEDIR))
295                 return xstrdup(OPT_STRING_VAL(TFORTUNE, BASEDIR));
296         home = get_homedir();
297         xasprintf(&basedir, "%s/.tfortune", home);
298         free(home);
299         return basedir;
300 }
301
302 static char *get_epidir(void)
303 {
304         char *basedir, *epidir;
305         basedir = get_basedir();
306         xasprintf(&epidir, "%s/epigrams", basedir);
307         free(basedir);
308         return epidir;
309 }
310
311 static char *get_xdir(void)
312 {
313         char *basedir = get_basedir(), *xdir;
314         xasprintf(&xdir, "%s/expressions", basedir);
315         free(basedir);
316         return xdir;
317 }
318
319 static struct epi_iter *epi_iter_new(void)
320 {
321         struct epi_iter *eiter = xmalloc(sizeof(*eiter));
322         unsigned num_inputs = lls_num_inputs(sublpr);
323
324         if (num_inputs == 0) {
325                 struct regfile_iter *riter;
326                 struct iovec iov;
327                 char *epidir = get_epidir();
328
329                 regfile_iter_new(epidir, &riter);
330                 free(epidir);
331                 eiter->maps = NULL;
332                 eiter->num_maps = 0;
333                 for (;
334                         regfile_iter_map(riter, &iov);
335                         regfile_iter_next(riter)
336                 ) {
337                         eiter->num_maps++;
338                         eiter->maps = xrealloc(eiter->maps,
339                                 eiter->num_maps * sizeof(*eiter->maps));
340                         eiter->maps[eiter->num_maps - 1] = iov;
341                 }
342                 regfile_iter_free(riter);
343         } else {
344                 unsigned n;
345                 eiter->maps = xmalloc(num_inputs * sizeof(*eiter->maps));
346                 for (n = 0; n < num_inputs; n++)
347                         mmap_file(lls_input(n, sublpr), eiter->maps + n);
348                 eiter->num_maps = num_inputs;
349         }
350         eiter->map_num = 0;
351         eiter->epi.epi.base = NULL;
352         eiter->epi.epi.len = 0;
353         eiter->epi.tags.base = NULL;
354         eiter->epi.tags.len = 0;
355         eiter->num_epis = 0;
356         get_next_epi(eiter);
357         return eiter;
358 }
359
360 static const struct epigram *epi_iter_get(const struct epi_iter *eiter)
361 {
362         return (eiter->epi.epi.base && eiter->epi.tags.base)?
363                 &eiter->epi : NULL;
364 }
365
366 static unsigned epi_iter_num_maps(const struct epi_iter *eiter)
367 {
368         return eiter->num_maps;
369 }
370
371 static unsigned epi_iter_num_epis(const struct epi_iter *eiter)
372 {
373         return eiter->num_epis;
374 }
375
376 static void epi_iter_next(struct epi_iter *eiter)
377 {
378         get_next_epi(eiter);
379 }
380
381 static void epi_iter_free(struct epi_iter *eiter)
382 {
383         unsigned n;
384
385         if (!eiter)
386                 return;
387         for (n = 0; n < eiter->num_maps; n++)
388                 munmap(eiter->maps[n].iov_base, eiter->maps[n].iov_len);
389         free(eiter->maps);
390         free(eiter);
391 }
392
393 static int com_print(void)
394 {
395         int ret;
396         struct epigram *epis = NULL;
397         unsigned epis_sz = 0, nae = 0; /* number of admissible epis */
398         struct iovec tx;
399         struct txp_context *ast;
400         struct epi_iter *eiter;
401         const struct epigram *epi;
402
403         ret = read_tag_expression(&tx);
404         if (ret < 0)
405                 return ret;
406         ret = tx2ast(&tx, &ast);
407         if (ret < 0)
408                 goto free_tx;
409         for (
410                 eiter = epi_iter_new();
411                 (epi = epi_iter_get(eiter));
412                 epi_iter_next(eiter)
413         ) {
414                 if (!epi_admissible(epi, ast))
415                         continue;
416                 if (nae >= epis_sz) {
417                         epis_sz = 2 * epis_sz + 1;
418                         epis = xrealloc(epis, epis_sz * sizeof(*epis));
419                 }
420                 epis[nae++] = *epi;
421         }
422         if (OPT_GIVEN(PRINT, ALL))
423                 print_admissible_epigrams(epis, nae);
424         else
425                 print_random_epigram(epis, nae);
426         epi_iter_free(eiter);
427         free(epis);
428         txp_free(ast);
429         ret = 1;
430 free_tx:
431         free(tx.iov_base);
432         return ret;
433 }
434 EXPORT_CMD_HANDLER(print);
435
436 static char *get_editor(void)
437 {
438         char *val = getenv("TFORTUNE_EDITOR");
439
440         if (val && val[0])
441                 return xstrdup(val);
442         val = getenv("EDITOR");
443         if (val && val[0])
444                 return xstrdup(val);
445         return xstrdup("vi");
446 }
447
448 static void open_editor(const char *dir)
449 {
450         char *editor;
451         char **argv;
452         pid_t pid;
453         unsigned n, num_inputs = lls_num_inputs(sublpr);
454
455         if ((pid = fork()) < 0) {
456                 EMERG_LOG("fork error: %s\n", strerror(errno));
457                 exit(EXIT_FAILURE);
458         }
459         if (pid) { /* parent */
460                 wait(NULL);
461                 return;
462         }
463         editor = get_editor();
464         argv = xmalloc((num_inputs + 2) * sizeof(*argv));
465         argv[0] = editor;
466         for (n = 0; n < num_inputs; n++)
467                 xasprintf(&argv[n + 1], "%s/%s", dir, lls_input(n, sublpr));
468         argv[num_inputs + 1] = NULL;
469         execvp(editor, argv);
470         EMERG_LOG("execvp error: %s\n", strerror(errno));
471         _exit(EXIT_FAILURE);
472 }
473
474 static int create_dir(const char *path)
475 {
476         int ret;
477
478         ret = mkdir(path, 0777); /* rely on umask */
479         if (ret < 0) {
480                 if (errno == EEXIST)
481                         return 0;
482                 ERROR_LOG("could not create %s\n", path);
483                 return -ERRNO_TO_TF_ERROR(errno);
484         }
485         NOTICE_LOG("created directory %s\n", path);
486         return 1;
487 }
488
489 static int create_basedir(void)
490 {
491         char *basedir;
492         int ret;
493
494         basedir = get_basedir();
495         ret = create_dir(basedir);
496         free(basedir);
497         return ret;
498 }
499
500 static int generic_edit(const char *dir)
501 {
502         char *errctx;
503         int ret;
504         bool basedir_given = OPT_GIVEN(TFORTUNE, BASEDIR);
505
506         ret = lls_check_arg_count(sublpr, 1, INT_MAX, &errctx);
507         if (ret < 0) {
508                 ret = lopsub_error(ret, &errctx);
509                 return ret;
510         }
511         if (!basedir_given) {
512                 ret = create_basedir();
513                 if (ret < 0)
514                         return ret;
515                 ret = create_dir(dir);
516                 if (ret < 0)
517                         return ret;
518         }
519         open_editor(dir);
520         ret = 1;
521         return ret;
522 }
523
524 static int com_ede(void)
525 {
526         int ret;
527         char *epidir = get_epidir();
528         ret = generic_edit(epidir);
529         free(epidir);
530         return ret;
531 }
532 EXPORT_CMD_HANDLER(ede);
533
534 static int com_edx(void)
535 {
536         int ret;
537         char *xdir = get_xdir();
538         ret = generic_edit(xdir);
539         free(xdir);
540         return ret;
541 }
542 EXPORT_CMD_HANDLER(edx);
543
544 static int item_alpha_compare(const struct linhash_item **i1,
545                 const struct linhash_item **i2)
546 {
547         return strcmp((*i1)->key, (*i2)->key);
548 }
549
550 static int item_num_compare(const struct linhash_item **i1,
551                 const struct linhash_item **i2)
552 {
553         long unsigned v1 = (long unsigned)(*i1)->object;
554         long unsigned v2 = (long unsigned)(*i2)->object;
555
556         return v1 < v2? -1 : (v1 == v2? 0 : 1);
557 }
558
559 struct dentry {
560         char mode[11];
561         nlink_t nlink;
562         char *user, *group;
563         off_t size;
564         uint64_t mtime;
565         char *name;
566 };
567
568 static void make_dentry(const char *name, const struct stat *stat,
569                 struct dentry *d)
570 {
571         mode_t m = stat->st_mode;
572         struct group *g;
573         struct passwd *pwentry;
574
575         sprintf(d->mode, "----------");
576         if (S_ISREG(m))
577                 d->mode[0] = '-';
578         else if (S_ISDIR(m))
579                 d->mode[0] = 'd';
580         else if (S_ISCHR(m))
581                 d->mode[0] = 'c';
582         else if ((S_ISBLK(m)))
583                 d->mode[0] = 'b';
584         else if (S_ISLNK(m))
585                 d->mode[0] = 'l';
586         else if (S_ISFIFO(m))
587                 d->mode[0] = 'p';
588         else if ((S_ISSOCK(m)))
589                 d->mode[0] = 's';
590         else
591                 d->mode[0] = '?';
592
593         if (m & S_IRUSR)
594                 d->mode[1] = 'r';
595         if (m & S_IWUSR)
596                 d->mode[2] = 'w';
597         if (m & S_IXUSR) {
598                 if (m & S_ISUID)
599                         d->mode[3] = 's';
600                 else
601                         d->mode[3] = 'x';
602         } else if (m & S_ISUID)
603                 d->mode[3] = 'S';
604
605         if (m & S_IRGRP)
606                 d->mode[4] = 'r';
607         if (m & S_IWGRP)
608                 d->mode[5] = 'w';
609         if (m & S_IXGRP) {
610                 if (m & S_ISGID)
611                         d->mode[6] = 's';
612                 else
613                         d->mode[6] = 'x';
614         } else if (m & S_ISGID)
615                 d->mode[6] = 'S';
616
617         if (m & S_IROTH)
618                 d->mode[7] = 'r';
619         if (m & S_IWOTH)
620                 d->mode[8] = 'w';
621         if (m & S_IXOTH) {
622                 if (m & S_ISVTX)
623                         d->mode[9] = 't';
624                 else
625                         d->mode[9] = 'x';
626         } else if (m & S_ISVTX)
627                 d->mode[9] = 'T';
628
629         d->nlink = stat->st_nlink;
630
631         pwentry = getpwuid(stat->st_uid);
632         if (pwentry && pwentry->pw_name)
633                 d->user = xstrdup(pwentry->pw_name);
634         else
635                 xasprintf(&d->user, "%u", stat->st_uid);
636
637         g = getgrgid(stat->st_gid);
638         if (g && g->gr_name)
639                 d->group = xstrdup(g->gr_name);
640         else
641                 xasprintf(&d->group, "%u", stat->st_gid);
642         d->size = stat->st_size;
643         d->mtime = stat->st_mtime;
644         d->name = xstrdup(name);
645 }
646
647 static int num_digits(uint64_t x)
648 {
649         unsigned n = 1;
650
651         if (x != 0)
652                 while (x > 9) {
653                         x /= 10;
654                         n++;
655                 }
656         return n;
657 }
658
659 enum var_length_dentry_fields {
660         VLDF_NLINKS,
661         VLDF_USER,
662         VLDF_GROUP,
663         VLDF_SIZE,
664         NUM_VLDF
665 };
666
667 static void update_field_field_widths(int field_widths[NUM_VLDF],
668                 const struct dentry *d)
669 {
670         int *w, n;
671
672         w = field_widths + VLDF_NLINKS;
673         n = num_digits(d->nlink);
674         *w = MAX(*w, n);
675
676         w = field_widths + VLDF_USER;
677         n = strlen(d->user);
678         *w = MAX(*w, n);
679
680         w = field_widths + VLDF_GROUP;
681         n = strlen(d->group);
682         *w = MAX(*w, n);
683
684         w = field_widths + VLDF_SIZE;
685         n = num_digits(d->size);
686         *w = MAX(*w, n);
687 }
688
689 static void format_time(uint64_t seconds, uint64_t now, struct iovec *result)
690 {
691         struct tm *tm;
692         const uint64_t m = 6 * 30 * 24 * 3600; /* six months */
693         size_t nbytes;
694
695         tm = localtime((time_t *)&seconds);
696         assert(tm);
697
698         if (seconds > now - m && seconds < now + m) {
699                 nbytes = strftime(result->iov_base, result->iov_len,
700                         "%b %e %k:%M", tm);
701                 assert(nbytes > 0);
702         } else {
703                 nbytes = strftime(result->iov_base, result->iov_len,
704                         "%b %e  %Y", tm);
705                 assert(nbytes > 0);
706         }
707 }
708
709 static int list_directory(const char *dir, bool long_listing)
710 {
711         struct regfile_iter *riter;
712         const char *basename;
713         int ret, field_widths[NUM_VLDF] = {0};
714         struct dentry *dentries = NULL;
715         unsigned n, num_dentries = 0, dentries_size = 0;
716         struct timespec now;
717
718         for (
719                 regfile_iter_new(dir, &riter);
720                 (basename = regfile_iter_basename(riter));
721                 regfile_iter_next(riter)
722         ) {
723                 const struct stat *stat;
724                 struct dentry *dentry;
725                 if (!long_listing) {
726                         printf("%s\n", basename);
727                         continue;
728                 }
729                 num_dentries++;
730                 if (num_dentries > dentries_size) {
731                         dentries_size = 2 * dentries_size + 1;
732                         dentries = xrealloc(dentries,
733                                 dentries_size * sizeof(*dentries));
734                 }
735                 dentry = dentries + num_dentries - 1;
736                 stat = regfile_iter_stat(riter);
737                 make_dentry(basename, stat, dentry);
738                 update_field_field_widths(field_widths, dentry);
739         }
740         regfile_iter_free(riter);
741         if (!long_listing)
742                 return 0;
743         ret = clock_gettime(CLOCK_REALTIME, &now);
744         assert(ret == 0);
745         for (n = 0; n < num_dentries; n++) {
746                 struct dentry *d = dentries + n;
747                 char buf[30];
748                 struct iovec iov = {.iov_base = buf, .iov_len = sizeof(buf)};
749
750                 format_time(d->mtime, now.tv_sec, &iov);
751                 printf("%s %*lu %*s %*s %*" PRIu64 " %s %s\n",
752                         d->mode,
753                         field_widths[VLDF_NLINKS], (long unsigned)d->nlink,
754                         field_widths[VLDF_USER], d->user,
755                         field_widths[VLDF_GROUP], d->group,
756                         field_widths[VLDF_SIZE], (uint64_t)d->size,
757                         buf,
758                         d->name
759                 );
760                 free(d->user);
761                 free(d->group);
762                 free(d->name);
763         }
764         free(dentries);
765         return 1;
766 }
767
768 static int com_lse(void)
769 {
770         int ret;
771         char *dir = get_epidir();
772
773         ret = list_directory(dir, OPT_GIVEN(LSE, LONG));
774         free(dir);
775         return ret;
776 }
777 EXPORT_CMD_HANDLER(lse);
778
779 static int com_lsx(void)
780 {
781         int ret;
782         char *dir = get_xdir();
783
784         ret = list_directory(dir, OPT_GIVEN(LSE, LONG));
785         free(dir);
786         return ret;
787 }
788 EXPORT_CMD_HANDLER(lsx);
789
790 static struct linhash_table *hash_tags(unsigned *num_epi_files,
791                 unsigned *num_epis)
792 {
793         struct linhash_table *tagtab = linhash_new(3);
794         struct epi_iter *eiter;
795         const struct epigram *epi;
796
797         for (
798                 eiter = epi_iter_new();
799                 (epi = epi_iter_get(eiter));
800                 epi_iter_next(eiter)
801         ) {
802                 struct tag_iter *titer;
803                 const char *tag;
804                 for (
805                         titer = tag_iter_new(&epi->tags);
806                         (tag = tag_iter_get(titer));
807                         tag_iter_next(titer)
808                 ) {
809                         struct linhash_item item = {
810                                 .key = xstrdup(tag),
811                                 .object = (void *)1LU
812                         };
813                         void **object;
814                         if (linhash_insert(&item, tagtab, &object) < 0) {
815                                 long unsigned val = (long unsigned)*object;
816                                 val++;
817                                 *object = (void *)val;
818                                 free((char *)item.key);
819                         }
820                 }
821                 tag_iter_free(titer);
822         }
823         if (num_epi_files)
824                 *num_epi_files = epi_iter_num_maps(eiter);
825         if (num_epis)
826                 *num_epis = epi_iter_num_epis(eiter);
827         epi_iter_free(eiter);
828         return tagtab;
829 }
830
831 static int com_lst(void)
832 {
833         struct linhash_table *tagtab;
834         struct linhash_iterator *liter;
835         struct linhash_item *itemp;
836         linhash_comparator *comp = OPT_GIVEN(LST, SORT_BY_COUNT)?
837                 item_num_compare : item_alpha_compare;
838         bool reverse = OPT_GIVEN(LST, REVERSE);
839
840         tagtab = hash_tags(NULL, NULL);
841         for (
842                 liter = linhash_iterator_new(tagtab, comp, reverse);
843                 (itemp = linhash_iterator_item(liter));
844                 linhash_iterator_next(liter)
845         ) {
846                 if (OPT_GIVEN(LST, LONG))
847                         printf("%lu\t%s\n", (long unsigned)itemp->object,
848                                 itemp->key);
849                 else
850                         printf("%s\n", itemp->key);
851                 free((char *)itemp->key);
852         }
853         linhash_iterator_free(liter);
854         linhash_free(tagtab);
855         return 0;
856 }
857 EXPORT_CMD_HANDLER(lst);
858
859 static int com_stats(void)
860 {
861         struct linhash_table *tagtab;
862         struct linhash_iterator *liter;
863         struct linhash_item *itemp;
864         unsigned num_epi_files, num_epis, num_unique_tags, num_x = 0;
865         long unsigned num_tags = 0;
866         char *xdir, *lh_stats;
867         struct regfile_iter *riter;
868         bool verbose = OPT_GIVEN(STATS, VERBOSE);
869
870         tagtab = hash_tags(&num_epi_files, &num_epis);
871         for (
872                 liter = linhash_iterator_new(tagtab, NULL, false);
873                 (itemp = linhash_iterator_item(liter));
874                 linhash_iterator_next(liter)
875         )
876                 num_tags += (long unsigned)itemp->object;
877         num_unique_tags = linhash_num_items(tagtab);
878         linhash_iterator_free(liter);
879         if (verbose)
880                 lh_stats = linhash_statistics(tagtab);
881         linhash_free(tagtab);
882
883         xdir = get_xdir();
884         for (
885                 regfile_iter_new(xdir, &riter);
886                 regfile_iter_basename(riter);
887                 regfile_iter_next(riter)
888         )
889                 num_x++;
890         regfile_iter_free(riter);
891         free(xdir);
892         printf("number of tag expressions.......... %5u\n", num_x);
893         printf("number of epigram files............ %5u\n", num_epi_files);
894         printf("number of epigrams................. %5u\n", num_epis);
895         printf("number of tags..................... %5lu\n", num_tags);
896         printf("number of unique tags.............. %5u\n", num_unique_tags);
897         printf("average number of epigrams per file %8.02f\n",
898                 (float)num_epis / num_epi_files);
899         printf("average number of tags per epigram. %8.02f\n",
900                 (float)num_tags / num_epis);
901         printf("average number of tag recurrence... %8.02f\n",
902                 (float)num_tags / num_unique_tags);
903         if (verbose) {
904                 printf("\nlinear hashing statistics:\n%s\n", lh_stats);
905                 free(lh_stats);
906         }
907         return 1;
908 }
909 EXPORT_CMD_HANDLER(stats);
910
911 #define LSG_TFORTUNE_CMD(_name) #_name
912 static const char * const subcommand_names[] = {LSG_TFORTUNE_SUBCOMMANDS NULL};
913 #undef LSG_TFORTUNE_CMD
914
915 static void show_subcommand_summary(bool verbose)
916 {
917         int i;
918
919         printf("Available subcommands:\n");
920         if (verbose) {
921                 const struct lls_command *cmd;
922                 for (i = 1; (cmd = lls_cmd(i, tfortune_suite)); i++) {
923                         const char *purpose = lls_purpose(cmd);
924                         const char *name = lls_command_name(cmd);
925                         printf("%-11s%s\n", name, purpose);
926                 }
927         } else {
928                 unsigned n = 8;
929                 printf("\t");
930                 for (i = 0; i < LSG_NUM_TFORTUNE_SUBCOMMANDS; i++) {
931                         if (i > 0)
932                                 n += printf(", ");
933                         n += printf("%s", subcommand_names[i]);
934                         if (n > 70) {
935                                 printf("\n\t");
936                                 n = 8;
937                         }
938                 }
939                 printf("\n");
940         }
941 }
942
943 static int com_help(void)
944 {
945         int ret;
946         char *errctx, *help;
947         const char *arg;
948         const struct lls_command *cmd;
949
950         ret = lls_check_arg_count(sublpr, 0, 1, &errctx);
951         if (ret < 0)
952                 return lopsub_error(ret, &errctx);
953         if (lls_num_inputs(sublpr) == 0) {
954                 show_subcommand_summary(OPT_GIVEN(HELP, LONG));
955                 return 0;
956         }
957         arg = lls_input(0, sublpr);
958         ret = lls_lookup_subcmd(arg, tfortune_suite, &errctx);
959         if (ret < 0)
960                 return lopsub_error(ret, &errctx);
961         cmd = lls_cmd(ret, tfortune_suite);
962         if (OPT_GIVEN(HELP, LONG))
963                 help = lls_long_help(cmd);
964         else
965                 help = lls_short_help(cmd);
966         printf("%s\n", help);
967         free(help);
968         return 1;
969 }
970 EXPORT_CMD_HANDLER(help);
971
972 static void handle_help_and_version(void)
973 {
974         int i;
975         char *help;
976         const struct lls_command *cmd;
977
978         if (OPT_GIVEN(TFORTUNE, VERSION)) {
979                 printf("tfortune %s\n"
980                         "Copyright (C) " COPYRIGHT_YEAR " " AUTHOR ".\n"
981                         "License " LICENSE ": <" LICENSE_URL ">.\n"
982                         "This is free software: you are free to change and redistribute it.\n"
983                         "There is NO WARRANTY, to the extent permitted by law.\n"
984                         "Report bugs to " AUTHOR " <" PACKAGE_BUGREPORT ">.\n"
985                         ,
986                         tf_version()
987                 );
988                 exit(EXIT_SUCCESS);
989         }
990         cmd = CMD_PTR(TFORTUNE);
991         if (OPT_GIVEN(TFORTUNE, DETAILED_HELP))
992                 help = lls_long_help(cmd);
993         else if (OPT_GIVEN(TFORTUNE, HELP))
994                 help = lls_short_help(cmd);
995         else
996                 return;
997         printf("%s\n", help);
998         free(help);
999         if (OPT_GIVEN(TFORTUNE, DETAILED_HELP))
1000                 for (i = 1; (cmd = lls_cmd(i, tfortune_suite)); i++) {
1001                         help = lls_short_help(cmd);
1002                         printf("%s\n---\n", help);
1003                         free(help);
1004                 }
1005         else
1006                 show_subcommand_summary(true /* verbose */);
1007         exit(EXIT_SUCCESS);
1008 }
1009
1010 enum tf_word_type {
1011         WT_COMMAND_NAME,
1012         WT_DOUBLE_DASH, /* -- */
1013         WT_SHORT_OPT_WITH_ARG, /* -l */
1014         WT_SHORT_OPT_WITHOUT_ARG, /* -V, -abc=d */
1015         WT_LONG_OPT_WITH_ARG, /* --loglevel */
1016         WT_LONG_OPT_WITHOUT_ARG, /* --foo=bar --help */
1017         WT_OPTION_ARG,
1018         WT_NON_OPTION_ARG,
1019         WT_DUNNO,
1020 };
1021
1022 static bool is_short_opt(const char *word)
1023 {
1024         if (word[0] != '-')
1025                 return false;
1026         if (word[1] == '-')
1027                 return false;
1028         if (word[1] == '\0')
1029                 return false;
1030         return true;
1031 }
1032
1033 static bool is_long_opt(const char *word)
1034 {
1035         if (word[0] != '-')
1036                 return false;
1037         if (word[1] != '-')
1038                 return false;
1039         if (word[2] == '\0')
1040                 return false;
1041         return true;
1042 }
1043
1044 /* whether the next word will be an arg to this short opt */
1045 static int short_opt_needs_arg(const char *word,
1046                 const char * const *short_opts)
1047 {
1048         size_t n, len;
1049
1050         if (strchr(word, '='))
1051                 return false;
1052         len = strlen(word);
1053         for (n = 0; short_opts[n]; n++) {
1054                 const char *opt = short_opts[n];
1055                 if (word[len - 1] != opt[1])
1056                         continue;
1057                 if (opt[2] == '=')
1058                         return true;
1059                 else
1060                         return false;
1061         }
1062         return -1;
1063 }
1064
1065 /* whether the next word will be an arg to this long opt */
1066 static int long_opt_needs_arg(const char *word,
1067                 const char * const *long_opts)
1068 {
1069         size_t n;
1070
1071         if (strchr(word, '='))
1072                 return false;
1073         for (n = 0; long_opts[n]; n++) {
1074                 const char *opt = long_opts[n];
1075                 size_t len = strlen(opt);
1076
1077                 if (opt[len - 1] == '=')
1078                         len--;
1079                 if (strncmp(word + 2, opt + 2, len - 2))
1080                         continue;
1081                 if (opt[len] == '=')
1082                         return true;
1083                 else
1084                         return false;
1085         }
1086         return -1;
1087 }
1088
1089 static bool get_word_types(unsigned cword, unsigned arg0,
1090                 const char * const *short_opts, const char * const *long_opts,
1091                 enum tf_word_type *result)
1092 {
1093         const char *word;
1094         unsigned n;
1095         bool have_dd = false;
1096         int ret;
1097
1098         /* index zero is always the command name */
1099         assert(cword > arg0);
1100         result[arg0] = WT_COMMAND_NAME;
1101         for (n = arg0 + 1; n < cword; n++) {
1102                 enum tf_word_type prev_type = result[n - 1];
1103
1104                 if (have_dd) {
1105                         result[n] = WT_NON_OPTION_ARG;
1106                         continue;
1107                 }
1108                 if (prev_type == WT_SHORT_OPT_WITH_ARG) {
1109                         result[n] = WT_OPTION_ARG;
1110                         continue;
1111                 }
1112                 if (prev_type == WT_LONG_OPT_WITH_ARG) {
1113                         result[n] = WT_OPTION_ARG;
1114                         continue;
1115                 }
1116                 word = lls_input(n, sublpr);
1117                 if (strcmp(word, "--") == 0) {
1118                         result[n] = WT_DOUBLE_DASH;
1119                         have_dd = true;
1120                         continue;
1121                 }
1122                 if (is_short_opt(word)) {
1123                         ret = short_opt_needs_arg(word, short_opts);
1124                         if (ret < 0)
1125                                 goto dunno;
1126                         if (ret > 0)
1127                                 result[n] = WT_SHORT_OPT_WITH_ARG;
1128                         else
1129                                 result[n] = WT_SHORT_OPT_WITHOUT_ARG;
1130                         continue;
1131                 }
1132                 if (is_long_opt(word)) {
1133                         ret = long_opt_needs_arg(word, long_opts);
1134                         if (ret < 0)
1135                                 goto dunno;
1136                         if (ret > 0)
1137                                 result[n] = WT_LONG_OPT_WITH_ARG;
1138                         else
1139                                 result[n] = WT_LONG_OPT_WITHOUT_ARG;
1140                         continue;
1141                 }
1142                 result[n] = WT_NON_OPTION_ARG;
1143         }
1144         return have_dd;
1145 dunno:
1146         for (; n <= cword; n++)
1147                 result[n] = WT_DUNNO;
1148         return false;
1149 }
1150
1151 #define DUMMY_COMPLETER(_name) static char **complete_ ## _name( \
1152         __attribute__ ((unused)) uint32_t cword, \
1153         __attribute__ ((unused)) unsigned arg0, \
1154         __attribute__ ((unused)) bool have_dd \
1155         ) {return NULL;}
1156
1157 DUMMY_COMPLETER(tfortune)
1158 DUMMY_COMPLETER(compgen)
1159 DUMMY_COMPLETER(completer)
1160
1161 static const char * const supercmd_opts[] = {LSG_TFORTUNE_TFORTUNE_OPTS, NULL};
1162
1163 static void print_zero_terminated_list(const char * const *list)
1164 {
1165         const char * const *c;
1166         for (c = list; *c; c++)
1167                 printf("%s%c", *c, '\0');
1168 }
1169
1170 static void print_option_list(const char * const *opts)
1171 {
1172         const char * const *c;
1173
1174         for (c = opts; *c; c++) {
1175                 int len = strlen(*c);
1176                 assert(len > 0);
1177                 if ((*c)[len - 1] == '=')
1178                         len--;
1179                 printf("%.*s%c", len, *c, '\0');
1180         }
1181 }
1182
1183 static void activate_dirname_completion(void)
1184 {
1185         printf("%c", '\0');
1186         printf("-o dirnames%c", '\0');
1187 }
1188
1189 static void complete_loglevels(void)
1190 {
1191         unsigned n;
1192         const struct lls_option *opt = lls_opt(LSG_TFORTUNE_TFORTUNE_OPT_LOGLEVEL,
1193                 CMD_PTR(TFORTUNE));
1194
1195         for (n = 0; n < LSG_NUM_TFORTUNE_TFORTUNE_LOGLEVEL_VALUES; n++) {
1196                 const char *v = lls_enum_string_val(n, opt);
1197                 printf("%s%c", v, '\0');
1198         }
1199 }
1200
1201 static char **complete_dentries(const char *dir)
1202 {
1203         const char *bn;
1204         struct regfile_iter *riter;
1205         unsigned n;
1206         char **result = NULL;
1207
1208         regfile_iter_new(dir, &riter);
1209         for (
1210                 n = 0;
1211                 (bn = regfile_iter_basename(riter));
1212                 regfile_iter_next(riter), n++
1213         ) {
1214                 result = xrealloc(result, (n + 2) * sizeof(*result));
1215                 result[n] = xstrdup(bn);
1216                 result[n + 1] = NULL;
1217         }
1218         return result;
1219 }
1220
1221 static char **complete_ede(__attribute__ ((unused)) uint32_t cword,
1222                 __attribute__ ((unused)) unsigned arg0,
1223                 __attribute__ ((unused)) bool have_dd)
1224 {
1225         char **result, *epidir = get_epidir();
1226
1227         result = complete_dentries(epidir);
1228         free(epidir);
1229         return result;
1230 }
1231
1232 static char **complete_edx(__attribute__ ((unused)) uint32_t cword,
1233                 __attribute__ ((unused)) unsigned arg0,
1234                 __attribute__ ((unused)) bool have_dd)
1235 {
1236         char **result, *xdir = get_xdir();
1237
1238         result = complete_dentries(xdir);
1239         free(xdir);
1240         return result;
1241 }
1242
1243 static char **complete_std_opts(bool have_dd, const char * const *opts)
1244 {
1245         if (have_dd)
1246                 print_option_list(opts);
1247         else
1248                 print_option_list(supercmd_opts);
1249         return NULL;
1250 }
1251
1252 static char **complete_stats(__attribute__ ((unused)) uint32_t cword,
1253                 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1254 {
1255         const char * const opts[] = {LSG_TFORTUNE_STATS_OPTS, NULL};
1256         return complete_std_opts(have_dd, opts);
1257 }
1258
1259 static char **complete_lse(__attribute__ ((unused)) uint32_t cword,
1260                 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1261 {
1262         const char * const opts[] = {LSG_TFORTUNE_LSE_OPTS, NULL};
1263         return complete_std_opts(have_dd, opts);
1264 }
1265
1266 static char **complete_lst(__attribute__ ((unused)) uint32_t cword,
1267                 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1268 {
1269         const char * const opts[] = {LSG_TFORTUNE_LST_OPTS, NULL};
1270         return complete_std_opts(have_dd, opts);
1271 }
1272
1273 static char **complete_lsx(__attribute__ ((unused)) uint32_t cword,
1274                 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1275 {
1276         const char * const opts[] = {LSG_TFORTUNE_LSX_OPTS, NULL};
1277         return complete_std_opts(have_dd, opts);
1278 }
1279
1280 static char **complete_help(__attribute__ ((unused)) uint32_t cword,
1281                 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1282 {
1283         const char * const opts[] = {LSG_TFORTUNE_HELP_OPTS, NULL};
1284
1285         if (!have_dd)
1286                 print_option_list(supercmd_opts);
1287         else
1288                 print_option_list(opts);
1289         print_zero_terminated_list(subcommand_names);
1290         return NULL;
1291 }
1292
1293 static char **complete_print(uint32_t cword, unsigned arg0, bool have_dd)
1294 {
1295         const char * const short_opts[] = {LSG_TFORTUNE_PRINT_SHORT_OPTS, NULL};
1296         const char * const long_opts[] = {LSG_TFORTUNE_PRINT_LONG_OPTS, NULL};
1297         const char * const opts[] = {LSG_TFORTUNE_PRINT_OPTS, NULL};
1298         enum tf_word_type *word_types, prev_type;
1299         const char *prev;
1300         char **result, *xdir;
1301
1302         word_types = xmalloc(cword * sizeof(*word_types));
1303         get_word_types(cword, arg0, short_opts, long_opts, word_types);
1304         prev = lls_input(cword - 1, sublpr);
1305         prev_type = word_types[cword - 1];
1306         free(word_types);
1307         switch (prev_type) {
1308                 case WT_COMMAND_NAME:
1309                 case WT_SHORT_OPT_WITHOUT_ARG:
1310                 case WT_OPTION_ARG:
1311                 case WT_LONG_OPT_WITHOUT_ARG:
1312                 case WT_DOUBLE_DASH:
1313                         if (!have_dd)
1314                                 print_option_list(supercmd_opts);
1315                         else
1316                                 print_option_list(opts);
1317                         return NULL;
1318                 case WT_SHORT_OPT_WITH_ARG:
1319                         if (strcmp(prev, "-x") == 0)
1320                                 goto complete_expression;
1321                         break;
1322                 case WT_LONG_OPT_WITH_ARG:
1323                         if (strcmp(prev, "--expression") == 0)
1324                                 goto complete_expression;
1325                         break;
1326                 default:
1327                         return NULL;
1328         }
1329 complete_expression:
1330         xdir = get_xdir();
1331         result = complete_dentries(xdir);
1332         free(xdir);
1333         return result;
1334 }
1335
1336 typedef char **(*completer)(uint32_t cword, unsigned arg0, bool have_dd);
1337
1338 #define LSG_TFORTUNE_CMD(_name) complete_ ## _name
1339 static const completer completers[] = {LSG_TFORTUNE_COMMANDS};
1340 #undef LSG_TFORTUNE_CMD
1341
1342 static int call_subcmd_completer(unsigned cmd_num, int arg0, uint32_t cword,
1343                 bool have_dd)
1344 {
1345         char **c, **candidates = completers[cmd_num](cword, arg0, have_dd);
1346
1347         if (!candidates)
1348                 return 0;
1349         for (c = candidates; *c; c++) {
1350                 printf("%s%c", *c, '\0');
1351                 free(*c);
1352         }
1353         free(candidates);
1354         return 1;
1355 }
1356
1357 static bool need_subcommand_completer(uint32_t cword, unsigned subcmd_idx,
1358                 const enum tf_word_type *word_types, bool have_dd)
1359 {
1360         enum tf_word_type prev_type;
1361         const char *word;
1362
1363         if (subcmd_idx == 0)
1364                 return false;
1365         if (have_dd)
1366                 return true;
1367         prev_type = word_types[cword - 1];
1368         assert(prev_type != WT_COMMAND_NAME);
1369         switch (prev_type) {
1370                 case WT_SHORT_OPT_WITH_ARG:
1371                 case WT_LONG_OPT_WITH_ARG:
1372                 case WT_DUNNO:
1373                         return false;
1374                 default:
1375                         break;
1376         }
1377         word = lls_input(cword, sublpr);
1378         if (is_short_opt(word))
1379                 return false;
1380         if (is_long_opt(word))
1381                 return false;
1382         return true;
1383 }
1384
1385 static int com_compgen(void)
1386 {
1387         unsigned n;
1388         uint32_t cword = OPT_UINT32_VAL(COMPGEN, CURRENT_WORD_INDEX);
1389         int ret;
1390         unsigned subcmd_idx;
1391         const char *word, *prev;
1392         const char * const short_opts[] = {LSG_TFORTUNE_TFORTUNE_SHORT_OPTS, NULL};
1393         const char * const long_opts[] = {LSG_TFORTUNE_TFORTUNE_LONG_OPTS, NULL};
1394         enum tf_word_type *word_types, prev_type;
1395         bool have_dd;
1396
1397         if (cword == 0 || cword > lls_num_inputs(sublpr)) {
1398                 ERROR_LOG("current word index == %u!?\n", cword);
1399                 return -ERRNO_TO_TF_ERROR(EINVAL);
1400         }
1401         word_types = xmalloc(cword * sizeof(*word_types));
1402         have_dd = get_word_types(cword, 0, short_opts, long_opts, word_types);
1403         /*
1404          * Locate the subcommand argument, if present. It is always the first
1405          * non-option argument.
1406          */
1407         subcmd_idx = 0;
1408         for (n = 1; n < cword; n++) {
1409                 if (word_types[n] != WT_NON_OPTION_ARG)
1410                         continue;
1411                 subcmd_idx = n;
1412                 break;
1413         }
1414         if (need_subcommand_completer(cword, subcmd_idx, word_types, have_dd)) {
1415                 free(word_types);
1416                 word = lls_input(subcmd_idx, sublpr);
1417                 ret = lls_lookup_subcmd(word, tfortune_suite, NULL);
1418                 if (ret < 0) /* invalid subcommand */
1419                         return 0;
1420                 return call_subcmd_completer(ret, subcmd_idx, cword, have_dd);
1421         }
1422         /* no subcommand */
1423         prev_type = word_types[cword - 1];
1424         prev = lls_input(cword - 1, sublpr);
1425         free(word_types);
1426         switch (prev_type) {
1427                 case WT_DUNNO:
1428                         return 0;
1429                 case WT_COMMAND_NAME:
1430                 case WT_SHORT_OPT_WITHOUT_ARG:
1431                 case WT_OPTION_ARG:
1432                 case WT_NON_OPTION_ARG:
1433                 case WT_LONG_OPT_WITHOUT_ARG:
1434                         if (!have_dd)
1435                                 print_option_list(supercmd_opts);
1436                         /* fall through */
1437                 case WT_DOUBLE_DASH:
1438                         print_zero_terminated_list(subcommand_names);
1439                         break;
1440                 case WT_SHORT_OPT_WITH_ARG:
1441                         if (strcmp(prev, "-b") == 0) {
1442                                 activate_dirname_completion();
1443                                 return 1;
1444                         }
1445                         if (strcmp(prev, "-l") == 0) {
1446                                 complete_loglevels();
1447                                 return 1;
1448                         }
1449                         break;
1450                 case WT_LONG_OPT_WITH_ARG:
1451                         if (strcmp(prev, "--basename") == 0) {
1452                                 activate_dirname_completion();
1453                                 return 1;
1454                         }
1455                         if (strcmp(prev, "--loglevel") == 0) {
1456                                 complete_loglevels();
1457                                 return 1;
1458                         }
1459                         break;
1460         }
1461         return 0;
1462 }
1463 EXPORT_CMD_HANDLER(compgen);
1464
1465 static int com_completer(void)
1466 {
1467         printf("%s\n",
1468                 "_tfortune() \n"
1469                 "{ \n"
1470                         "local -i i offset=${TF_OFFSET:-0} \n"
1471                         "local w compopts= have_empty=false\n"
1472                         "local cur=\"${COMP_WORDS[$COMP_CWORD]}\" \n"
1473
1474                         "i=0 \n"
1475                         "COMPREPLY=() \n"
1476                         "while read -d '' w; do \n"
1477                                 "[[ -z \"$w\" ]] && { have_empty=true; continue; }\n"
1478                                 "if [[ $have_empty == true ]]; then\n"
1479                                         "compopt $w\n"
1480                                 "else \n"
1481                                         "[[ \"$w\" != \"$cur\"* ]] && continue \n"
1482                                         "COMPREPLY[i]=\"$w\" \n"
1483                                         "let i++ \n"
1484                                 "fi \n"
1485                         "done < <(tfortune -- compgen --current-word-index \\\n"
1486                         "\"$((COMP_CWORD + offset))\" -- $TF_EXTRA \"${COMP_WORDS[@]}\")\n"
1487                 "} \n"
1488                 "complete -F _tfortune tfortune \n"
1489         );
1490         if (OPT_GIVEN(COMPLETER, ALIAS)) {
1491                 const char *ali = OPT_STRING_VAL(PRINT, EXPRESSION);
1492                 printf("alias %s=\"tfortune --\"\n", ali);
1493                 printf("_%s() { \n"
1494                                 "COMP_WORDS[0]='--'\n"
1495                                 "TF_EXTRA='tf' \n"
1496                                 "TF_OFFSET=1 \n"
1497                                 "_tfortune \"$@\" \n"
1498                                 "unset TF_EXTRA TF_OFFSET\n"
1499                         "}\n",
1500                         ali
1501                 );
1502                 printf("complete -F _%s %s \n", ali, ali);
1503         }
1504         return 1;
1505 }
1506 EXPORT_CMD_HANDLER(completer);
1507
1508 int main(int argc, char **argv)
1509 {
1510         char *errctx;
1511         int ret;
1512         const struct lls_command *cmd = CMD_PTR(TFORTUNE), *subcmd;
1513         const struct tf_user_data *ud;
1514         unsigned num_inputs;
1515
1516         ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
1517         if (ret < 0) {
1518                 lopsub_error(ret, &errctx);
1519                 exit(EXIT_FAILURE);
1520         }
1521         loglevel_arg_val = OPT_UINT32_VAL(TFORTUNE, LOGLEVEL);
1522         handle_help_and_version();
1523         num_inputs = lls_num_inputs(lpr);
1524         if (num_inputs == 0) {
1525                 show_subcommand_summary(true /* verbose */);
1526                 ret = 0;
1527                 goto free_lpr;
1528         }
1529         ret = lls_lookup_subcmd(argv[argc - num_inputs], tfortune_suite, &errctx);
1530         if (ret < 0) {
1531                 ret = lopsub_error(ret, &errctx);
1532                 goto free_lpr;
1533         }
1534         subcmd = lls_cmd(ret, tfortune_suite);
1535         ret = lls_parse(num_inputs, argv + argc - num_inputs, subcmd,
1536                 &sublpr, &errctx);
1537         if (ret < 0) {
1538                 ret = lopsub_error(ret, &errctx);
1539                 goto free_lpr;
1540         }
1541         ud = lls_user_data(subcmd);
1542         ret = ud->handler();
1543         lls_free_parse_result(sublpr, subcmd);
1544         if (ret < 0)
1545                 ERROR_LOG("%s\n", tf_strerror(-ret));
1546 free_lpr:
1547         lls_free_parse_result(lpr, cmd);
1548         exit(ret >= 0? EXIT_SUCCESS : EXIT_FAILURE);
1549 }