package discovery import ( "strings" ) // Frontmatter holds the leniently parsed leading YAML block of an item. type Frontmatter struct { Name string Description string Tags []string Requires []string } // ParseFrontmatter extracts name, description, tags and requires from a // leading `---` fenced block. Parsing is deliberately lenient: a block that // opens but never closes, or contains lines without a colon, yields warnings // instead of failures; the parseable prefix still wins. Files without a // leading fence have no frontmatter and no warning. func ParseFrontmatter(data []byte) (Frontmatter, []string) { var fm Frontmatter var warnings []string lines := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n") if len(lines) == 0 || strings.TrimSpace(lines[0]) != "---" { return fm, nil } closed := false for _, line := range lines[1:] { if strings.TrimSpace(line) == "---" { closed = true break } trimmed := strings.TrimSpace(line) if trimmed == "" { continue } key, value, ok := strings.Cut(trimmed, ":") if !ok { warnings = append(warnings, "malformed frontmatter line (missing colon): "+trimmed) continue } key = strings.TrimSpace(key) value = strings.TrimSpace(value) switch strings.ToLower(key) { case "name": fm.Name = value case "description": fm.Description = value case "tags": fm.Tags = splitList(value) case "requires": fm.Requires = splitList(value) } } if !closed { warnings = append(warnings, "frontmatter block not closed") } return fm, warnings } func splitList(v string) []string { if v == "" { return nil } parts := strings.Split(v, ",") var out []string for _, p := range parts { if t := strings.TrimSpace(p); t != "" { out = append(out, t) } } return out }