Commit fa46c3f157
Unsigned
Layout: unified · split
Sources/OrgSwift/AST/OrgParser.swift +6 −2
| @@ -138,7 +138,10 @@ public enum OrgParser { | ||
| 138 | 138 | while index < lines.count { |
| 139 | 139 | let candidate = lines[index] |
| 140 | 140 | let candidateTrimmed = candidate.trimmingCharacters(in: .whitespaces) |
| 141 | if candidateTrimmed.isEmpty || startsNewConstruct(candidateTrimmed, raw: candidate) { break } | |
| 141 | // The first line is always taken: nothing above claimed it, and | |
| 142 | // leaving it would parse the same line forever. | |
| 143 | if !paragraph.isEmpty, | |
| 144 | candidateTrimmed.isEmpty || startsNewConstruct(candidateTrimmed, raw: candidate) { break } | |
| 142 | 145 | paragraph.append(candidateTrimmed) |
| 143 | 146 | index += 1 |
| 144 | 147 | } |
| @@ -153,7 +156,8 @@ public enum OrgParser { | ||
| 153 | 156 | |
| 154 | 157 | /// Would this line begin a construct other than the paragraph currently being consumed? |
| 155 | 158 | private static func startsNewConstruct(_ trimmed: String, raw: String) -> Bool { |
| 156 | if trimmed.hasPrefix("#+") || trimmed.hasPrefix("#") { return true } | |
| 159 | // A keyword or a comment; `#12 fixed this` is prose. | |
| 160 | if trimmed.hasPrefix("#+") || trimmed == "#" || trimmed.hasPrefix("# ") { return true } | |
| 157 | 161 | if trimmed.firstMatch(of: /^\*{1,6}\s+/) != nil { return true } |
| 158 | 162 | if isOrgHorizontalRule(trimmed) { return true } |
| 159 | 163 | if isTableLine(trimmed) { return true } |
Tests/OrgSwiftTests/OrgTreeTests.swift +22
| @@ -9,6 +9,28 @@ import Testing | ||
| 9 | 9 | /// than a second parser. Corpus conformance is gated separately, in `ConformanceTests`. |
| 10 | 10 | struct ASTParseTests { |
| 11 | 11 | |
| 12 | @Test(.timeLimit(.minutes(1))) | |
| 13 | func aHashLineThatIsNotACommentIsAParagraph() { | |
| 14 | let doc = OrgParser.parse("#12 fixed this\nsee #1") | |
| 15 | guard case .paragraph(let objects) = doc.elements.first else { | |
| 16 | Issue.record("expected a paragraph, got \(doc.elements)"); return | |
| 17 | } | |
| 18 | #expect(doc.elements.count == 1) | |
| 19 | #expect(!objects.isEmpty) | |
| 20 | } | |
| 21 | ||
| 22 | @Test(.timeLimit(.minutes(1))) | |
| 23 | func aHashLineEndsNoParagraph() { | |
| 24 | let doc = OrgParser.parse("see\n#12 for context") | |
| 25 | #expect(doc.elements.count == 1) | |
| 26 | } | |
| 27 | ||
| 28 | @Test(.timeLimit(.minutes(1))) | |
| 29 | func aCommentStillEndsAParagraph() { | |
| 30 | let doc = OrgParser.parse("one\n# a comment\ntwo") | |
| 31 | #expect(doc.elements.count == 2) | |
| 32 | } | |
| 33 | ||
| 12 | 34 | @Test |
| 13 | 35 | func headingCarriesTodoPriorityAndTags() { |
| 14 | 36 | let doc = OrgParser.parse("* TODO [#A] Write the parser :work:rust:") |