package note import ( "strconv" "strings" "unicode" ) type Section struct { ID string Heading string Content string } // ParseH2Heading returns the heading text for a level-two markdown heading. func ParseH2Heading(line string) (string, bool) { trimmed := strings.TrimLeft(line, " \t") if !strings.HasPrefix(trimmed, "## ") { return "", false } return strings.TrimSpace(strings.TrimPrefix(trimmed, "## ")), true } // ParseSections splits markdown into level-two heading sections. func ParseSections(content string) []Section { if content == "" { return nil } lines := strings.Split(content, "\n") var sections []Section var current *Section var builder strings.Builder counts := make(map[string]int) flush := func() { if current == nil { return } current.Content = builder.String() sections = append(sections, *current) current = nil builder.Reset() } for i, line := range lines { heading, ok := ParseH2Heading(line) if ok { flush() base := slugifyHeading(heading) if base == "" { base = "section" } counts[base]++ id := base if counts[base] > 1 { id = base + "-" + strconv.Itoa(counts[base]) } current = &Section{ ID: id, Heading: heading, } } if current != nil { builder.WriteString(line) if i < len(lines)-1 { builder.WriteString("\n") } } } flush() return sections } func slugifyHeading(input string) string { in := strings.TrimSpace(strings.ToLower(input)) if in == "" { return "" } var b strings.Builder prevDash := false for _, r := range in { if unicode.IsLetter(r) || unicode.IsDigit(r) { b.WriteRune(r) prevDash = false continue } if !prevDash { b.WriteByte('-') prevDash = true } } out := b.String() out = strings.Trim(out, "-") return out }