Initialize blog scaffold
Add the CLI, site, and sample content so the project can run locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9d2628b318
commit
b78f4b39c9
40 changed files with 9140 additions and 0 deletions
130
backend/internal/cli/content.go
Normal file
130
backend/internal/cli/content.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func loadPosts(root string) ([]postFile, error) {
|
||||
postsDir := filepath.Join(root, defaultPostsDir)
|
||||
entries, err := os.ReadDir(postsDir)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var posts []postFile
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
|
||||
continue
|
||||
}
|
||||
|
||||
post, err := readPostFile(filepath.Join(postsDir, entry.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
posts = append(posts, post)
|
||||
}
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func loadPostBySlug(root string, slug string) (postFile, error) {
|
||||
cleanSlug := sanitizeSlug(slug)
|
||||
if cleanSlug == "" {
|
||||
return postFile{}, errors.New("missing slug")
|
||||
}
|
||||
|
||||
posts, err := loadPosts(root)
|
||||
if err != nil {
|
||||
return postFile{}, err
|
||||
}
|
||||
for _, post := range posts {
|
||||
fileSlug := strings.TrimSuffix(filepath.Base(post.Path), ".md")
|
||||
if post.Frontmatter.Slug == cleanSlug || fileSlug == cleanSlug {
|
||||
return post, nil
|
||||
}
|
||||
}
|
||||
return postFile{}, fmt.Errorf("post not found: %s", cleanSlug)
|
||||
}
|
||||
|
||||
func readPostFile(path string) (postFile, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return postFile{}, err
|
||||
}
|
||||
|
||||
frontmatter, body, err := splitFrontmatter(data)
|
||||
if err != nil {
|
||||
return postFile{}, fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
|
||||
var meta postFrontmatter
|
||||
if err := yaml.Unmarshal(frontmatter, &meta); err != nil {
|
||||
return postFile{}, fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
if meta.Slug == "" {
|
||||
meta.Slug = strings.TrimSuffix(filepath.Base(path), ".md")
|
||||
}
|
||||
if meta.Status == "" {
|
||||
meta.Status = "draft"
|
||||
}
|
||||
|
||||
return postFile{
|
||||
Path: path,
|
||||
Frontmatter: meta,
|
||||
Body: strings.TrimPrefix(string(body), "\n"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func writePostFile(post postFile) error {
|
||||
var frontmatter bytes.Buffer
|
||||
encoder := yaml.NewEncoder(&frontmatter)
|
||||
encoder.SetIndent(2)
|
||||
if err := encoder.Encode(post.Frontmatter); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := encoder.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var output bytes.Buffer
|
||||
output.WriteString("---\n")
|
||||
output.Write(frontmatter.Bytes())
|
||||
output.WriteString("---\n\n")
|
||||
output.WriteString(strings.TrimLeft(post.Body, "\n"))
|
||||
|
||||
tmp := post.Path + ".tmp"
|
||||
if err := os.WriteFile(tmp, output.Bytes(), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, post.Path)
|
||||
}
|
||||
|
||||
func splitFrontmatter(data []byte) ([]byte, []byte, error) {
|
||||
if !bytes.HasPrefix(data, []byte("---\n")) {
|
||||
return nil, nil, errors.New("missing frontmatter opening marker")
|
||||
}
|
||||
|
||||
rest := data[len("---\n"):]
|
||||
idx := bytes.Index(rest, []byte("\n---"))
|
||||
if idx < 0 {
|
||||
return nil, nil, errors.New("missing frontmatter closing marker")
|
||||
}
|
||||
|
||||
frontmatter := rest[:idx]
|
||||
body := rest[idx+len("\n---"):]
|
||||
if bytes.HasPrefix(body, []byte("\r\n")) {
|
||||
body = body[2:]
|
||||
} else if bytes.HasPrefix(body, []byte("\n")) {
|
||||
body = body[1:]
|
||||
}
|
||||
return frontmatter, body, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue