package parser import ( "bytes" "fmt" "gopkg.in/yaml.v3" ) // frontmatterDelim is the delimiter line that opens and closes a YAML // frontmatter block at the top of a slide file. var frontmatterDelim = []byte("---") // frontmatter holds the metadata fields recognized in a slide's YAML // frontmatter block. type frontmatter struct { Title string `yaml:"title"` Class string `yaml:"class"` Notes string `yaml:"notes"` Skip bool `yaml:"skip"` Incremental bool `yaml:"incremental"` } // splitFrontmatter separates a leading YAML frontmatter block (delimited by // "---" lines) from the remaining Markdown body. If data has no frontmatter // block, it returns a nil frontmatter slice and the original data as body. func splitFrontmatter(data []byte) (fm []byte, body []byte) { rest := bytes.TrimLeft(data, "\r\n") if !bytes.HasPrefix(rest, frontmatterDelim) { return nil, data } afterOpen := rest[len(frontmatterDelim):] afterOpen = bytes.TrimLeft(afterOpen, " \t") if len(afterOpen) > 0 && afterOpen[0] != '\n' && afterOpen[0] != '\r' { // The "---" is followed by other content on the same line, so it is // not a frontmatter delimiter. return nil, data } nlIdx := bytes.IndexByte(afterOpen, '\n') if nlIdx == -1 { return nil, data } remainder := afterOpen[nlIdx+1:] closeIdx := bytes.Index(remainder, []byte("\n---")) if closeIdx == -1 { return nil, data } fm = remainder[:closeIdx] after := remainder[closeIdx+len("\n---"):] after = bytes.TrimLeft(after, "\r") if nl := bytes.IndexByte(after, '\n'); nl != -1 { after = after[nl+1:] } else { after = nil } return fm, after } // parseFrontmatter extracts and parses the frontmatter block from data, // returning the parsed metadata and the remaining Markdown body. func parseFrontmatter(data []byte) (frontmatter, []byte, error) { raw, body := splitFrontmatter(data) var fm frontmatter if raw == nil { return fm, body, nil } if err := yaml.Unmarshal(raw, &fm); err != nil { return fm, body, fmt.Errorf("parsing frontmatter: %w", err) } return fm, body, nil }