first commit
This commit is contained in:
commit
cfcd2ed6eb
|
@ -0,0 +1,3 @@
|
||||||
|
.idea
|
||||||
|
bin
|
||||||
|
*~
|
|
@ -0,0 +1,2 @@
|
||||||
|
build:
|
||||||
|
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o bin/ni main.go
|
|
@ -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")
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
Loading…
Reference in New Issue