diff options
| author | Gab Virebent <gabriel1@virebent.art> | 2026-07-07 16:24:28 +0200 |
|---|---|---|
| committer | Gab Virebent <gabriel1@virebent.art> | 2026-07-07 16:24:28 +0200 |
| commit | 37f156c30e2a2b01c840d39f0dd2bdbf811732d6 (patch) | |
| tree | e2ce84dc7f88ce0eb312899448f91b7bb93ca8a3 /internal/cms/content.go | |
| download | gemcms-37f156c30e2a2b01c840d39f0dd2bdbf811732d6.tar.gz gemcms-37f156c30e2a2b01c840d39f0dd2bdbf811732d6.tar.xz gemcms-37f156c30e2a2b01c840d39f0dd2bdbf811732d6.zip | |
Diffstat (limited to 'internal/cms/content.go')
| -rw-r--r-- | internal/cms/content.go | 166 |
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) +} |
