This commit is contained in:
adnano 2020-09-22 19:46:30 -04:00
parent 81bf4f8e9b
commit d0713476c0
4 changed files with 215 additions and 228 deletions

View file

@ -1,80 +0,0 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
)
type Index struct {
Files map[string][]byte
Dirs map[string][]string
}
func NewIndex() *Index {
return &Index{
Files: map[string][]byte{},
Dirs: map[string][]string{},
}
}
// ReadDir reads a directory and indexes the files and directories within it.
func (index *Index) ReadDir(srcDir string, dstDir string) error {
entries, err := ioutil.ReadDir(srcDir)
if err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
srcPath := filepath.Join(srcDir, name)
dstPath := filepath.Join(dstDir, name)
if entry.IsDir() {
index.Dirs[dstPath] = []string{}
index.ReadDir(srcPath, dstPath)
} else {
content, err := ioutil.ReadFile(srcPath)
if err != nil {
return err
}
index.Files[dstPath] = content
index.Dirs[dstDir] = append(index.Dirs[dstDir], dstPath)
}
}
return nil
}
// Write writes the contents of the Index to the provided destination directory.
func (index *Index) Write(dstDir string) error {
// Empty the destination directory
if err := os.RemoveAll(dstDir); err != nil {
return err
}
if err := os.MkdirAll(dstDir, 0755); err != nil {
return err
}
for path := range index.Files {
// Create any parent directories
if dir := filepath.Dir(path); dir != "." {
dstPath := filepath.Join(dstDir, dir)
if err := os.MkdirAll(dstPath, 0755); err != nil {
return err
}
}
// Write the file
dstPath := filepath.Join(dstDir, path)
f, err := os.Create(dstPath)
if err != nil {
return err
}
data := index.Files[path]
if _, err := f.Write(data); err != nil {
return err
}
}
return nil
}

212
kiln.go Normal file
View file

@ -0,0 +1,212 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
"time"
)
type Site struct {
Files map[string][]byte
Dirs map[string][]string
Pages map[string]*Page
Templates *template.Template
}
func LoadSite(srcDir string) (*Site, error) {
tmpl, err := template.New("templates").ParseGlob("templates/*.gmi")
if err != nil {
return nil, err
}
site := &Site{
Files: map[string][]byte{},
Dirs: map[string][]string{},
Pages: map[string]*Page{},
Templates: tmpl,
}
if err := site.ReadDir(srcDir, ""); err != nil {
return nil, err
}
return site, nil
}
// ReadDir reads a directory and indexes the files and directories within it.
func (s *Site) ReadDir(srcDir string, dstDir string) error {
entries, err := ioutil.ReadDir(srcDir)
if err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
srcPath := filepath.Join(srcDir, name)
dstPath := filepath.Join(dstDir, name)
if entry.IsDir() {
s.Dirs[dstPath] = []string{}
s.ReadDir(srcPath, dstPath)
} else {
content, err := ioutil.ReadFile(srcPath)
if err != nil {
return err
}
s.Files[dstPath] = content
s.Dirs[dstDir] = append(s.Dirs[dstDir], dstPath)
}
}
return nil
}
// Write writes the contents of the Index to the provided destination directory.
func (s *Site) Write(dstDir string) error {
// Empty the destination directory
if err := os.RemoveAll(dstDir); err != nil {
return err
}
if err := os.MkdirAll(dstDir, 0755); err != nil {
return err
}
for path := range s.Files {
// Create any parent directories
if dir := filepath.Dir(path); dir != "." {
dstPath := filepath.Join(dstDir, dir)
if err := os.MkdirAll(dstPath, 0755); err != nil {
return err
}
}
// Write the file
dstPath := filepath.Join(dstDir, path)
f, err := os.Create(dstPath)
if err != nil {
return err
}
data := s.Files[path]
if _, err := f.Write(data); err != nil {
return err
}
}
return nil
}
// Manipulate processes and manipulates the site's content.
func (s *Site) Manipulate() error {
for path := range s.Files {
switch filepath.Ext(path) {
case ".gmi":
// Gather page data
content := string(s.Files[path])
page := NewPage(path, content)
builder := &strings.Builder{}
tmpl := s.Templates.Lookup("page.gmi")
if err := tmpl.Execute(builder, page); err != nil {
return err
}
page.Content = builder.String()
s.Pages[path] = page
s.Files[path] = []byte(page.Content)
}
}
for dir := range s.Dirs {
// Gather section data
section := NewSection(dir)
for _, path := range s.Dirs[dir] {
switch filepath.Ext(path) {
case ".gmi":
section.Pages = append(section.Pages, s.Pages[path])
}
}
dstPath := filepath.Join(dir, "index.gmi")
builder := &strings.Builder{}
tmpl := s.Templates.Lookup("section.gmi")
if err := tmpl.Execute(builder, section); err != nil {
return err
}
s.Files[dstPath] = []byte(builder.String())
}
return nil
}
// Page represents a page.
type Page struct {
// The path to this page.
Path string
// 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 string
}
var titleRE = regexp.MustCompile("^# ?([^#\r\n]+)")
func NewPage(path string, content string) *Page {
page := &Page{
Path: path,
Permalink: "/" + path,
Content: content,
}
// Try to parse the date from the page filename
const layout = "2006-01-02"
base := filepath.Base(path)
if len(base) >= len(layout) {
dateStr := base[:len(layout)]
if date, err := time.Parse(layout, dateStr); err == nil {
page.Date = date
}
}
// Try to parse the title from the contents
if submatches := titleRE.FindStringSubmatch(content); submatches != nil {
page.Title = submatches[1]
}
return page
}
// Section represents a section (i.e., a directory).
type Section struct {
// The path to this section.
Path string
// The permalink to this section.
Permalink string
// The pages in this section.
Pages []*Page
}
// NewSection returns a new Section with the given path.
func NewSection(path string) *Section {
var permalink string
if path == "" {
permalink = "/"
} else {
permalink = "/" + path + "/"
}
return &Section{
Path: path,
Permalink: permalink,
}
}
// Sort sorts pages by date.
func (s *Section) Sort() {
// TODO: Implement this
}

