aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2025-05-11 07:58:03 -0300
committerEuAndreh <eu@euandre.org>2025-05-11 07:58:03 -0300
commit04c91c40d548bb70062fcadeeeea37fd4319e066 (patch)
treee6c9c2d04de7544e5d3b0f2d48076c1da97a8975
parentsrc/gistatic.go: Add missing trailing newline to usage() output (diff)
downloadgistatic-04c91c40d548bb70062fcadeeeea37fd4319e066.tar.gz
gistatic-04c91c40d548bb70062fcadeeeea37fd4319e066.tar.xz
Finish branches.html and setup i18n of manpages and HTML strings
-rw-r--r--.gitignore6
-rw-r--r--Makefile43
-rw-r--r--deps.mk7
-rw-r--r--doc/gistatic.en.0.adoc7
-rw-r--r--doc/gistatic.pt.0.adoc7
-rwxr-xr-xmkdeps.sh10
-rw-r--r--po/doc/doc.pot39
-rw-r--r--po/doc/note.txt5
-rw-r--r--po/doc/po4a.cfg5
-rw-r--r--po/doc/pt.po40
-rw-r--r--po/gistatic/gistatic.pot86
-rw-r--r--po/gistatic/po4a.cfg3
-rw-r--r--po/gistatic/pt.po87
-rw-r--r--src/gistatic.go1064
-rw-r--r--tests/gistatic.go102
15 files changed, 1481 insertions, 30 deletions
diff --git a/.gitignore b/.gitignore
index d6ef39d..c48a832 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
-/src/version.go
+!/doc/*.adoc
+/doc/*
+/po/*/*.mo
+/src/meta.go
/*.bin
-/*.db
/src/*.a
/src/*.bin
/src/*cgo*
diff --git a/Makefile b/Makefile
index 072737b..23d9063 100644
--- a/Makefile
+++ b/Makefile
@@ -24,16 +24,22 @@ GOLDFLAGS = -L $(GOLIBDIR)
.SUFFIXES:
-.SUFFIXES: .go .a .bin .bin-check
+.SUFFIXES: .go .a .bin .bin-check .adoc .po .mo
.go.a:
go tool compile -I $(@D) $(GOCFLAGS) -o $@ -p $(*F) \
`find $< $$(if [ $(*F) != main ]; then \
- echo src/$(NAME).go src/version.go; fi) | uniq`
+ echo src/$(NAME).go src/meta.go; fi) | uniq`
.a.bin:
go tool link -L $(@D) $(GOLDFLAGS) -o $@ --extldflags '$(LDLIBS)' $<
+.adoc:
+ asciidoctor -b manpage -o $@ $<
+
+.po.mo:
+ msgfmt -c -o $@ $<
+
all:
@@ -46,19 +52,23 @@ mains.bin = $(mains.go:.go=.bin)
functional-tests/lib.a = $(functional-tests/lib.go:.go=.a)
fuzz-targets/lib.a = $(fuzz-targets/lib.go:.go=.a)
benchmarks/lib.a = $(benchmarks/lib.go:.go=.a)
+manpages.N = $(manpages.N.adoc:.adoc=)
+sources.mo = $(sources.po:.po=.mo)
sources = \
src/$(NAME).go \
- src/version.go \
+ src/meta.go \
src/main.go \
derived-assets = \
- src/version.go \
+ src/meta.go \
$(libs.a) \
$(mains.a) \
$(mains.bin) \
$(NAME).bin \
+ $(manpages.N) \
+ $(sources.mo) \
side-assets = \
tests/fuzz/corpus/ \
@@ -72,15 +82,18 @@ all: $(derived-assets)
$(libs.a): Makefile deps.mk
-$(libs.a): src/$(NAME).go src/version.go
+$(libs.a): src/$(NAME).go src/meta.go
$(fuzz-targets/lib.a):
go tool compile $(GOCFLAGS) -o $@ -p $(NAME) -d=libfuzzer \
- $*.go src/$(NAME).go src/version.go
+ $*.go src/$(NAME).go src/meta.go
-src/version.go: Makefile
- echo 'package $(NAME); const Version = "$(VERSION)"' > $@
+src/meta.go: Makefile
+ echo 'package $(NAME)' > $@
+ echo 'const Version = "$(VERSION)"' >> $@
+ echo 'const Name = "$(NAME)"' >> $@
+ echo 'const LOCALEDIR = "$(LOCALEDIR)"' >> $@
$(NAME).bin: src/main.bin
ln -fs src/main.bin $@
@@ -137,6 +150,17 @@ bench: $(benchmarks/main.bin-check)
+i18n-doc:
+ po4a po/doc/po4a.cfg
+
+i18n-code:
+ gotext src/$(NAME).go > po/$(NAME)/$(NAME).pot
+ po4a po/$(NAME)/po4a.cfg
+
+i18n: i18n-doc i18n-code
+
+
+
## Remove *all* derived artifacts produced during the build.
## A dedicated test asserts that this is always true.
clean:
@@ -154,6 +178,7 @@ install: all
cp $(NAME).bin '$(DESTDIR)$(BINDIR)'/$(NAME)
cp src/$(NAME).a '$(DESTDIR)$(GOLIBDIR)'
cp $(sources) '$(DESTDIR)$(SRCDIR)'
+ mandir install '$(DESTDIR)$(MANDIR)' $(manpages.N)
## Uninstalls from $(DESTDIR)$(PREFIX). This is a perfect mirror
## of the "install" target, and removes *all* that was installed.
@@ -164,6 +189,8 @@ uninstall:
'$(DESTDIR)$(GOLIBDIR)'/$(NAME).a \
'$(DESTDIR)$(SRCDIR)' \
+ mandir uninstall '$(DESTDIR)$(MANDIR)' $(manpages.N)
+
ALWAYS:
diff --git a/deps.mk b/deps.mk
index 330faad..02fb1b7 100644
--- a/deps.mk
+++ b/deps.mk
@@ -12,6 +12,13 @@ mains.go = \
tests/fuzz/internal-queue/main.go \
tests/main.go \
+manpages.N.adoc = \
+ doc/gistatic.en.0.adoc \
+ doc/gistatic.pt.0.adoc \
+
+sources.po = \
+ po/gistatic/pt.po \
+
functional-tests/lib.go = \
tests/functional/empty-repository/gistatic.go \
diff --git a/doc/gistatic.en.0.adoc b/doc/gistatic.en.0.adoc
new file mode 100644
index 0000000..85f48ad
--- /dev/null
+++ b/doc/gistatic.en.0.adoc
@@ -0,0 +1,7 @@
+= gistatic(0)
+
+== NAME
+
+gistatic - .
+
+Something, something.
diff --git a/doc/gistatic.pt.0.adoc b/doc/gistatic.pt.0.adoc
new file mode 100644
index 0000000..85f48ad
--- /dev/null
+++ b/doc/gistatic.pt.0.adoc
@@ -0,0 +1,7 @@
+= gistatic(0)
+
+== NAME
+
+gistatic - .
+
+Something, something.
diff --git a/mkdeps.sh b/mkdeps.sh
index e8da8a4..ee497ed 100755
--- a/mkdeps.sh
+++ b/mkdeps.sh
@@ -13,8 +13,18 @@ mains() {
find src tests -name '*.go' | grep '/main\.go$'
}
+docs() {
+ find doc/*.adoc
+}
+
+pos() {
+ find po/ -name '*.po' | grep -v '^po/doc/'
+}
+
libs | varlist 'libs.go'
mains | varlist 'mains.go'
+docs | varlist 'manpages.N.adoc'
+pos | varlist 'sources.po'
find tests/functional/*/*.go -not -name main.go | varlist 'functional-tests/lib.go'
find tests/functional/*/main.go | varlist 'functional-tests/main.go'
diff --git a/po/doc/doc.pot b/po/doc/doc.pot
new file mode 100644
index 0000000..7a24056
--- /dev/null
+++ b/po/doc/doc.pot
@@ -0,0 +1,39 @@
+# SOME DESCRIPTIVE TITLE
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2025-05-10 17:17-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. type: Title =
+#: doc/gistatic.en.0.adoc:1
+#, no-wrap
+msgid "gistatic(0)"
+msgstr ""
+
+#. type: Title ==
+#: doc/gistatic.en.0.adoc:3
+#, no-wrap
+msgid "NAME"
+msgstr ""
+
+#. type: Plain text
+#: doc/gistatic.en.0.adoc:6
+msgid "gistatic - ."
+msgstr ""
+
+#. type: Plain text
+#: doc/gistatic.en.0.adoc:7
+msgid "Something, something."
+msgstr ""
diff --git a/po/doc/note.txt b/po/doc/note.txt
new file mode 100644
index 0000000..45279a4
--- /dev/null
+++ b/po/doc/note.txt
@@ -0,0 +1,5 @@
+PO4A-HEADER: mode=eof
+
+
+
+// Generated from po4a(1).
diff --git a/po/doc/po4a.cfg b/po/doc/po4a.cfg
new file mode 100644
index 0000000..f47447c
--- /dev/null
+++ b/po/doc/po4a.cfg
@@ -0,0 +1,5 @@
+[options] --keep 0 --master-charset UTF-8 --localized-charset UTF-8 --addendum-charset UTF-8
+
+[po_directory] po/doc/
+
+[type: asciidoc] doc/gistatic.en.0.adoc $lang:doc/gistatic.$lang.0.adoc
diff --git a/po/doc/pt.po b/po/doc/pt.po
new file mode 100644
index 0000000..4f18856
--- /dev/null
+++ b/po/doc/pt.po
@@ -0,0 +1,40 @@
+# Portuguese translations for package.
+# Copyright (C) 2025 THE 'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the package.
+# Automatically generated, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: EMAIL\n"
+"POT-Creation-Date: 2025-05-10 17:17-0300\n"
+"PO-Revision-Date: 2025-05-10 17:09-0300\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. type: Title =
+#: doc/gistatic.en.0.adoc:1
+#, no-wrap
+msgid "gistatic(0)"
+msgstr ""
+
+#. type: Title ==
+#: doc/gistatic.en.0.adoc:3
+#, no-wrap
+msgid "NAME"
+msgstr ""
+
+#. type: Plain text
+#: doc/gistatic.en.0.adoc:6
+msgid "gistatic - ."
+msgstr ""
+
+#. type: Plain text
+#: doc/gistatic.en.0.adoc:7
+msgid "Something, something."
+msgstr ""
diff --git a/po/gistatic/gistatic.pot b/po/gistatic/gistatic.pot
new file mode 100644
index 0000000..4bb37b7
--- /dev/null
+++ b/po/gistatic/gistatic.pot
@@ -0,0 +1,86 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr "Project-Id-Version: \n"
+ "Report-Msgid-Bugs-To: EMAIL\n"
+ "POT-Creation-Date: 2025-05-11 07:56-0300\n"
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+ "Language-Team: LANGUAGE <LL@li.org>\n"
+ "Language: \n"
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=CHARSET\n"
+ "Content-Transfer-Encoding: 8bit\n"
+
+#: src/gistatic.go:917 src/gistatic.go:997
+msgid "Outlined icon of 3 melting ice cubes"
+msgstr ""
+
+#: src/gistatic.go:920 src/gistatic.go:1000
+msgid "en"
+msgstr ""
+
+#: src/gistatic.go:929
+msgid "Date"
+msgstr ""
+
+#: src/gistatic.go:1002
+msgid "Repositories"
+msgstr ""
+
+#: src/gistatic.go:1004
+msgid "Description"
+msgstr ""
+
+#: src/gistatic.go:922
+msgid "tags"
+msgstr ""
+
+#: src/gistatic.go:924
+msgid "commit"
+msgstr ""
+
+#: src/gistatic.go:1003
+msgid "Name"
+msgstr ""
+
+#: src/gistatic.go:1005
+msgid "Last commit"
+msgstr ""
+
+#: src/gistatic.go:921
+msgid "branches"
+msgstr ""
+
+#: src/gistatic.go:926
+msgid "Tag"
+msgstr ""
+
+#: src/gistatic.go:927
+msgid "Commit message"
+msgstr ""
+
+#: src/gistatic.go:928
+msgid "Author"
+msgstr ""
+
+#: src/gistatic.go:1001
+msgid "Listing of repositories"
+msgstr ""
+
+#: src/gistatic.go:923
+msgid "log"
+msgstr ""
+
+#: src/gistatic.go:925
+msgid "Branch"
+msgstr ""
+
+#: src/gistatic.go:1006
+msgid "Generated with"
+msgstr ""
+
diff --git a/po/gistatic/po4a.cfg b/po/gistatic/po4a.cfg
new file mode 100644
index 0000000..5c2eb8f
--- /dev/null
+++ b/po/gistatic/po4a.cfg
@@ -0,0 +1,3 @@
+[options] --keep 0 --master-charset UTF-8 --localized-charset UTF-8 --addendum-charset UTF-8
+
+[po_directory] po/gistatic/
diff --git a/po/gistatic/pt.po b/po/gistatic/pt.po
new file mode 100644
index 0000000..5a3e054
--- /dev/null
+++ b/po/gistatic/pt.po
@@ -0,0 +1,87 @@
+# Portuguese translations for package.
+# Copyright (C) 2025 THE 'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the package.
+# Automatically generated, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: EMAIL\n"
+"POT-Creation-Date: 2025-05-11 07:56-0300\n"
+"PO-Revision-Date: 2025-05-10 17:50-0300\n"
+"Last-Translator: EuAndreh <eu@euandre.org>\n"
+"Language-Team: none\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.5\n"
+
+#: src/gistatic.go:917 src/gistatic.go:997
+msgid "Outlined icon of 3 melting ice cubes"
+msgstr ""
+
+#: src/gistatic.go:920 src/gistatic.go:1000
+msgid "en"
+msgstr "pt"
+
+#: src/gistatic.go:929
+msgid "Date"
+msgstr ""
+
+#: src/gistatic.go:1002
+msgid "Repositories"
+msgstr "Repositórios"
+
+#: src/gistatic.go:1004
+msgid "Description"
+msgstr ""
+
+#: src/gistatic.go:922
+msgid "tags"
+msgstr ""
+
+#: src/gistatic.go:924
+msgid "commit"
+msgstr ""
+
+#: src/gistatic.go:1003
+msgid "Name"
+msgstr ""
+
+#: src/gistatic.go:1005
+msgid "Last commit"
+msgstr ""
+
+#: src/gistatic.go:921
+msgid "branches"
+msgstr ""
+
+#: src/gistatic.go:926
+msgid "Tag"
+msgstr ""
+
+#: src/gistatic.go:927
+msgid "Commit message"
+msgstr ""
+
+#: src/gistatic.go:928
+msgid "Author"
+msgstr ""
+
+#: src/gistatic.go:1001
+msgid "Listing of repositories"
+msgstr "Lista de repositórios"
+
+#: src/gistatic.go:923
+msgid "log"
+msgstr ""
+
+#: src/gistatic.go:925
+msgid "Branch"
+msgstr ""
+
+#: src/gistatic.go:1006
+msgid "Generated with"
+msgstr ""
diff --git a/src/gistatic.go b/src/gistatic.go
index fdbd2c4..3a56db2 100644
--- a/src/gistatic.go
+++ b/src/gistatic.go
@@ -3,25 +3,1055 @@ package gistatic
import (
"flag"
"fmt"
+ "html/template"
"io"
"os"
+ "os/exec"
+ "path"
+ "slices"
+ "strings"
g "gobang"
+ gt "gotext"
)
+const (
+ dateFmt = "--date=format:%Y-%m-%dT%H:%M"
+)
+
+var (
+ fn_wd = os.Getwd
+ fn_mkdirp = func(path string) error {
+ return os.MkdirAll(path, 0777)
+ }
+ fn_writeFile = func(path string, content string) error {
+ return os.WriteFile(path, []byte(content), 0644)
+ }
+
+ logoSVGUncolored = g.Heredoc(`
+ <?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 fill="$color" d="M 0 8 L 1 8 L 1 9 L 0 9 L 0 8 Z" />
+ <path fill="$color" d="M 0 13 L 1 13 L 1 14 L 0 14 L 0 13 Z" />
+ <path fill="$color" d="M 1 8 L 2 8 L 2 9 L 1 9 L 1 8 Z" />
+ <path fill="$color" d="M 1 13 L 2 13 L 2 14 L 1 14 L 1 13 Z" />
+ <path fill="$color" d="M 2 8 L 3 8 L 3 9 L 2 9 L 2 8 Z" />
+ <path fill="$color" d="M 2 13 L 3 13 L 3 14 L 2 14 L 2 13 Z" />
+ <path fill="$color" d="M 3 8 L 4 8 L 4 9 L 3 9 L 3 8 Z" />
+ <path fill="$color" d="M 3 13 L 4 13 L 4 14 L 3 14 L 3 13 Z" />
+ <path fill="$color" d="M 4 7 L 5 7 L 5 8 L 4 8 L 4 7 Z" />
+ <path fill="$color" d="M 4 8 L 5 8 L 5 9 L 4 9 L 4 8 Z" />
+ <path fill="$color" d="M 4 13 L 5 13 L 5 14 L 4 14 L 4 13 Z" />
+ <path fill="$color" d="M 5 6 L 6 6 L 6 7 L 5 7 L 5 6 Z" />
+ <path fill="$color" d="M 5 7 L 6 7 L 6 8 L 5 8 L 5 7 Z" />
+ <path fill="$color" d="M 5 13 L 6 13 L 6 14 L 5 14 L 5 13 Z" />
+ <path fill="$color" d="M 6 5 L 7 5 L 7 6 L 6 6 L 6 5 Z" />
+ <path fill="$color" d="M 6 6 L 7 6 L 7 7 L 6 7 L 6 6 Z" />
+ <path fill="$color" d="M 6 14 L 7 14 L 7 15 L 6 15 L 6 14 Z" />
+ <path fill="$color" d="M 7 1 L 8 1 L 8 2 L 7 2 L 7 1 Z" />
+ <path fill="$color" d="M 7 14 L 8 14 L 8 15 L 7 15 L 7 14 Z" />
+ <path fill="$color" d="M 7 15 L 8 15 L 8 16 L 7 16 L 7 15 Z" />
+ <path fill="$color" d="M 7 2 L 8 2 L 8 3 L 7 3 L 7 2 Z" />
+ <path fill="$color" d="M 7 3 L 8 3 L 8 4 L 7 4 L 7 3 Z" />
+ <path fill="$color" d="M 7 4 L 8 4 L 8 5 L 7 5 L 7 4 Z" />
+ <path fill="$color" d="M 7 5 L 8 5 L 8 6 L 7 6 L 7 5 Z" />
+ <path fill="$color" d="M 8 1 L 9 1 L 9 2 L 8 2 L 8 1 Z" />
+ <path fill="$color" d="M 8 15 L 9 15 L 9 16 L 8 16 L 8 15 Z" />
+ <path fill="$color" d="M 9 1 L 10 1 L 10 2 L 9 2 L 9 1 Z" />
+ <path fill="$color" d="M 9 2 L 10 2 L 10 3 L 9 3 L 9 2 Z" />
+ <path fill="$color" d="M 9 6 L 10 6 L 10 7 L 9 7 L 9 6 Z" />
+ <path fill="$color" d="M 9 15 L 10 15 L 10 16 L 9 16 L 9 15 Z" />
+ <path fill="$color" d="M 10 2 L 11 2 L 11 3 L 10 3 L 10 2 Z" />
+ <path fill="$color" d="M 10 3 L 11 3 L 11 4 L 10 4 L 10 3 Z" />
+ <path fill="$color" d="M 10 4 L 11 4 L 11 5 L 10 5 L 10 4 Z" />
+ <path fill="$color" d="M 10 5 L 11 5 L 11 6 L 10 6 L 10 5 Z" />
+ <path fill="$color" d="M 10 6 L 11 6 L 11 7 L 10 7 L 10 6 Z" />
+ <path fill="$color" d="M 11 6 L 12 6 L 12 7 L 11 7 L 11 6 Z" />
+ <path fill="$color" d="M 11 8 L 12 8 L 12 9 L 11 9 L 11 8 Z" />
+ <path fill="$color" d="M 10 15 L 11 15 L 11 16 L 10 16 L 10 15 Z" />
+ <path fill="$color" d="M 11 10 L 12 10 L 12 11 L 11 11 L 11 10 Z" />
+ <path fill="$color" d="M 11 12 L 12 12 L 12 13 L 11 13 L 11 12 Z" />
+ <path fill="$color" d="M 11 14 L 12 14 L 12 15 L 11 15 L 11 14 Z" />
+ <path fill="$color" d="M 11 15 L 12 15 L 12 16 L 11 16 L 11 15 Z" />
+ <path fill="$color" d="M 12 6 L 13 6 L 13 7 L 12 7 L 12 6 Z" />
+ <path fill="$color" d="M 12 8 L 13 8 L 13 9 L 12 9 L 12 8 Z" />
+ <path fill="$color" d="M 12 10 L 13 10 L 13 11 L 12 11 L 12 10 Z" />
+ <path fill="$color" d="M 12 12 L 13 12 L 13 13 L 12 13 L 12 12 Z" />
+ <path fill="$color" d="M 12 14 L 13 14 L 13 15 L 12 15 L 12 14 Z" />
+ <path fill="$color" d="M 13 6 L 14 6 L 14 7 L 13 7 L 13 6 Z" />
+ <path fill="$color" d="M 13 8 L 14 8 L 14 9 L 13 9 L 13 8 Z" />
+ <path fill="$color" d="M 13 10 L 14 10 L 14 11 L 13 11 L 13 10 Z" />
+ <path fill="$color" d="M 13 12 L 14 12 L 14 13 L 13 13 L 13 12 Z" />
+ <path fill="$color" d="M 13 13 L 14 13 L 14 14 L 13 14 L 13 13 Z" />
+ <path fill="$color" d="M 13 14 L 14 14 L 14 15 L 13 15 L 13 14 Z" />
+ <path fill="$color" d="M 14 7 L 15 7 L 15 8 L 14 8 L 14 7 Z" />
+ <path fill="$color" d="M 14 8 L 15 8 L 15 9 L 14 9 L 14 8 Z" />
+ <path fill="$color" d="M 14 9 L 15 9 L 15 10 L 14 10 L 14 9 Z" />
+ <path fill="$color" d="M 14 10 L 15 10 L 15 11 L 14 11 L 14 10 Z" />
+ <path fill="$color" d="M 14 11 L 15 11 L 15 12 L 14 12 L 14 11 Z" />
+ <path fill="$color" d="M 14 12 L 15 12 L 15 13 L 14 13 L 14 12 Z" />
+ </svg>
+ `)
+
+ styleCSS = g.Heredoc(`
+ :root {
+ --color: black;
+ --background-color: white;
+ --background-contrast-color: hsl(0, 0%, 98%);
+ --hover-color: hsl(0, 0%, 93%);
+ --nav-color: hsl(0, 0%, 87%);
+ --selected-color: hsl(0, 0%, 80%);
+ --diff-added-color: hsl(120, 100%, 23%);
+ --diff-removed-color: hsl(0, 100%, 47%);
+ }
+
+ @media(prefers-color-scheme: dark) {
+ :root {
+ --color: white;
+ --background-color: black;
+ --background-contrast-color: hsl(0, 0%, 2%);
+ --hover-color: hsl(0, 0%, 7%);
+ --nav-color: hsl(0, 0%, 13%);
+ --selected-color: hsl(0, 0%, 20%);
+ }
+
+ 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;
+ }
+
+
+ /* commit page */
+
+ .diff-added, .diff-removed {
+ text-decoration: none;
+ }
+
+ .diff-added:target, .diff-removed:target {
+ background-color: var(--selected-color);
+ }
+
+ .diff-added, .diff-added:visited {
+ color: var(--diff-added-color);
+ }
+
+ .diff-removed, .diff-removed:visited {
+ color: var(--diff-removed-color);
+ }
+
+
+ /* log page */
+
+ .log-commit-box {
+ padding: 1em;
+ margin: 1em;
+ background-color: var(--background-contrast-color);
+ }
+
+ .log-commit-tag {
+ padding: 2px;
+ border: 1px solid;
+ color: var(--color);
+ }
+
+ .log-head-highlight {
+ background-color: #ff8888; /* FIXME: hsl + dark-mode */
+ }
+
+ .log-branch-highlight {
+ background-color: #88ff88; /* FIXME: hsl + dark-mode */
+ }
+
+ .log-tag-highlight {
+ background-color: #ffff88; /* FIXME: hsl + dark-mode */
+ }
+
+ .pre-wrapping {
+ overflow: auto;
+ margin: 1em;
+ }
+
+ .log-notes {
+ /* FIXME: yellow box goes until the end of the screen */
+ padding: 1em;
+ background-color: #ffd; /* FIXME: hsl + dark-mode */
+ }
+
+ .log-pagination {
+ text-align: center;
+ margin: 2em;
+ }
+
+
+ footer {
+ text-align: center;
+ }
+ `)
+
+ listingHTML = g.Must(template.New("").Parse(g.Heredoc(`
+ <!DOCTYPE html>
+ <html lang="{{.language}}">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="{{.pageDescription}}" />
+ <link rel="icon" type="image/svg+xml" href="img/favicon.svg" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <title>{{.title}}</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <picture>
+ <source srcset="img/logo/dark.svg" media="(prefers-color-scheme: dark)" />
+ <img src="img/logo/light.svg" alt="{{.logoAlt}}" />
+ </picture>
+ <h1 class="header-description">
+ {{.title}}
+ </h1>
+ </div>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ {{.name}}
+ </th>
+ <th>
+ {{.description}}
+ </th>
+ <th>
+ {{.lastCommit}}
+ </th>
+ </tr>
+ </thead>
+ <tbody>{{range .repositories}}
+ <tr>
+ <td>
+ <a href="{{.name}}">
+ {{.name}}
+ </a>
+ </td>
+ <td>
+ {{.description}}
+ </td>
+ <td>
+ <time datetime="{{.lastCommitDate}}">{{.lastCommitDate}}</time>
+ </td>
+ </tr>{{end}}
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ {{.generatedWith}} <a href="https://euandre.org/s/gistatic/">gistatic</a>
+ </p>
+ </footer>
+ </body>
+ </html>
+ `)))
+
+ repoindexHTML = g.Must(template.New("").Parse(g.Heredoc(`
+ <!DOCTYPE html>
+ <html lang="{{.language}}">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="{{.pageDescription}}" />
+ <link rel="icon" type="image/svg+xml" href="img/favicon.svg" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <title>{{.title}}</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <picture>
+ <source srcset="img/logo/dark.svg" media="(prefers-color-scheme: dark)" />
+ <img src="img/logo/light.svg" alt="{{.logoAlt}}" />
+ </picture>
+ <h1 class="header-description">
+ {{.title}}
+ </h1>
+ </div>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ {{.name}}
+ </th>
+ <th>
+ {{.description}}
+ </th>
+ <th>
+ {{.lastCommit}}
+ </th>
+ </tr>
+ </thead>
+ <tbody>{{range .Repositories}}
+ <tr>
+ <td>
+ <a href="{{.name}}">
+ {{.name}}
+ </a>
+ </td>
+ <td>
+ {{.description}}
+ </td>
+ <td>
+ <time datetime="{{.lastCommitDate}}">{{.lastCommitDate}}</time>
+ </td>
+ </tr>{{end}}
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ {{.generatedWith}} <a href="https://euandre.org/s/gistatic/">gistatic</a>
+ </p>
+ </footer>
+ </body>
+ </html>
+ `)))
+
+ branchesHTML = g.Must(template.New("").Parse(g.Heredoc(`
+ <!DOCTYPE html>
+ <html lang="{{.language}}">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="{{.metadata.description}}" />
+ <link rel="icon" type="image/svg+xml" href="../img/favicon.svg" />
+ <link rel="stylesheet" type="text/css" href="../style.css" />
+ <title>{{.branches}} · {{.metadata.name}}</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <a href="../">
+ <picture>
+ <source srcset="../img/logo/dark.svg" media="(prefers-color-scheme: dark)" />
+ <img src="../img/logo/light.svg" alt="{{.logoAlt}}" />
+ </picture>
+ </a>
+ <div class="header-description">
+ <h1>
+ <a href="./">
+ {{.metadata.name}}
+ </a>
+ </h1>
+ <h2>
+ {{.metadata.description}}
+ </h2>{{if .cloneURL}}
+ <code>
+ git clone {{.cloneURL}}/{{.metadata.name}}/
+ </code>{{end}}
+ </div>
+ </div>
+ <nav>
+ <ul>
+ <li class="selected-nav-item">
+ <a href="branches.html">
+ {{.branches}}
+ </a>
+ </li>
+ <li>
+ <a href="tags.html">
+ {{.tags}}
+ </a>
+ </li>
+ <li>
+ <a href="{{.log}}/">
+ {{.log}}
+ </a>
+ </li>
+ </ul>
+ </nav>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ {{.branch}}
+ </th>
+ <th>
+ {{.commitMessage}}
+ </th>
+ <th>
+ {{.author}}
+ </th>
+ <th>
+ {{.date}}
+ </th>
+ </tr>
+ </thead>
+ <tbody>{{range .data.branches}}
+ <tr>
+ <td>
+ <a href="{{$.log}}/{{.name}}.html">
+ {{.name}}
+ </a>
+ </td>
+ <td>
+ <a href="{{$.commit}}/{{.sha}}.html">
+ {{.messageSummary}}
+ </a>
+ </td>
+ <td>
+ {{.author}}
+ </td>
+ <td>
+ <time datetime="{{.date}}">{{.date}}</time>
+ </td>
+ </tr>{{end}}
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ {{.generatedWith}} <a href="https://euandre.org/s/gistatic/">gistatic</a>
+ </p>
+ </footer>
+ </body>
+ </html>
+ `)))
+
+ tagsHTML = g.Must(template.New("").Parse(g.Heredoc(`
+ <!DOCTYPE html>
+ <html lang="{{.language}}">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="{{.pageDescription}}" />
+ <link rel="icon" type="image/svg+xml" href="img/favicon.svg" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <title>{{.title}}</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <picture>
+ <source srcset="img/logo/dark.svg" media="(prefers-color-scheme: dark)" />
+ <img src="img/logo/light.svg" alt="{{.logoAlt}}" />
+ </picture>
+ <h1 class="header-description">
+ {{.title}}
+ </h1>
+ </div>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ {{.name}}
+ </th>
+ <th>
+ {{.description}}
+ </th>
+ <th>
+ {{.lastCommit}}
+ </th>
+ </tr>
+ </thead>
+ <tbody>{{range .Repositories}}
+ <tr>
+ <td>
+ <a href="{{.name}}">
+ {{.name}}
+ </a>
+ </td>
+ <td>
+ {{.description}}
+ </td>
+ <td>
+ <time datetime="{{.lastCommitDate}}">{{.lastCommitDate}}</time>
+ </td>
+ </tr>{{end}}
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ {{.generatedWith}} <a href="https://euandre.org/s/gistatic/">gistatic</a>
+ </p>
+ </footer>
+ </body>
+ </html>
+ `)))
+
+ logHTML = g.Must(template.New("").Parse(g.Heredoc(`
+ <!DOCTYPE html>
+ <html lang="{{.language}}">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="{{.pageDescription}}" />
+ <link rel="icon" type="image/svg+xml" href="img/favicon.svg" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <title>{{.title}}</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <picture>
+ <source srcset="img/logo/dark.svg" media="(prefers-color-scheme: dark)" />
+ <img src="img/logo/light.svg" alt="{{.logoAlt}}" />
+ </picture>
+ <h1 class="header-description">
+ {{.title}}
+ </h1>
+ </div>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ {{.name}}
+ </th>
+ <th>
+ {{.description}}
+ </th>
+ <th>
+ {{.lastCommit}}
+ </th>
+ </tr>
+ </thead>
+ <tbody>{{range .Repositories}}
+ <tr>
+ <td>
+ <a href="{{.name}}">
+ {{.name}}
+ </a>
+ </td>
+ <td>
+ {{.description}}
+ </td>
+ <td>
+ <time datetime="{{.lastCommitDate}}">{{.lastCommitDate}}</time>
+ </td>
+ </tr>{{end}}
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ {{.generatedWith}} <a href="https://euandre.org/s/gistatic/">gistatic</a>
+ </p>
+ </footer>
+ </body>
+ </html>
+ `)))
+
+ commitHTML = g.Must(template.New("").Parse(g.Heredoc(`
+ <!DOCTYPE html>
+ <html lang="{{.language}}">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="{{.pageDescription}}" />
+ <link rel="icon" type="image/svg+xml" href="img/favicon.svg" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <title>{{.title}}</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <picture>
+ <source srcset="img/logo/dark.svg" media="(prefers-color-scheme: dark)" />
+ <img src="img/logo/light.svg" alt="{{.logoAlt}}" />
+ </picture>
+ <h1 class="header-description">
+ {{.title}}
+ </h1>
+ </div>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ {{.name}}
+ </th>
+ <th>
+ {{.description}}
+ </th>
+ <th>
+ {{.lastCommit}}
+ </th>
+ </tr>
+ </thead>
+ <tbody>{{range .Repositories}}
+ <tr>
+ <td>
+ <a href="{{.name}}">
+ {{.name}}
+ </a>
+ </td>
+ <td>
+ {{.description}}
+ </td>
+ <td>
+ <time datetime="{{.lastCommitDate}}">{{.lastCommitDate}}</time>
+ </td>
+ </tr>{{end}}
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ {{.generatedWith}} <a href="https://euandre.org/s/gistatic/">gistatic</a>
+ </p>
+ </footer>
+ </body>
+ </html>
+ `)))
+
+ sourceHTML = g.Must(template.New("").Parse(g.Heredoc(`
+ <!DOCTYPE html>
+ <html lang="{{.language}}">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="description" content="{{.pageDescription}}" />
+ <link rel="icon" type="image/svg+xml" href="img/favicon.svg" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <title>{{.title}}</title>
+ </head>
+ <body>
+ <header>
+ <div class="header-horizontal-grouping">
+ <picture>
+ <source srcset="img/logo/dark.svg" media="(prefers-color-scheme: dark)" />
+ <img src="img/logo/light.svg" alt="{{.logoAlt}}" />
+ </picture>
+ <h1 class="header-description">
+ {{.title}}
+ </h1>
+ </div>
+ <hr />
+ </header>
+ <main>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ {{.name}}
+ </th>
+ <th>
+ {{.description}}
+ </th>
+ <th>
+ {{.lastCommit}}
+ </th>
+ </tr>
+ </thead>
+ <tbody>{{range .repositories}}
+ <tr>
+ <td>
+ <a href="{{.name}}">
+ {{.name}}
+ </a>
+ </td>
+ <td>
+ {{.description}}
+ </td>
+ <td>
+ <time datetime="{{.lastCommitDate}}">{{.lastCommitDate}}</time>
+ </td>
+ </tr>{{end}}
+ </tbody>
+ </table>
+ </main>
+ <footer>
+ <hr />
+ <p>
+ {{.generatedWith}} <a href="https://euandre.org/s/gistatic/">gistatic</a>
+ </p>
+ </footer>
+ </body>
+ </html>
+ `)))
+
+)
+
+
+
+type repositoryTemplateT struct{
+ template *template.Template
+ path string
+ keys map[string]interface{}
+}
+
type argsT struct{
- outputPath string
- cloneUrl string
- allArgs []string
+ allArgs []string
+ subArgs []string
+ cloneURL string
+ outdir string
}
+
+func logoSVG(color string) string {
+ return strings.ReplaceAll(logoSVGUncolored, "$color", color)
+}
+
+func generateAssets(outdir string) error {
+ err := fn_mkdirp(outdir + "/img/logo")
+ if err != nil {
+ return err
+ }
+
+ err = fn_writeFile(outdir + "/img/favicon.svg", logoSVG("black"))
+ if err != nil {
+ return err
+ }
+
+ err = fn_writeFile(outdir + "/img/logo/light.svg", logoSVG("black"))
+ if err != nil {
+ return err
+ }
+
+ err = fn_writeFile(outdir + "/img/logo/dark.svg", logoSVG("white"))
+ if err != nil {
+ return err
+ }
+
+ err = fn_writeFile(outdir + "/style.css", styleCSS)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func writePage(
+ dir string,
+ t *template.Template,
+ filename string,
+ keys map[string]interface{},
+) {
+ fullpath := dir + "/" + filename
+ s := strings.Builder{}
+ g.PanicIf(fn_mkdirp(path.Dir(fullpath)))
+ g.PanicIf(t.Execute(&s, keys))
+ g.PanicIf(fn_writeFile(fullpath, s.String()))
+}
+
+func cmd(stderr io.Writer, name string, args ...string) (string, error) {
+ command := exec.Command(name, args...)
+ command.Stderr = stderr
+ outBytes, err := command.Output()
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(string(outBytes)), nil
+}
+
+func branchesData(
+ gitcmd func(...string) (string, error),
+) ([]map[string]interface{}, error) {
+ branches := []map[string]interface{}{}
+ names, err := gitcmd("branch", "--format", "%(refname:lstrip=2)")
+ if err != nil {
+ return nil, err
+ }
+
+ for _, name := range strings.Split(names, "\n") {
+ sha, err := gitcmd("rev-parse", name)
+ if err != nil {
+ return nil, err
+ }
+
+ messageSummary, err := gitcmd("log", "-1", "--format=%s", sha)
+ if err != nil {
+ return nil, err
+ }
+
+ author, err := gitcmd("log", "-1", "--format=%an", sha)
+ if err != nil {
+ return nil, err
+ }
+
+ date, err := gitcmd(
+ "log", "-1", "--format=%cd", dateFmt, sha,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ branches = append(branches, map[string]interface{}{
+ "name": name,
+ "sha": sha,
+ "messageSummary": messageSummary,
+ "author": author,
+ "date": date,
+ })
+ }
+
+ return branches, nil
+}
+
+func generateRepository(
+ args argsT,
+ path string,
+ stderr io.Writer,
+ metadata map[string]interface{},
+) error {
+ gitcmd := func(args ...string) (string, error) {
+ return cmd(
+ stderr,
+ "git",
+ slices.Concat(
+ []string{"-C", path},
+ args,
+ )...,
+ )
+ }
+
+ branches, err := branchesData(gitcmd)
+ if err != nil {
+ return err
+ }
+
+ dir := args.outdir + "/" + (metadata["name"].(string))
+ keys := map[string]interface{}{
+ "metadata": metadata,
+ "cloneURL": args.cloneURL,
+ "logoAlt": gt.Gettext(
+ "Outlined icon of 3 melting ice cubes",
+ ),
+ "language": gt.Gettext("en"),
+ "branches": gt.Gettext("branches"),
+ "tags": gt.Gettext("tags"),
+ "log": gt.Gettext("log"),
+ "commit": gt.Gettext("commit"),
+ "branch": gt.Gettext("Branch"),
+ "tag": gt.Gettext("Tag"),
+ "commitMessage": gt.Gettext("Commit message"),
+ "author": gt.Gettext("Author"),
+ "date": gt.Gettext("Date"),
+ "data": map[string]interface{}{
+ "branches": branches,
+ },
+ }
+
+ writePage(dir, repoindexHTML, "index.html", keys)
+ writePage(dir, branchesHTML, "branches.html", keys)
+ // commits := allCommits()
+
+ return nil
+}
+
+func basicRepositoryMetadata(path string) (map[string]interface{}, error) {
+ gitdirBytes, err := exec.Command(
+ "git", "-C", path, "rev-parse", "--git-dir",
+ ).Output()
+ if err != nil {
+ return nil, err
+ }
+ gitdir := strings.TrimSpace(string(gitdirBytes))
+
+ descriptionPath := path + "/" + gitdir + "/" + "description"
+ descriptionBytes, err := os.ReadFile(descriptionPath)
+ if err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+ description := strings.TrimSpace(string(descriptionBytes))
+
+ lastCommitBytes, err := exec.Command(
+ "git", "-C", path, "log", "-1", "--format=%cd", dateFmt,
+ ).Output()
+ if err != nil {
+ return nil, err
+ }
+ lastCommit := strings.TrimSpace(string(lastCommitBytes))
+
+ return map[string]interface{}{
+ "name": path,
+ "description": description,
+ "lastCommitDate": lastCommit,
+ }, nil
+}
+
+func generateRepositories(
+ args argsT,
+ stderr io.Writer,
+) ([]map[string]interface{}, error) {
+ allMetadata := []map[string]interface{}{}
+ for _, path := range args.subArgs {
+ metadata, err := basicRepositoryMetadata(path)
+ if err != nil {
+ return nil, err
+ }
+
+ err = generateRepository(args, path, stderr, metadata)
+ if err != nil {
+ return nil, err
+ }
+
+ allMetadata = append(allMetadata, metadata)
+ }
+
+ return allMetadata, nil
+}
+
+func generateListing(args argsT, allMetadata []map[string]interface{}) error {
+ keys := map[string]interface{}{
+ "logoAlt": gt.Gettext(
+ "Outlined icon of 3 melting ice cubes",
+ ),
+ "language": gt.Gettext("en"),
+ "pageDescription": gt.Gettext("Listing of repositories"),
+ "title": gt.Gettext("Repositories"),
+ "name": gt.Gettext("Name"),
+ "description": gt.Gettext("Description"),
+ "lastCommit": gt.Gettext("Last commit"),
+ "generatedWith": gt.Gettext("Generated with"),
+ "repositories": allMetadata,
+ }
+ s := strings.Builder{}
+ err := listingHTML.Execute(&s, keys)
+ if err != nil {
+ return err
+ }
+
+ err = fn_writeFile(args.outdir + "/index.html", s.String())
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func run(args argsT, _ io.Reader, _ io.Writer, stderr io.Writer) int {
+ err := fn_mkdirp(args.outdir)
+ if err != nil {
+ fmt.Fprintln(stderr, err)
+ return 1
+ }
+
+ err = generateAssets(args.outdir)
+ if err != nil {
+ fmt.Fprintln(stderr, err)
+ return 1
+ }
+
+ allMetadata, err := generateRepositories(args, stderr)
+ if err != nil {
+ fmt.Fprintln(stderr, err)
+ return 1
+ }
+
+ err = generateListing(args, allMetadata)
+ if err != nil {
+ fmt.Fprintln(stderr, err)
+ return 1
+ }
+
+ return 0
+}
+
func usage(argv0 string, w io.Writer) {
fmt.Fprintf(
w,
- "Usage: %s [-o DIRECTORY] [-u CLONE_URL] REPOSITORY\n",
+ "Usage: %s [-o DIRECTORY] [-u CLONE_URL] REPOSITORY...\n",
argv0,
)
}
@@ -32,12 +1062,13 @@ func getopt(allArgs []string, w io.Writer) (argsT, int) {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.Usage = func() {}
fs.SetOutput(w)
- outputPath := fs.String(
+
+ outdir := fs.String(
"o",
- g.Must(os.Getwd()),
- "The directory where to write the generated files",
+ g.Must(fn_wd()),
+ "Where to store the generated files",
)
- cloneUrl := fs.String(
+ cloneURL := fs.String(
"u",
"",
"The prefix of the online cloning addresss",
@@ -48,27 +1079,20 @@ func getopt(allArgs []string, w io.Writer) (argsT, int) {
}
subArgs := fs.Args()
- if len(subArgs) == 0 {
- fmt.Fprintf(w, "Missing DIRECTORY.\n")
- usage(argv0, w)
- return argsT{}, 2
- }
return argsT{
- outputPath: *outputPath,
- cloneUrl: *cloneUrl,
- allArgs: allArgs,
+ allArgs: allArgs,
+ subArgs: subArgs,
+ cloneURL: *cloneURL,
+ outdir: *outdir,
}, 0
}
-func run(args argsT, stdin io.Reader, stdout io.Writer, stderr io.Writer) int {
- return 0
-}
-
func Main() {
g.Init()
+ gt.Init(Name, LOCALEDIR)
args, rc := getopt(os.Args, os.Stderr)
g.ExitIf(rc)
os.Exit(run(args, os.Stdin, os.Stdout, os.Stderr))
diff --git a/tests/gistatic.go b/tests/gistatic.go
index 9f09a22..5798a0c 100644
--- a/tests/gistatic.go
+++ b/tests/gistatic.go
@@ -1,4 +1,106 @@
package gistatic
+import (
+ "strings"
+
+ g "gobang"
+)
+
+
+
+func test_usage() {
+ g.TestStart("usage()")
+
+ g.Testing("it writes the usage string to the io.Writer", func() {
+ w := strings.Builder{}
+ usage("xxx", &w)
+
+ expected := g.Heredoc(`
+ Usage: xxx [-o DIRECTORY] [-u CLONE_URL] REPOSITORY...
+ `)
+ g.TAssertEqual(w.String(), expected)
+ })
+}
+
+func test_getopt() {
+ g.TestStart("getopt()")
+
+ usage := g.Heredoc(`
+ Usage: $0 [-o DIRECTORY] [-u CLONE_URL] REPOSITORY...
+ `)
+
+ g.Testing("we supress the default error message", func() {
+ w := strings.Builder{}
+ argv := []string{"$0", "-h"}
+ _, rc := getopt(argv, &w)
+
+ g.TAssertEqual(w.String(), usage)
+ g.TAssertEqual(rc, 2)
+ })
+
+ g.Testing("we get unsupported flag error", func() {
+ w := strings.Builder{}
+ argv := []string{"$0", "-A"}
+ _, rc := getopt(argv, &w)
+
+ const message = "flag provided but not defined: -A\n"
+ g.TAssertEqual(w.String(), message + usage)
+ g.TAssertEqual(rc, 2)
+ })
+
+ g.Testing("we get incorrect use of flag error", func() {
+ w1 := strings.Builder{}
+ argv1 := []string{"$0", "-u"}
+ _, rc1 := getopt(argv1, &w1)
+
+ const message1 = "flag needs an argument: -u\n"
+ g.TAssertEqual(w1.String(), message1 + usage)
+ g.TAssertEqual(rc1, 2)
+
+ w2 := strings.Builder{}
+ argv2 := []string{"$0", "-o"}
+ _, rc2 := getopt(argv2, &w2)
+
+ const message2 = "flag needs an argument: -o\n"
+ g.TAssertEqual(w2.String(), message2 + usage)
+ g.TAssertEqual(rc2, 2)
+ })
+
+ g.Testing("the argsT has the flag URL value", func() {
+ fn_wdSaved := fn_wd
+ fn_wd = func() (string, error) {
+ return "virtual working directory", nil
+ }
+
+ w := strings.Builder{}
+ argv := []string{"$0", "-u", "proto://URL", "a-path"}
+ expected := argsT{
+ cloneURL: "proto://URL",
+ allArgs: []string{"$0", "-u", "proto://URL", "a-path"},
+ subArgs: []string{"a-path"},
+ outdir: "virtual working directory",
+ }
+
+ args, rc := getopt(argv, &w)
+
+ fn_wd = fn_wdSaved
+
+ g.TAssertEqual(w.String(), "")
+ g.TAssertEqual(rc, 0)
+ g.TAssertEqual(args, expected)
+ })
+}
+
+func test_run() {
+ // FIXME
+}
+
+
+
func MainTest() {
+ g.Init()
+
+ test_usage()
+ test_getopt()
+ test_run()
}