com_stats(): Avoid division by zero.
[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 num_epi_files > 0? (float)num_epis / num_epi_files : 0);
899 printf("average number of tags per epigram. %8.02f\n", num_epis > 0?
900 (float)num_tags / num_epis : 0);
901 printf("average number of tag recurrence... %8.02f\n", num_unique_tags > 0?
902 (float)num_tags / num_unique_tags : 0);
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 if (n > 70) {
934 printf("\n\t");
935 n = 8;
936 }
937 n += printf("%s", subcommand_names[i]);
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 const char *GET_VERSION(void);
973 static void handle_help_and_version(void)
974 {
975 int i;
976 char *help;
977 const struct lls_command *cmd;
978
979 if (OPT_GIVEN(TFORTUNE, VERSION)) {
980 printf(PACKAGE " %s\n"
981 "Copyright (C) " COPYRIGHT_YEAR " " AUTHOR ".\n"
982 "License: " LICENSE ": <" LICENSE_URL ">.\n"
983 "This is free software: you are free to change and redistribute it.\n"
984 "There is NO WARRANTY, to the extent permitted by law.\n"
985 "\n"
986 "Web page: " PACKAGE_HOMEPAGE "\n"
987 "Clone URL: " CLONE_URL "\n"
988 "Gitweb: " GITWEB_URL "\n"
989 "Author's Home Page: " HOME_URL "\n"
990 "Send feedback to: " AUTHOR " <" EMAIL ">\n"
991 ,
992 GET_VERSION()
993 );
994 exit(EXIT_SUCCESS);
995 }
996 cmd = CMD_PTR(TFORTUNE);
997 if (OPT_GIVEN(TFORTUNE, DETAILED_HELP))
998 help = lls_long_help(cmd);
999 else if (OPT_GIVEN(TFORTUNE, HELP))
1000 help = lls_short_help(cmd);
1001 else
1002 return;
1003 printf("%s\n", help);
1004 free(help);
1005 if (OPT_GIVEN(TFORTUNE, DETAILED_HELP))
1006 for (i = 1; (cmd = lls_cmd(i, tfortune_suite)); i++) {
1007 help = lls_short_help(cmd);
1008 printf("%s\n---\n", help);
1009 free(help);
1010 }
1011 else
1012 show_subcommand_summary(true /* verbose */);
1013 exit(EXIT_SUCCESS);
1014 }
1015
1016 enum tf_word_type {
1017 WT_COMMAND_NAME,
1018 WT_DOUBLE_DASH, /* -- */
1019 WT_SHORT_OPT_WITH_ARG, /* -l */
1020 WT_SHORT_OPT_WITHOUT_ARG, /* -V, -abc=d */
1021 WT_LONG_OPT_WITH_ARG, /* --loglevel */
1022 WT_LONG_OPT_WITHOUT_ARG, /* --foo=bar --help */
1023 WT_OPTION_ARG,
1024 WT_NON_OPTION_ARG,
1025 WT_DUNNO,
1026 };
1027
1028 static bool is_short_opt(const char *word)
1029 {
1030 if (word[0] != '-')
1031 return false;
1032 if (word[1] == '-')
1033 return false;
1034 if (word[1] == '\0')
1035 return false;
1036 return true;
1037 }
1038
1039 static bool is_long_opt(const char *word)
1040 {
1041 if (word[0] != '-')
1042 return false;
1043 if (word[1] != '-')
1044 return false;
1045 if (word[2] == '\0')
1046 return false;
1047 return true;
1048 }
1049
1050 /* whether the next word will be an arg to this short opt */
1051 static int short_opt_needs_arg(const char *word,
1052 const char * const *short_opts)
1053 {
1054 size_t n, len;
1055
1056 if (strchr(word, '='))
1057 return false;
1058 len = strlen(word);
1059 for (n = 0; short_opts[n]; n++) {
1060 const char *opt = short_opts[n];
1061 if (word[len - 1] != opt[1])
1062 continue;
1063 if (opt[2] == '=')
1064 return true;
1065 else
1066 return false;
1067 }
1068 return -1;
1069 }
1070
1071 /* whether the next word will be an arg to this long opt */
1072 static int long_opt_needs_arg(const char *word,
1073 const char * const *long_opts)
1074 {
1075 size_t n;
1076
1077 if (strchr(word, '='))
1078 return false;
1079 for (n = 0; long_opts[n]; n++) {
1080 const char *opt = long_opts[n];
1081 size_t len = strlen(opt);
1082
1083 if (opt[len - 1] == '=')
1084 len--;
1085 if (strncmp(word + 2, opt + 2, len - 2))
1086 continue;
1087 if (opt[len] == '=')
1088 return true;
1089 else
1090 return false;
1091 }
1092 return -1;
1093 }
1094
1095 static bool get_word_types(unsigned cword, unsigned arg0,
1096 const char * const *short_opts, const char * const *long_opts,
1097 enum tf_word_type *result)
1098 {
1099 const char *word;
1100 unsigned n;
1101 bool have_dd = false;
1102 int ret;
1103
1104 /* index zero is always the command name */
1105 assert(cword > arg0);
1106 result[arg0] = WT_COMMAND_NAME;
1107 for (n = arg0 + 1; n < cword; n++) {
1108 enum tf_word_type prev_type = result[n - 1];
1109
1110 if (have_dd) {
1111 result[n] = WT_NON_OPTION_ARG;
1112 continue;
1113 }
1114 if (prev_type == WT_SHORT_OPT_WITH_ARG) {
1115 result[n] = WT_OPTION_ARG;
1116 continue;
1117 }
1118 if (prev_type == WT_LONG_OPT_WITH_ARG) {
1119 result[n] = WT_OPTION_ARG;
1120 continue;
1121 }
1122 word = lls_input(n, sublpr);
1123 if (strcmp(word, "--") == 0) {
1124 result[n] = WT_DOUBLE_DASH;
1125 have_dd = true;
1126 continue;
1127 }
1128 if (is_short_opt(word)) {
1129 ret = short_opt_needs_arg(word, short_opts);
1130 if (ret < 0)
1131 goto dunno;
1132 if (ret > 0)
1133 result[n] = WT_SHORT_OPT_WITH_ARG;
1134 else
1135 result[n] = WT_SHORT_OPT_WITHOUT_ARG;
1136 continue;
1137 }
1138 if (is_long_opt(word)) {
1139 ret = long_opt_needs_arg(word, long_opts);
1140 if (ret < 0)
1141 goto dunno;
1142 if (ret > 0)
1143 result[n] = WT_LONG_OPT_WITH_ARG;
1144 else
1145 result[n] = WT_LONG_OPT_WITHOUT_ARG;
1146 continue;
1147 }
1148 result[n] = WT_NON_OPTION_ARG;
1149 }
1150 return have_dd;
1151 dunno:
1152 for (; n <= cword; n++)
1153 result[n] = WT_DUNNO;
1154 return false;
1155 }
1156
1157 #define DUMMY_COMPLETER(_name) static char **complete_ ## _name( \
1158 __attribute__ ((unused)) uint32_t cword, \
1159 __attribute__ ((unused)) unsigned arg0, \
1160 __attribute__ ((unused)) bool have_dd \
1161 ) {return NULL;}
1162
1163 DUMMY_COMPLETER(tfortune)
1164 DUMMY_COMPLETER(compgen)
1165 DUMMY_COMPLETER(completer)
1166
1167 static const char * const supercmd_opts[] = {LSG_TFORTUNE_TFORTUNE_OPTS, NULL};
1168
1169 static void print_zero_terminated_list(const char * const *list)
1170 {
1171 const char * const *c;
1172 for (c = list; *c; c++)
1173 printf("%s%c", *c, '\0');
1174 }
1175
1176 static void print_option_list(const char * const *opts)
1177 {
1178 const char * const *c;
1179
1180 for (c = opts; *c; c++) {
1181 int len = strlen(*c);
1182 assert(len > 0);
1183 if ((*c)[len - 1] == '=')
1184 len--;
1185 printf("%.*s%c", len, *c, '\0');
1186 }
1187 }
1188
1189 static void activate_dirname_completion(void)
1190 {
1191 printf("%c", '\0');
1192 printf("-o dirnames%c", '\0');
1193 }
1194
1195 static void complete_loglevels(void)
1196 {
1197 unsigned n;
1198 const struct lls_option *opt = lls_opt(LSG_TFORTUNE_TFORTUNE_OPT_LOGLEVEL,
1199 CMD_PTR(TFORTUNE));
1200
1201 for (n = 0; n < LSG_NUM_TFORTUNE_TFORTUNE_LOGLEVEL_VALUES; n++) {
1202 const char *v = lls_enum_string_val(n, opt);
1203 printf("%s%c", v, '\0');
1204 }
1205 }
1206
1207 static char **complete_dentries(const char *dir)
1208 {
1209 const char *bn;
1210 struct regfile_iter *riter;
1211 unsigned n;
1212 char **result = NULL;
1213
1214 regfile_iter_new(dir, &riter);
1215 for (
1216 n = 0;
1217 (bn = regfile_iter_basename(riter));
1218 regfile_iter_next(riter), n++
1219 ) {
1220 result = xrealloc(result, (n + 2) * sizeof(*result));
1221 result[n] = xstrdup(bn);
1222 result[n + 1] = NULL;
1223 }
1224 return result;
1225 }
1226
1227 static char **complete_ede(__attribute__ ((unused)) uint32_t cword,
1228 __attribute__ ((unused)) unsigned arg0,
1229 __attribute__ ((unused)) bool have_dd)
1230 {
1231 char **result, *epidir = get_epidir();
1232
1233 result = complete_dentries(epidir);
1234 free(epidir);
1235 return result;
1236 }
1237
1238 static char **complete_edx(__attribute__ ((unused)) uint32_t cword,
1239 __attribute__ ((unused)) unsigned arg0,
1240 __attribute__ ((unused)) bool have_dd)
1241 {
1242 char **result, *xdir = get_xdir();
1243
1244 result = complete_dentries(xdir);
1245 free(xdir);
1246 return result;
1247 }
1248
1249 static char **complete_std_opts(bool have_dd, const char * const *opts)
1250 {
1251 if (have_dd)
1252 print_option_list(opts);
1253 else
1254 print_option_list(supercmd_opts);
1255 return NULL;
1256 }
1257
1258 static char **complete_stats(__attribute__ ((unused)) uint32_t cword,
1259 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1260 {
1261 const char * const opts[] = {LSG_TFORTUNE_STATS_OPTS, NULL};
1262 return complete_std_opts(have_dd, opts);
1263 }
1264
1265 static char **complete_lse(__attribute__ ((unused)) uint32_t cword,
1266 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1267 {
1268 const char * const opts[] = {LSG_TFORTUNE_LSE_OPTS, NULL};
1269 return complete_std_opts(have_dd, opts);
1270 }
1271
1272 static char **complete_lst(__attribute__ ((unused)) uint32_t cword,
1273 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1274 {
1275 const char * const opts[] = {LSG_TFORTUNE_LST_OPTS, NULL};
1276 return complete_std_opts(have_dd, opts);
1277 }
1278
1279 static char **complete_lsx(__attribute__ ((unused)) uint32_t cword,
1280 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1281 {
1282 const char * const opts[] = {LSG_TFORTUNE_LSX_OPTS, NULL};
1283 return complete_std_opts(have_dd, opts);
1284 }
1285
1286 static char **complete_help(__attribute__ ((unused)) uint32_t cword,
1287 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1288 {
1289 const char * const opts[] = {LSG_TFORTUNE_HELP_OPTS, NULL};
1290
1291 if (!have_dd)
1292 print_option_list(supercmd_opts);
1293 else
1294 print_option_list(opts);
1295 print_zero_terminated_list(subcommand_names);
1296 return NULL;
1297 }
1298
1299 static char **complete_print(uint32_t cword, unsigned arg0, bool have_dd)
1300 {
1301 const char * const short_opts[] = {LSG_TFORTUNE_PRINT_SHORT_OPTS, NULL};
1302 const char * const long_opts[] = {LSG_TFORTUNE_PRINT_LONG_OPTS, NULL};
1303 const char * const opts[] = {LSG_TFORTUNE_PRINT_OPTS, NULL};
1304 enum tf_word_type *word_types, prev_type;
1305 const char *prev;
1306 char **result, *xdir;
1307
1308 word_types = xmalloc(cword * sizeof(*word_types));
1309 get_word_types(cword, arg0, short_opts, long_opts, word_types);
1310 prev = lls_input(cword - 1, sublpr);
1311 prev_type = word_types[cword - 1];
1312 free(word_types);
1313 switch (prev_type) {
1314 case WT_COMMAND_NAME:
1315 case WT_SHORT_OPT_WITHOUT_ARG:
1316 case WT_OPTION_ARG:
1317 case WT_LONG_OPT_WITHOUT_ARG:
1318 case WT_DOUBLE_DASH:
1319 if (!have_dd)
1320 print_option_list(supercmd_opts);
1321 else
1322 print_option_list(opts);
1323 return NULL;
1324 case WT_SHORT_OPT_WITH_ARG:
1325 if (strcmp(prev, "-x") == 0)
1326 goto complete_expression;
1327 break;
1328 case WT_LONG_OPT_WITH_ARG:
1329 if (strcmp(prev, "--expression") == 0)
1330 goto complete_expression;
1331 break;
1332 default:
1333 return NULL;
1334 }
1335 complete_expression:
1336 xdir = get_xdir();
1337 result = complete_dentries(xdir);
1338 free(xdir);
1339 return result;
1340 }
1341
1342 typedef char **(*completer)(uint32_t cword, unsigned arg0, bool have_dd);
1343
1344 #define LSG_TFORTUNE_CMD(_name) complete_ ## _name
1345 static const completer completers[] = {LSG_TFORTUNE_COMMANDS};
1346 #undef LSG_TFORTUNE_CMD
1347
1348 static int call_subcmd_completer(unsigned cmd_num, int arg0, uint32_t cword,
1349 bool have_dd)
1350 {
1351 char **c, **candidates = completers[cmd_num](cword, arg0, have_dd);
1352
1353 if (!candidates)
1354 return 0;
1355 for (c = candidates; *c; c++) {
1356 printf("%s%c", *c, '\0');
1357 free(*c);
1358 }
1359 free(candidates);
1360 return 1;
1361 }
1362
1363 static bool need_subcommand_completer(uint32_t cword, unsigned subcmd_idx,
1364 const enum tf_word_type *word_types, bool have_dd)
1365 {
1366 enum tf_word_type prev_type;
1367 const char *word;
1368
1369 if (subcmd_idx == 0)
1370 return false;
1371 if (have_dd)
1372 return true;
1373 prev_type = word_types[cword - 1];
1374 assert(prev_type != WT_COMMAND_NAME);
1375 switch (prev_type) {
1376 case WT_SHORT_OPT_WITH_ARG:
1377 case WT_LONG_OPT_WITH_ARG:
1378 case WT_DUNNO:
1379 return false;
1380 default:
1381 break;
1382 }
1383 word = lls_input(cword, sublpr);
1384 if (is_short_opt(word))
1385 return false;
1386 if (is_long_opt(word))
1387 return false;
1388 return true;
1389 }
1390
1391 static int com_compgen(void)
1392 {
1393 unsigned n;
1394 uint32_t cword = OPT_UINT32_VAL(COMPGEN, CURRENT_WORD_INDEX);
1395 int ret;
1396 unsigned subcmd_idx;
1397 const char *word, *prev;
1398 const char * const short_opts[] = {LSG_TFORTUNE_TFORTUNE_SHORT_OPTS, NULL};
1399 const char * const long_opts[] = {LSG_TFORTUNE_TFORTUNE_LONG_OPTS, NULL};
1400 enum tf_word_type *word_types, prev_type;
1401 bool have_dd;
1402
1403 if (cword == 0 || cword > lls_num_inputs(sublpr)) {
1404 ERROR_LOG("current word index == %u!?\n", cword);
1405 return -ERRNO_TO_TF_ERROR(EINVAL);
1406 }
1407 word_types = xmalloc(cword * sizeof(*word_types));
1408 have_dd = get_word_types(cword, 0, short_opts, long_opts, word_types);
1409 /*
1410 * Locate the subcommand argument, if present. It is always the first
1411 * non-option argument.
1412 */
1413 subcmd_idx = 0;
1414 for (n = 1; n < cword; n++) {
1415 if (word_types[n] != WT_NON_OPTION_ARG)
1416 continue;
1417 subcmd_idx = n;
1418 break;
1419 }
1420 if (need_subcommand_completer(cword, subcmd_idx, word_types, have_dd)) {
1421 free(word_types);
1422 word = lls_input(subcmd_idx, sublpr);
1423 ret = lls_lookup_subcmd(word, tfortune_suite, NULL);
1424 if (ret < 0) /* invalid subcommand */
1425 return 0;
1426 return call_subcmd_completer(ret, subcmd_idx, cword, have_dd);
1427 }
1428 /* no subcommand */
1429 prev_type = word_types[cword - 1];
1430 prev = lls_input(cword - 1, sublpr);
1431 free(word_types);
1432 switch (prev_type) {
1433 case WT_DUNNO:
1434 return 0;
1435 case WT_COMMAND_NAME:
1436 case WT_SHORT_OPT_WITHOUT_ARG:
1437 case WT_OPTION_ARG:
1438 case WT_NON_OPTION_ARG:
1439 case WT_LONG_OPT_WITHOUT_ARG:
1440 if (!have_dd)
1441 print_option_list(supercmd_opts);
1442 /* fall through */
1443 case WT_DOUBLE_DASH:
1444 print_zero_terminated_list(subcommand_names);
1445 break;
1446 case WT_SHORT_OPT_WITH_ARG:
1447 if (strcmp(prev, "-b") == 0) {
1448 activate_dirname_completion();
1449 return 1;
1450 }
1451 if (strcmp(prev, "-l") == 0) {
1452 complete_loglevels();
1453 return 1;
1454 }
1455 break;
1456 case WT_LONG_OPT_WITH_ARG:
1457 if (strcmp(prev, "--basedir") == 0) {
1458 activate_dirname_completion();
1459 return 1;
1460 }
1461 if (strcmp(prev, "--loglevel") == 0) {
1462 complete_loglevels();
1463 return 1;
1464 }
1465 break;
1466 }
1467 return 0;
1468 }
1469 EXPORT_CMD_HANDLER(compgen);
1470
1471 static int com_completer(void)
1472 {
1473 printf("%s\n",
1474 "_tfortune() \n"
1475 "{ \n"
1476 "local -i i offset=${TF_OFFSET:-0} \n"
1477 "local w compopts= have_empty=false\n"
1478 "local cur=\"${COMP_WORDS[$COMP_CWORD]}\" \n"
1479
1480 "i=0 \n"
1481 "COMPREPLY=() \n"
1482 "while read -d '' w; do \n"
1483 "[[ -z \"$w\" ]] && { have_empty=true; continue; }\n"
1484 "if [[ $have_empty == true ]]; then\n"
1485 "compopt $w\n"
1486 "else \n"
1487 "[[ \"$w\" != \"$cur\"* ]] && continue \n"
1488 "COMPREPLY[i]=\"$w\" \n"
1489 "let i++ \n"
1490 "fi \n"
1491 "done < <(tfortune -- compgen --current-word-index \\\n"
1492 "\"$((COMP_CWORD + offset))\" -- $TF_EXTRA \"${COMP_WORDS[@]}\")\n"
1493 "} \n"
1494 "complete -F _tfortune tfortune \n"
1495 );
1496 if (OPT_GIVEN(COMPLETER, ALIAS)) {
1497 const char *ali = OPT_STRING_VAL(PRINT, EXPRESSION);
1498 printf("alias %s=\"tfortune --\"\n", ali);
1499 printf("_%s() { \n"
1500 "COMP_WORDS[0]='--'\n"
1501 "TF_EXTRA='tf' \n"
1502 "TF_OFFSET=1 \n"
1503 "_tfortune \"$@\" \n"
1504 "unset TF_EXTRA TF_OFFSET\n"
1505 "}\n",
1506 ali
1507 );
1508 printf("complete -F _%s %s \n", ali, ali);
1509 }
1510 return 1;
1511 }
1512 EXPORT_CMD_HANDLER(completer);
1513
1514 int main(int argc, char **argv)
1515 {
1516 char *errctx;
1517 int ret;
1518 const struct lls_command *cmd = CMD_PTR(TFORTUNE), *subcmd;
1519 const struct tf_user_data *ud;
1520 unsigned num_inputs;
1521
1522 ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
1523 if (ret < 0) {
1524 lopsub_error(ret, &errctx);
1525 exit(EXIT_FAILURE);
1526 }
1527 loglevel_arg_val = OPT_UINT32_VAL(TFORTUNE, LOGLEVEL);
1528 handle_help_and_version();
1529 num_inputs = lls_num_inputs(lpr);
1530 if (num_inputs == 0) {
1531 show_subcommand_summary(true /* verbose */);
1532 ret = 0;
1533 goto free_lpr;
1534 }
1535 ret = lls_lookup_subcmd(argv[argc - num_inputs], tfortune_suite, &errctx);
1536 if (ret < 0) {
1537 ret = lopsub_error(ret, &errctx);
1538 goto free_lpr;
1539 }
1540 subcmd = lls_cmd(ret, tfortune_suite);
1541 ret = lls_parse(num_inputs, argv + argc - num_inputs, subcmd,
1542 &sublpr, &errctx);
1543 if (ret < 0) {
1544 ret = lopsub_error(ret, &errctx);
1545 goto free_lpr;
1546 }
1547 ud = lls_user_data(subcmd);
1548 ret = ud->handler();
1549 lls_free_parse_result(sublpr, subcmd);
1550 if (ret < 0)
1551 ERROR_LOG("%s\n", tf_strerror(-ret));
1552 free_lpr:
1553 lls_free_parse_result(lpr, cmd);
1554 exit(ret >= 0? EXIT_SUCCESS : EXIT_FAILURE);
1555 }