markdown hotplugging

Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
Naman Sood 2021-03-15 19:20:25 -04:00
parent ab5aae8bd1
commit 00f27f0cbe
4 changed files with 93 additions and 5 deletions

49
page.go
View file

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"os" "os"
"sort"
"github.com/aymerick/raymond" "github.com/aymerick/raymond"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -20,7 +21,7 @@ import (
type Metadata struct { type Metadata struct {
Title string Title string
Summary string Summary string
Date string // TODO: better representation? time.Time might cause timezone issues... Time int64 // unix timestamp
} }
// Page stores the contents of a blog post. // Page stores the contents of a blog post.
@ -76,3 +77,49 @@ func newPage(slug string) (*Page, error) {
func (p *Page) render(tpl *raymond.Template) (string, error) { func (p *Page) render(tpl *raymond.Template) (string, error) {
return tpl.Exec(p) return tpl.Exec(p)
} }
func (p *Page) String() string {
return p.Slug
}
type pages []*Page
func insertOrUpdate(ps pages, p *Page) pages {
defer sort.Sort(ps)
for i, pg := range ps {
if pg.Slug == p.Slug {
ps[i] = p
return ps
}
}
ps = append(ps, p)
return ps
}
func remove(ps pages, slug string) pages {
for i, pg := range ps {
if pg.Slug == slug {
ps = append(ps[:i], ps[i+1:]...)
break
}
}
fmt.Println(ps)
return ps
}
// Len implements sort.Interface
func (ps pages) Len() int {
return len(ps)
}
// Less implements sort.Interface
func (ps pages) Less(i, j int) bool {
return ps[i].Metadata.Time > ps[j].Metadata.Time
}
// Swap implements sort.Interface
func (ps pages) Swap(i, j int) {
temp := ps[i]
ps[i] = ps[j]
ps[j] = temp
}

View file

@ -7,8 +7,11 @@ import (
"net/http" "net/http"
"os" "os"
"runtime" "runtime"
"sort"
"strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/aymerick/raymond" "github.com/aymerick/raymond"
"github.com/rjeczalik/notify" "github.com/rjeczalik/notify"
@ -16,11 +19,13 @@ import (
) )
type server struct { type server struct {
pages []*Page
staticHandler http.Handler staticHandler http.Handler
tplMutex sync.RWMutex tplMutex sync.RWMutex
templates map[string]*raymond.Template templates map[string]*raymond.Template
pgMutex sync.RWMutex
pages
} }
func newServer() (*server, error) { func newServer() (*server, error) {
@ -43,6 +48,27 @@ func newServer() (*server, error) {
return nil, err return nil, err
} }
pagesLn := &listener{
folder: "posts/",
update: func(file string) error {
s.pgMutex.Lock()
defer s.pgMutex.Unlock()
page, err := newPage(strings.TrimSuffix(file, ".md"))
if err != nil {
return err
}
s.pages = insertOrUpdate(s.pages, page)
return nil
},
clean: func(file string) error {
s.pgMutex.Lock()
defer s.pgMutex.Unlock()
s.pages = remove(s.pages, strings.TrimSuffix(file, ".md"))
return nil
},
}
go pagesLn.listen()
templatesLn := &listener{ templatesLn := &listener{
folder: "templates/", folder: "templates/",
update: func(file string) error { update: func(file string) error {
@ -99,7 +125,9 @@ func (s *server) refreshPages() error {
return err return err
} }
s.pages = make([]*Page, 0, len(files)) s.pgMutex.Lock()
defer s.pgMutex.Unlock()
s.pages = make(pages, 0, len(files))
for _, f := range files { for _, f := range files {
filename := f.Name() filename := f.Name()
@ -112,6 +140,7 @@ func (s *server) refreshPages() error {
log.Printf("Loaded page %s", filename) log.Printf("Loaded page %s", filename)
} }
} }
sort.Sort(s.pages)
return nil return nil
} }
@ -121,6 +150,14 @@ func loadTemplate(file string) (*raymond.Template, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("Could not parse %s template: %w", file, err) return nil, fmt.Errorf("Could not parse %s template: %w", file, err)
} }
tpl.RegisterHelper("datetime", func(timeStr string) string {
timestamp, err := strconv.ParseInt(timeStr, 10, 64)
if err != nil {
log.Printf("Could not parse timestamp '%v', falling back to current time", timeStr)
timestamp = time.Now().Unix()
}
return time.Unix(timestamp, 0).Format("Jan 2 2006, 3:04 PM")
})
log.Printf("Loaded template: %s", file) log.Printf("Loaded template: %s", file)
return tpl, nil return tpl, nil
} }
@ -303,6 +340,8 @@ func (s *server) router(res http.ResponseWriter, req *http.Request) {
return return
} }
s.pgMutex.RLock()
defer s.pgMutex.RUnlock()
for _, p := range s.pages { for _, p := range s.pages {
if p.Slug == slug { if p.Slug == slug {
s.renderPage(p, res, req) s.renderPage(p, res, req)
@ -345,6 +384,8 @@ func (s *server) homePage(res http.ResponseWriter, req *http.Request) {
var posts string var posts string
s.pgMutex.RLock()
defer s.pgMutex.RUnlock()
for _, p := range s.pages { for _, p := range s.pages {
summary, err := p.render(s.templates["summary"]) summary, err := p.render(s.templates["summary"])
if err != nil { if err != nil {

View file

@ -1,5 +1,5 @@
<article> <article>
<date>{{metadata.date}}</date> <date>{{datetime metadata.time}}</date>
<h1>{{metadata.title}}</h1> <h1>{{metadata.title}}</h1>
<h2>{{metadata.summary}}</h2> <h2>{{metadata.summary}}</h2>
<section class="content"> <section class="content">

View file

@ -1,5 +1,5 @@
<article> <article>
<date>{{metadata.date}}</date> <date>{{datetime metadata.time}}</date>
<h1>{{metadata.title}}</h1> <h1>{{metadata.title}}</h1>
<h2>{{metadata.summary}}</h2> <h2>{{metadata.summary}}</h2>
<a class="load-content" href="/{{slug}}">Read more &rarr;</a> <a class="load-content" href="/{{slug}}">Read more &rarr;</a>