package markdown import ( "bytes" "fmt" "strings" "gopkg.in/yaml.v3" ) // ParsedContent represents a markdown file with frontmatter type ParsedContent struct { Frontmatter map[string]interface{} Content string } // Parse extracts YAML frontmatter and markdown content from a file func Parse(input string) (*ParsedContent, error) { // Check if the file starts with frontmatter delimiter if !strings.HasPrefix(input, "---\n") && !strings.HasPrefix(input, "---\r\n") { // No frontmatter, return entire content as markdown return &ParsedContent{ Frontmatter: make(map[string]interface{}), Content: input, }, nil } // Find the end of frontmatter lines := strings.Split(input, "\n") endIdx := -1 for i := 1; i < len(lines); i++ { trimmed := strings.TrimSpace(lines[i]) if trimmed == "---" { endIdx = i break } } // If we didn't find the closing delimiter, treat as no frontmatter if endIdx == -1 { return &ParsedContent{ Frontmatter: make(map[string]interface{}), Content: input, }, nil } // Extract frontmatter YAML (between the delimiters) frontmatterLines := lines[1:endIdx] frontmatterYAML := strings.Join(frontmatterLines, "\n") // Parse YAML var frontmatter map[string]interface{} if err := yaml.Unmarshal([]byte(frontmatterYAML), &frontmatter); err != nil { return nil, fmt.Errorf("failed to parse frontmatter YAML: %w", err) } // Extract content (everything after the closing delimiter) contentLines := lines[endIdx+1:] content := strings.Join(contentLines, "\n") // Trim leading newline if present content = strings.TrimPrefix(content, "\n") return &ParsedContent{ Frontmatter: frontmatter, Content: content, }, nil } // Serialize combines frontmatter and content back into a single string func Serialize(frontmatter map[string]interface{}, content string) (string, error) { // If no frontmatter, return just the content if len(frontmatter) == 0 { return content, nil } // Marshal frontmatter to YAML var buf bytes.Buffer encoder := yaml.NewEncoder(&buf) encoder.SetIndent(2) if err := encoder.Encode(frontmatter); err != nil { return "", fmt.Errorf("failed to marshal frontmatter: %w", err) } if err := encoder.Close(); err != nil { return "", fmt.Errorf("failed to close encoder: %w", err) } // Combine with delimiters yamlStr := buf.String() // Remove trailing newline from YAML output yamlStr = strings.TrimSuffix(yamlStr, "\n") result := fmt.Sprintf("---\n%s\n---\n%s", yamlStr, content) return result, nil }