summaryrefslogtreecommitdiffstats
path: root/internal/cms/content.go
diff options
context:
space:
mode:
authorGab Virebent <gabriel1@virebent.art>2026-07-07 16:24:28 +0200
committerGab Virebent <gabriel1@virebent.art>2026-07-07 16:24:28 +0200
commit37f156c30e2a2b01c840d39f0dd2bdbf811732d6 (patch)
treee2ce84dc7f88ce0eb312899448f91b7bb93ca8a3 /internal/cms/content.go
downloadgemcms-main.tar.gz
gemcms-main.tar.xz
gemcms-main.zip
Initial commitHEADmain
Diffstat (limited to 'internal/cms/content.go')
-rw-r--r--internal/cms/content.go166
1 files changed, 166 insertions, 0 deletions
diff --git a/internal/cms/content.go b/internal/cms/content.go
new file mode 100644
index 0000000..f4b0343
--- /dev/null
+++ b/internal/cms/content.go
@@ -0,0 +1,166 @@
+package cms
+
+import (
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+func LoadDocuments(root string) ([]Document, error) {
+ contentRoot := filepath.Join(root, "content")
+ var docs []Document
+ if _, err := os.Stat(contentRoot); err != nil {
+ return nil, err
+ }
+ err := filepath.WalkDir(contentRoot, func(path string, entry fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if entry.IsDir() || filepath.Ext(path) != ".gmi" {
+ return nil
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ doc, err := ParseDocument(path, string(data))
+ if err != nil {
+ return err
+ }
+ docs = append(docs, doc)
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ sort.Slice(docs, func(i, j int) bool {
+ if docs[i].Type != docs[j].Type {
+ return docs[i].Type < docs[j].Type
+ }
+ if docs[i].Date != docs[j].Date {
+ return docs[i].Date > docs[j].Date
+ }
+ return docs[i].Slug < docs[j].Slug
+ })
+ return docs, nil
+}
+
+func ParseDocument(path, data string) (Document, error) {
+ meta, body, hasMeta := splitFrontmatter(data)
+ doc := Document{
+ SourcePath: path,
+ Body: strings.TrimLeft(body, "\n"),
+ }
+ if hasMeta {
+ table, err := parseMetadata(meta)
+ if err != nil {
+ return Document{}, fmt.Errorf("%s: parse frontmatter: %w", path, err)
+ }
+ doc.Type = table.values["type"]
+ doc.Slug = table.values["slug"]
+ doc.Title = table.values["title"]
+ doc.Date = table.values["date"]
+ doc.Section = table.values["section"]
+ doc.Tags = table.lists["tags"]
+ doc.Draft = table.values["draft"] == "true"
+ }
+ if doc.Type == "" {
+ doc.Type = inferType(path)
+ }
+ if doc.Slug == "" {
+ doc.Slug = inferSlug(path)
+ }
+ if doc.Title == "" {
+ doc.Title = inferTitle(doc.Body, doc.Slug)
+ }
+ if doc.Type == "article" {
+ if doc.Section == "" {
+ doc.Section = "articles"
+ }
+ if doc.Date == "" {
+ doc.Date = inferDate(path)
+ }
+ }
+ return doc, nil
+}
+
+func writeDocument(path string, doc Document) error {
+ var b strings.Builder
+ b.WriteString("+++\n")
+ b.WriteString("title = " + formatString(doc.Title) + "\n")
+ b.WriteString("type = " + formatString(doc.Type) + "\n")
+ b.WriteString("slug = " + formatString(doc.Slug) + "\n")
+ if doc.Date != "" {
+ b.WriteString("date = " + formatString(doc.Date) + "\n")
+ }
+ if doc.Section != "" {
+ b.WriteString("section = " + formatString(doc.Section) + "\n")
+ }
+ if len(doc.Tags) > 0 {
+ b.WriteString("tags = " + formatStringList(doc.Tags) + "\n")
+ }
+ if doc.Draft {
+ b.WriteString("draft = true\n")
+ }
+ b.WriteString("+++\n\n")
+ b.WriteString(strings.TrimLeft(doc.Body, "\n"))
+ return writeNewFile(path, []byte(b.String()))
+}
+
+func splitFrontmatter(data string) (string, string, bool) {
+ data = strings.ReplaceAll(data, "\r\n", "\n")
+ delimiter := ""
+ switch {
+ case strings.HasPrefix(data, "+++\n"):
+ delimiter = "+++"
+ case strings.HasPrefix(data, "---\n"):
+ delimiter = "---"
+ default:
+ return "", data, false
+ }
+ rest := strings.TrimPrefix(data, delimiter+"\n")
+ idx := strings.Index(rest, "\n"+delimiter+"\n")
+ if idx < 0 {
+ return "", data, false
+ }
+ meta := rest[:idx]
+ body := rest[idx+len("\n"+delimiter+"\n"):]
+ return meta, body, true
+}
+
+func inferType(path string) string {
+ clean := filepath.ToSlash(path)
+ if strings.Contains(clean, "/articles/") {
+ return "article"
+ }
+ return "page"
+}
+
+func inferSlug(path string) string {
+ name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
+ if len(name) > len("2006-01-02-") && name[4] == '-' && name[7] == '-' && name[10] == '-' {
+ name = name[11:]
+ }
+ return slugify(name)
+}
+
+func inferDate(path string) string {
+ name := filepath.Base(path)
+ if len(name) >= len("2006-01-02") && name[4] == '-' && name[7] == '-' {
+ return name[:10]
+ }
+ return ""
+}
+
+func inferTitle(body, slug string) string {
+ for _, line := range strings.Split(body, "\n") {
+ line = strings.TrimSpace(line)
+ if strings.HasPrefix(line, "# ") {
+ return strings.TrimSpace(strings.TrimPrefix(line, "# "))
+ }
+ }
+ return titleize(slug)
+}