import Foundation /// The parts of what someone wrote folded away by `
`, as GitHub's /// Markdown and the web pages (`folds` in crates/knotless-serve/src/web.rs) /// draw them: a line starting `
`, its `` on that line or the /// next, then Markdown, to a `
` on a line of its own, or the end. enum MarkdownFolds { struct Fold: Equatable { /// What its summary says, or "Details", as a browser says without one. let summary: String /// Whether it starts open, as `
` does. let open: Bool /// Its lines past the summary. let inside: Range /// The line after its `
`. let next: Int } /// The fold line `at` of `lines` opens, none past `end`, or nil. static func fold(_ lines: [String], at: Int, end: Int) -> Fold? { guard let opened = opens(trimmed(lines[at])) else { return nil } let open = opened.0 var rest = opened.1 var from = at + 1 if rest.isEmpty, from < end, starts(trimmed(lines[from]), "") != nil { rest = trimmed(lines[from]) from += 1 } var summary = "Details" if let after = starts(rest, ""), let close = rest.range(of: "", options: .caseInsensitive, range: after..`. if let inner = fold(lines, at: line, end: end) { line = inner.next continue } } line += 1 } return Fold(summary: summary, open: open, inside: from.. String { line.trimmingCharacters(in: .whitespaces) } /// Where `line` goes on past `tag`, in any case, if it starts with it. private static func starts(_ line: String, _ tag: String) -> String.Index? { line.range(of: tag, options: [.caseInsensitive, .anchored])?.upperBound } /// Whether `line` starts with `
` (false) or `
` /// (true), and what follows it. private static func opens(_ line: String) -> (Bool, String)? { for (tag, open) in [("
", false), ("
", true)] { if let after = starts(line, tag) { return (open, line[after...].trimmingCharacters(in: .whitespaces)) } } return nil } private static func closes(_ line: String) -> Bool { line.caseInsensitiveCompare("
") == .orderedSame } }