prose/post.go
Naman Sood 810af7fec0 post: incorrect use of defer
Signed-off-by: Naman Sood <mail@nsood.in>
2021-03-19 01:31:20 -04:00

175 lines
3.5 KiB
Go

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 {
for i, post := range pl {
if post.Slug == p.Slug {
pl[i] = p
sort.Sort(pl)
return pl
}
}
pl = append(pl, p)
sort.Sort(pl)
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
}