/* 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 struct Tag { char *tag; int num; regex_t regex; }; static int compar(const void *a, const void *b) { return ((const struct Tag *)a)->num - ((const struct Tag *)b)->num; } 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 char *hstrstr(char *haystack, 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 *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; if (pre) printf("
");
	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) 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;
		}

		char *text = tag->tag;
		char *match = (pipe ? hstrstr(buf, text) : strstr(buf, text));
		if (!match && tag->tag[0] == 'M') {
			text = "main";
			match = (pipe ? hstrstr(buf, text) : strstr(buf, text));
		}
		if (match) escape(!pipe, buf, match - buf);
		printf("tag, strlen(tag->tag));
		printf("\" href=\"#");
		escape(true, tag->tag, strlen(tag->tag));
		printf("\">");
		if (match) {
			match += escape(!pipe, match, strlen(text));
		} else {
			escape(!pipe, buf, strlen(buf));
		}
		printf("");
		if (match) escape(!pipe, match, strlen(match));
	}
	if (pre) printf("
"); if (!index) return EX_OK; qsort(tags, len, sizeof(*tags), compar); printf("\n"); }