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 }