summaryrefslogtreecommitdiff
path: root/tfortune.c
diff options
context:
space:
mode:
Diffstat (limited to 'tfortune.c')
-rw-r--r--tfortune.c252
1 files changed, 252 insertions, 0 deletions
diff --git a/tfortune.c b/tfortune.c
new file mode 100644
index 0000000..7447059
--- /dev/null
+++ b/tfortune.c
@@ -0,0 +1,252 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/mman.h>
+#include <stdbool.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+#include <sys/time.h>
+#include <lopsub.h>
+
+#include "tf.h"
+#include "tfortune.lsg.h"
+
+#define TF_SEP "---- "
+
+struct tf_map {
+ void *map;
+ off_t len;
+};
+
+struct tf_cookie {
+ unsigned input_num;
+ off_t offset, len;
+};
+
+static void mmap_file(const char *path, struct tf_map *map)
+{
+ int fd, ret;
+
+ ret = open(path, 0);
+ if (ret < 0) {
+ perror("open");
+ exit(EXIT_FAILURE);
+ }
+ fd = ret;
+ map->len = lseek(fd, 0, SEEK_END);
+ if (map->len == (off_t)-1) {
+ perror("lseek");
+ exit(EXIT_FAILURE);
+ }
+ map->map = mmap(NULL, map->len, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (map->map == MAP_FAILED) {
+ perror("mmap");
+ exit(EXIT_FAILURE);
+ }
+ ret = close(fd);
+ assert(ret >= 0);
+}
+
+static bool tag_given(const char *tag, const char *tags, size_t sz)
+{
+ bool found = false;
+ char *p, *str = strndup(tags, sz);
+
+ assert(str);
+ p = strtok(str, ",");
+ while (p) {
+ //fprintf(stderr, "sz: %zu, compare: %s <-> %.*s\n", sz, tag, (int) sz, p);
+ if (strcmp(p, tag) == 0) {
+ found = true;
+ break;
+ }
+ p = strtok(NULL, ",");
+ }
+ free(str);
+ return found;
+}
+
+static bool tags_ok(const char *tags, size_t sz, struct lls_parse_result *lpr)
+{
+ unsigned n, given;
+ const struct lls_opt_result *r;
+ const char *arg;
+
+ r = lls_opt_result(LSG_TFORTUNE_TFORTUNE_OPT_ACCEPT, lpr);
+ given = lls_opt_given(r);
+ if (given == 0)
+ goto check_reject;
+ /* check if any of the given accept tags is set */
+ for (n = 0; n < given; n++) {
+ arg = lls_string_val(n, r);
+ if (tag_given(arg, tags, sz))
+ goto check_reject;
+ }
+ return false; /* accept tag(s) given, but none was set */
+check_reject:
+ /* check if none of the given reject tags is set */
+ r = lls_opt_result(LSG_TFORTUNE_TFORTUNE_OPT_REJECT, lpr);
+ given = lls_opt_given(r);
+ for (n = 0; n < given; n++) {
+ arg = lls_string_val(n, r);
+ if (tag_given(arg, tags, sz))
+ return false;
+ }
+ return true;
+}
+
+static void print_tags(const char *tags, size_t sz, FILE *f)
+{
+ char *p, *str = strndup(tags, sz);
+
+ assert(str);
+ p = strtok(str, ",");
+ while (p) {
+ fprintf(f, "%s\n", p);
+ p = strtok(NULL, ",");
+ }
+ free(str);
+}
+
+static void print_random_cookie(const struct tf_cookie *cookies,
+ unsigned num_cookies, const struct tf_map *maps)
+{
+ long unsigned r;
+ const struct tf_cookie *cookie;
+ struct timeval tv;
+
+ if (num_cookies == 0) {
+ fprintf(stderr, "no matching cookie\n");
+ return;
+ }
+ gettimeofday(&tv, NULL);
+ srandom((unsigned)tv.tv_usec);
+ r = ((num_cookies + 0.0) * (random() / (RAND_MAX + 1.0)));
+ cookie = cookies + r;
+ assert(r < num_cookies);
+ printf("%.*s", (int)cookie->len,
+ (char *)maps[cookie->input_num].map + cookie->offset);
+}
+
+static void make_cookies(struct tf_map *maps, struct lls_parse_result *lpr)
+{
+ struct tf_cookie *cookies = NULL;
+ unsigned n, cookies_size = 0, num_cookies = 0, num_inputs;
+ size_t sep_len = strlen(TF_SEP);
+ const struct lls_opt_result *r_s;
+ FILE *f = NULL;
+
+ r_s = lls_opt_result(LSG_TFORTUNE_TFORTUNE_OPT_STATISTICS, lpr);
+ if (lls_opt_given(r_s)) {
+ f = popen("sort | uniq -c | sort -n", "w");
+ assert(f);
+ }
+ num_inputs = lls_num_inputs(lpr);
+ for (n = 0; n < num_inputs; n++) {
+ struct tf_map *m = maps + n;
+ const char *start = m->map, *end = m->map + m->len;
+ const char *buf = start, *cookie_start = start;
+
+ while (buf < end) {
+ struct tf_cookie *cookie;
+ const char *p, *cr, *tags;
+ size_t sz;
+
+ cr = memchr(buf, '\n', end - buf);
+ if (!cr)
+ break;
+ p = cr + 1;
+ if (!cookie_start)
+ cookie_start = p;
+ if (p + sep_len >= end)
+ break;
+ if (strncmp(p, TF_SEP, sep_len) != 0) {
+ buf = p;
+ continue;
+ }
+ tags = p + sep_len;
+ cr = memchr(tags, '\n', end - tags);
+ if (cr)
+ sz = cr - tags;
+ else
+ sz = end - tags;
+ if (!tags_ok(tags, sz, lpr)) {
+ if (!cr)
+ break;
+ buf = cr;
+ cookie_start = NULL;
+ continue;
+ }
+ num_cookies++;
+ if (lls_opt_given(r_s)) {
+ print_tags(tags, sz, f);
+ if (!cr)
+ break;
+ buf = cr;
+ continue;
+ }
+ if (num_cookies > cookies_size) {
+ cookies_size = 2 * cookies_size + 1;
+ cookies = realloc(cookies,
+ cookies_size * sizeof(*cookies));
+ assert(cookies);
+ }
+ cookie = cookies + num_cookies - 1;
+ cookie->input_num = n;
+ cookie->offset = cookie_start - start;
+ cookie->len = p - cookie_start;
+ buf = p + sep_len;
+ cookie_start = NULL;
+ }
+ }
+ if (f)
+ pclose(f);
+ if (!lls_opt_given(r_s))
+ print_random_cookie(cookies, num_cookies, maps);
+ else
+ printf("num cookies: %d\n", num_cookies);
+ free(cookies);
+}
+
+int main(int argc, char **argv)
+{
+ char *errctx;
+ struct lls_parse_result *lpr;
+ int ret;
+ struct tf_map *maps;
+ unsigned n, num_inputs;
+ const struct lls_command *cmd = lls_cmd(0, tfortune_suite);
+
+ ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
+ if (ret < 0) {
+ fprintf(stderr, "%s: %s\n", errctx? errctx : "",
+ lls_strerror(-ret));
+ free(errctx);
+ exit(EXIT_FAILURE);
+ }
+ if (lls_num_inputs(lpr) == 0) {
+ char *help = lls_short_help(cmd);
+ fprintf(stderr, "%s\n", help);
+ free(help);
+ ret = 0;
+ goto free_lpr;
+ }
+ num_inputs = lls_num_inputs(lpr);
+ maps = xmalloc(num_inputs * sizeof(*maps));
+ for (n = 0; n < num_inputs; n++)
+ mmap_file(lls_input(n, lpr), maps + n);
+ make_cookies(maps, lpr);
+ free(maps);
+ ret = EXIT_SUCCESS;
+free_lpr:
+ lls_free_parse_result(lpr, cmd);
+ return ret;
+}