// Package frontmatter parses the key: value front-matter block that may // open a mopac-pdf markdown input (delimited by --- lines). package frontmatter import ( "fmt" "strings" ) // Meta is the parsed front-matter. The well-known keys mopac-pdf templates // consume are Title, Subtitle, Author, Date, Classification and Template; // anything else found in the block is kept in Extra (never an error: the // currency of this stack is loose markdown). type Meta struct { Title string Subtitle string Author string Date string Classification string Template string Extra map[string]string } // Split separates an optional front-matter block from the markdown body and // parses it. Input without a leading --- line is all body, empty Meta. func Split(input string) (Meta, string, error) { var meta Meta first, rest, ok := strings.Cut(input, "\n") if strings.TrimRight(first, "\r") != "---" || !ok { return meta, input, nil } end := -1 var block []string for i, line := range strings.Split(rest, "\n") { trimmed := strings.TrimRight(line, "\r") if trimmed == "---" || trimmed == "..." { end = i break } block = append(block, trimmed) } if end < 0 { return meta, input, fmt.Errorf("frontmatter: opening --- without closing ---") } body := strings.Join(strings.Split(rest, "\n")[end+1:], "\n") body = strings.TrimPrefix(body, "\n") meta.Extra = map[string]string{} for _, line := range block { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { continue } key, value, ok := strings.Cut(line, ":") if !ok { return meta, "", fmt.Errorf("frontmatter: not a key: value line: %q", line) } key = strings.TrimSpace(key) value = strings.Trim(strings.TrimSpace(value), `"'`) switch strings.ToLower(key) { case "title": meta.Title = value case "subtitle": meta.Subtitle = value case "author": meta.Author = value case "date": meta.Date = value case "classification": meta.Classification = value case "template": meta.Template = value default: meta.Extra[key] = value } } return meta, body, nil }