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 }}
{{ end }} {{ end }} ` var tplChangelog = ` {{ define "content" }} {{ end }} ` var tplFeed = ` {{ define "content" }} {{ .url }} {{ .TimeRFC3339 }} {{ range .files }} {{ .Name }} {{ .TimeRFC3339 }} {{ $.url }}{{ .Name }} {{ 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)), `$1`)), "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") }