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
|
||||
|
||||
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.0-20200928222852-855eff6d8802/go.mod h1:4MWQDsleal4HRi/LuxxM6ymWJQikP3Gh7xZindVCHzg=
|
||||
git.sr.ht/~adnano/gmi v0.0.1-alpha h1:6SRpwsKRowrKfExhyYQaQ0SBYcMIhHG4hUaWF67iP4k=
|
||||
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-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -72,7 +73,7 @@ func (s *Site) Write(dstDir string) error {
|
|||
}
|
||||
|
||||
// Write the directory
|
||||
return s.Directory.Write(dstDir)
|
||||
return s.Directory.Write(dstDir, OutputGemini)
|
||||
}
|
||||
|
||||
// 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
|
||||
if dir.Index == nil {
|
||||
path := filepath.Join(dir.Path, "index.gmi")
|
||||
builder := &strings.Builder{}
|
||||
var b bytes.Buffer
|
||||
tmpl := s.Templates.Lookup("index.gmi")
|
||||
if tmpl != nil {
|
||||
if err := tmpl.Execute(builder, dir); err != nil {
|
||||
if err := tmpl.Execute(&b, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
content := builder.String()
|
||||
content := b.Bytes()
|
||||
permalink := filepath.Dir(path)
|
||||
if permalink == "." {
|
||||
permalink = ""
|
||||
|
@ -94,7 +95,7 @@ func (s *Site) Manipulate(dir *Directory) error {
|
|||
page := &Page{
|
||||
Path: path,
|
||||
Permalink: "/" + permalink,
|
||||
Content: content,
|
||||
content: content,
|
||||
}
|
||||
dir.Index = page
|
||||
}
|
||||
|
@ -102,12 +103,12 @@ func (s *Site) Manipulate(dir *Directory) error {
|
|||
|
||||
// Manipulate pages
|
||||
for i := range dir.Pages {
|
||||
builder := &strings.Builder{}
|
||||
var b bytes.Buffer
|
||||
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
|
||||
}
|
||||
dir.Pages[i].Content = builder.String()
|
||||
dir.Pages[i].content = b.Bytes()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -125,13 +126,17 @@ type Page struct {
|
|||
// Ex: 2020-09-22-hello-world.gmi
|
||||
Date time.Time
|
||||
// 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?")
|
||||
|
||||
// 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
|
||||
var date time.Time
|
||||
const layout = "2006-01-02"
|
||||
|
@ -160,18 +165,20 @@ func NewPage(path string, content string) *Page {
|
|||
|
||||
// Try to parse the title from the contents
|
||||
var title string
|
||||
if submatches := titleRE.FindStringSubmatch(content); submatches != nil {
|
||||
title = submatches[1]
|
||||
if submatches := titleRE.FindSubmatch(content); submatches != nil {
|
||||
title = string(submatches[1])
|
||||
// Remove the title from the contents
|
||||
content = content[len(submatches[0]):]
|
||||
}
|
||||
|
||||
permalink := strings.TrimSuffix(path, ".gmi")
|
||||
|
||||
return &Page{
|
||||
Path: path,
|
||||
Permalink: "/" + path,
|
||||
Permalink: "/" + permalink + "/",
|
||||
Title: title,
|
||||
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) {
|
||||
case ".gmi", ".gemini":
|
||||
// Gather page data
|
||||
content := string(content)
|
||||
page := NewPage(path, content)
|
||||
|
||||
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.
|
||||
func (d *Directory) Write(dstDir string) error {
|
||||
func (d *Directory) Write(dstDir string, format OutputFormat) error {
|
||||
// Create the directory
|
||||
dirPath := filepath.Join(dstDir, d.Path)
|
||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||
|
@ -257,33 +263,53 @@ func (d *Directory) Write(dstDir string) error {
|
|||
|
||||
// Write the files
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := []byte(page.Content)
|
||||
if _, err := f.Write(data); err != nil {
|
||||
if _, err := f.Write(content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Write the index file
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := []byte(d.Index.Content)
|
||||
if _, err := f.Write(data); err != nil {
|
||||
if _, err := f.Write(content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Write subdirectories
|
||||
for _, dir := range d.Directories {
|
||||
dir.Write(dstDir)
|
||||
dir.Write(dstDir, format)
|
||||
}
|
||||
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 (
|
||||
serveSite bool
|
||||
toHtml bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&serveSite, "serve", false, "serve the site")
|
||||
flag.BoolVar(&toHtml, "html", false, "output HTML")
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -39,6 +41,11 @@ func build() error {
|
|||
if err := site.Write("dst"); err != nil {
|
||||
return err
|
||||
}
|
||||
if toHtml {
|
||||
if err := site.Directory.Write("dst.html", OutputHTML); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
129
render.go
129
render.go
|
@ -1,89 +1,72 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"git.sr.ht/~adnano/gmi"
|
||||
)
|
||||
|
||||
// GeminiToHTML reads Gemini from the provided reader and returns an HTML string.
|
||||
func GeminiToHTML(r io.Reader) string {
|
||||
const spacetab = " \t"
|
||||
func GeminiToHTML(r io.Reader) []byte {
|
||||
var b bytes.Buffer
|
||||
var pre bool
|
||||
var list bool
|
||||
|
||||
type parseCtx int
|
||||
b.WriteString("<link rel='stylesheet' href='/style.css'>\n")
|
||||
|
||||
const (
|
||||
parseCtxNone = iota
|
||||
parseCtxPre
|
||||
parseCtxList
|
||||
)
|
||||
|
||||
var builder strings.Builder
|
||||
var ctx parseCtx
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
text := gmi.Parse(r)
|
||||
for _, l := range text {
|
||||
if _, ok := l.(gmi.LineListItem); ok {
|
||||
if !list {
|
||||
list = true
|
||||
b.WriteString("<ul>\n")
|
||||
}
|
||||
} else if list {
|
||||
list = false
|
||||
b.WriteString("</ul>\n")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "```") {
|
||||
if ctx != parseCtxPre {
|
||||
builder.WriteString("<pre>\n")
|
||||
ctx = parseCtxPre
|
||||
switch l.(type) {
|
||||
case gmi.LineLink:
|
||||
link := l.(gmi.LineLink)
|
||||
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 {
|
||||
builder.WriteString("</pre>\n")
|
||||
ctx = parseCtxNone
|
||||
b.WriteString("</pre>\n")
|
||||
}
|
||||
} else if ctx == parseCtxPre {
|
||||
builder.WriteString(line)
|
||||
builder.WriteString("\n")
|
||||
} else if strings.HasPrefix(line, "*") {
|
||||
if ctx != parseCtxList {
|
||||
builder.WriteString("<ul>\n")
|
||||
ctx = parseCtxList
|
||||
}
|
||||
builder.WriteString("<li>")
|
||||
line = line[1:]
|
||||
builder.WriteString(strings.TrimLeft(line, spacetab))
|
||||
builder.WriteString("</li>\n")
|
||||
} else if ctx == parseCtxList {
|
||||
builder.WriteString("</ul>\n")
|
||||
ctx = parseCtxNone
|
||||
} else if strings.HasPrefix(line, "###") {
|
||||
line = line[3:]
|
||||
line = strings.TrimLeft(line, spacetab)
|
||||
builder.WriteString("<h3>" + line + "</h3>\n")
|
||||
} else if strings.HasPrefix(line, "##") {
|
||||
line = line[2:]
|
||||
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")
|
||||
case gmi.LinePreformattedText:
|
||||
text := string(l.(gmi.LinePreformattedText))
|
||||
fmt.Fprintf(&b, "%s\n", html.EscapeString(text))
|
||||
case gmi.LineHeading1:
|
||||
text := string(l.(gmi.LineHeading1))
|
||||
fmt.Fprintf(&b, "<h1>%s</h1>\n", html.EscapeString(text))
|
||||
case gmi.LineHeading2:
|
||||
text := string(l.(gmi.LineHeading2))
|
||||
fmt.Fprintf(&b, "<h2>%s</h2>\n", html.EscapeString(text))
|
||||
case gmi.LineHeading3:
|
||||
text := string(l.(gmi.LineHeading3))
|
||||
fmt.Fprintf(&b, "<h3>%s</h3>\n", html.EscapeString(text))
|
||||
case gmi.LineListItem:
|
||||
text := string(l.(gmi.LineListItem))
|
||||
fmt.Fprintf(&b, "<li>%s</li>\n", html.EscapeString(text))
|
||||
case gmi.LineQuote:
|
||||
text := string(l.(gmi.LineQuote))
|
||||
fmt.Fprintf(&b, "<blockquote>%s</blockquote>\n", html.EscapeString(text))
|
||||
case gmi.LineText:
|
||||
text := string(l.(gmi.LineText))
|
||||
fmt.Fprintf(&b, "<p>%s</p>\n", html.EscapeString(text))
|
||||
}
|
||||
}
|
||||
|
||||
// Close any opened tags
|
||||
switch ctx {
|
||||
case parseCtxPre:
|
||||
builder.WriteString("</pre>\n")
|
||||
case parseCtxList:
|
||||
builder.WriteString("</ul>\n")
|
||||
}
|
||||
return builder.String()
|
||||
return b.Bytes()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue