package parser import "bytes" // slideBreakMarker is a line that, on its own, forces a new slide within a // single source file, even though the surrounding content stays in one // file (and shares its frontmatter). var slideBreakMarker = []byte("") // splitSlideBreaks splits body into one or more parts at lines consisting // solely of slideBreakMarker. Matches inside fenced code blocks (``` or // ~~~) are ignored, so a marker shown as example text isn't mistaken for an // actual break. If no marker is found, it returns body unchanged as the // only part. func splitSlideBreaks(body []byte) [][]byte { lines := bytes.Split(body, []byte("\n")) var parts [][]byte var current [][]byte var fence fenceState for _, line := range lines { trimmed := bytes.TrimSpace(line) if !fence.active && bytes.Equal(trimmed, slideBreakMarker) { parts = append(parts, bytes.TrimSpace(bytes.Join(current, []byte("\n")))) current = nil continue } fence.toggle(trimmed) current = append(current, line) } parts = append(parts, bytes.TrimSpace(bytes.Join(current, []byte("\n")))) if len(parts) == 1 { return [][]byte{body} } nonEmpty := parts[:0] for _, p := range parts { if len(p) > 0 { nonEmpty = append(nonEmpty, p) } } return nonEmpty } // fenceState tracks whether the line currently being scanned lies inside a // fenced code block, so a slide-break marker appearing as example text // inside a fence isn't treated as an actual split point. type fenceState struct { active bool char byte count int } // toggle updates the fence state for one already-trimmed line. func (f *fenceState) toggle(trimmed []byte) { ch, count, ok := parseFenceLine(trimmed) if !ok { return } switch { case !f.active: f.active, f.char, f.count = true, ch, count case ch == f.char && count >= f.count: f.active = false } } // parseFenceLine reports whether trimmed is a fenced-code-block delimiter // line (a run of three or more backticks or tildes), returning the fence // character and run length. func parseFenceLine(trimmed []byte) (ch byte, count int, ok bool) { if len(trimmed) < 3 { return 0, 0, false } ch = trimmed[0] if ch != '`' && ch != '~' { return 0, 0, false } for count < len(trimmed) && trimmed[count] == ch { count++ } if count < 3 { return 0, 0, false } // A backtick fence's info string cannot itself contain a backtick. if ch == '`' && bytes.IndexByte(trimmed[count:], '`') != -1 { return 0, 0, false } return ch, count, true }