ni/main.go

208 lines
4.3 KiB
Go

package main
import (
"bytes"
"fmt"
"html/template"
"io/ioutil"
"log"
"ni/gmi2html"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
txt "text/template"
"time"
)
var tplPage = `
{{ define "content" }}
{{ .Content }}
{{ if .Backlinks }}
<hr>
<p id="backlinks"><b>backlinks:</b></p>
<ul>
{{ range .Backlinks }}
<li><a href="{{ . }}">{{ . }}</a></li>
{{ end }}
</ul>
{{ end }}
{{ end }}
`
var tplChangelog = `
{{ define "content" }}
<ul>
{{ range . }}
<li><a href="{{ .Name }}">{{ .Name }}</a> {{ .TimeFormatted }}</li>
{{ end }}
</ul>
{{ end }}
`
var tplFeed = `
{{ define "content" }}
<id>{{ .url }}</id>
<updated>{{ .TimeRFC3339 }}</updated>
<link rel="self" href="{{ .url }}atom.xml"/>
{{ range .files }}
<entry>
<title>{{ .Name }}</title>
<link rel="alternate" href="{{ $.url }}{{ .Name }}"/>
<updated>{{ .TimeRFC3339 }}</updated>
<id>{{ $.url }}{{ .Name }}</id>
</entry>
{{ end }}
{{ end }}
`
var filename = regexp.MustCompile(`^[a-z0-9\-]+\.gmi$`)
var re = regexp.MustCompile(`\[\[([a-z0-9\-]+)\]\]`)
var PageContent *template.Template
var PageChangelog *template.Template
var atomFeed *txt.Template
func backlinks(in, name string) []string {
var rv []string
bl, err := exec.Command("/bin/sh", "-c", "grep -l '\\[\\["+name[:len(name)-4]+"\\]\\]' "+in+"/*.gmi").Output()
if err != nil {
return rv
}
for _, link := range strings.Fields(string(bl)) {
oname := outputName(link[len(in+"/"):])
if oname != outputName(name) {
rv = append(rv, oname)
}
}
return rv
}
func process(in, name string, d []byte) []byte {
html := bytes.NewBufferString("")
err := PageContent.Execute(html, map[string]interface{}{
"Title": name,
"Content": template.HTML(re.ReplaceAllString(gmi2html.Convert(string(d)), `<a href="$1.html">$1</a>`)),
"Backlinks": backlinks(in, name),
})
if err != nil {
log.Fatal(err)
}
return html.Bytes()
}
func buildChangelog(f []File) []byte {
html := bytes.NewBufferString("")
err := PageChangelog.Execute(html, f)
if err != nil {
log.Fatal(err)
}
return html.Bytes()
}
func buildAtomFeed(f []File, url string) []byte {
html := bytes.NewBufferString("")
err := atomFeed.Execute(html, map[string]interface{}{
"TimeRFC3339": time.Now().Format(time.RFC3339),
"files": f,
"url": url,
})
if err != nil {
log.Fatal(err)
}
return html.Bytes()
}
type File struct {
Name string
UpdatedAt time.Time
TimeFormatted string
TimeRFC3339 string
}
func outputName(inputName string) string {
return inputName[:len(inputName)-3] + "html"
}
func main() {
if len(os.Args) != 6 {
log.Fatal("Usage: ni input output template.html atom-template.xml url")
}
in := os.Args[1]
out := os.Args[2]
tpl := os.Args[3]
atomTpl := os.Args[4]
url := os.Args[5]
b, err := os.ReadFile(tpl)
if err != nil {
log.Fatal(err)
}
PageContent = template.Must(template.New("").Parse(string(b) + tplPage))
PageChangelog = template.Must(template.New("").Parse(string(b) + tplChangelog))
b, err = os.ReadFile(atomTpl)
if err != nil {
log.Fatal(err)
}
atomFeed = txt.Must(txt.New("").Parse(string(b) + tplFeed))
files, err := ioutil.ReadDir(in)
if err != nil {
log.Fatal(err)
}
var changelog []File
for _, file := range files {
if !filename.MatchString(file.Name()) {
continue
}
file, err := os.Stat(filepath.Join(in, file.Name()))
if err != nil {
log.Fatal(err)
}
updatedAt := file.ModTime()
changelog = append(changelog, File{
UpdatedAt: updatedAt,
TimeFormatted: updatedAt.Format("2006-01-02"),
TimeRFC3339: updatedAt.Format(time.RFC3339),
Name: outputName(file.Name()),
})
data, err := os.ReadFile(filepath.Join(in, file.Name()))
if err != nil {
log.Fatal(err)
}
output := process(in, file.Name(), data)
err = os.WriteFile(filepath.Join(out, outputName(file.Name())), output, 0644)
if err != nil {
log.Fatal(err)
}
fmt.Print(".")
}
sort.Slice(changelog, func(i, j int) bool {
return changelog[i].UpdatedAt.After(changelog[j].UpdatedAt)
})
if err != nil {
log.Fatal(err)
}
err = os.WriteFile(filepath.Join(out, "changelog.html"), buildChangelog(changelog), 0644)
if err != nil {
log.Fatal(err)
}
feed := changelog
if len(changelog) > 20 {
feed = feed[0:20]
}
err = os.WriteFile(filepath.Join(out, "atom.xml"), buildAtomFeed(feed, url), 0644)
if err != nil {
log.Fatal(err)
}
fmt.Println("Done")
}