stagit

static page generator for git
git clone https://github.com/michaelneuper/stagit.git
README | Files | Log | Refs | LICENSE

commit 98deb9605ffe190673a1d0d7ca8f653c74c7be80
parent 60e6b177730ef068415639bf48471431dc69870b
Author: Michael Neuper <michael@michaelneuper.com>
Date:   Sun, 25 Oct 2026 22:13:31 +0200

render README.org with pandoc

README.md is preferred, then README.org, then a plain README. The org
file is converted by pandoc as a standalone document, so #+title is kept,
and its body goes through the same link and image rewriting as markdown.
Without pandoc it is shown as plain text, with a warning.

Diffstat:
M.github/workflows/mirror.yml | 4++--
MREADME.md | 12+++++++-----
Mstagit.c | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mstyle.css | 6++++++
4 files changed, 87 insertions(+), 18 deletions(-)

diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml @@ -20,10 +20,10 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Install libgit2 and md4c + - name: Install libgit2, md4c and pandoc run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends libgit2-dev libmd4c-dev libmd4c-html0-dev + sudo apt-get install -y --no-install-recommends libgit2-dev libmd4c-dev libmd4c-html0-dev pandoc - name: Build stagit run: make diff --git a/README.md b/README.md @@ -6,10 +6,11 @@ mirror of my public GitHub repos. The upstream README is in `README`. ## Changes from upstream -- Each repo opens on its README (`index.html`), with markdown rendered by - [md4c](https://github.com/mity/md4c). `README.md` is preferred over - `README`, which is shown as plain text. Repos without either open on the - log. +- Each repo opens on its README (`index.html`). `README.md` is rendered by + [md4c](https://github.com/mity/md4c) and `README.org` by + [pandoc](https://pandoc.org); a plain `README`, or a `README.org` when + pandoc isn't installed, is shown as plain text. They're looked for in that + order, and repos without one open on the log. - stagit-index links to each repo's front page instead of its log. - The menu reads README | Files | Log | Refs. - Files are browsed by directory: `files.html` lists the top level, each @@ -25,7 +26,8 @@ To see only the code changes: `git diff 1.3 -- '*.c' Makefile`. ## Building the site -Needs libgit2, md4c and the GitHub CLI (`brew install libgit2 md4c gh`). +Needs libgit2, md4c, pandoc and the GitHub CLI +(`brew install libgit2 md4c pandoc gh`). ./build.sh python3 -m http.server -d public diff --git a/stagit.c b/stagit.c @@ -71,7 +71,7 @@ static char cloneurl[1024]; static char *submodules; static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:LICENSE.txt", "HEAD:COPYING" }; static char *license; -static char *readmefiles[] = { "HEAD:README.md", "HEAD:README" }; +static char *readmefiles[] = { "HEAD:README.md", "HEAD:README.org", "HEAD:README" }; static char *readme; static long long nlogcommits = -1; /* -1 indicates not used */ @@ -1143,14 +1143,76 @@ writereadmehtml(FILE *fp, const char *s) fputs(s, fp); } +char * +markdownhtml(const char *s, size_t len) +{ + char *html = NULL; + size_t htmllen; + FILE *htmlfp; + + if (!(htmlfp = open_memstream(&html, &htmllen))) + err(1, "open_memstream"); + if (md_html(s, len, writemarkdown, htmlfp, MD_DIALECT_GITHUB, 0)) + errx(1, "md_html: '%s'", readme); + if (fclose(htmlfp)) + err(1, "fclose: '%s'", readme); + + return html; +} + +/* README.org converted by pandoc, or NULL if that fails, for example + * because pandoc is not installed */ +char * +orghtml(const char *s, size_t len) +{ + char tmppath[] = "/tmp/stagit.XXXXXX", cmd[128], buf[BUFSIZ]; + char *html = NULL, *body, *end; + size_t n, htmllen; + FILE *tmpfp, *pp, *htmlfp; + int fd, status; + + if ((fd = mkstemp(tmppath)) < 0) + err(1, "mkstemp"); + if (!(tmpfp = fdopen(fd, "w"))) + err(1, "fdopen: '%s'", tmppath); + fwrite(s, 1, len, tmpfp); + checkfileerror(tmpfp, tmppath, 'w'); + fclose(tmpfp); + + /* a standalone document keeps #+title; only its body is used */ + snprintf(cmd, sizeof(cmd), + "pandoc -f org -t html5 -s -M pagetitle=README '%s'", tmppath); + if (!(pp = popen(cmd, "r"))) + err(1, "popen"); + if (!(htmlfp = open_memstream(&html, &htmllen))) + err(1, "open_memstream"); + while ((n = fread(buf, 1, sizeof(buf), pp)) > 0) + fwrite(buf, 1, n, htmlfp); + status = pclose(pp); + if (fclose(htmlfp)) + err(1, "fclose: '%s'", readme); + unlink(tmppath); + + if (status || !(body = strstr(html, "<body>")) || + !(end = strstr(body, "</body>"))) { + warnx("pandoc failed for '%s', showing it as plain text", readme); + free(html); + return NULL; + } + body += strlen("<body>"); + memmove(html, body, end - body); + html[end - body] = '\0'; + + return html; +} + void writereadme(FILE *fp) { git_object *obj = NULL; char spec[PATH_MAX], *html = NULL; const char *s, *ext; - size_t len, htmllen; - FILE *htmlfp; + size_t len; snprintf(spec, sizeof(spec), "HEAD:%s", readme); if (git_revparse_single(&obj, repo, spec)) @@ -1158,15 +1220,14 @@ writereadme(FILE *fp) s = git_blob_rawcontent((git_blob *)obj); len = git_blob_rawsize((git_blob *)obj); + /* rendered to memory first, to rewrite links and image sources */ + if ((ext = strrchr(readme, '.')) && !strcmp(ext, ".md")) + html = markdownhtml(s, len); + else if (ext && !strcmp(ext, ".org")) + html = orghtml(s, len); + fputs("<div id=\"readme\">\n", fp); - if ((ext = strrchr(readme, '.')) && !strcmp(ext, ".md")) { - /* render to memory first, to rewrite image sources */ - if (!(htmlfp = open_memstream(&html, &htmllen))) - err(1, "open_memstream"); - if (md_html(s, len, writemarkdown, htmlfp, MD_DIALECT_GITHUB, 0)) - errx(1, "md_html: '%s'", readme); - if (fclose(htmlfp)) - err(1, "fclose: '%s'", readme); + if (html) { writereadmehtml(fp, html); free(html); } else { diff --git a/style.css b/style.css @@ -289,3 +289,9 @@ pre a.d { #readme th { background: var(--row); } + +/* checklists: ul.task-list from pandoc, li.task-list-item from md4c */ +#readme .task-list, +#readme .task-list-item { + list-style: none; +}