mirror of
https://git.sr.ht/~adnano/kiln
synced 2024-10-30 01:13:08 +00:00
Improve HTML renderer
This commit is contained in:
parent
8b811c4e23
commit
fb1b053da4
2
go.mod
2
go.mod
|
@ -2,4 +2,4 @@ module kiln
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require git.sr.ht/~adnano/gmi v0.0.0-20200928222852-855eff6d8802
|
require git.sr.ht/~adnano/gmi v0.0.1-alpha
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1,5 +1,5 @@
|
||||||
git.sr.ht/~adnano/gmi v0.0.0-20200928222852-855eff6d8802 h1:VAuDKKZWyq3/UkBuIeLEQ1qjRqiRfACfErdvBNdYz9A=
|
git.sr.ht/~adnano/gmi v0.0.1-alpha h1:6SRpwsKRowrKfExhyYQaQ0SBYcMIhHG4hUaWF67iP4k=
|
||||||
git.sr.ht/~adnano/gmi v0.0.0-20200928222852-855eff6d8802/go.mod h1:4MWQDsleal4HRi/LuxxM6ymWJQikP3Gh7xZindVCHzg=
|
git.sr.ht/~adnano/gmi v0.0.1-alpha/go.mod h1:4MWQDsleal4HRi/LuxxM6ymWJQikP3Gh7xZindVCHzg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
|
72
kiln.go
72
kiln.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -72,7 +73,7 @@ func (s *Site) Write(dstDir string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the directory
|
// Write the directory
|
||||||
return s.Directory.Write(dstDir)
|
return s.Directory.Write(dstDir, OutputGemini)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manipulate processes and manipulates the site's content.
|
// Manipulate processes and manipulates the site's content.
|
||||||
|
@ -80,13 +81,13 @@ func (s *Site) Manipulate(dir *Directory) error {
|
||||||
// Write the directory index file, if it doesn't exist
|
// Write the directory index file, if it doesn't exist
|
||||||
if dir.Index == nil {
|
if dir.Index == nil {
|
||||||
path := filepath.Join(dir.Path, "index.gmi")
|
path := filepath.Join(dir.Path, "index.gmi")
|
||||||
builder := &strings.Builder{}
|
var b bytes.Buffer
|
||||||
tmpl := s.Templates.Lookup("index.gmi")
|
tmpl := s.Templates.Lookup("index.gmi")
|
||||||
if tmpl != nil {
|
if tmpl != nil {
|
||||||
if err := tmpl.Execute(builder, dir); err != nil {
|
if err := tmpl.Execute(&b, dir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
content := builder.String()
|
content := b.Bytes()
|
||||||
permalink := filepath.Dir(path)
|
permalink := filepath.Dir(path)
|
||||||
if permalink == "." {
|
if permalink == "." {
|
||||||
permalink = ""
|
permalink = ""
|
||||||
|
@ -94,7 +95,7 @@ func (s *Site) Manipulate(dir *Directory) error {
|
||||||
page := &Page{
|
page := &Page{
|
||||||
Path: path,
|
Path: path,
|
||||||
Permalink: "/" + permalink,
|
Permalink: "/" + permalink,
|
||||||
Content: content,
|
content: content,
|
||||||
}
|
}
|
||||||
dir.Index = page
|
dir.Index = page
|
||||||
}
|
}
|
||||||
|
@ -102,12 +103,12 @@ func (s *Site) Manipulate(dir *Directory) error {
|
||||||
|
|
||||||
// Manipulate pages
|
// Manipulate pages
|
||||||
for i := range dir.Pages {
|
for i := range dir.Pages {
|
||||||
builder := &strings.Builder{}
|
var b bytes.Buffer
|
||||||
tmpl := s.Templates.Lookup("page.gmi")
|
tmpl := s.Templates.Lookup("page.gmi")
|
||||||
if err := tmpl.Execute(builder, dir.Pages[i]); err != nil {
|
if err := tmpl.Execute(&b, dir.Pages[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dir.Pages[i].Content = builder.String()
|
dir.Pages[i].content = b.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -125,13 +126,17 @@ type Page struct {
|
||||||
// Ex: 2020-09-22-hello-world.gmi
|
// Ex: 2020-09-22-hello-world.gmi
|
||||||
Date time.Time
|
Date time.Time
|
||||||
// The content of this page.
|
// The content of this page.
|
||||||
Content string
|
content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Page) Content() string {
|
||||||
|
return string(p.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
var titleRE = regexp.MustCompile("^# ?([^#\r\n]+)\r?\n?\r?\n?")
|
var titleRE = regexp.MustCompile("^# ?([^#\r\n]+)\r?\n?\r?\n?")
|
||||||
|
|
||||||
// NewPage returns a new Page with the given path and content.
|
// NewPage returns a new Page with the given path and content.
|
||||||
func NewPage(path string, content string) *Page {
|
func NewPage(path string, content []byte) *Page {
|
||||||
// Try to parse the date from the page filename
|
// Try to parse the date from the page filename
|
||||||
var date time.Time
|
var date time.Time
|
||||||
const layout = "2006-01-02"
|
const layout = "2006-01-02"
|
||||||
|
@ -160,18 +165,20 @@ func NewPage(path string, content string) *Page {
|
||||||
|
|
||||||
// Try to parse the title from the contents
|
// Try to parse the title from the contents
|
||||||
var title string
|
var title string
|
||||||
if submatches := titleRE.FindStringSubmatch(content); submatches != nil {
|
if submatches := titleRE.FindSubmatch(content); submatches != nil {
|
||||||
title = submatches[1]
|
title = string(submatches[1])
|
||||||
// Remove the title from the contents
|
// Remove the title from the contents
|
||||||
content = content[len(submatches[0]):]
|
content = content[len(submatches[0]):]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
permalink := strings.TrimSuffix(path, ".gmi")
|
||||||
|
|
||||||
return &Page{
|
return &Page{
|
||||||
Path: path,
|
Path: path,
|
||||||
Permalink: "/" + path,
|
Permalink: "/" + permalink + "/",
|
||||||
Title: title,
|
Title: title,
|
||||||
Date: date,
|
Date: date,
|
||||||
Content: content,
|
content: content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +236,6 @@ func (d *Directory) Read(site *Site, srcDir string, path string) error {
|
||||||
switch filepath.Ext(name) {
|
switch filepath.Ext(name) {
|
||||||
case ".gmi", ".gemini":
|
case ".gmi", ".gemini":
|
||||||
// Gather page data
|
// Gather page data
|
||||||
content := string(content)
|
|
||||||
page := NewPage(path, content)
|
page := NewPage(path, content)
|
||||||
|
|
||||||
if name == "index.gmi" {
|
if name == "index.gmi" {
|
||||||
|
@ -248,7 +254,7 @@ func (d *Directory) Read(site *Site, srcDir string, path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the Directory to the provided destination path.
|
// Write writes the Directory to the provided destination path.
|
||||||
func (d *Directory) Write(dstDir string) error {
|
func (d *Directory) Write(dstDir string, format OutputFormat) error {
|
||||||
// Create the directory
|
// Create the directory
|
||||||
dirPath := filepath.Join(dstDir, d.Path)
|
dirPath := filepath.Join(dstDir, d.Path)
|
||||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||||
|
@ -257,33 +263,53 @@ func (d *Directory) Write(dstDir string) error {
|
||||||
|
|
||||||
// Write the files
|
// Write the files
|
||||||
for _, page := range d.Pages {
|
for _, page := range d.Pages {
|
||||||
dstPath := filepath.Join(dstDir, page.Path)
|
path, content := format(page)
|
||||||
|
dstPath := filepath.Join(dstDir, path)
|
||||||
|
dir := filepath.Dir(dstPath)
|
||||||
|
os.MkdirAll(dir, 0755)
|
||||||
f, err := os.Create(dstPath)
|
f, err := os.Create(dstPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data := []byte(page.Content)
|
if _, err := f.Write(content); err != nil {
|
||||||
if _, err := f.Write(data); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the index file
|
// Write the index file
|
||||||
if d.Index != nil {
|
if d.Index != nil {
|
||||||
dstPath := filepath.Join(dstDir, d.Index.Path)
|
path, content := format(d.Index)
|
||||||
|
dstPath := filepath.Join(dstDir, path)
|
||||||
f, err := os.Create(dstPath)
|
f, err := os.Create(dstPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data := []byte(d.Index.Content)
|
if _, err := f.Write(content); err != nil {
|
||||||
if _, err := f.Write(data); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write subdirectories
|
// Write subdirectories
|
||||||
for _, dir := range d.Directories {
|
for _, dir := range d.Directories {
|
||||||
dir.Write(dstDir)
|
dir.Write(dstDir, format)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OutputFormat represents an output format.
|
||||||
|
type OutputFormat func(*Page) (path string, content []byte)
|
||||||
|
|
||||||
|
func OutputGemini(p *Page) (path string, content []byte) {
|
||||||
|
const indexPath = "index.gmi"
|
||||||
|
path = filepath.Join(p.Permalink, indexPath)
|
||||||
|
content = p.content
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func OutputHTML(p *Page) (path string, content []byte) {
|
||||||
|
const indexPath = "index.html"
|
||||||
|
path = filepath.Join(p.Permalink, indexPath)
|
||||||
|
r := bytes.NewReader(p.content)
|
||||||
|
content = GeminiToHTML(r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
7
main.go
7
main.go
|
@ -10,10 +10,12 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
serveSite bool
|
serveSite bool
|
||||||
|
toHtml bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&serveSite, "serve", false, "serve the site")
|
flag.BoolVar(&serveSite, "serve", false, "serve the site")
|
||||||
|
flag.BoolVar(&toHtml, "html", false, "output HTML")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -39,6 +41,11 @@ func build() error {
|
||||||
if err := site.Write("dst"); err != nil {
|
if err := site.Write("dst"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if toHtml {
|
||||||
|
if err := site.Directory.Write("dst.html", OutputHTML); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
129
render.go
129
render.go
|
@ -1,89 +1,72 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
"git.sr.ht/~adnano/gmi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GeminiToHTML reads Gemini from the provided reader and returns an HTML string.
|
// GeminiToHTML reads Gemini from the provided reader and returns an HTML string.
|
||||||
func GeminiToHTML(r io.Reader) string {
|
func GeminiToHTML(r io.Reader) []byte {
|
||||||
const spacetab = " \t"
|
var b bytes.Buffer
|
||||||
|
var pre bool
|
||||||
|
var list bool
|
||||||
|
|
||||||
type parseCtx int
|
b.WriteString("<link rel='stylesheet' href='/style.css'>\n")
|
||||||
|
|
||||||
const (
|
text := gmi.Parse(r)
|
||||||
parseCtxNone = iota
|
for _, l := range text {
|
||||||
parseCtxPre
|
if _, ok := l.(gmi.LineListItem); ok {
|
||||||
parseCtxList
|
if !list {
|
||||||
)
|
list = true
|
||||||
|
b.WriteString("<ul>\n")
|
||||||
var builder strings.Builder
|
}
|
||||||
var ctx parseCtx
|
} else if list {
|
||||||
|
list = false
|
||||||
scanner := bufio.NewScanner(r)
|
b.WriteString("</ul>\n")
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if len(line) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(line, "```") {
|
switch l.(type) {
|
||||||
if ctx != parseCtxPre {
|
case gmi.LineLink:
|
||||||
builder.WriteString("<pre>\n")
|
link := l.(gmi.LineLink)
|
||||||
ctx = parseCtxPre
|
url := html.EscapeString(link.URL)
|
||||||
|
name := html.EscapeString(link.Name)
|
||||||
|
if name == "" {
|
||||||
|
name = url
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, "<p><a href='%s'>%s</a></p>", url, name)
|
||||||
|
case gmi.LinePreformattingToggle:
|
||||||
|
pre = !pre
|
||||||
|
if pre {
|
||||||
|
b.WriteString("<pre>\n")
|
||||||
} else {
|
} else {
|
||||||
builder.WriteString("</pre>\n")
|
b.WriteString("</pre>\n")
|
||||||
ctx = parseCtxNone
|
|
||||||
}
|
}
|
||||||
} else if ctx == parseCtxPre {
|
case gmi.LinePreformattedText:
|
||||||
builder.WriteString(line)
|
text := string(l.(gmi.LinePreformattedText))
|
||||||
builder.WriteString("\n")
|
fmt.Fprintf(&b, "%s\n", html.EscapeString(text))
|
||||||
} else if strings.HasPrefix(line, "*") {
|
case gmi.LineHeading1:
|
||||||
if ctx != parseCtxList {
|
text := string(l.(gmi.LineHeading1))
|
||||||
builder.WriteString("<ul>\n")
|
fmt.Fprintf(&b, "<h1>%s</h1>\n", html.EscapeString(text))
|
||||||
ctx = parseCtxList
|
case gmi.LineHeading2:
|
||||||
}
|
text := string(l.(gmi.LineHeading2))
|
||||||
builder.WriteString("<li>")
|
fmt.Fprintf(&b, "<h2>%s</h2>\n", html.EscapeString(text))
|
||||||
line = line[1:]
|
case gmi.LineHeading3:
|
||||||
builder.WriteString(strings.TrimLeft(line, spacetab))
|
text := string(l.(gmi.LineHeading3))
|
||||||
builder.WriteString("</li>\n")
|
fmt.Fprintf(&b, "<h3>%s</h3>\n", html.EscapeString(text))
|
||||||
} else if ctx == parseCtxList {
|
case gmi.LineListItem:
|
||||||
builder.WriteString("</ul>\n")
|
text := string(l.(gmi.LineListItem))
|
||||||
ctx = parseCtxNone
|
fmt.Fprintf(&b, "<li>%s</li>\n", html.EscapeString(text))
|
||||||
} else if strings.HasPrefix(line, "###") {
|
case gmi.LineQuote:
|
||||||
line = line[3:]
|
text := string(l.(gmi.LineQuote))
|
||||||
line = strings.TrimLeft(line, spacetab)
|
fmt.Fprintf(&b, "<blockquote>%s</blockquote>\n", html.EscapeString(text))
|
||||||
builder.WriteString("<h3>" + line + "</h3>\n")
|
case gmi.LineText:
|
||||||
} else if strings.HasPrefix(line, "##") {
|
text := string(l.(gmi.LineText))
|
||||||
line = line[2:]
|
fmt.Fprintf(&b, "<p>%s</p>\n", html.EscapeString(text))
|
||||||
line = strings.TrimLeft(line, spacetab)
|
|
||||||
builder.WriteString("<h2>" + line + "</h2>\n")
|
|
||||||
} else if strings.HasPrefix(line, "#") {
|
|
||||||
line = line[1:]
|
|
||||||
line = strings.TrimLeft(line, spacetab)
|
|
||||||
builder.WriteString("<h1>" + line + "</h1>\n")
|
|
||||||
} else if strings.HasPrefix(line, "=>") {
|
|
||||||
line = line[2:]
|
|
||||||
line = strings.TrimLeft(line, spacetab)
|
|
||||||
split := strings.IndexAny(line, spacetab)
|
|
||||||
url := line[:split]
|
|
||||||
name := line[split:]
|
|
||||||
name = strings.TrimLeft(name, spacetab)
|
|
||||||
builder.WriteString("<p><a href='" + url + "'>" + name + "</a></p>\n")
|
|
||||||
} else if strings.HasPrefix(line, ">") {
|
|
||||||
builder.WriteString("<blockquote>" + line + "</blockquote>")
|
|
||||||
} else {
|
|
||||||
builder.WriteString("<p>" + line + "</p>\n")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return b.Bytes()
|
||||||
// Close any opened tags
|
|
||||||
switch ctx {
|
|
||||||
case parseCtxPre:
|
|
||||||
builder.WriteString("</pre>\n")
|
|
||||||
case parseCtxList:
|
|
||||||
builder.WriteString("</ul>\n")
|
|
||||||
}
|
|
||||||
return builder.String()
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue