rename 'pages' to 'posts' and move functionality into own file
Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
parent
00f27f0cbe
commit
ef112be9bd
3 changed files with 198 additions and 187 deletions
125
page.go
125
page.go
|
@ -1,125 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/aymerick/raymond"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/yuin/goldmark"
|
|
||||||
highlighting "github.com/yuin/goldmark-highlighting"
|
|
||||||
meta "github.com/yuin/goldmark-meta"
|
|
||||||
"github.com/yuin/goldmark/extension"
|
|
||||||
"github.com/yuin/goldmark/parser"
|
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Metadata stores the data about a page that needs to be visible
|
|
||||||
// at the home page.
|
|
||||||
type Metadata struct {
|
|
||||||
Title string
|
|
||||||
Summary string
|
|
||||||
Time int64 // unix timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Page stores the contents of a blog post.
|
|
||||||
type Page struct {
|
|
||||||
Slug string
|
|
||||||
Metadata Metadata
|
|
||||||
Contents string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPage(slug string) (*Page, error) {
|
|
||||||
data, err := os.ReadFile("posts/" + slug + ".md")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not read file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
md := goldmark.New(
|
|
||||||
goldmark.WithExtensions(
|
|
||||||
extension.Linkify,
|
|
||||||
extension.Strikethrough,
|
|
||||||
extension.Typographer,
|
|
||||||
meta.Meta,
|
|
||||||
highlighting.Highlighting,
|
|
||||||
),
|
|
||||||
goldmark.WithRendererOptions(
|
|
||||||
html.WithUnsafe(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
var converted bytes.Buffer
|
|
||||||
ctx := parser.NewContext()
|
|
||||||
err = md.Convert(data, &converted, parser.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse markdown: %s", err)
|
|
||||||
}
|
|
||||||
mdMap, err := meta.TryGet(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse metadata: %s", err)
|
|
||||||
}
|
|
||||||
var metadata Metadata
|
|
||||||
err = mapstructure.Decode(mdMap, &metadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not destructure metadata: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
page := &Page{
|
|
||||||
Slug: slug,
|
|
||||||
Metadata: metadata,
|
|
||||||
Contents: converted.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return page, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Page) render(tpl *raymond.Template) (string, error) {
|
|
||||||
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
|
|
||||||
}
|
|
174
post.go
Normal file
174
post.go
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aymerick/raymond"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
highlighting "github.com/yuin/goldmark-highlighting"
|
||||||
|
meta "github.com/yuin/goldmark-meta"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metadata stores the data about a post that needs to be visible
|
||||||
|
// at the home page.
|
||||||
|
type Metadata struct {
|
||||||
|
Title string
|
||||||
|
Summary string
|
||||||
|
Time int64 // unix timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post stores the contents of a blog post.
|
||||||
|
type Post struct {
|
||||||
|
Slug string
|
||||||
|
Metadata Metadata
|
||||||
|
Contents string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPost(slug string) (*Post, error) {
|
||||||
|
data, err := os.ReadFile("posts/" + slug + ".md")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
md := goldmark.New(
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
extension.Linkify,
|
||||||
|
extension.Strikethrough,
|
||||||
|
extension.Typographer,
|
||||||
|
meta.Meta,
|
||||||
|
highlighting.Highlighting,
|
||||||
|
),
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var converted bytes.Buffer
|
||||||
|
ctx := parser.NewContext()
|
||||||
|
err = md.Convert(data, &converted, parser.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse markdown: %s", err)
|
||||||
|
}
|
||||||
|
mdMap, err := meta.TryGet(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse metadata: %s", err)
|
||||||
|
}
|
||||||
|
var metadata Metadata
|
||||||
|
err = mapstructure.Decode(mdMap, &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not destructure metadata: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
post := &Post{
|
||||||
|
Slug: slug,
|
||||||
|
Metadata: metadata,
|
||||||
|
Contents: converted.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return post, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Post) render(tpl *raymond.Template) (string, error) {
|
||||||
|
return tpl.Exec(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Post) String() string {
|
||||||
|
return p.Slug
|
||||||
|
}
|
||||||
|
|
||||||
|
type postList []*Post
|
||||||
|
|
||||||
|
func newPostList() (postList, error) {
|
||||||
|
files, err := os.ReadDir("posts/")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pl := make(postList, 0, len(files))
|
||||||
|
for _, f := range files {
|
||||||
|
filename := f.Name()
|
||||||
|
|
||||||
|
if strings.HasSuffix(filename, ".md") {
|
||||||
|
post, err := newPost(strings.TrimSuffix(filename, ".md"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not render %s: %s", filename, err)
|
||||||
|
}
|
||||||
|
pl = append(pl, post)
|
||||||
|
log.Printf("Loaded post %s", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(pl)
|
||||||
|
|
||||||
|
return pl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertOrUpdatePost(pl postList, p *Post) postList {
|
||||||
|
defer sort.Sort(pl)
|
||||||
|
for i, post := range pl {
|
||||||
|
if post.Slug == p.Slug {
|
||||||
|
pl[i] = p
|
||||||
|
return pl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pl = append(pl, p)
|
||||||
|
return pl
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePost(pl postList, slug string) postList {
|
||||||
|
for i, post := range pl {
|
||||||
|
if post.Slug == slug {
|
||||||
|
pl = append(pl[:i], pl[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(pl)
|
||||||
|
return pl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements sort.Interface
|
||||||
|
func (pl postList) Len() int {
|
||||||
|
return len(pl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less implements sort.Interface
|
||||||
|
func (pl postList) Less(i, j int) bool {
|
||||||
|
return pl[i].Metadata.Time > pl[j].Metadata.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap implements sort.Interface
|
||||||
|
func (pl postList) Swap(i, j int) {
|
||||||
|
temp := pl[i]
|
||||||
|
pl[i] = pl[j]
|
||||||
|
pl[j] = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPostListener(update func(func(postList) postList)) *listener {
|
||||||
|
ln := &listener{
|
||||||
|
folder: "posts/",
|
||||||
|
update: func(file string) error {
|
||||||
|
post, err := newPost(strings.TrimSuffix(file, ".md"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
update(func(oldList postList) postList {
|
||||||
|
return insertOrUpdatePost(oldList, post)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
clean: func(file string) error {
|
||||||
|
update(func(oldList postList) postList {
|
||||||
|
return removePost(oldList, strings.TrimSuffix(file, ".md"))
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return ln
|
||||||
|
}
|
86
server.go
86
server.go
|
@ -7,7 +7,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -24,8 +23,8 @@ type server struct {
|
||||||
tplMutex sync.RWMutex
|
tplMutex sync.RWMutex
|
||||||
templates map[string]*raymond.Template
|
templates map[string]*raymond.Template
|
||||||
|
|
||||||
pgMutex sync.RWMutex
|
postsMutex sync.RWMutex
|
||||||
pages
|
postList
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer() (*server, error) {
|
func newServer() (*server, error) {
|
||||||
|
@ -33,10 +32,13 @@ func newServer() (*server, error) {
|
||||||
staticHandler: http.FileServer(http.Dir("static/")),
|
staticHandler: http.FileServer(http.Dir("static/")),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.refreshPages()
|
posts, err := newPostList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
s.postsMutex.Lock()
|
||||||
|
s.postList = posts
|
||||||
|
s.postsMutex.Unlock()
|
||||||
|
|
||||||
err = s.refreshTemplates()
|
err = s.refreshTemplates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -48,26 +50,12 @@ func newServer() (*server, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pagesLn := &listener{
|
postsLn := newPostListener(func(updateFn func(postList) postList) {
|
||||||
folder: "posts/",
|
s.postsMutex.Lock()
|
||||||
update: func(file string) error {
|
defer s.postsMutex.Unlock()
|
||||||
s.pgMutex.Lock()
|
s.postList = updateFn(s.postList)
|
||||||
defer s.pgMutex.Unlock()
|
})
|
||||||
page, err := newPage(strings.TrimSuffix(file, ".md"))
|
go postsLn.listen()
|
||||||
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/",
|
||||||
|
@ -119,32 +107,6 @@ func newServer() (*server, error) {
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) refreshPages() error {
|
|
||||||
files, err := os.ReadDir("posts/")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.pgMutex.Lock()
|
|
||||||
defer s.pgMutex.Unlock()
|
|
||||||
s.pages = make(pages, 0, len(files))
|
|
||||||
for _, f := range files {
|
|
||||||
filename := f.Name()
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".md") {
|
|
||||||
page, err := newPage(strings.TrimSuffix(filename, ".md"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not render %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
s.pages = append(s.pages, page)
|
|
||||||
log.Printf("Loaded page %s", filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(s.pages)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadTemplate(file string) (*raymond.Template, error) {
|
func loadTemplate(file string) (*raymond.Template, error) {
|
||||||
tpl, err := raymond.ParseFile("templates/" + file + ".html")
|
tpl, err := raymond.ParseFile("templates/" + file + ".html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -340,11 +302,11 @@ func (s *server) router(res http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.pgMutex.RLock()
|
s.postsMutex.RLock()
|
||||||
defer s.pgMutex.RUnlock()
|
defer s.postsMutex.RUnlock()
|
||||||
for _, p := range s.pages {
|
for _, p := range s.postList {
|
||||||
if p.Slug == slug {
|
if p.Slug == slug {
|
||||||
s.renderPage(p, res, req)
|
s.postPage(p, res, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -358,7 +320,7 @@ func (s *server) errorInRequest(res http.ResponseWriter, req *http.Request, err
|
||||||
log.Printf("ERR %s: %s", req.URL.Path, err)
|
log.Printf("ERR %s: %s", req.URL.Path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) createPage(title, contents string) (string, error) {
|
func (s *server) createWebPage(title, contents string) (string, error) {
|
||||||
ctx := map[string]interface{}{
|
ctx := map[string]interface{}{
|
||||||
"title": title,
|
"title": title,
|
||||||
"contents": contents,
|
"contents": contents,
|
||||||
|
@ -366,13 +328,13 @@ func (s *server) createPage(title, contents string) (string, error) {
|
||||||
return s.templates["page"].Exec(ctx)
|
return s.templates["page"].Exec(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) renderPage(p *Page, res http.ResponseWriter, req *http.Request) {
|
func (s *server) postPage(p *Post, res http.ResponseWriter, req *http.Request) {
|
||||||
res.Header().Add("content-type", "text/html")
|
res.Header().Add("content-type", "text/html")
|
||||||
contents, err := p.render(s.templates["fullpost"])
|
contents, err := p.render(s.templates["fullpost"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.errorInRequest(res, req, err)
|
s.errorInRequest(res, req, err)
|
||||||
}
|
}
|
||||||
page, err := s.createPage(p.Metadata.Title, contents)
|
page, err := s.createWebPage(p.Metadata.Title, contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.errorInRequest(res, req, err)
|
s.errorInRequest(res, req, err)
|
||||||
}
|
}
|
||||||
|
@ -384,17 +346,17 @@ func (s *server) homePage(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
var posts string
|
var posts string
|
||||||
|
|
||||||
s.pgMutex.RLock()
|
s.postsMutex.RLock()
|
||||||
defer s.pgMutex.RUnlock()
|
defer s.postsMutex.RUnlock()
|
||||||
for _, p := range s.pages {
|
for _, p := range s.postList {
|
||||||
summary, err := p.render(s.templates["summary"])
|
summary, err := p.render(s.templates["summary"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("could not render page summary for %s", p.Slug)
|
log.Printf("could not render post summary for %s", p.Slug)
|
||||||
}
|
}
|
||||||
posts = posts + summary
|
posts = posts + summary
|
||||||
}
|
}
|
||||||
|
|
||||||
page, err := s.createPage("Home", posts)
|
page, err := s.createWebPage("Home", posts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.errorInRequest(res, req, err)
|
s.errorInRequest(res, req, err)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue