Skip to content

Commit 0904c81

Browse files
committed
Section 5.3 of the CommonMark spec was not implemented fully. This change implements the full specification. This is an invasive change that slightly changes the public interface.
1 parent 5b366fe commit 0904c81

13 files changed

+262
-126
lines changed

Sources/MarkdownKit/Block.swift

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public enum Block: Equatable, CustomStringConvertible, CustomDebugStringConverti
2929
case document(Blocks)
3030
case blockquote(Blocks)
3131
case list(Int?, Bool, Blocks)
32-
case listItem(ListType, Bool, Blocks)
32+
case listItem(ListType, ListDensity, Blocks)
3333
case paragraph(Text)
3434
case heading(Int, Text)
3535
case indentedCode(Lines)
@@ -54,8 +54,8 @@ public enum Block: Equatable, CustomStringConvertible, CustomDebugStringConverti
5454
} else {
5555
return "list(\(tight ? "tight" : "loose"), \(Block.string(from: blocks)))"
5656
}
57-
case .listItem(let type, let tight, let blocks):
58-
return "listItem(\(type), \(tight ? "tight" : "loose"), \(Block.string(from: blocks)))"
57+
case .listItem(let type, let density, let blocks):
58+
return "listItem(\(type), \(density), \(Block.string(from: blocks)))"
5959
case .paragraph(let text):
6060
return "paragraph(\(text.debugDescription))"
6161
case .heading(let level, let text):
@@ -239,6 +239,60 @@ public enum Block: Equatable, CustomStringConvertible, CustomDebugStringConverti
239239
}
240240
}
241241

242+
public enum ListDensity: Equatable, CustomStringConvertible, CustomDebugStringConvertible {
243+
case initial
244+
case loose
245+
case tight
246+
247+
public init(tight: Bool) {
248+
self = tight ? .tight : .initial
249+
}
250+
251+
public var isTight: Bool {
252+
switch self {
253+
case .initial, .loose:
254+
return false
255+
case .tight:
256+
return true
257+
}
258+
}
259+
260+
public var isTightInitially: Bool {
261+
switch self {
262+
case .loose:
263+
return false
264+
case .initial, .tight:
265+
return true
266+
}
267+
}
268+
269+
public func merge(tight: Bool) -> ListDensity {
270+
switch self {
271+
case .initial:
272+
return tight ? .initial : .loose
273+
case .loose:
274+
return .loose
275+
case .tight:
276+
return tight ? .tight : .loose
277+
}
278+
}
279+
280+
public var description: String {
281+
switch self {
282+
case .initial:
283+
return "initial"
284+
case .loose:
285+
return "loose"
286+
case .tight:
287+
return "tight"
288+
}
289+
}
290+
291+
public var debugDescription: String {
292+
return self.description
293+
}
294+
}
295+
242296
///
243297
/// Enumeration of Markdown list types.
244298
///

Sources/MarkdownKit/HTML/HtmlGenerator.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ open class HtmlGenerator {
5353
}
5454
return res
5555
}
56-
56+
5757
open func generate(block: Block, parent: Parent, tight: Bool = false) -> String {
5858
switch block {
5959
case .document(_):
@@ -77,11 +77,15 @@ open class HtmlGenerator {
7777
return "<li>" + self.generate(text: text) + "</li>\n"
7878
} else {
7979
return "<li>" +
80-
self.generate(blocks: blocks, parent: .block(block, parent)) +
80+
self.generate(blocks: blocks, parent: .block(block, parent), tight: tight) +
8181
"</li>\n"
8282
}
8383
case .paragraph(let text):
84-
return "<p>" + self.generate(text: text) + "</p>\n"
84+
if tight {
85+
return self.generate(text: text) + "\n"
86+
} else {
87+
return "<p>" + self.generate(text: text) + "</p>\n"
88+
}
8589
case .heading(let n, let text):
8690
let tag = "h\(n > 0 && n < 7 ? n : 1)>"
8791
return "<\(tag)\(self.generate(text: text))</\(tag)\n"

Sources/MarkdownKit/Parser/BlockParser.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ open class BlockParser {
5050

5151
public func consumeParagraphLines() {
5252
self.docParser.prevParagraphLines = nil
53+
self.docParser.prevParagraphLinesTight = false
5354
}
5455

5556
public var line: Substring {

Sources/MarkdownKit/Parser/Container.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ import Foundation
2626
/// i.e. a container that has an enclosing container.
2727
///
2828
open class Container: CustomDebugStringConvertible {
29-
public var content: [Block] = []
29+
public private(set) var content: [Block] = []
30+
public var density: ListDensity? = nil
31+
32+
open func append(block: Block, tight: Bool) {
33+
if !content.isEmpty || self.density == nil {
34+
self.density = self.density?.merge(tight: tight) ?? .tight
35+
}
36+
self.content.append(block)
37+
}
3038

3139
open func makeBlock(_ docParser: DocumentParser) -> Block {
3240
return .document(docParser.bundle(blocks: self.content))
@@ -64,7 +72,7 @@ open class NestedContainer: Container {
6472
open var indentRequired: Bool {
6573
return false
6674
}
67-
75+
6876
open func skipIndent(input: String,
6977
startIndex: String.Index,
7078
endIndex: String.Index) -> String.Index? {
@@ -105,7 +113,7 @@ open class NestedContainer: Container {
105113
if self === container {
106114
return self
107115
} else {
108-
self.outer.content.append(self.makeBlock(docParser))
116+
self.outer.append(block: self.makeBlock(docParser), tight: self.density?.isTight ?? true)
109117
return self.outer.return(to: container, for: docParser)
110118
}
111119
}

Sources/MarkdownKit/Parser/DocumentParser.swift

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,25 @@ open class DocumentParser {
3838
fileprivate var currentContainer: Container
3939

4040
internal var prevParagraphLines: Text?
41-
internal var line: Substring
42-
internal var contentStartIndex: Substring.Index
43-
internal var contentEndIndex: Substring.Index
44-
internal var lineIndent: Int
45-
internal var lineEmpty: Bool
46-
internal var prevLineEmpty: Bool
41+
internal var prevParagraphLinesTight: Bool
42+
43+
/// Current line being parsed
44+
internal fileprivate(set) var line: Substring
45+
46+
/// Start index on `line` where the content is (indentation was skipped)
47+
internal fileprivate(set) var contentStartIndex: Substring.Index
48+
49+
/// End index on `line` where the content is
50+
internal fileprivate(set) var contentEndIndex: Substring.Index
51+
52+
/// Number of identation characters at beginning of line
53+
internal fileprivate(set) var lineIndent: Int
54+
55+
/// Is the line empty?
56+
internal fileprivate(set) var lineEmpty: Bool
57+
58+
/// Was the previous line empty?
59+
internal fileprivate(set) var prevLineEmpty: Bool
4760

4861
/// Initializer
4962
public init(blockParsers: [BlockParser.Type], input: String) {
@@ -54,6 +67,7 @@ open class DocumentParser {
5467
self.container = docContainer
5568
self.currentContainer = docContainer
5669
self.prevParagraphLines = nil
70+
self.prevParagraphLinesTight = false
5771
self.line = input[input.startIndex..<input.startIndex]
5872
self.contentStartIndex = self.line.startIndex
5973
self.contentEndIndex = self.line.endIndex
@@ -71,6 +85,7 @@ open class DocumentParser {
7185
state.container = self.container
7286
state.currentContainer = self.currentContainer
7387
state.prevParagraphLines = self.prevParagraphLines
88+
state.prevParagraphLinesTight = self.prevParagraphLinesTight
7489
state.line = self.line
7590
state.contentStartIndex = self.contentStartIndex
7691
state.contentEndIndex = self.contentEndIndex
@@ -84,6 +99,7 @@ open class DocumentParser {
8499
self.container = state.container
85100
self.currentContainer = state.currentContainer
86101
self.prevParagraphLines = state.prevParagraphLines
102+
self.prevParagraphLinesTight = state.prevParagraphLinesTight
87103
self.line = state.line
88104
self.contentStartIndex = state.contentStartIndex
89105
self.contentEndIndex = state.contentEndIndex
@@ -101,9 +117,10 @@ open class DocumentParser {
101117
return
102118
}
103119
if let lines = self.prevParagraphLines {
104-
self.container.content.append(.paragraph(lines.finalized()))
120+
self.container.append(block: .paragraph(lines.finalized()), tight: self.prevParagraphLinesTight)
105121
self.container = self.container.return(to: self.currentContainer, for: self)
106122
self.prevParagraphLines = nil
123+
self.prevParagraphLinesTight = false
107124
}
108125
guard self.index! < self.input.endIndex else {
109126
self.index = nil
@@ -187,11 +204,12 @@ open class DocumentParser {
187204
self.container = self.container.return(to: self.currentContainer, for: self)
188205
self.currentContainer = self.container
189206
for blockParser in self.blockParsers {
207+
let tight = !self.prevLineEmpty
190208
switch blockParser.parse() {
191209
case .none:
192210
break
193211
case .block(let block):
194-
self.container.content.append(block)
212+
self.container.append(block: block, tight: tight)
195213
continue loop
196214
case .container(let constr):
197215
self.currentContainer = constr(self.container)
@@ -200,22 +218,27 @@ open class DocumentParser {
200218
}
201219
}
202220
var lines = Text()
221+
let linesTight = !self.prevLineEmpty
203222
lines.append(line: self.trimLine(), withHardLineBreak: self.hasHardLineBreak())
204223
self.readNextLine()
205224
while !self.finished && !self.lineEmpty {
206225
self.prevParagraphLines = lines
226+
self.prevParagraphLinesTight = linesTight
227+
let tight = !self.prevLineEmpty
207228
for blockParser in self.blockParsers {
208229
if blockParser.mayInterruptParagraph {
209230
switch blockParser.parse() {
210231
case .none:
211232
break
212233
case .block(let block):
213-
self.container.content.append(block)
234+
self.container.append(block: block, tight: tight)
214235
self.prevParagraphLines = nil
236+
self.prevParagraphLinesTight = false
215237
continue loop
216238
case .container(let constr):
217239
if let plines = self.prevParagraphLines {
218-
self.container.content.append(.paragraph(plines.finalized()))
240+
self.container.append(block: .paragraph(plines.finalized()),
241+
tight: self.prevParagraphLinesTight)
219242
self.container = constr(
220243
self.container.return(to: self.currentContainer, for: self))
221244
self.currentContainer = self.container
@@ -224,15 +247,17 @@ open class DocumentParser {
224247
self.container = self.currentContainer
225248
}
226249
self.prevParagraphLines = nil
250+
self.prevParagraphLinesTight = false
227251
continue loop
228252
}
229253
}
230254
}
231255
self.prevParagraphLines = nil
256+
self.prevParagraphLinesTight = false
232257
lines.append(line: self.trimLine(), withHardLineBreak: self.hasHardLineBreak())
233258
self.readNextLine()
234259
}
235-
self.container.content.append(.paragraph(lines.finalized()))
260+
self.container.append(block: .paragraph(lines.finalized()), tight: linesTight)
236261
}
237262
}
238263
self.container = self.container.return(for: self)
@@ -251,7 +276,7 @@ open class DocumentParser {
251276
if let ltype = listType {
252277
if type.compatible(with: ltype) {
253278
items.append(block)
254-
if !t || !nested.isSingleton {
279+
if !t.isTight {
255280
tight = false
256281
}
257282
} else {
@@ -264,7 +289,7 @@ open class DocumentParser {
264289
} else {
265290
listType = type
266291
items.append(block)
267-
if !nested.isSingleton {
292+
if !t.isTightInitially {
268293
tight = false
269294
}
270295
}
@@ -314,6 +339,7 @@ internal struct DocumentParserState {
314339
fileprivate var container: Container
315340
fileprivate var currentContainer: Container
316341
fileprivate var prevParagraphLines: Text?
342+
fileprivate var prevParagraphLinesTight: Bool
317343
fileprivate var line: Substring
318344
fileprivate var contentStartIndex: Substring.Index
319345
fileprivate var contentEndIndex: Substring.Index
@@ -326,6 +352,7 @@ internal struct DocumentParserState {
326352
self.container = docParser.container
327353
self.currentContainer = docParser.currentContainer
328354
self.prevParagraphLines = docParser.prevParagraphLines
355+
self.prevParagraphLinesTight = docParser.prevParagraphLinesTight
329356
self.line = docParser.line
330357
self.contentStartIndex = docParser.contentStartIndex
331358
self.contentEndIndex = docParser.contentEndIndex

Sources/MarkdownKit/Parser/ListItemParser.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,12 @@ open class ListItemParser: BlockParser {
4444
private class BulletListItemContainer: NestedContainer {
4545
let bullet: Character
4646
let indent: Int
47-
let tight: Bool
4847

4948
init(bullet: Character, tight: Bool, indent: Int, outer: Container) {
5049
self.bullet = bullet
5150
self.indent = indent
52-
self.tight = tight
5351
super.init(outer: outer)
52+
self.density = .init(tight: tight)
5453
}
5554

5655
public override func skipIndent(input: String,
@@ -76,7 +75,7 @@ open class ListItemParser: BlockParser {
7675
}
7776

7877
public override func makeBlock(_ docParser: DocumentParser) -> Block {
79-
return .listItem(.bullet(self.bullet), self.tight, docParser.bundle(blocks: self.content))
78+
return .listItem(.bullet(self.bullet), self.density ?? .tight, docParser.bundle(blocks: self.content))
8079
}
8180

8281
public override var debugDescription: String {
@@ -94,7 +93,7 @@ open class ListItemParser: BlockParser {
9493

9594
public override func makeBlock(_ docParser: DocumentParser) -> Block {
9695
return .listItem(.ordered(self.number, self.bullet),
97-
self.tight,
96+
self.density ?? .tight,
9897
docParser.bundle(blocks: self.content))
9998
}
10099

Sources/MarkdownKitProcess/main.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ if CommandLine.arguments.count == 2 {
101101
for (sourceUrl, optTargetUrl) in sourceTarget {
102102
if let textContent = try? String(contentsOf: sourceUrl) {
103103
let markdownContent = MarkdownParser.standard.parse(textContent)
104+
print(markdownContent)
104105
let htmlContent = HtmlGenerator.standard.generate(doc: markdownContent)
105106
if let targetUrl = optTargetUrl {
106107
if fileManager.fileExists(atPath: targetUrl.path) {

0 commit comments

Comments
 (0)