/* Copyright (C) 2021 C. McEnroe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include static char *nomagic(const char *pattern) { char *buf = malloc(2 * strlen(pattern) + 1); if (!buf) err(EX_OSERR, "malloc"); char *ptr = buf; for (const char *ch = pattern; *ch; ++ch) { if (strchr(".[*", *ch)) *ptr++ = '\\'; *ptr++ = *ch; } *ptr = '\0'; return buf; } static size_t escape(bool esc, const char *ptr, size_t len) { if (!esc) { fwrite(ptr, len, 1, stdout); return len; } for (size_t i = 0; i < len; ++i) { switch (ptr[i]) { break; case '&': printf("&"); break; case '<': printf("<"); break; case '"': printf("""); break; default: putchar(ptr[i]); } } return len; } static void id(const char *tag) { for (const char *ch = tag; *ch; ++ch) { if (isalnum(*ch) || strchr("-._", *ch)) { putchar(*ch); } else { putchar('_'); } } } static char *hstrstr(const char *haystack, const char *needle) { while (haystack) { char *elem = strchr(haystack, '<'); char *match = strstr(haystack, needle); if (!match) return NULL; if (!elem || match < elem) return match; haystack = strchr(elem, '>'); } return NULL; } int main(int argc, char *argv[]) { bool pre = false; bool pipe = false; bool index = false; const char *tagsFile = "tags"; for (int opt; 0 < (opt = getopt(argc, argv, "f:ipx"));) { switch (opt) { break; case 'f': tagsFile = optarg; break; case 'i': pipe = true; break; case 'p': pre = true; break; case 'x': index = true; break; default: return EX_USAGE; } } if (optind == argc) errx(EX_USAGE, "name required"); const char *name = argv[optind]; FILE *file = fopen(tagsFile, "r"); if (!file) err(EX_NOINPUT, "%s", tagsFile); size_t len = 0; size_t cap = 256; struct Tag { char *tag; int num; regex_t regex; } *tags = malloc(cap * sizeof(*tags)); if (!tags) err(EX_OSERR, "malloc"); char *buf = NULL; size_t bufCap = 0; while (0 < getline(&buf, &bufCap, file)) { char *line = buf; char *tag = strsep(&line, "\t"); char *file = strsep(&line, "\t"); char *def = strsep(&line, "\n"); if (!tag || !file || !def) errx(EX_DATAERR, "malformed tags file"); if (strcmp(file, name)) continue; if (len == cap) { tags = realloc(tags, (cap *= 2) * sizeof(*tags)); if (!tags) err(EX_OSERR, "realloc"); } tags[len].tag = strdup(tag); if (!tags[len].tag) err(EX_OSERR, "strdup"); tags[len].num = 0; if (def[0] == '/' || def[0] == '?') { def++; def[strlen(def)-1] = '\0'; char *search = nomagic(def); int error = regcomp( &tags[len].regex, search, REG_NEWLINE | REG_NOSUB ); free(search); if (error) { warnx("invalid regex for tag %s: %s", tag, def); continue; } } else { tags[len].num = strtol(def, &def, 10); if (*def) { warnx("invalid line number for tag %s: %s", tag, def); continue; } } len++; } fclose(file); file = fopen(name, "r"); if (!file) err(EX_NOINPUT, "%s", name); int num = 0; printf(pre ? "
" : index ? "
    \n" : ""); while (0 < getline(&buf, &bufCap, file) && ++num) { struct Tag *tag = NULL; for (size_t i = 0; i < len; ++i) { if (tags[i].num) { if (num != tags[i].num) continue; } else { if (regexec(&tags[i].regex, buf, 0, NULL, 0)) continue; } tag = &tags[i]; tag->num = num; break; } if (index) { if (!tag) continue; printf("
  • tag); printf("\">"); escape(true, tag->tag, strlen(tag->tag)); printf("
  • \n"); continue; } if (pipe) { ssize_t len = getline(&buf, &bufCap, stdin); if (len < 0) { errx(EX_DATAERR, "missing line %d on standard input", num); } } if (!tag) { escape(!pipe, buf, strlen(buf)); continue; } size_t mlen = strlen(tag->tag); char *match = (pipe ? hstrstr : strstr)(buf, tag->tag); while (match > buf && isalnum(match[-1])) { match = (pipe ? hstrstr : strstr)(&match[mlen], tag->tag); } if (!match && tag->tag[0] == 'M') { mlen = 4; match = (pipe ? hstrstr : strstr)(buf, "main"); } if (!match) { mlen = strlen(buf) - 1; match = buf; } escape(!pipe, buf, match - buf); printf("tag); printf("\" href=\"#"); id(tag->tag); printf("\">"); match += escape(!pipe, match, mlen); printf(""); escape(!pipe, match, strlen(match)); } printf(pre ? "
" : index ? "\n" : ""); }