85
main.go
View file

@ -2,9 +2,6 @@ package main
import ( import (
"log" "log"
"path/filepath"
"regexp"
"time"
) )
func main() { func main() {
@ -14,91 +11,15 @@ func main() {
} }
func run() error { func run() error {
index := NewIndex() site, err := LoadSite("src")
if err := index.ReadDir("src", ""); err != nil {
return err
}
manipulator, err := NewManipulator()
if err != nil { if err != nil {
return err return err
} }
if err := manipulator.Manipulate(index); err != nil { if err := site.Manipulate(); err != nil {
return err return err
} }
if err := site.Write("dst"); err != nil {
if err := index.Write("dst"); err != nil {
return err return err
} }
return nil return nil
} }
// Page represents a page.
type Page struct {
// The path to this page.
Path string
// 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 string
}
var titleRE = regexp.MustCompile("^# ?([^#\r\n]+)")
func NewPage(path string, content string) *Page {
page := &Page{
Path: path,
Permalink: "/" + path,
Content: content,
}
// Try to parse the date from the page filename
const layout = "2006-01-02"
base := filepath.Base(path)
if len(base) >= len(layout) {
dateStr := base[:len(layout)]
if date, err := time.Parse(layout, dateStr); err == nil {
page.Date = date
}
}
// Try to parse the title from the contents
if submatches := titleRE.FindStringSubmatch(content); submatches != nil {
page.Title = submatches[1]
}
return page
}
// Section represents a section (i.e., a directory).
type Section struct {
// The path to this section.
Path string
// The permalink to this section.
Permalink string
// The pages in this section.
Pages []*Page
}
// NewSection returns a new Section with the given path.
func NewSection(path string) *Section {
var permalink string
if path == "" {
permalink = "/"
} else {
permalink = "/" + path + "/"
}
return &Section{
Path: path,
Permalink: permalink,
}
}
// Sort sorts pages by date.
func (s *Section) Sort() {
// TODO: Implement this
}

View file

@ -1,66 +0,0 @@
package main
import (
"path/filepath"
"strings"
"text/template"
)
type Manipulator struct {
Pages map[string]*Page
// Templates
Templates *template.Template
}
func NewManipulator() (*Manipulator, error) {
tmpl, err := template.New("templates").ParseGlob("templates/*.gmi")
if err != nil {
return nil, err
}
return &Manipulator{
Pages: map[string]*Page{},
Templates: tmpl,
}, nil
}
func (m *Manipulator) Manipulate(index *Index) error {
for path := range index.Files {
switch filepath.Ext(path) {
case ".gmi":
// Gather page data
content := string(index.Files[path])
page := NewPage(path, content)
builder := &strings.Builder{}
tmpl := m.Templates.Lookup("page.gmi")
if err := tmpl.Execute(builder, page); err != nil {
return err
}
page.Content = builder.String()
m.Pages[path] = page
index.Files[path] = []byte(page.Content)
}
}
for dir := range index.Dirs {
// Load section data
section := NewSection(dir)
for _, path := range index.Dirs[dir] {
switch filepath.Ext(path) {
case ".gmi":
section.Pages = append(section.Pages, m.Pages[path])
}
}
dstPath := filepath.Join(dir, "index.gmi")
builder := &strings.Builder{}
tmpl := m.Templates.Lookup("section.gmi")
if err := tmpl.Execute(builder, section); err != nil {
return err
}
index.Files[dstPath] = []byte(builder.String())
}
return nil
}