From f147c8f6fcc5c87d484e3b30c02d3b2cd88e409b Mon Sep 17 00:00:00 2001 From: Thomas Jensen Date: Sat, 3 Apr 2021 14:13:33 +0200 Subject: [PATCH] Enable querying of the design list by tag #23 --- src/boxes.c | 148 +++++++++++++++++++++++++++--- src/boxes.in.h | 7 ++ src/generate.c | 2 +- src/parser.y | 8 +- test/151_tag_query_no_results.txt | 6 ++ test/163_tag_query_minus3.txt | 9 ++ test/164_tag_query_empty.txt | 7 ++ 7 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 test/151_tag_query_no_results.txt create mode 100644 test/163_tag_query_minus3.txt create mode 100644 test/164_tag_query_empty.txt diff --git a/src/boxes.c b/src/boxes.c index c242f8f..d706bb2 100644 --- a/src/boxes.c +++ b/src/boxes.c @@ -45,6 +45,13 @@ #include "unicode.h" + +typedef struct { + char *tag; + size_t count; +} tagstats_t; + + /* _\|/_ (o o) +----oOO-{_}-OOo------------------------------------------------------------+ @@ -130,7 +137,7 @@ static void usage_long(FILE *st) static int validate_tag(char *tag) { - if (strcmp(tag, "(all)") == 0 || strcmp(tag, "(undoc)") == 0) { + if (strcmp(tag, QUERY_ALL) == 0 || strcmp(tag, QUERY_UNDOC) == 0) { return 1; } return tag_is_valid(tag); @@ -143,6 +150,7 @@ static char **parse_tag_query(char *optarg) char **result = NULL; char *dup = strdup(optarg); /* required because strtok() modifies its input */ + int contains_positive_element = 0; size_t num_expr = 0; for (char *q = strtok(dup, ","); q != NULL; q = strtok(NULL, ",")) { @@ -152,15 +160,24 @@ static char **parse_tag_query(char *optarg) continue; } - if (trimmed[0] == '+' || trimmed[0] == '-') { - if (!validate_tag(trimmed + 1)) { - fprintf(stderr, "%s: not a tag -- %s\n", PROJECT, trimmed + 1); - return NULL; - } - } else if (!validate_tag(trimmed)) { - fprintf(stderr, "%s: not a tag -- %s\n", PROJECT, trimmed); + if (trimmed[0] != '-') { + contains_positive_element = 1; + } + + char *raw_tag = (trimmed[0] == '+' || trimmed[0] == '-') ? (trimmed + 1) : trimmed; + if (!validate_tag(raw_tag)) { + fprintf(stderr, "%s: not a tag -- %s\n", PROJECT, raw_tag); return NULL; } + if (result != NULL) { + for (size_t i = 0; result[i] != NULL; ++i) { + char *restag = (result[i][0] == '+' || result[i][0] == '-') ? (result[i] + 1) : result[i]; + if (strcasecmp(restag, raw_tag) == 0) { + fprintf(stderr, "%s: duplicate query expression -- %s\n", PROJECT, trimmed); + return NULL; + } + } + } ++num_expr; result = (char **) realloc(result, (num_expr + 1) * sizeof(char *)); @@ -172,6 +189,23 @@ static char **parse_tag_query(char *optarg) result[num_expr] = NULL; } BFREE(dup); + + if (num_expr == 0) { + fprintf(stderr, "%s: empty tag query -- %s\n", PROJECT, optarg); + return NULL; + } + + if (!contains_positive_element) { + ++num_expr; + result = (char **) realloc(result, (num_expr + 1) * sizeof(char *)); + if (result == NULL) { + perror(PROJECT); + } + else { + result[num_expr - 1] = QUERY_ALL; + result[num_expr] = NULL; + } + } return result; } @@ -827,10 +861,89 @@ static char *names(design_t *design) +int query_is_undoc() +{ + return opt.query != NULL && strcmp(opt.query[0], QUERY_UNDOC) == 0 && opt.query[1] == NULL; +} + + + static int filter_by_tag(char **tags) { - // TODO - return 1; // TODO or 0 if the tags don't match + #ifdef DEBUG + fprintf(stderr, "filter_by_tag("); + for (size_t tidx = 0; tags[tidx] != NULL; ++tidx) { + fprintf(stderr, "%s%s", tidx > 0 ? ", " : "", tags[tidx]); + } + #endif + + int result = array_contains0(opt.query, QUERY_ALL); + if (opt.query != NULL) { + for (size_t qidx = 0; opt.query[qidx] != NULL; ++qidx) { + if (opt.query[qidx][0] == '+') { + result = array_contains0(tags, opt.query[qidx] + 1); + if (!result) { + break; + } + } + else if (opt.query[qidx][0] == '-') { + if (array_contains0(tags, opt.query[qidx] + 1)) { + result = 0; + break; + } + } + else if (array_contains0(tags, opt.query[qidx])) { + result = 1; + } + } + } + + #ifdef DEBUG + fprintf(stderr, ") -> %d\n", result); + #endif + return result; +} + + + +static void count_tag(char *tag, tagstats_t **tagstats, size_t *num_tags) +{ + if (*tagstats != NULL) { + for (size_t i = 0; i < (*num_tags); ++i) { + if (strcasecmp((*tagstats)[i].tag, tag) == 0) { + ++((*tagstats)[i].count); + return; + } + } + } + + ++(*num_tags); + *tagstats = realloc(*tagstats, (*num_tags) * sizeof(tagstats_t)); + (*tagstats)[(*num_tags) - 1].tag = tag; + (*tagstats)[(*num_tags) - 1].count = 1; +} + + + +static int tagstats_sort(const void *p1, const void *p2) +{ + return strcasecmp(((tagstats_t *) p1)->tag, ((tagstats_t *) p2)->tag); +} + + + +static void print_tags(tagstats_t *tagstats, size_t num_tags) +{ + if (tagstats != NULL) { + qsort(tagstats, num_tags, sizeof(tagstats_t), tagstats_sort); + for (size_t tidx = 0; tidx < num_tags; ++tidx) { + if (tidx > 0) { + fprintf(opt.outfile, " | "); + } + fprintf(opt.outfile, "%s (%d)", tagstats[tidx].tag, (int) tagstats[tidx].count); + } + fprintf(opt.outfile, "\n"); + } } @@ -997,7 +1110,7 @@ static int list_styles() /* * Display all shapes */ - if (opt.query != NULL) { + if (query_is_undoc()) { fprintf(opt.outfile, "Sample:\n%s\n", d->sample); } else { @@ -1036,6 +1149,8 @@ static int list_styles() } qsort(list, anz_designs, sizeof(design_t *), style_sort); + tagstats_t *tagstats = NULL; + size_t num_tags = 0; if (opt.query == NULL) { print_design_list_header(); } @@ -1065,9 +1180,18 @@ static int list_styles() fprintf(opt.outfile, "%s:\n\n%s\n\n", all_names, list[i]->sample); } BFREE(all_names); + + for (size_t tidx = 0; list[i]->tags[tidx] != NULL; ++tidx) { + count_tag(list[i]->tags[tidx], &tagstats, &num_tags); + } } } - BFREE (list); + BFREE(list); + + if (opt.query == NULL) { + print_tags(tagstats, num_tags); + BFREE(tagstats); + } } return 0; diff --git a/src/boxes.in.h b/src/boxes.in.h index a84d5ad..46d60c6 100644 --- a/src/boxes.in.h +++ b/src/boxes.in.h @@ -145,6 +145,13 @@ typedef struct { /* Command line options: */ extern opt_t opt; +#define QUERY_ALL "(all)" +#define QUERY_UNDOC "(undoc)" + +/** Check if -q "(undoc)" was specified. */ +int query_is_undoc(); + + typedef struct { size_t len; /* length of visible text in columns (visible character positions in a text terminal), which is the same as the length of the 'text' field */ diff --git a/src/generate.c b/src/generate.c index c7089d0..7a26432 100644 --- a/src/generate.c +++ b/src/generate.c @@ -1034,7 +1034,7 @@ int output_box(const sentry_t *thebox) } /* add info line for web ui if requested with -q */ - if (opt.query != NULL) { + if (query_is_undoc()) { fprintf(opt.outfile, "%d ", (int) (thebox[BTOP].height + vfill1_save - skip_start)); for (j = 0; j < input.anz_lines; j++) { fprintf(opt.outfile, "%d%s", diff --git a/src/parser.y b/src/parser.y index 139051e..23d81a1 100644 --- a/src/parser.y +++ b/src/parser.y @@ -745,9 +745,7 @@ layout: layout entry | layout block | entry | block ; tag_entry: STRING { - if (tag_record(bison_args, $1) != 0) { - YYABORT; - } + tag_record(bison_args, $1); /* discard return code (we print warnings, but tolerate the problem) */ } tag_list: tag_entry | tag_list ',' tag_entry; @@ -794,9 +792,7 @@ entry: KEYWORD STRING } } else if (strcasecmp ($1, "tags") == 0) { - if (tag_record(bison_args, $2) != 0) { - YYABORT; - } + tag_record(bison_args, $2); /* discard return code (we print warnings, but tolerate the problem) */ } else if (strcasecmp ($1, "indent") == 0) { if (strcasecmp ($2, "text") == 0 || diff --git a/test/151_tag_query_no_results.txt b/test/151_tag_query_no_results.txt new file mode 100644 index 0000000..5170061 --- /dev/null +++ b/test/151_tag_query_no_results.txt @@ -0,0 +1,6 @@ +:ARGS +-f 14x_tag_query.cfg -q non-existent +:INPUT +:OUTPUT-FILTER +:EXPECTED +:EOF diff --git a/test/163_tag_query_minus3.txt b/test/163_tag_query_minus3.txt new file mode 100644 index 0000000..13810d2 --- /dev/null +++ b/test/163_tag_query_minus3.txt @@ -0,0 +1,9 @@ +:ARGS +-f 14x_tag_query.cfg -q -tag1,tag2 +:INPUT +:OUTPUT-FILTER +:EXPECTED +designB +aliasB1 (alias) +aliasB2 (alias) +:EOF diff --git a/test/164_tag_query_empty.txt b/test/164_tag_query_empty.txt new file mode 100644 index 0000000..60fe405 --- /dev/null +++ b/test/164_tag_query_empty.txt @@ -0,0 +1,7 @@ +:ARGS +-q ,, +:INPUT +:OUTPUT-FILTER +:EXPECTED-ERROR 1 +boxes: empty tag query -- ,, +:EOF