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