208 lines
4.3 KiB
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")
|
|
}
|