1 /* SPDX-License-Identifier: GPL-3.0-only */
20 #include "tfortune.lsg.h"
22 #define TF_SEP "---- "
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)))
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 \
49 struct map_chunk epi, tags;
52 static int lopsub_error(int lopsub_ret, char **errctx)
54 const char *msg = lls_strerror(-lopsub_ret);
56 ERROR_LOG("%s: %s\n", *errctx, msg);
58 ERROR_LOG("%s\n", msg);
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;
71 unsigned epi_len(const struct epi_properties *props)
73 return props->chunk->len;
76 char *epi_text(const struct epi_properties *props)
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';
85 bool epi_has_tag(const char *tag, const struct epi_properties *props)
87 return linhash_lookup(tag, props->tagtab);
92 char *saveptr; /* for strtok_r(3) */
96 static struct tag_iter *tag_iter_new(const struct map_chunk *tags)
98 struct tag_iter *titer = xmalloc(sizeof(*titer));
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);
107 static const char *tag_iter_get(const struct tag_iter *titer)
112 static void tag_iter_next(struct tag_iter *titer)
114 titer->token = strtok_r(NULL, ",", &titer->saveptr);
117 static void tag_iter_free(struct tag_iter *titer)
125 static bool epi_admissible(const struct epigram *epi,
126 const struct txp_context *ast)
130 struct epi_properties props = {
132 .tagtab = linhash_new(3),
135 struct tag_iter *titer;
139 titer = tag_iter_new(&epi->tags);
140 (p = tag_iter_get(titer));
143 struct linhash_item item = {.key = p};
144 linhash_insert(&item, props.tagtab, NULL);
148 admissible = txp_eval_ast(ast, &props);
149 linhash_free(props.tagtab);
150 tag_iter_free(titer);
154 static void print_epigram(const struct epigram *epi, bool print_tags)
156 printf("%.*s", (int)epi->epi.len, epi->epi.base);
158 printf(TF_SEP "%.*s\n", (int)epi->tags.len, epi->tags.base);
161 static void print_admissible_epigrams(const struct epigram *epis,
166 for (n = 0; n < num_epis; n++)
167 print_epigram(epis + n, OPT_GIVEN(PRINT, TAGS));
170 static void print_random_epigram(const struct epigram *epis, unsigned num_epis)
173 const struct epigram *epi;
177 ERROR_LOG("no matching epigram\n");
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);
185 print_epigram(epi, OPT_GIVEN(PRINT, TAGS));
188 static int read_tag_expression(struct iovec *result)
190 const char *tx = OPT_STRING_VAL(PRINT, EXPRESSION);
194 if (strcmp(tx, "-")) {
197 char *home = get_homedir();
198 xasprintf(&filename, "%s/.tfortune/expressions/%s",
202 filename = xstrdup(tx);
203 ret = open(filename, O_RDONLY);
205 ret = -ERRNO_TO_TF_ERROR(errno);
206 ERROR_LOG("could not open %s\n", filename);
214 ret = fd2buf(fd, result);
219 static int tx2ast(const struct iovec *tx, struct txp_context **ast)
224 ret = txp_init(tx, ast, &errmsg);
226 ERROR_LOG("could not parse tag expression: %s\n", errmsg);
242 static bool get_next_epi(struct epi_iter *eiter, bool skip)
244 const char *epi_start = NULL;
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;
252 if (!epi_start && !skip && eiter->epi.tags.base)
253 epi_start = eiter->epi.tags.base
254 + eiter->epi.tags.len + 1;
256 epi_start = iov->iov_base;
259 const size_t sep_len = strlen(TF_SEP);
260 const char *p, *cr, *tags;
263 cr = memchr(buf, '\n', end - buf);
267 if (p + sep_len >= end)
269 if (strncmp(p, TF_SEP, sep_len) != 0) {
274 cr = memchr(tags, '\n', end - tags);
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;
287 eiter->epi.epi.base = NULL;
288 eiter->epi.epi.len = 0;
289 eiter->epi.tags.base = NULL;
290 eiter->epi.tags.len = 0;
294 static char *get_basedir(void)
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);
305 static char *get_epidir(void)
307 char *basedir, *epidir;
310 basedir = get_basedir();
311 xasprintf(&epidir, "%s/epigrams", basedir);
313 if (!OPT_GIVEN(TFORTUNE, BASEDIR) && stat(epidir, &s) < 0) {
315 epidir = xstrdup(DATADIR "/tfortunes/epigrams");
316 INFO_LOG("falling back to system-wide epidir %s\n", epidir);
321 static char *get_xdir(void)
323 char *basedir = get_basedir(), *xdir;
324 xasprintf(&xdir, "%s/expressions", basedir);
329 static struct epi_iter *epi_iter_new(const struct lls_parse_result *parse_result)
331 struct epi_iter *eiter = xmalloc(sizeof(*eiter));
332 unsigned num_inputs = parse_result? lls_num_inputs(parse_result) : 0;
334 if (num_inputs == 0) {
335 struct regfile_iter *riter;
337 char *epidir = get_epidir();
339 regfile_iter_new(epidir, &riter);
342 eiter->basenames = NULL;
345 regfile_iter_map(riter, &iov);
346 regfile_iter_next(riter)
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));
357 regfile_iter_free(riter);
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);
367 eiter->num_maps = num_inputs;
370 eiter->epi.epi.base = NULL;
371 eiter->epi.epi.len = 0;
372 eiter->epi.tags.base = NULL;
373 eiter->epi.tags.len = 0;
375 get_next_epi(eiter, false /* do not skip */);
379 static const struct epigram *epi_iter_get(const struct epi_iter *eiter)
381 return (eiter->epi.epi.base && eiter->epi.tags.base)?
385 static const char *epi_iter_basename(const struct epi_iter *eiter)
387 return eiter->basenames[eiter->map_num];
390 static unsigned epi_iter_num_maps(const struct epi_iter *eiter)
392 return eiter->num_maps;
395 static unsigned epi_iter_num_epis(const struct epi_iter *eiter)
397 return eiter->num_epis;
400 static void epi_iter_next(struct epi_iter *eiter, bool skip)
402 get_next_epi(eiter, skip);
405 static void epi_iter_free(struct epi_iter *eiter)
411 for (n = 0; n < eiter->num_maps; n++)
412 munmap(eiter->maps[n].iov_base, eiter->maps[n].iov_len);
414 free(eiter->basenames);
418 static int com_print(void)
421 struct epigram *epis = NULL;
422 unsigned epis_sz = 0, nae = 0; /* number of admissible epis */
424 struct txp_context *ast;
425 struct epi_iter *eiter;
426 const struct epigram *epi;
428 ret = read_tag_expression(&tx);
431 ret = tx2ast(&tx, &ast);
435 eiter = epi_iter_new(sublpr);
436 (epi = epi_iter_get(eiter));
437 epi_iter_next(eiter, false /* do not skip */)
439 if (!epi_admissible(epi, ast))
441 if (nae >= epis_sz) {
442 epis_sz = 2 * epis_sz + 1;
443 epis = xrealloc(epis, epis_sz * sizeof(*epis));
447 if (OPT_GIVEN(PRINT, ALL))
448 print_admissible_epigrams(epis, nae);
450 print_random_epigram(epis, nae);
451 epi_iter_free(eiter);
459 EXPORT_CMD_HANDLER(print);
461 static char *get_editor(void)
463 char *val = getenv("TFORTUNE_EDITOR");
467 val = getenv("EDITOR");
470 return xstrdup("vi");
473 static void open_editor(char **argv)
477 if ((pid = fork()) < 0) {
478 EMERG_LOG("fork error: %s\n", strerror(errno));
481 if (pid) { /* parent */
485 argv[0] = get_editor();
486 execvp(argv[0], argv);
487 EMERG_LOG("execvp error: %s\n", strerror(errno));
491 static int com_locate(void)
495 struct txp_context *ast;
496 struct epi_iter *eiter;
497 const struct epigram *epi;
503 bool edit = OPT_GIVEN(LOCATE, EDIT);
505 ret = lls_check_arg_count(sublpr, 1, 1, &errctx);
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);
515 argv = xmalloc((argc + 1) * sizeof(char *));
516 epidir = get_epidir();
519 eiter = epi_iter_new(NULL);
520 (skip = false, epi = epi_iter_get(eiter));
521 epi_iter_next(eiter, skip)
523 if (!epi_admissible(epi, ast))
527 printf("%s\n", epi_iter_basename(eiter));
531 argv = xrealloc(argv, (argc + 1) * sizeof(char *));
532 xasprintf(&argv[argc - 1], "%s/%s", epidir,
533 xstrdup(epi_iter_basename(eiter)));
535 epi_iter_free(eiter);
548 EXPORT_CMD_HANDLER(locate);
550 static int create_dir(const char *path)
554 ret = mkdir(path, 0777); /* rely on umask */
558 ERROR_LOG("could not create %s\n", path);
559 return -ERRNO_TO_TF_ERROR(errno);
561 NOTICE_LOG("created directory %s\n", path);
565 static int create_basedir(void)
570 basedir = get_basedir();
571 ret = create_dir(basedir);
576 static int generic_edit(const char *dir)
580 bool basedir_given = OPT_GIVEN(TFORTUNE, BASEDIR);
582 unsigned n, num_inputs = lls_num_inputs(sublpr);
584 ret = lls_check_arg_count(sublpr, 1, INT_MAX, &errctx);
586 ret = lopsub_error(ret, &errctx);
589 if (!basedir_given) {
590 ret = create_basedir();
593 ret = create_dir(dir);
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;
605 static int com_ede(void)
608 char *epidir = get_epidir();
609 ret = generic_edit(epidir);
613 EXPORT_CMD_HANDLER(ede);
615 static int com_edx(void)
618 char *xdir = get_xdir();
619 ret = generic_edit(xdir);
623 EXPORT_CMD_HANDLER(edx);
625 static int item_alpha_compare(const struct linhash_item **i1,
626 const struct linhash_item **i2)
628 return strcmp((*i1)->key, (*i2)->key);
631 static int item_num_compare(const struct linhash_item **i1,
632 const struct linhash_item **i2)
634 long unsigned v1 = (long unsigned)(*i1)->object;
635 long unsigned v2 = (long unsigned)(*i2)->object;
637 return v1 < v2? -1 : (v1 == v2? 0 : 1);
649 static void make_dentry(const char *name, const struct stat *stat,
652 mode_t m = stat->st_mode;
654 struct passwd *pwentry;
656 sprintf(d->mode, "----------");
663 else if ((S_ISBLK(m)))
667 else if (S_ISFIFO(m))
669 else if ((S_ISSOCK(m)))
683 } else if (m & S_ISUID)
695 } else if (m & S_ISGID)
707 } else if (m & S_ISVTX)
710 d->nlink = stat->st_nlink;
712 pwentry = getpwuid(stat->st_uid);
713 if (pwentry && pwentry->pw_name)
714 d->user = xstrdup(pwentry->pw_name);
716 xasprintf(&d->user, "%u", stat->st_uid);
718 g = getgrgid(stat->st_gid);
720 d->group = xstrdup(g->gr_name);
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);
728 static int num_digits(uint64_t x)
740 enum var_length_dentry_fields {
748 static void update_field_field_widths(int field_widths[NUM_VLDF],
749 const struct dentry *d)
753 w = field_widths + VLDF_NLINKS;
754 n = num_digits(d->nlink);
757 w = field_widths + VLDF_USER;
761 w = field_widths + VLDF_GROUP;
762 n = strlen(d->group);
765 w = field_widths + VLDF_SIZE;
766 n = num_digits(d->size);
770 static void format_time(uint64_t seconds, uint64_t now, struct iovec *result)
773 const uint64_t m = 6 * 30 * 24 * 3600; /* six months */
776 tm = localtime((time_t *)&seconds);
779 if (seconds > now - m && seconds < now + m) {
780 nbytes = strftime(result->iov_base, result->iov_len,
784 nbytes = strftime(result->iov_base, result->iov_len,
790 static int list_directory(const char *dir, bool long_listing)
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;
800 regfile_iter_new(dir, &riter);
801 (basename = regfile_iter_basename(riter));
802 regfile_iter_next(riter)
804 const struct stat *stat;
805 struct dentry *dentry;
807 printf("%s\n", basename);
811 if (num_dentries > dentries_size) {
812 dentries_size = 2 * dentries_size + 1;
813 dentries = xrealloc(dentries,
814 dentries_size * sizeof(*dentries));
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);
821 regfile_iter_free(riter);
824 ret = clock_gettime(CLOCK_REALTIME, &now);
826 for (n = 0; n < num_dentries; n++) {
827 struct dentry *d = dentries + n;
829 struct iovec iov = {.iov_base = buf, .iov_len = sizeof(buf)};
831 format_time(d->mtime, now.tv_sec, &iov);
832 printf("%s %*lu %*s %*s %*" PRIu64 " %s %s\n",
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,
849 static int com_lse(void)
852 char *dir = get_epidir();
854 ret = list_directory(dir, OPT_GIVEN(LSE, LONG));
858 EXPORT_CMD_HANDLER(lse);
860 static int com_lsx(void)
863 char *dir = get_xdir();
865 ret = list_directory(dir, OPT_GIVEN(LSE, LONG));
869 EXPORT_CMD_HANDLER(lsx);
871 static struct linhash_table *hash_tags(unsigned *num_epi_files,
874 struct linhash_table *tagtab = linhash_new(3);
875 struct epi_iter *eiter;
876 const struct epigram *epi;
879 eiter = epi_iter_new(sublpr);
880 (epi = epi_iter_get(eiter));
881 epi_iter_next(eiter, false /* do not skip */)
883 struct tag_iter *titer;
886 titer = tag_iter_new(&epi->tags);
887 (tag = tag_iter_get(titer));
890 struct linhash_item item = {
892 .object = (void *)1LU
895 if (linhash_insert(&item, tagtab, &object) < 0) {
896 long unsigned val = (long unsigned)*object;
898 *object = (void *)val;
899 free((char *)item.key);
902 tag_iter_free(titer);
905 *num_epi_files = epi_iter_num_maps(eiter);
907 *num_epis = epi_iter_num_epis(eiter);
908 epi_iter_free(eiter);
912 static int com_lst(void)
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);
921 tagtab = hash_tags(NULL, NULL);
923 liter = linhash_iterator_new(tagtab, comp, reverse);
924 (itemp = linhash_iterator_item(liter));
925 linhash_iterator_next(liter)
927 if (OPT_GIVEN(LST, LONG))
928 printf("%lu\t%s\n", (long unsigned)itemp->object,
931 printf("%s\n", itemp->key);
932 free((char *)itemp->key);
934 linhash_iterator_free(liter);
935 linhash_free(tagtab);
938 EXPORT_CMD_HANDLER(lst);
940 static int com_stats(void)
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;
948 struct regfile_iter *riter;
950 tagtab = hash_tags(&num_epi_files, &num_epis);
952 liter = linhash_iterator_new(tagtab, NULL, false);
953 (itemp = linhash_iterator_item(liter));
954 linhash_iterator_next(liter)
956 free((char *)itemp->key);
957 num_tags += (long unsigned)itemp->object;
959 num_unique_tags = linhash_num_items(tagtab);
960 linhash_iterator_free(liter);
963 regfile_iter_new(xdir, &riter);
964 regfile_iter_basename(riter);
965 regfile_iter_next(riter)
968 regfile_iter_free(riter);
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);
986 linhash_free(tagtab);
989 EXPORT_CMD_HANDLER(stats);
991 #define LSG_TFORTUNE_CMD(_name) #_name
992 static const char * const subcommand_names[] = {LSG_TFORTUNE_SUBCOMMANDS NULL};
993 #undef LSG_TFORTUNE_CMD
995 static void show_subcommand_summary(bool verbose)
999 printf("Available subcommands:\n");
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);
1010 for (i = 0; i < LSG_NUM_TFORTUNE_SUBCOMMANDS; i++) {
1017 n += printf("%s", subcommand_names[i]);
1023 static int com_help(void)
1026 char *errctx, *help;
1028 const struct lls_command *cmd;
1030 ret = lls_check_arg_count(sublpr, 0, 1, &errctx);
1032 return lopsub_error(ret, &errctx);
1033 if (lls_num_inputs(sublpr) == 0) {
1034 show_subcommand_summary(OPT_GIVEN(HELP, LONG));
1037 arg = lls_input(0, sublpr);
1038 ret = lls_lookup_subcmd(arg, tfortune_suite, &errctx);
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);
1045 help = lls_short_help(cmd);
1046 printf("%s\n", help);
1050 EXPORT_CMD_HANDLER(help);
1052 const char *GET_VERSION(void);
1053 static void handle_help_and_version(void)
1057 const struct lls_command *cmd;
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"
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"
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);
1083 printf("%s\n", 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);
1092 show_subcommand_summary(true /* verbose */);
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 */
1108 static bool is_short_opt(const char *word)
1114 if (word[1] == '\0')
1119 static bool is_long_opt(const char *word)
1125 if (word[2] == '\0')
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)
1136 if (strchr(word, '='))
1139 for (n = 0; short_opts[n]; n++) {
1140 const char *opt = short_opts[n];
1141 if (word[len - 1] != opt[1])
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)
1157 if (strchr(word, '='))
1159 for (n = 0; long_opts[n]; n++) {
1160 const char *opt = long_opts[n];
1161 size_t len = strlen(opt);
1163 if (opt[len - 1] == '=')
1165 if (strncmp(word + 2, opt + 2, len - 2))
1167 if (opt[len] == '=')
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)
1181 bool have_dd = false;
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];
1191 result[n] = WT_NON_OPTION_ARG;
1194 if (prev_type == WT_SHORT_OPT_WITH_ARG) {
1195 result[n] = WT_OPTION_ARG;
1198 if (prev_type == WT_LONG_OPT_WITH_ARG) {
1199 result[n] = WT_OPTION_ARG;
1202 word = lls_input(n, sublpr);
1203 if (strcmp(word, "--") == 0) {
1204 result[n] = WT_DOUBLE_DASH;
1208 if (is_short_opt(word)) {
1209 ret = short_opt_needs_arg(word, short_opts);
1213 result[n] = WT_SHORT_OPT_WITH_ARG;
1215 result[n] = WT_SHORT_OPT_WITHOUT_ARG;
1218 if (is_long_opt(word)) {
1219 ret = long_opt_needs_arg(word, long_opts);
1223 result[n] = WT_LONG_OPT_WITH_ARG;
1225 result[n] = WT_LONG_OPT_WITHOUT_ARG;
1228 result[n] = WT_NON_OPTION_ARG;
1232 for (; n <= cword; n++)
1233 result[n] = WT_DUNNO;
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 \
1243 DUMMY_COMPLETER(tfortune)
1244 DUMMY_COMPLETER(compgen)
1245 DUMMY_COMPLETER(completer)
1247 static const char * const supercmd_opts[] = {LSG_TFORTUNE_TFORTUNE_OPTS, NULL};
1249 static void print_zero_terminated_list(const char * const *list)
1251 const char * const *c;
1252 for (c = list; *c; c++)
1253 printf("%s%c", *c, '\0');
1256 static void print_option_list(const char * const *opts)
1258 const char * const *c;
1260 for (c = opts; *c; c++) {
1261 int len = strlen(*c);
1263 if ((*c)[len - 1] == '=')
1265 printf("%.*s%c", len, *c, '\0');
1269 static void activate_dirname_completion(void)
1272 printf("-o dirnames%c", '\0');
1275 static void complete_loglevels(void)
1278 const struct lls_option *opt = lls_opt(LSG_TFORTUNE_TFORTUNE_OPT_LOGLEVEL,
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');
1287 static char **complete_dentries(const char *dir)
1290 struct regfile_iter *riter;
1292 char **result = NULL;
1294 regfile_iter_new(dir, &riter);
1297 (bn = regfile_iter_basename(riter));
1298 regfile_iter_next(riter), n++
1300 result = xrealloc(result, (n + 2) * sizeof(*result));
1301 result[n] = xstrdup(bn);
1302 result[n + 1] = NULL;
1307 static char **complete_ede(__attribute__ ((unused)) uint32_t cword,
1308 __attribute__ ((unused)) unsigned arg0,
1309 __attribute__ ((unused)) bool have_dd)
1311 char **result, *epidir = get_epidir();
1313 result = complete_dentries(epidir);
1318 static char **complete_edx(__attribute__ ((unused)) uint32_t cword,
1319 __attribute__ ((unused)) unsigned arg0,
1320 __attribute__ ((unused)) bool have_dd)
1322 char **result, *xdir = get_xdir();
1324 result = complete_dentries(xdir);
1329 static char **complete_std_opts(bool have_dd, const char * const *opts)
1332 print_option_list(opts);
1334 print_option_list(supercmd_opts);
1338 static char **complete_stats(__attribute__ ((unused)) uint32_t cword,
1339 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1341 const char * const opts[] = {LSG_TFORTUNE_STATS_OPTS, NULL};
1342 return complete_std_opts(have_dd, opts);
1345 static char **complete_locate(__attribute__ ((unused)) uint32_t cword,
1346 __attribute__ ((unused)) unsigned arg0,
1347 __attribute__ ((unused)) bool have_dd)
1352 static char **complete_lse(__attribute__ ((unused)) uint32_t cword,
1353 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1355 const char * const opts[] = {LSG_TFORTUNE_LSE_OPTS, NULL};
1356 return complete_std_opts(have_dd, opts);
1359 static char **complete_lst(__attribute__ ((unused)) uint32_t cword,
1360 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1362 const char * const opts[] = {LSG_TFORTUNE_LST_OPTS, NULL};
1363 return complete_std_opts(have_dd, opts);
1366 static char **complete_lsx(__attribute__ ((unused)) uint32_t cword,
1367 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1369 const char * const opts[] = {LSG_TFORTUNE_LSX_OPTS, NULL};
1370 return complete_std_opts(have_dd, opts);
1373 static char **complete_help(__attribute__ ((unused)) uint32_t cword,
1374 __attribute__ ((unused)) unsigned arg0, bool have_dd)
1376 const char * const opts[] = {LSG_TFORTUNE_HELP_OPTS, NULL};
1379 print_option_list(supercmd_opts);
1381 print_option_list(opts);
1382 print_zero_terminated_list(subcommand_names);
1386 static char **complete_print(uint32_t cword, unsigned arg0, bool have_dd)
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;
1393 char **result, *xdir;
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];
1400 switch (prev_type) {
1401 case WT_COMMAND_NAME:
1402 case WT_SHORT_OPT_WITHOUT_ARG:
1404 case WT_LONG_OPT_WITHOUT_ARG:
1405 case WT_DOUBLE_DASH:
1407 print_option_list(supercmd_opts);
1409 print_option_list(opts);
1411 case WT_SHORT_OPT_WITH_ARG:
1412 if (strcmp(prev, "-x") == 0)
1413 goto complete_expression;
1415 case WT_LONG_OPT_WITH_ARG:
1416 if (strcmp(prev, "--expression") == 0)
1417 goto complete_expression;
1422 complete_expression:
1424 result = complete_dentries(xdir);
1429 typedef char **(*completer)(uint32_t cword, unsigned arg0, bool have_dd);
1431 #define LSG_TFORTUNE_CMD(_name) complete_ ## _name
1432 static const completer completers[] = {LSG_TFORTUNE_COMMANDS};
1433 #undef LSG_TFORTUNE_CMD
1435 static int call_subcmd_completer(unsigned cmd_num, int arg0, uint32_t cword,
1438 char **c, **candidates = completers[cmd_num](cword, arg0, have_dd);
1442 for (c = candidates; *c; c++) {
1443 printf("%s%c", *c, '\0');
1450 static bool need_subcommand_completer(uint32_t cword, unsigned subcmd_idx,
1451 const enum tf_word_type *word_types, bool have_dd)
1453 enum tf_word_type prev_type;
1456 if (subcmd_idx == 0)
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:
1470 word = lls_input(cword, sublpr);
1471 if (is_short_opt(word))
1473 if (is_long_opt(word))
1478 static int com_compgen(void)
1481 uint32_t cword = OPT_UINT32_VAL(COMPGEN, CURRENT_WORD_INDEX);
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;
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);
1494 word_types = xmalloc(cword * sizeof(*word_types));
1495 have_dd = get_word_types(cword, 0, short_opts, long_opts, word_types);
1497 * Locate the subcommand argument, if present. It is always the first
1498 * non-option argument.
1501 for (n = 1; n < cword; n++) {
1502 if (word_types[n] != WT_NON_OPTION_ARG)
1507 if (need_subcommand_completer(cword, subcmd_idx, word_types, have_dd)) {
1509 word = lls_input(subcmd_idx, sublpr);
1510 ret = lls_lookup_subcmd(word, tfortune_suite, NULL);
1511 if (ret < 0) /* invalid subcommand */
1513 return call_subcmd_completer(ret, subcmd_idx, cword, have_dd);
1516 prev_type = word_types[cword - 1];
1517 prev = lls_input(cword - 1, sublpr);
1519 switch (prev_type) {
1522 case WT_COMMAND_NAME:
1523 case WT_SHORT_OPT_WITHOUT_ARG:
1525 case WT_NON_OPTION_ARG:
1526 case WT_LONG_OPT_WITHOUT_ARG:
1528 print_option_list(supercmd_opts);
1530 case WT_DOUBLE_DASH:
1531 print_zero_terminated_list(subcommand_names);
1533 case WT_SHORT_OPT_WITH_ARG:
1534 if (strcmp(prev, "-b") == 0) {
1535 activate_dirname_completion();
1538 if (strcmp(prev, "-l") == 0) {
1539 complete_loglevels();
1543 case WT_LONG_OPT_WITH_ARG:
1544 if (strcmp(prev, "--basedir") == 0) {
1545 activate_dirname_completion();
1548 if (strcmp(prev, "--loglevel") == 0) {
1549 complete_loglevels();
1556 EXPORT_CMD_HANDLER(compgen);
1558 static int com_completer(void)
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"
1569 "while read -d '' w; do \n"
1570 "[[ -z \"$w\" ]] && { have_empty=true; continue; }\n"
1571 "if [[ $have_empty == true ]]; then\n"
1574 "[[ \"$w\" != \"$cur\"* ]] && continue \n"
1575 "COMPREPLY[i]=\"$w\" \n"
1578 "done < <(tfortune -- compgen --current-word-index \\\n"
1579 "\"$((COMP_CWORD + offset))\" -- $TF_EXTRA \"${COMP_WORDS[@]}\")\n"
1581 "complete -F _tfortune tfortune \n"
1583 if (OPT_GIVEN(COMPLETER, ALIAS)) {
1584 const char *ali = OPT_STRING_VAL(PRINT, EXPRESSION);
1585 printf("alias %s=\"tfortune --\"\n", ali);
1587 "COMP_WORDS[0]='--'\n"
1590 "_tfortune \"$@\" \n"
1591 "unset TF_EXTRA TF_OFFSET\n"
1595 printf("complete -F _%s %s \n", ali, ali);
1599 EXPORT_CMD_HANDLER(completer);
1601 int main(int argc, char **argv)
1605 const struct lls_command *cmd = CMD_PTR(TFORTUNE), *subcmd;
1606 const struct tf_user_data *ud;
1607 unsigned num_inputs;
1609 ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
1611 lopsub_error(ret, &errctx);
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 */);
1622 ret = lls_lookup_subcmd(argv[argc - num_inputs], tfortune_suite, &errctx);
1624 ret = lopsub_error(ret, &errctx);
1627 subcmd = lls_cmd(ret, tfortune_suite);
1628 ret = lls_parse(num_inputs, argv + argc - num_inputs, subcmd,
1631 ret = lopsub_error(ret, &errctx);
1634 ud = lls_user_data(subcmd);
1635 ret = ud->handler();
1636 lls_free_parse_result(sublpr, subcmd);
1638 ERROR_LOG("%s\n", tf_strerror(-ret));
1640 lls_free_parse_result(lpr, cmd);
1641 exit(ret >= 0? EXIT_SUCCESS : EXIT_FAILURE);