diff options
Diffstat (limited to 'internal/content')
-rw-r--r-- | internal/content/posts.go | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/internal/content/posts.go b/internal/content/posts.go new file mode 100644 index 0000000..f4c6c76 --- /dev/null +++ b/internal/content/posts.go @@ -0,0 +1,136 @@ +package content + +import ( + "bytes" + "os" + "path" + "path/filepath" + "slices" + "strings" + "time" + + "go.alanpearce.eu/x/log" + + "github.com/adrg/frontmatter" + mapset "github.com/deckarep/golang-set/v2" + fences "github.com/stefanfritsch/goldmark-fences" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + htmlrenderer "github.com/yuin/goldmark/renderer/html" + "gitlab.com/tozd/go/errors" +) + +type PostMatter struct { + Date time.Time `toml:"date"` + Description string `toml:"description"` + Title string `toml:"title"` + Taxonomies struct { + Tags []string `toml:"tags"` + } `toml:"taxonomies"` +} + +type Post struct { + Input string + Output string + Basename string + URL string + Content string + PostMatter +} + +type Tags mapset.Set[string] + +var markdown = goldmark.New( + goldmark.WithRendererOptions( + htmlrenderer.WithUnsafe(), + ), + goldmark.WithExtensions( + extension.GFM, + extension.Footnote, + extension.Typographer, + &fences.Extender{}, + ), +) + +func GetPost(filename string) (*PostMatter, []byte, error) { + matter := PostMatter{} + content, err := os.Open(filename) + if err != nil { + return nil, nil, errors.WithMessagef(err, "could not open post %s", filename) + } + defer content.Close() + rest, err := frontmatter.MustParse(content, &matter) + if err != nil { + return nil, nil, errors.WithMessagef( + err, + "could not parse front matter of post %s", + filename, + ) + } + + return &matter, rest, nil +} + +func RenderMarkdown(content []byte) (string, error) { + var buf bytes.Buffer + if err := markdown.Convert(content, &buf); err != nil { + return "", errors.WithMessage(err, "could not convert markdown content") + } + + return buf.String(), nil +} + +type Config struct { + Root string + InputDir string + OutputDir string +} + +func ReadPosts(config *Config, log *log.Logger) ([]Post, Tags, error) { + tags := mapset.NewSet[string]() + posts := []Post{} + subdir := filepath.Join(config.Root, config.InputDir) + files, err := os.ReadDir(subdir) + if err != nil { + return nil, nil, errors.WithMessagef(err, "could not read post directory %s", subdir) + } + outputReplacer := strings.NewReplacer(config.Root, config.OutputDir, ".md", "/index.html") + urlReplacer := strings.NewReplacer(config.Root, "", ".md", "/") + for _, f := range files { + pathFromRoot := filepath.Join(subdir, f.Name()) + if !f.IsDir() && path.Ext(pathFromRoot) == ".md" { + output := outputReplacer.Replace(pathFromRoot) + url := urlReplacer.Replace(pathFromRoot) + log.Debug("reading post", "post", pathFromRoot) + matter, content, err := GetPost(pathFromRoot) + if err != nil { + return nil, nil, err + } + + for _, tag := range matter.Taxonomies.Tags { + tags.Add(strings.ToLower(tag)) + } + + log.Debug("rendering markdown in post", "post", pathFromRoot) + html, err := RenderMarkdown(content) + if err != nil { + return nil, nil, err + } + post := Post{ + Input: pathFromRoot, + Output: output, + Basename: filepath.Base(url), + URL: url, + PostMatter: *matter, + Content: html, + } + + posts = append(posts, post) + } + } + slices.SortFunc(posts, func(a, b Post) int { + return b.Date.Compare(a.Date) + }) + + return posts, tags, nil +} |