aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2021-09-08 06:48:58 -0300
committerEuAndreh <eu@euandre.org>2021-09-08 07:01:48 -0300
commitb03b35d768a60c81b28138e2f4f5cd9a0a5ca721 (patch)
treeca3f079e87b9085fe429af330b9698292221d351
parentTODOs.md: Add #question-ab994373-9c09-c4f9-07cf-962f64443231 (diff)
downloadgistatic-b03b35d768a60c81b28138e2f4f5cd9a0a5ca721.tar.gz
gistatic-b03b35d768a60c81b28138e2f4f5cd9a0a5ca721.tar.xz
src/gistatic.in: Initial sh version
I got a bit frustrated that libgit2 didn't offer an API or "git archive" commands. I started implementing generating tarballs from scratch in src/tar.c and I'm quite liking it: the specification is very small, and the code can be very simple, since all I'm doing is writing fresh tarballs, and not reading or updating them. However I felt a bit locked-in to libgit2 itself, and what a detour from my original goal that is, and the question "what should libgit2 provide" came up to my mind. This made me realize that libgit2 is playing catch-up with Git itself, for as long as Git doesn't explicit has an explicit API, a standard, a public version of its internal libgit.a, or something like that. In fact, I'm locked in to Git, even. So even though a C version would probably be much faster, it wouldn't really have less dependencies, and that's what I'm actually optimising for: having the software be as portable as possible. On that front, C is unbeatable with sh as a close second. But the extreme portability of C aren't being fully exploited here: libgit2 does depend on non-POSIX things like CMake (and quick grep even shows references to -D_GNU_SOURCE!!), and Git's Makefile itself isn't POSIX at all. The point is: by depending on either Git or libgit2, I'm already loosing many selling points of writing the software in C, and sh becomes much more attractive. Had existed a common DVCS interface that could make me decouple gistatic from Git somehow I would insist a bit more in C, but now I'm switching to sh. The fact that I was able to get further with sh in one sitting than I did with C shows that a) I'm a bit less fluent in C than I would like (at least for now ^^) and b) that it is actually much simpler to do. I am quite satisfied with the quality of C code that I got so far. The error handling and propagation is pretty robust, and the implementation is very disciplined. I did most of the development with Valgrind, and other sanitizers would help even further, with some fuzzers on top.
-rw-r--r--.gitignore1
-rw-r--r--Makefile5
-rwxr-xr-xsrc/gistatic.in752
3 files changed, 756 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 8a60a57..3e18645 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/public/
+/src/gistatic
/src/config.h
/libgistatic.a
/gistatic
diff --git a/Makefile b/Makefile
index 5aa3159..48a52ef 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,7 @@ LDLIBS = -lgit2
-e 's:@DATE@:$(DATE):g' \
-e 's:@NAME@:$(NAME):g' \
< $< > $@
+ if [ -x $< ]; then chmod +x $@; fi
.c.o:
$(CC) $(CFLAGS) -o $@ -c $<
@@ -44,7 +45,7 @@ all-objects = $(lib-objects) src/main.o
t-objects = $(sources:.c=.to) src/tests-lib.to src/main.to
-all: libgistatic.a gistatic $(manpages)
+all: libgistatic.a gistatic src/gistatic $(manpages)
libgistatic.a: $(lib-objects)
$(AR) $(ARFLAGS) $@ $(lib-objects)
@@ -85,7 +86,7 @@ clean:
rm -rf \
public/ $(manpages) README.*.md CHANGELOG.*.md messages.mo \
vgcore.* tmp/ src/config.h $(all-objects) $(t-objects) \
- libgistatic.a gistatic gistatic-tests \
+ libgistatic.a gistatic gistatic-tests src/gistatic \
tests/resources/repositories/repo-1/.git \
tests/resources/repositories/repo-2/.git
diff --git a/src/gistatic.in b/src/gistatic.in
new file mode 100755
index 0000000..f18742a
--- /dev/null
+++ b/src/gistatic.in
@@ -0,0 +1,752 @@
+#!/bin/sh
+# shellcheck disable=2034 disable=2059
+set -eu
+
+MSG_USAGE_EN='Usage:
+ @NAME@ -i -o DIRECTORY REPOSITORY...
+ @NAME@ -o DIRECTORY -u CLONE_URL REPOSITORY
+ @NAME@ [-hV]
+'
+
+MSG_HELP_EN='Options:
+ -i build the index page of the repositories
+ -u CLONE_URL address to be shown alongside "git clone"
+ -o DIRECTORY output where to place the generated files
+ -h, --help show this help
+ -V, --version print the version information
+
+See "man @NAME@" for more information.'
+
+MSG_MISSING_CLIARG_EN='Missing %s\n'
+MSG_INCOMPATIBLE_OPTIONS_EN='Incompatible options -u and -i'
+MSG_MISSING_ARGS_EN='Missing [PATH | [PATHS]]'
+MSG_LANGNAME_EN='en'
+MSG_INDEX_DESCRIPTION_EN='Index of repositories'
+MSG_DEFAULT_TITLE_EN='Repositories'
+MSG_LOGO_ALT_INDEX_EN='Logo image of the repository list'
+MSG_LOGO_ALT_REPOSITORY_EN='Logo image of the repository'
+MSG_NAME_EN='Name'
+MSG_DESCRIPTION_EN='Description'
+MSG_LAST_COMMIT_EN='Last commit'
+MSG_FOOTER_TEMPLATE_EN='Generated with <a href="https://euandreh.xyz/@NAME@/">@NAME@</a>.'
+MSG_COMMIT_FEED_EN='commit feed'
+MSG_TAGS_FEED_EN='tags feed'
+MSG_NAV_FILES_EN='files'
+MSG_NAV_LOG_EN='log'
+MSG_NAV_REFS_EN='refs'
+MSG_THEAD_BRANCH_EN='Branch'
+MSG_THEAD_COMMITMSG_EN='Commit message'
+MSG_THEAD_AUTHOR_EN='Author'
+MSG_THEAD_DATE_EN='Date'
+MSG_THEAD_TAG_EN='Tag'
+MSG_TITLE_REFS_EN='refs'
+
+set_lang() {
+ lang="$1"
+ eval "
+ MSG_USAGE=\$MSG_USAGE_$lang
+ MSG_HELP=\$MSG_HELP_$lang
+ MSG_MISSING_CLIARG=\$MSG_MISSING_CLIARG_$lang
+ MSG_INCOMPATIBLE_OPTIONS=\$MSG_INCOMPATIBLE_OPTIONS_$lang
+ MSG_MISSING_ARGS=\$MSG_MISSING_ARGS_$lang
+ MSG_LANGNAME=\$MSG_LANGNAME_$lang
+ MSG_INDEX_DESCRIPTION=\$MSG_INDEX_DESCRIPTION_$lang
+ MSG_DEFAULT_TITLE=\$MSG_DEFAULT_TITLE_$lang
+ MSG_LOGO_ALT_INDEX=\$MSG_LOGO_ALT_INDEX_$lang
+ MSG_LOGO_ALT_REPOSITORY=\$MSG_LOGO_ALT_REPOSITORY_$lang
+ MSG_NAME=\$MSG_NAME_$lang
+ MSG_DESCRIPTION=\$MSG_DESCRIPTION_$lang
+ MSG_LAST_COMMIT=\$MSG_LAST_COMMIT_$lang
+ MSG_FOOTER_TEMPLATE=\$MSG_FOOTER_TEMPLATE_$lang
+ MSG_COMMIT_FEED=\$MSG_COMMIT_FEED_$lang
+ MSG_TAGS_FEED=\$MSG_TAGS_FEED_$lang
+ MSG_NAV_FILES=\$MSG_NAV_FILES_$lang
+ MSG_NAV_LOG=\$MSG_NAV_LOG_$lang
+ MSG_NAV_REFS=\$MSG_NAV_REFS_$lang
+ MSG_THEAD_BRANCH=\$MSG_THEAD_BRANCH_$lang
+ MSG_THEAD_COMMITMSG=\$MSG_THEAD_COMMITMSG_$lang
+ MSG_THEAD_AUTHOR=\$MSG_THEAD_AUTHOR_$lang
+ MSG_THEAD_DATE=\$MSG_THEAD_DATE_$lang
+ MSG_THEAD_TAG=\$MSG_THEAD_TAG_$lang
+ MSG_TITLE_REFS=\$MSG_TITLE_REFS_$lang
+"
+}
+
+get_lang() {
+ # LC_MESSAGES="ll_CC.CODESET@modifier" -> ll_CC, where quotes
+ # are optional
+ locale 2>/dev/null |
+ grep LC_MESSAGES |
+ cut -d. -f1 |
+ cut -d\" -f2 |
+ cut -d= -f2
+}
+
+case "$(get_lang)" in
+ *)
+ set_lang EN
+ ;;
+esac
+
+# Utilities
+
+escape() {
+ # FIXME: HTML escape
+ echo "$1"
+}
+
+realpath() {
+ mkdir -p "$1"
+ cd "$1"
+ pwd
+}
+
+
+# Helpers
+
+usage() {
+ printf '%s\n' "$MSG_USAGE"
+}
+
+help() {
+ printf '%s\n' "$MSG_HELP"
+}
+
+version() {
+ printf '@NAME@-@VERSION@ @DATE@\n'
+}
+
+print_logo() {
+ cat <<EOF
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
+ <path d="M 0 8 L 1 8 L 1 9 L 0 9 L 0 8 Z" />
+ <path d="M 0 13 L 1 13 L 1 14 L 0 14 L 0 13 Z" />
+ <path d="M 1 8 L 2 8 L 2 9 L 1 9 L 1 8 Z" />
+ <path d="M 1 13 L 2 13 L 2 14 L 1 14 L 1 13 Z" />
+ <path d="M 2 8 L 3 8 L 3 9 L 2 9 L 2 8 Z" />
+ <path d="M 2 13 L 3 13 L 3 14 L 2 14 L 2 13 Z" />
+ <path d="M 3 8 L 4 8 L 4 9 L 3 9 L 3 8 Z" />
+ <path d="M 3 13 L 4 13 L 4 14 L 3 14 L 3 13 Z" />
+ <path d="M 4 7 L 5 7 L 5 8 L 4 8 L 4 7 Z" />
+ <path d="M 4 8 L 5 8 L 5 9 L 4 9 L 4 8 Z" />
+ <path d="M 4 13 L 5 13 L 5 14 L 4 14 L 4 13 Z" />
+ <path d="M 5 6 L 6 6 L 6 7 L 5 7 L 5 6 Z" />
+ <path d="M 5 7 L 6 7 L 6 8 L 5 8 L 5 7 Z" />
+ <path d="M 5 13 L 6 13 L 6 14 L 5 14 L 5 13 Z" />
+ <path d="M 6 5 L 7 5 L 7 6 L 6 6 L 6 5 Z" />
+ <path d="M 6 6 L 7 6 L 7 7 L 6 7 L 6 6 Z" />
+ <path d="M 6 14 L 7 14 L 7 15 L 6 15 L 6 14 Z" />
+ <path d="M 7 1 L 8 1 L 8 2 L 7 2 L 7 1 Z" />
+ <path d="M 7 14 L 8 14 L 8 15 L 7 15 L 7 14 Z" />
+ <path d="M 7 15 L 8 15 L 8 16 L 7 16 L 7 15 Z" />
+ <path d="M 7 2 L 8 2 L 8 3 L 7 3 L 7 2 Z" />
+ <path d="M 7 3 L 8 3 L 8 4 L 7 4 L 7 3 Z" />
+ <path d="M 7 4 L 8 4 L 8 5 L 7 5 L 7 4 Z" />
+ <path d="M 7 5 L 8 5 L 8 6 L 7 6 L 7 5 Z" />
+ <path d="M 8 1 L 9 1 L 9 2 L 8 2 L 8 1 Z" />
+ <path d="M 8 15 L 9 15 L 9 16 L 8 16 L 8 15 Z" />
+ <path d="M 9 1 L 10 1 L 10 2 L 9 2 L 9 1 Z" />
+ <path d="M 9 2 L 10 2 L 10 3 L 9 3 L 9 2 Z" />
+ <path d="M 9 6 L 10 6 L 10 7 L 9 7 L 9 6 Z" />
+ <path d="M 9 15 L 10 15 L 10 16 L 9 16 L 9 15 Z" />
+ <path d="M 10 2 L 11 2 L 11 3 L 10 3 L 10 2 Z" />
+ <path d="M 10 3 L 11 3 L 11 4 L 10 4 L 10 3 Z" />
+ <path d="M 10 4 L 11 4 L 11 5 L 10 5 L 10 4 Z" />
+ <path d="M 10 5 L 11 5 L 11 6 L 10 6 L 10 5 Z" />
+ <path d="M 10 6 L 11 6 L 11 7 L 10 7 L 10 6 Z" />
+ <path d="M 11 6 L 12 6 L 12 7 L 11 7 L 11 6 Z" />
+ <path d="M 11 8 L 12 8 L 12 9 L 11 9 L 11 8 Z" />
+ <path d="M 10 15 L 11 15 L 11 16 L 10 16 L 10 15 Z" />
+ <path d="M 11 10 L 12 10 L 12 11 L 11 11 L 11 10 Z" />
+ <path d="M 11 12 L 12 12 L 12 13 L 11 13 L 11 12 Z" />
+ <path d="M 11 14 L 12 14 L 12 15 L 11 15 L 11 14 Z" />
+ <path d="M 11 15 L 12 15 L 12 16 L 11 16 L 11 15 Z" />
+ <path d="M 12 6 L 13 6 L 13 7 L 12 7 L 12 6 Z" />
+ <path d="M 12 8 L 13 8 L 13 9 L 12 9 L 12 8 Z" />
+ <path d="M 12 10 L 13 10 L 13 11 L 12 11 L 12 10 Z" />
+ <path d="M 12 12 L 13 12 L 13 13 L 12 13 L 12 12 Z" />
+ <path d="M 12 14 L 13 14 L 13 15 L 12 15 L 12 14 Z" />
+ <path d="M 13 6 L 14 6 L 14 7 L 13 7 L 13 6 Z" />
+ <path d="M 13 8 L 14 8 L 14 9 L 13 9 L 13 8 Z" />
+ <path d="M 13 10 L 14 10 L 14 11 L 13 11 L 13 10 Z" />
+ <path d="M 13 12 L 14 12 L 14 13 L 13 13 L 13 12 Z" />
+ <path d="M 13 13 L 14 13 L 14 14 L 13 14 L 13 13 Z" />
+ <path d="M 13 14 L 14 14 L 14 15 L 13 15 L 13 14 Z" />
+ <path d="M 14 7 L 15 7 L 15 8 L 14 8 L 14 7 Z" />
+ <path d="M 14 8 L 15 8 L 15 9 L 14 9 L 14 8 Z" />
+ <path d="M 14 9 L 15 9 L 15 10 L 14 10 L 14 9 Z" />
+ <path d="M 14 10 L 15 10 L 15 11 L 14 11 L 14 10 Z" />
+ <path d="M 14 11 L 15 11 L 15 12 L 14 12 L 14 11 Z" />
+ <path d="M 14 12 L 15 12 L 15 13 L 14 13 L 14 12 Z" />
+</svg>
+EOF
+}
+
+print_style() {
+ cat <<EOF
+:root {
+ --color: black;
+ --background-color: white;
+ --hover-color: hsl(0, 0%, 93%);
+ --nav-color: hsl(0, 0%, 87%);
+}
+
+@media(prefers-color-scheme: dark) {
+ :root {
+ --color: white;
+ --background-color: black;
+ --hover-color: hsl(0, 0%, 7%);
+ --nav-color: hsl(0, 0%, 13%);
+ }
+
+ body {
+ color: var(--color);
+ background-color: var(--background-color);
+ }
+
+ a {
+ color: hsl(211, 100%, 60%);
+ }
+
+ a:visited {
+ color: hsl(242, 100%, 80%);
+ }
+}
+
+body {
+ font-family: monospace;
+ max-width: 1100px;
+ margin: 0 auto 0 auto;
+}
+
+.logo {
+ height: 6em;
+ width: 6em;
+}
+
+.header-horizontal-grouping {
+ display: flex;
+ align-items: center;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.header-description {
+ margin-left: 2em;
+}
+
+nav {
+ margin-top: 2em;
+}
+
+nav ul {
+ display: flex;
+ list-style-type: none;
+ margin-bottom: 0;
+}
+
+nav li {
+ margin-left: 10px;
+}
+
+nav a, nav a:visited {
+ padding: 2px 8px 0px 8px;
+ color: var(--color);
+}
+
+.selected-nav-item {
+ background-color: var(--nav-color);
+}
+
+hr {
+ margin-top: 0;
+ border: 0;
+ border-top: 3px solid var(--nav-color);
+}
+
+table {
+ margin: 2em auto;
+}
+
+th {
+ padding-bottom: 1em;
+}
+
+tbody tr:hover {
+ background-color: var(--hover-color);
+}
+
+td {
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+footer {
+ text-align: center;
+}
+EOF
+}
+
+print_index_header() {
+ cat <<EOF
+<!DOCTYPE html>
+<html lang="$(escape "$MSG_LANGNAME")">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="$(escape "$MSG_INDEX_DESCRIPTION")" />
+ <link rel="icon" type="image/svg+xml" href="logo.svg" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <title>$(escape "$TITLE")"</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <img alt="$(escape "$MSG_LOGO_ALT_INDEX")" src="logo.svg" />
+ <h1 class="header-description">
+ $(escape "$TITLE")
+ </h1>
+ </div>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ $(escape "$MSG_NAME")
+ </th>
+ <th>
+ $(escape "$MSG_DESCRIPTION")
+ </th>
+ <th>
+ $(escape "$MSG_LAST_COMMIT")
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+EOF
+}
+
+print_index_row() {
+ repo="$(basename "$(realpath "${1%.git}")")"
+ description="$(cat "$1/description" 2>/dev/null ||:)"
+ last_commit_date="$(git -C "$1" log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' 2>/dev/null ||:)"
+ cat <<EOF
+ <tr>
+ <td>
+ <a href="$(escape "$repo")">
+ $(escape "$repo")
+ </a>
+ </td>
+ <td>
+ $(escape "$description")
+ </td>
+ <td>
+ $(escape "$last_commit_date")
+ </td>
+ </tr>
+EOF
+}
+
+print_index_footer() {
+ cat <<EOF
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ $(escape "$MSG_FOOTER_TEMPLATE")
+ </p>
+ </footer>
+ </body>
+</html>
+EOF
+}
+
+index_write() {
+ rm -f "$OUTDIR/index.html"
+ print_index_header >> "$OUTDIR/index.html"
+ for r in "$@"; do
+ print_index_row "$r" >> "$OUTDIR/index.html"
+ done
+ print_index_footer >> "$OUTDIR/index.html"
+
+ print_logo > "$OUTDIR/logo.svg"
+ print_style > "$OUTDIR/style.css"
+}
+
+print_repo_refs() {
+ repo="$1"
+ description="$2"
+ show_signature="$3"
+ cat <<EOF
+<!DOCTYPE html>
+<html lang="$(escape "$MSG_LANGNAME")">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="$(escape "$description")" />
+ <link rel="icon" type="image/svg+xml" href="logo.svg" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <link rel="alternate" type="application/atom+xml" href="commits.xml" title="$(escape "$repo") - $(escape "$MSG_COMMIT_FEED")" hreflang="$(escape "$MSG_LANGNAME")" />
+ <link rel="alternate" type="application/atom+xml" href="tags.xml" title="$(escape "$repo") - $(escape "$MSG_TAGS_FEED")" hreflang="$(escape "$MSG_LANGNAME")" />
+ <title>$(escape "$repo") - $(escape "$MSG_TITLE_REFS")</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <a href="../">
+ <img alt="$(escape "$MSG_LOGO_ALT_REPOSITORY")" class="logo" src="logo.svg" />
+ </a>
+ <div class="header-description">
+ <h1>
+ <a href="./">
+ $(escape "$repo")
+ </a>
+ </h1>
+ <h2>
+ $(escape "$description") </h2> <code> git clone $(escape "$CLONE_URL") </code> </div>
+ </div>
+ <nav>
+ <ul>
+ <li>
+ <a href="files.html">
+ $(escape "$MSG_NAV_FILES")
+ </a>
+ </li>
+ <li>
+ <a href="log.html">
+ $(escape "$MSG_NAV_LOG")
+ </a>
+ </li>
+ <li class="selected-nav-item">
+ <a href="refs.html">
+ $(escape "$MSG_NAV_REFS")
+ </a>
+ </li>
+ </ul>
+ </nav>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ $(escape "$MSG_THEAD_BRANCH")
+ </th>
+ <th>
+ $(escape "$MSG_THEAD_COMMITMSG")
+ </th>
+ <th>
+ $(escape "$MSG_THEAD_AUTHOR")
+ </th>
+ <th>
+ $(escape "$MSG_THEAD_DATE")
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+EOF
+
+ for branch in $(git branch --format '%(refname:lstrip=2)'); do
+ sha="$(git rev-parse "$branch")"
+ cat <<EOF
+ <tr>
+ <td>
+ <a href="log/$(escape "$branch").html">
+ $(escape "$branch")
+ </a>
+ </td>
+ <td>
+ <a href="commit/$(escape "$sha").html">
+ $(escape "$(git log -1 --format=%B "$sha")")
+ </a>
+ </td>
+ <td>
+ $(escape "$(git log -1 --format=%an "$sha")")
+ </td>
+ <td>
+ $(escape "$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' "$sha")")
+ </td>
+ </tr>
+EOF
+ done
+
+ cat <<EOF
+ </tbody>
+ </table>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ $(escape "$MSG_THEAD_TAG")
+ </th>
+ <th>
+ $(escape "$MSG_THEAD_AUTHOR")
+ </th>
+ <th>
+ $(escape "$MSG_THEAD_DATE")
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+EOF
+
+ for tag in $(git tag); do
+ if [ "$show_signature" = true ]; then
+ tarball_link="(<a href=\"tarballs/$(escape "$repo")-$(escape "$tag").tar.gz\">tarball</a>,
+ <a href=\"tarballs/$(escape "$repo")-$(escape "$tag").tar.gz.asc\">sig</a>)"
+ else
+ tarball_link="(<a href=\"tarballs/$(escape "$repo")-$(escape "$tag").tar.gz\">tarball</a>)"
+ fi
+ cat <<EOF
+ <tr>
+ <td>
+ <a href="tag/$(escape "$tag").html">
+ $(escape "$tag")
+ </a>
+ $tarball_link
+ </td>
+ <td>
+ $(escape "$(git log -1 --format=%an "$tag")")
+ </td>
+ <td>
+ $(escape "$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' "$tag")")
+ </td>
+ </tr>
+EOF
+ done
+
+ cat <<EOF
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ $(escape "$MSG_FOOTER_TEMPLATE")
+ </p>
+ </footer>
+ </body>
+</html>
+EOF
+}
+
+print_repo_commit_page() {
+ cat <<EOF
+<!DOCTYPE html>
+<html lang="$(escape "$MSG_LANGNAME")">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="$(escape "$description")" />
+ <link rel="icon" type="image/svg+xml" href="../logo.svg" />
+ <link rel="stylesheet" type="text/css" href="../style.css" />
+ <link rel="alternate" type="application/atom+xml" href="../commits.xml" title="$(escape "$repo") - $(escape "$MSG_COMMIT_FEED")" hreflang="$(escape "$MSG_LANGNAME")" />
+ <link rel="alternate" type="application/atom+xml" href="../tags.xml" title="$(escape "$repo") - $(escape "$MSG_TAGS_FEED")" hreflang="$(escape "$MSG_LANGNAME")" />
+ <title>$(escape "$repo") - FIXME put content here</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <a href="../../">
+ <img alt="$(escape "$MSG_LOGO_ALT_REPOSITORY")" class="logo" src="../logo.svg" />
+ </a>
+ <div class="header-description">
+ <h1>
+ <a href="../">
+ $(escape "$repo")
+ </a>
+ </h1>
+ <h2>
+ $(escape "$description")
+ </h2>
+ <code>
+ git clone $(escape "$CLONE_URL")
+ </code>
+ </div>
+ </div>
+ <nav>
+ <ul>
+ <li>
+ <a href="files.html">
+ $(escape "$MSG_NAV_FILES")
+ </a>
+ </li>
+ <li>
+ <a href="log.html">
+ $(escape "$MSG_NAV_LOG")
+ </a>
+ </li>
+ <li>
+ <a href="refs.html">
+ $(escape "$MSG_NAV_REFS")
+ </a>
+ </li>
+ </ul>
+ </nav>
+ <hr />
+ </header>
+ <main>
+ CODE HR
+ </main>
+ <footer>
+ <hr />
+ <p>
+ $(escape "$MSG_FOOTER_TEMPLATE")
+ </p>
+ </footer>
+ </body>
+</html>
+EOF
+}
+
+repo_tarballs_write() {
+ repo="$1"
+ mkdir -p "$OUTDIR/tarballs" "$CACHE_DIR/tarballs"
+ for branch in $(git branch --format '%(refname:lstrip=2)'); do
+ git archive --prefix "$repo-$branch/" "$branch" \
+ -o "$OUTDIR/tarballs/$repo-$branch.tar.xz"
+ done
+
+ for tag in $(git tag); do
+ TARBALL_PATH="tarballs/$repo-$tag.tar.gz"
+ if [ -e "$OUTDIR/$TARBALL_PATH" ]; then
+ continue
+ fi
+ if [ ! -e "$CACHE_DIR/$TARBALL_PATH" ]; then
+ git archive --prefix "$repo-$tag/" "$tag" \
+ -o "$CACHE_DIR/$TARBALL_PATH"
+ fi
+ cp "$CACHE_DIR/$TARBALL_PATH" "$OUTDIR/$TARBALL_PATH"
+ done
+
+ # FIXME: write signatures
+}
+
+repo_commits_write() {
+ mkdir -p "$OUTDIR/commit" "$CACHE_DIR/commit"
+ for branch in $(git branch --format '%(refname:lstrip=2)'); do
+ for commit in $(git rev-list "$branch"); do
+ COMMIT_PATH="commit/$commit.html"
+ if [ -e "$OUTDIR/$COMMIT_PATH" ]; then
+ # Assume all previous history exists, too
+ break
+ fi
+ if [ ! -e "CACHE_DIR/$COMMIT_PATH" ]; then
+ print_repo_commit_page "$repo" "$commit"
+ print_repo_commit_page "$repo" "$commit" \
+ > "$CACHE_DIR/$COMMIT_PATH"
+ fi
+ cp "$CACHE_DIR/$COMMIT_PATH" "$OUTDIR/$COMMIT_PATH"
+
+ # FIXME: dbg
+ return
+ done
+ done
+}
+
+repo_write() {
+ cd "$1"
+ repo="$(basename "$(realpath "${1%.git}")")"
+ description="$(cat "$1/description" 2>/dev/null ||:)"
+
+ # FIXME: hardcoded "true"
+ repo_tarballs_write "$repo"
+ print_repo_refs "$repo" "$description" true > "$OUTDIR/refs.html"
+
+ repo_commits_write "$repo"
+
+ print_logo > "$OUTDIR/logo.svg"
+ print_style > "$OUTDIR/style.css"
+ cd - > /dev/null
+}
+
+
+for flag in "$@"; do
+ case "$flag" in
+ --)
+ break
+ ;;
+ --help)
+ usage
+ help
+ exit
+ ;;
+ --version)
+ version
+ exit
+ ;;
+ *)
+ ;;
+ esac
+done
+
+INDEX=false
+TITLE="$MSG_DEFAULT_TITLE"
+OUTDIR=
+CLONE_URL=
+CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/gistatic"
+while getopts 'o:t:u:ihV' flag; do
+ case "$flag" in
+ i)
+ INDEX=true
+ ;;
+ t)
+ TITLE="$OPTARG"
+ ;;
+ o)
+ OUTDIR="$(realpath "$OPTARG")"
+ ;;
+ u)
+ CLONE_URL="$OPTARG"
+ ;;
+ h)
+ usage
+ help
+ exit
+ ;;
+ V)
+ version
+ exit
+ ;;
+ *)
+ usage >&2
+ exit 2
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+assert_arg() {
+ if [ -z "$1" ]; then
+ printf "$MSG_MISSING_CLIARG" "$2" >&2
+ exit 2
+ fi
+}
+
+assert_arg "$OUTDIR" '-o DIRECTORY'
+
+if [ "$INDEX" = false ]; then
+ assert_arg "$CLONE_URL" '-u CLONE_URL'
+elif [ -n "$CLONE_URL" ]; then
+ printf '%s\n' "$MSG_INCOMPATIBLE_OPTIONS" >&2
+ exit 2
+fi
+
+if [ -z "${1:-}" ]; then
+ printf '%s\n' "$MSG_MISSING_ARGS" >&2
+ exit 2
+fi
+
+mkdir -p "$OUTDIR"
+
+if [ "$INDEX" = true ]; then
+ index_write "$@"
+else
+ repo_write "$1"
+fi