Welcome to little lamb

Code » qmdoc » commit d1ba71c

Some refactoring & start adding CSS handling

author Olivier Brunel
2022-12-25 19:15:57 UTC
committer Olivier Brunel
2022-12-25 19:15:57 UTC
parent b89b8bf02860665795fbe91cd92f99f1d5690e18

Some refactoring & start adding CSS handling

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;
 }