This commit is contained in:
adnano 2020-09-29 21:30:50 -04:00
parent 3f38235d4b
commit 399e543b4a
5 changed files with 124 additions and 132 deletions

2
go.mod
View file

@ -2,4 +2,4 @@ module kiln
go 1.15
require git.sr.ht/~adnano/gmi v0.0.1-alpha
require git.sr.ht/~adnano/gmi v0.0.1-alpha.1

4
go.sum
View file

@ -1,5 +1,5 @@
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=
git.sr.ht/~adnano/gmi v0.0.1-alpha.1 h1:zXcPeenDDt3opygDCunf52x3IJdVFEc1V0wf8aHshzc=
git.sr.ht/~adnano/gmi v0.0.1-alpha.1/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=

213
kiln.go
View file

@ -18,29 +18,42 @@ import (
// Site represents a kiln site.
type Site struct {
URL string // Site URL
Directory *Directory // Site directory
Templates *template.Template // Templates
Title string // Site title.
URL string // Site URL.
dir *Dir // Site directory.
tmpl *template.Template // Templates.
feeds bool // Whether or not to generate feeds.
}
// Load loads the Site from the specified source directory.
func (s *Site) Load(srcDir string) error {
s.Directory = NewDirectory("")
tmpl, err := template.New("templates").ParseGlob("templates/*.gmi")
if err != nil {
// load loads the Site from the current directory.
func (s *Site) load() error {
// Load content
const srcDir = "src"
s.dir = NewDir("")
if err := site.dir.Read(srcDir, ""); err != nil {
return err
}
s.Templates = tmpl
if err := site.Directory.Read(""); err != nil {
// Load templates
const tmplDir = "templates"
s.tmpl = template.New(tmplDir)
// Add default templates
s.tmpl.AddParseTree("atom.xml", atomTmpl.Tree)
s.tmpl.AddParseTree("index.gmi", indexTmpl.Tree)
s.tmpl.AddParseTree("page.gmi", pageTmpl.Tree)
// Add function to get site
s.tmpl = s.tmpl.Funcs(template.FuncMap{
"site": func() *Site { return s },
})
var err error
s.tmpl, err = s.tmpl.ParseGlob(filepath.Join(tmplDir, "*.gmi"))
if err != nil {
return err
}
return nil
}
// Write writes the contents of the Index to the provided destination directory.
func (s *Site) Write(dstDir string, format OutputFormat) error {
// write writes the contents of the Index to the provided destination directory.
func (s *Site) write(dstDir string, format OutputFormat) error {
// Empty the destination directory
if err := os.RemoveAll(dstDir); err != nil {
return err
@ -49,105 +62,88 @@ func (s *Site) Write(dstDir string, format OutputFormat) error {
if err := os.MkdirAll(dstDir, 0755); err != nil {
return err
}
// Write the directory
return s.Directory.Write(dstDir, format)
return s.dir.Write(dstDir, format)
}
// Manipulate processes and manipulates the site's content.
func (s *Site) Manipulate(dir *Directory) error {
// manipulate processes and manipulates the site's content.
func (s *Site) manipulate() error {
// FIXME: manipulate doesn't descend into subdirectories
// Write the directory index file, if it doesn't exist
if dir.Index == nil {
path := filepath.Join(dir.Permalink, "index.gmi")
const indexPath = "index.gmi"
if s.dir.index == nil {
var b bytes.Buffer
tmpl := s.Templates.Lookup("index.gmi")
if tmpl == nil {
tmpl = indexTmpl
}
if err := tmpl.Execute(&b, dir); err != nil {
tmpl := s.tmpl.Lookup(indexPath)
if err := tmpl.Execute(&b, s.dir); err != nil {
return err
}
content := b.Bytes()
permalink := filepath.Dir(path)
if permalink == "." {
permalink = ""
s.dir.index = &Page{
Permalink: s.dir.Permalink,
content: b.Bytes(),
}
page := &Page{
Permalink: "/" + permalink,
content: content,
}
dir.Index = page
}
// Manipulate pages
for i := range dir.Pages {
const pagePath = "page.gmi"
for i := range s.dir.Pages {
var b bytes.Buffer
tmpl := s.Templates.Lookup("page.gmi")
if tmpl == nil {
tmpl = pageTmpl
}
if err := tmpl.Execute(&b, dir.Pages[i]); err != nil {
tmpl := s.tmpl.Lookup(pagePath)
if err := tmpl.Execute(&b, s.dir.Pages[i]); err != nil {
return err
}
dir.Pages[i].content = b.Bytes()
s.dir.Pages[i].content = b.Bytes()
}
return nil
}
// Sort sorts the site's pages by date.
func (s *Site) Sort() {
s.Directory.Sort()
// sort sorts the site's pages by date.
func (s *Site) sort() {
s.dir.Sort()
}
// Feed represents a feed.
type Feed struct {
Title string
Path string
SiteURL string
Updated time.Time
Entries []*Page
Title string // Feed title.
Path string // Feed path.
URL string // Site URL.
Updated time.Time // Last updated time.
Entries []*Page // Feed entries.
}
// CreateFeeds creates Atom feeds.
func (s *Site) CreateFeeds() error {
// createFeeds creates Atom feeds.
func (s *Site) createFeeds() error {
const atomPath = "atom.xml"
var b bytes.Buffer
tmpl := s.Templates.Lookup(atomPath)
if tmpl == nil {
tmpl = atomTmpl
}
tmpl := s.tmpl.Lookup(atomPath)
feed := &Feed{
Title: "Example Feed",
Path: filepath.Join(s.Directory.Permalink, atomPath),
SiteURL: s.URL,
Title: s.Title,
Path: filepath.Join(s.dir.Permalink, atomPath),
URL: s.URL,
Updated: time.Now(),
Entries: s.Directory.Pages,
Entries: s.dir.Pages,
}
if err := tmpl.Execute(&b, feed); err != nil {
return err
}
path := filepath.Join(s.Directory.Permalink, atomPath)
s.Directory.Static[path] = b.Bytes()
path := filepath.Join(s.dir.Permalink, atomPath)
s.dir.Files[path] = b.Bytes()
return nil
}
// Page represents a page.
type Page struct {
// The permalink to this page.
Permalink string
// The title of this page, parsed from the Gemini contents.
Title string
// The date of the page. Dates are specified in the filename.
// Ex: 2020-09-22-hello-world.gmi
Date time.Time
// The content of this page.
content []byte
Title string // The title of this page.
Permalink string // The permalink to this page.
Date time.Time // The date of the page.
content []byte // The content of this page.
}
// Content returns the page content as a string.
// Used in templates.
func (p *Page) Content() string {
return string(p.content)
}
// Regexp to parse title from Gemini files
var titleRE = regexp.MustCompile("^# ?([^#\r\n]+)\r?\n?\r?\n?")
// NewPage returns a new Page with the given path and content.
@ -196,82 +192,77 @@ func NewPage(path string, content []byte) *Page {
}
}
// Directory represents a directory of pages.
type Directory struct {
// The permalink to this directory.
Permalink string
// The pages in this directory.
Pages []*Page
// The subdirectories of this directory.
Directories []*Directory
// The index file (index.gmi).
Index *Page
// Static files
Static map[string][]byte
// Dir represents a directory.
type Dir struct {
Permalink string // Permalink to this directory.
Pages []*Page // Pages in this directory.
Dirs []*Dir // Subdirectories.
Files map[string][]byte // Static files.
index *Page // The index file (index.gmi).
}
// NewDirectory returns a new Directory with the given path.
func NewDirectory(path string) *Directory {
// NewDir returns a new Dir with the given path.
func NewDir(path string) *Dir {
var permalink string
if path == "" {
permalink = "/"
} else {
permalink = "/" + path + "/"
}
return &Directory{
return &Dir{
Permalink: permalink,
Static: map[string][]byte{},
Files: map[string][]byte{},
}
}
// Read reads from a directory and indexes the files and directories within it.
func (d *Directory) Read(path string) error {
entries, err := ioutil.ReadDir(filepath.Join("src", path))
func (d *Dir) Read(srcDir string, path string) error {
entries, err := ioutil.ReadDir(filepath.Join(srcDir, path))
if err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
// Ignore names that start with '_'
if strings.HasPrefix(name, "_") {
continue
}
path := filepath.Join(path, name)
if entry.IsDir() {
// Gather directory data
path := filepath.Join(path, name)
dir := NewDirectory(path)
if err := dir.Read(path); err != nil {
dir := NewDir(path)
if err := dir.Read(srcDir, path); err != nil {
return err
}
d.Directories = append(d.Directories, dir)
d.Dirs = append(d.Dirs, dir)
} else {
srcPath := filepath.Join("src", path, name)
srcPath := filepath.Join(srcDir, path)
content, err := ioutil.ReadFile(srcPath)
if err != nil {
return err
}
switch filepath.Ext(name) {
case ".gmi", ".gemini":
path := filepath.Join(path, name)
case ".gmi":
// Gather page data
page := NewPage(path, content)
if name == "index.gmi" {
d.Index = page
d.index = page
} else {
d.Pages = append(d.Pages, page)
}
default:
// Static file
path := filepath.Join(path, name)
d.Static[path] = content
d.Files[path] = content
}
}
}
return nil
}
// Write writes the Directory to the provided destination path.
func (d *Directory) Write(dstDir string, format OutputFormat) error {
// Write writes the Dir to the provided destination path.
func (d *Dir) Write(dstDir string, format OutputFormat) error {
// Create the directory
dirPath := filepath.Join(dstDir, d.Permalink)
if err := os.MkdirAll(dirPath, 0755); err != nil {
@ -279,13 +270,13 @@ func (d *Directory) Write(dstDir string, format OutputFormat) error {
}
// Write static files
for path := range d.Static {
for path := range d.Files {
dstPath := filepath.Join(dstDir, path)
f, err := os.Create(dstPath)
if err != nil {
return err
}
data := d.Static[path]
data := d.Files[path]
if _, err := f.Write(data); err != nil {
return err
}
@ -307,8 +298,8 @@ func (d *Directory) Write(dstDir string, format OutputFormat) error {
}
// Write the index file
if d.Index != nil {
path, content := format(d.Index)
if d.index != nil {
path, content := format(d.index)
dstPath := filepath.Join(dstDir, path)
f, err := os.Create(dstPath)
if err != nil {
@ -320,19 +311,19 @@ func (d *Directory) Write(dstDir string, format OutputFormat) error {
}
// Write subdirectories
for _, dir := range d.Directories {
for _, dir := range d.Dirs {
dir.Write(dstDir, format)
}
return nil
}
// Sort sorts the directory's pages by date.
func (d *Directory) Sort() {
func (d *Dir) Sort() {
sort.Slice(d.Pages, func(i, j int) bool {
return d.Pages[i].Date.After(d.Pages[j].Date)
})
// Sort subdirectories
for _, d := range d.Directories {
for _, d := range d.Dirs {
d.Sort()
}
}

13
main.go
View file

@ -19,6 +19,7 @@ func init() {
flag.BoolVar(&serveSite, "serve", false, "serve the site")
flag.BoolVar(&toHtml, "html", false, "output HTML")
flag.BoolVar(&toAtom, "atom", false, "output Atom feed")
flag.StringVar(&site.Title, "title", "", "site title")
flag.StringVar(&site.URL, "url", "", "site URL")
}
@ -35,23 +36,23 @@ func main() {
// build the site
func build() error {
if err := site.Load("src"); err != nil {
if err := site.load(); err != nil {
return err
}
site.Sort()
if err := site.Manipulate(site.Directory); err != nil {
site.sort()
if err := site.manipulate(); err != nil {
return err
}
if toAtom {
if err := site.CreateFeeds(); err != nil {
if err := site.createFeeds(); err != nil {
return err
}
}
if err := site.Write("dst", OutputGemini); err != nil {
if err := site.write("dst", OutputGemini); err != nil {
return err
}
if toHtml {
if err := site.Write("dst.html", OutputHTML); err != nil {
if err := site.write("dst.html", OutputHTML); err != nil {
return err
}
}

View file

@ -7,28 +7,28 @@ import "text/template"
const atom_xml = `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ .Title }}</title>
<link href="{{ .SiteURL }}"/>
<link rel="self" href="{{ .SiteURL }}{{ .Path }}"/>
<link href="{{ .URL }}"/>
<link rel="self" href="{{ .URL }}{{ .Path }}"/>
<updated>{{ .Updated }}</updated>
<id>{{ .SiteURL }}{{ .Path }}</id>
{{ $siteURL := .SiteURL }}
{{ range .Entries }}
<id>{{ .URL }}{{ .Path }}</id>
{{- $url := .URL -}}
{{- range .Entries }}
<entry>
<title>{{ .Title }}</title>
<link href="{{ $siteURL }}{{ .Permalink }}"/>
<id>{{ $siteURL }}{{ .Permalink }}</id>
<link href="{{ $url }}{{ .Permalink }}"/>
<id>{{ $url }}{{ .Permalink }}</id>
<updated>{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}</updated>
<content src="{{ $siteURL }}{{ .Permalink }}" type="text/gemini"></content>
<content src="{{ $url }}{{ .Permalink }}" type="text/gemini"></content>
</entry>
{{ end }}
{{ end -}}
</feed>`
const index_gmi = `# Index of {{ .Permalink }}
{{ range .Directories }}=> {{ .Permalink }}
{{ end }}
{{ range .Dirs }}=> {{ .Permalink }}
{{ end -}}
{{ range .Pages }}=> {{ .Permalink }}
{{ end }}`
{{ end -}}`
const page_gmi = `# {{ .Title }}