first commit

This commit is contained in:
m15o 2021-11-02 06:28:59 +01:00
commit cfcd2ed6eb
5 changed files with 278 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
bin
*~

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
build:
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o bin/ni main.go

114
gmi2html/convert.go Normal file
View File

@ -0,0 +1,114 @@
package gmi2html
import (
"html"
"regexp"
"strings"
)
var heading1Regexp = regexp.MustCompile("^# (.*)$")
var heading2Regexp = regexp.MustCompile("^## (.*)$")
var heading3Regexp = regexp.MustCompile("^### (.*)$")
var linkRegexp = regexp.MustCompile("^=> ([^\\s]+) ?(.+)?$")
var blockquoteRegexp = regexp.MustCompile("^> (.*)$")
var preRegexp = regexp.MustCompile("^```.*$")
var bulletRegexp = regexp.MustCompile(`^\* ?(.*)$`)
func clearLinkMode(linkMode *bool, rv *[]string) {
if *linkMode {
*rv = append(*rv, "</p>")
*linkMode = false
}
}
func clearUlMode(ulMode *bool, rv *[]string) {
if *ulMode {
*rv = append(*rv, "</ul>")
*ulMode = false
}
}
func sanitize(input string) string {
return html.EscapeString(input)
}
func Convert(gmi string) string {
var rv []string
preMode := false
ulMode := false
linkMode := false
for _, l := range strings.Split(gmi, "\n") {
l = strings.TrimRight(l, "\r")
if preMode {
switch {
case preRegexp.MatchString(l):
rv = append(rv, "</pre>")
preMode = false
default:
rv = append(rv, sanitize(l))
}
} else {
switch {
case heading1Regexp.MatchString(l):
clearUlMode(&ulMode, &rv)
clearLinkMode(&linkMode, &rv)
matches := heading1Regexp.FindStringSubmatch(l)
rv = append(rv, "<h1>"+sanitize(matches[1])+"</h1>")
case heading2Regexp.MatchString(l):
clearUlMode(&ulMode, &rv)
clearLinkMode(&linkMode, &rv)
matches := heading2Regexp.FindStringSubmatch(l)
rv = append(rv, "<h2>"+sanitize(matches[1])+"</h2>")
case heading3Regexp.MatchString(l):
clearUlMode(&ulMode, &rv)
clearLinkMode(&linkMode, &rv)
matches := heading3Regexp.FindStringSubmatch(l)
rv = append(rv, "<h3>"+sanitize(matches[1])+"</h3>")
case blockquoteRegexp.MatchString(l):
clearUlMode(&ulMode, &rv)
clearLinkMode(&linkMode, &rv)
matches := blockquoteRegexp.FindStringSubmatch(l)
rv = append(rv, "<blockquote>"+sanitize(matches[1])+"</blockquote>")
case linkRegexp.MatchString(l):
clearUlMode(&ulMode, &rv)
matches := linkRegexp.FindStringSubmatch(l)
if len(matches[2]) == 0 {
matches[2] = matches[1]
}
if strings.HasSuffix(matches[1], ".png") || strings.HasSuffix(matches[1], ".PNG") || strings.HasSuffix(matches[1], ".jpg") || strings.HasSuffix(matches[1], ".JPG") || strings.HasSuffix(matches[1], ".jpeg") || strings.HasSuffix(matches[1], ".gif") || strings.HasSuffix(matches[1], ".GIF") {
rv = append(rv, "<img src=\""+sanitize(matches[1])+"\"/>")
continue
}
if linkMode {
rv = append(rv, "<a href=\""+sanitize(matches[1])+"\">"+sanitize(matches[2])+"</a><br/>")
continue
}
rv = append(rv, "<p><a href=\""+sanitize(matches[1])+"\">"+sanitize(matches[2])+"</a><br/>")
linkMode = true
case preRegexp.MatchString(l):
clearUlMode(&ulMode, &rv)
clearLinkMode(&linkMode, &rv)
rv = append(rv, "<pre>")
preMode = true
case bulletRegexp.MatchString(l):
clearLinkMode(&linkMode, &rv)
matches := bulletRegexp.FindStringSubmatch(l)
if ulMode {
rv = append(rv, "<li>"+sanitize(matches[1])+"</li>")
continue
}
rv = append(rv, "<ul>\n<li>"+sanitize(matches[1])+"</li>")
ulMode = true
default:
clearUlMode(&ulMode, &rv)
clearLinkMode(&linkMode, &rv)
if len(l) != 0 {
rv = append(rv, "<p>"+sanitize(l)+"</p>")
}
}
}
}
clearUlMode(&ulMode, &rv)
clearLinkMode(&linkMode, &rv)
return strings.Join(rv, "\n")
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module ni
go 1.16

156
main.go Normal file
View File

@ -0,0 +1,156 @@
package main
import (
"bytes"
"fmt"
"html/template"
"io/ioutil"
"log"
"ni/gmi2html"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
)
var tplPage = `
{{ define "content" }}
{{ .Content }}
{{ if .Backlinks }}
<h2>Backlinks</h2>
<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 filename = regexp.MustCompile(`^[a-z0-9\-]+\.gmi$`)
var re = regexp.MustCompile(`\[\[([a-z0-9\-]+)\]\]`)
var PageContent *template.Template
var PageChangelog *template.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()
}
type File struct {
Name string
UpdatedAt time.Time
TimeFormatted string
}
func outputName(inputName string) string {
return inputName[:len(inputName)-3] + "html"
}
func main() {
if len(os.Args) != 4 {
log.Fatal("Usage: ni input output template.html")
}
in := os.Args[1]
out := os.Args[2]
tpl := os.Args[3]
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))
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"),
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)
}
fmt.Println("Done")
}