author | Olivier Brunel
<jjk@jjacky.com> 2022-12-25 19:15:57 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2022-12-25 19:15:57 UTC |
parent | b89b8bf02860665795fbe91cd92f99f1d5690e18 |
main.c | +220 | -24 |
diff --git a/main.c b/main.c index a94ba86..962bd49 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,4 @@ -#define _GNU_SOURCE +#define _GNU_SOURCE /* memmem() */ #include <stdio.h> #include <stdarg.h> #include <stdlib.h> @@ -6,11 +6,17 @@ #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> +#include <getopt.h> #include <errno.h> #include <err.h> #include "md4c.h" +const char *progname; +#define ret(r,...) { warn(__VA_ARGS__); return r; } +#define retx(r,...) { warnx(__VA_ARGS__); return r; } + enum { + ERR_NONE = 0, ERR_USAGE = -1, ERR_IO = -5, ERR_PARSER = -6, @@ -19,14 +25,38 @@ enum { ERR_INVALID = -9, }; +enum { + OPT_NO_CSS = (1 << 0), + OPT_INLINE_CSS = (1 << 1), +}; + enum { CODE_LINES = (1 << 0), CODE_HIGHLIGHT = (1 << 1), CODE_BUFFERED = (CODE_LINES | CODE_HIGHLIGHT), }; +enum { + CSS_STRUCT, + CSS_COMMON, + CSS_LIGHT, + CSS_DARK, + NB_CSS +}; +static struct css { + const char *file; + char *buf; +} css[NB_CSS] = { + { "struct.css" }, + { "common.css" }, + { "light.css" }, + { "dark.css" } +}; + struct ctx { + int options; FILE *out; + struct css *css; struct { int flags; int from; @@ -153,7 +183,26 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) switch (type) { case MD_BLOCK_DOC: - return raw_str(f, "<html><body>"); + { + int r = raw_str(f, "<!DOCTYPE html>\n<html><head>"); + if (r < 0) return r; + if (ctx->options & OPT_INLINE_CSS) { + for (int i = 0; i < NB_CSS; ++i) { + if (raw_str(f, "<style>") < 0 + || raw_str(f, ctx->css[i].buf) < 0 + || raw_str(f, "</style>") < 0) + return ERR_IO; + } + } else if (!(ctx->options & OPT_NO_CSS)) { + for (int i = 0; i < NB_CSS; ++i) { + r = raw_printf(f, "<link rel=\"stylesheet\" href=\"%s\">", ctx->css[i].file); + if (r < 0) return r; + } + } + r = raw_str(f, "</head><body><main>"); + if (r < 0) return r; + } + break; case MD_BLOCK_QUOTE: return raw_str(f, "<blockquote>"); @@ -317,13 +366,13 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) r = highlight_escape_text(f, ctx->buf.str, ctx->buf.len); else r = escape_text(f, ctx->buf.str, ctx->buf.len); - if (r < 0) return ERR_IO; + if (r < 0) return r; if (ctx->code.flags & CODE_LINES) r = raw_str(f, "</code>"); else r = raw_str(f, "</pre>"); - if (r < 0) return ERR_IO; + if (r < 0) return r; free(ctx->buf.str); ctx->code.flags = 0; @@ -460,7 +509,7 @@ text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) return raw_str(f, "\n"); case MD_TEXT_ENTITY: - return escape_text(stdout, text, size); + return escape_text(f, text, size); case MD_TEXT_CODE: { @@ -504,8 +553,8 @@ text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) break; } } - if (i == n && escape_text(f, text, size) < 0) - return ERR_IO; + if (i == n) + return escape_text(f, text, size); break; case MD_TEXT_LATEXMATH: @@ -515,22 +564,17 @@ text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) return 0; } - -int -main (int argc, char *argv[]) +static int +convert_file(const char *file, struct ctx *ctx) { - if (argc != 2) return -ERR_USAGE; - const char *file = argv[1]; - int fd = open(file, O_RDONLY | O_CLOEXEC | O_NONBLOCK); - if (fd < 0) err(-ERR_IO, "cannot open '%s'", file); + if (fd < 0) ret(ERR_IO, "cannot open '%s'", file); struct stat st; if (fstat(fd, &st) < 0) { - int e = errno; + warn("cannot stat '%s'", file); close(fd); - errno = e; - err(-ERR_IO, "cannot stat '%s'", file); + return ERR_IO; } size_t done = 0, len = st.st_size; @@ -548,10 +592,10 @@ main (int argc, char *argv[]) len -= sr; } if (done < len) { - int e = errno; + if (!errno) errno = ENODATA; + warn("cannot read '%s'", file); close(fd); - errno = (e) ? e : ENODATA; - err(-ERR_IO, "cannot read '%s'", file); + return ERR_IO; } close(fd); @@ -564,10 +608,162 @@ main (int argc, char *argv[]) .leave_span = leave_span, .text = text, }; - struct ctx ctx = { .out = stdout }; - int r = md_parse(buf, done, &parser, &ctx); - if (r < 0) errx(-ERR_PARSER, "parser internal error %d", -r); - else if (r > 0) errx(-r, "parser error %d", r); + int r = md_parse(buf, done, &parser, ctx); + if (r < 0) { + retx(ERR_PARSER, "parser internal error %d", -r); + } else if (r > 0) { + retx(-r - 100, "parser error %d", r); + } + + return 0; +} + +static int +load_file(const char *file, char **dst) +{ + int fd = open(file, O_RDONLY | O_CLOEXEC | O_NONBLOCK); + if (fd < 0) ret(ERR_IO, "cannot open '%s'", file); + + struct stat st; + if (fstat(fd, &st) < 0) { + warn("cannot stat '%s'", file); + close(fd); + return ERR_IO; + } + + FILE *f = fdopen(fd, "re"); + if (!f) { + warn("cannot open '%s'", file); + close(fd); + return ERR_IO; + } + + *dst = malloc(st.st_size + 1); + if (!*dst) { + warn("cannot load '%s'", file); + fclose(f); + return ERR_MEM; + } + + if (fread(*dst, 1, st.st_size, f) != st.st_size) { + free(*dst); + fclose(f); + retx(ERR_IO, "cannot read '%s'", file); + } + (*dst)[st.st_size] = 0; + + fclose(f); + return 0; +} + +static int +write_file(int fdp, const char *path, const char *file, const char *data) +{ + int fd = openat(fdp, file, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NONBLOCK, 0644); + if (fd < 0) ret(ERR_IO, "cannot create '%s/%s'", path, file); + + FILE *f = fdopen(fd, "w"); + if (!f) { + warn("cannot open '%s/%s'", path, file); + close(fd); + return ERR_IO; + } + + size_t dlen = strlen(data); + if (fwrite(data, 1, dlen, f) != dlen) { + fclose(f); + retx(ERR_IO, "cannot write '%s/%s'", path, file); + } + + fclose(f); + return 0; +} + +static void +usage(int err) +{ + printf("usage: %s [OPTION..] FILE...\n", progname); + _exit((err) ? -ERR_USAGE : ERR_NONE); +} + +int +main (int argc, char *argv[]) +{ + progname = strrchr(argv[0], '/'); + if (progname) ++progname; + else progname = argv[0]; + + int options = 0; + char *destdir = "."; + + int c; + struct option opts[] = { + { "no-css", no_argument, NULL, 'C' }, + { "destdir", required_argument, NULL, 'd' }, + { "inline-css", no_argument, NULL, 'I' }, + { NULL, 0, NULL, 0 }, + }; + while ((c = getopt_long(argc, argv, "Cd:I", opts, NULL)) != -1) switch (c) { + case 'C': + options |= OPT_NO_CSS; + break; + case 'd': + destdir = optarg; + break; + case 'I': + options |= OPT_INLINE_CSS; + break; + case '?': + usage(1); + } + if (optind == argc) usage(1); + if ((options & (OPT_NO_CSS | OPT_INLINE_CSS)) == (OPT_NO_CSS | OPT_INLINE_CSS)) + retx(-ERR_USAGE, "cannot use '%s' and '%s' together", "--no-css", "--inline-css"); + + int fddest = open(destdir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (fddest < 0) err(ERR_IO, "cannot open '%s'", destdir); + + int r; + + if (!(options & OPT_NO_CSS)) { + printf("%s CSS files...\n", (options & OPT_INLINE_CSS) ? "Loading" : "Copying"); + for (int i = 0; i < NB_CSS; ++i) { + r = load_file(css[i].file, &css[i].buf); + if (r < 0) return -r; + if (!(options & OPT_INLINE_CSS)) { + r = write_file(fddest, destdir, css[i].file, css[i].buf); + if (r < 0) return -r; + } + } + } + + struct ctx ctx = { .options = options, .css = css }; + + for (int i = optind; i < argc; ++i) { + const char *sce = argv[i]; + size_t scelen = strlen(sce); + + if (strcmp(sce + scelen - 3, ".md")) + errx(-ERR_IO, "cannot convert '%s': File must be a markdown file (*.md)", sce); + + char dst[scelen + 3]; + memcpy(dst, sce, scelen - 2); + memcpy(dst + scelen - 2, "html", 5); + + printf("Converting %s...\n", sce); + + int fd = openat(fddest, dst, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NONBLOCK, 0644); + if (fd < 0) err(-ERR_IO, "cannot create '%s/%s'", destdir, dst); + + ctx.out = fdopen(fd, "we"); + if (!ctx.out) err(-ERR_IO, "cannot open '%s/%s'", destdir, dst); + + r = convert_file(sce, &ctx); + if (r < 0) return -r; + + fclose(ctx.out); + } + printf("done.\n"); return 0; }