Skip to content

Commit c89ee5e

Browse files
committed
implement plain text markdown rendering target
1 parent f81cb38 commit c89ee5e

19 files changed

+397
-121
lines changed

Sources/MarkdownRendering/HTML.ContentEncoder (ext).swift renamed to Sources/MarkdownRendering/HTML/HTML.ContentEncoder (ext).swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import MarkdownABI
44
extension HTML.ContentEncoder
55
{
66
mutating
7-
func emit(element:MarkdownBytecode.Emission, with attributes:MarkdownAttributeContext)
7+
func emit(element:MarkdownBytecode.Emission,
8+
with attributes:MarkdownElementContext.AttributeContext)
89
{
910
let html:HTML.VoidElement
1011

@@ -38,12 +39,14 @@ extension HTML.ContentEncoder
3839
extension HTML.ContentEncoder
3940
{
4041
private mutating
41-
func open(_ element:HTML.ContainerElement, with attributes:MarkdownAttributeContext)
42+
func open(_ element:HTML.ContainerElement,
43+
with attributes:MarkdownElementContext.AttributeContext)
4244
{
4345
self.open(element) { attributes.encode(to: &$0) }
4446
}
4547
mutating
46-
func open(context:MarkdownElementContext, with attributes:MarkdownAttributeContext)
48+
func open(context:MarkdownElementContext,
49+
with attributes:MarkdownElementContext.AttributeContext)
4750
{
4851
switch context
4952
{

Sources/MarkdownRendering/HyperTextRenderableMarkdown.swift renamed to Sources/MarkdownRendering/HTML/HyperTextRenderableMarkdown.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@ extension HyperTextRenderableMarkdown
4444
/// If an error occurs, stops execution and throws the error, otherwise
4545
/// returns nil if successful. This function always closes any HTML elements
4646
/// it creates, even on error.
47+
///
48+
/// See ``PlainTextRenderableMarkdown.write(to:)`` for a simpler version of this function
49+
/// that only renders visible text.
4750
public
4851
func render(to html:inout HTML.ContentEncoder) throws
4952
{
50-
var attributes:MarkdownAttributeContext = .init()
53+
var attributes:MarkdownElementContext.AttributeContext = .init()
5154
var stack:[MarkdownElementContext] = []
5255

5356
defer
@@ -67,20 +70,18 @@ extension HyperTextRenderableMarkdown
6770
throw MarkdownRenderingError.invalidInstruction
6871

6972
case .attribute(let attribute, nil):
70-
attributes.commit()
71-
attributes.current = (attribute, [])
72-
73+
attributes.flush(beginning: attribute)
7374

7475
case .attribute(let attribute, let reference?):
75-
attributes.commit()
76+
attributes.flush()
7677

7778
if let value:String = self.load(reference, for: attribute)
7879
{
7980
attributes.append(value: value, as: attribute)
8081
}
8182

8283
case .emit(let element):
83-
attributes.commit()
84+
attributes.flush()
8485

8586
html.emit(newlines: &newlines)
8687
html.emit(element: element, with: attributes)
@@ -92,7 +93,7 @@ extension HyperTextRenderableMarkdown
9293
self.load(reference, into: &html)
9394

9495
case .push(let element):
95-
attributes.commit()
96+
attributes.flush()
9697

9798
let context:MarkdownElementContext = .init(from: element,
9899
attributes: &attributes)
@@ -120,7 +121,7 @@ extension HyperTextRenderableMarkdown
120121
}
121122

122123
case .utf8(let codeunit):
123-
guard case nil = attributes.current?.utf8.append(codeunit)
124+
guard case nil = attributes.buffer(utf8: codeunit)
124125
else
125126
{
126127
continue
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import HTML
2+
import MarkdownABI
3+
4+
extension MarkdownElementContext
5+
{
6+
struct AttributeContext
7+
{
8+
private
9+
var complete:[(name:HTML.Attribute, value:String)]
10+
private
11+
var current:MarkdownBytecode.Attribute?
12+
private
13+
var buffer:[UInt8]
14+
15+
init()
16+
{
17+
self.complete = []
18+
self.current = nil
19+
self.buffer = []
20+
}
21+
}
22+
}
23+
extension MarkdownElementContext.AttributeContext
24+
{
25+
func contains(_ attribute:HTML.Attribute) -> Bool
26+
{
27+
self.complete.contains { $0.name == attribute }
28+
}
29+
30+
mutating
31+
func append(class enumerated:some RawRepresentable<String>)
32+
{
33+
self.complete.append((.class, enumerated.rawValue))
34+
}
35+
mutating
36+
func append(value:String, as instruction:MarkdownBytecode.Attribute)
37+
{
38+
switch instruction
39+
{
40+
case .language: self.complete.append((.class, "language-\(value)"))
41+
42+
case .checkbox: self.complete.append((.type, "checkbox"))
43+
44+
case .center: self.complete.append((.align, "center"))
45+
case .left: self.complete.append((.align, "left"))
46+
case .right: self.complete.append((.align, "right"))
47+
48+
case .alt: self.complete.append((.alt, value))
49+
case .class: self.complete.append((.class, value))
50+
case .checked: self.complete.append((.checked, value))
51+
case .disabled: self.complete.append((.disabled, value))
52+
case .href: self.complete.append((.href, value))
53+
case .src: self.complete.append((.src, value))
54+
case .title: self.complete.append((.title, value))
55+
56+
case .external:
57+
self.complete.append((.rel, "\(HTML.Attribute.Rel.nofollow)"))
58+
self.complete.append((.rel, "\(HTML.Attribute.Rel.noopener)"))
59+
self.complete.append((.rel, "\(HTML.Attribute.Rel.google_ugc)"))
60+
self.complete.append((.href, value))
61+
}
62+
}
63+
}
64+
extension MarkdownElementContext.AttributeContext
65+
{
66+
mutating
67+
func buffer(utf8 codeunit:UInt8) -> Void?
68+
{
69+
self.current.map { _ in self.buffer.append(codeunit) }
70+
}
71+
/// Remove all attributes from the attribute context.
72+
mutating
73+
func clear()
74+
{
75+
self.complete.removeAll(keepingCapacity: true)
76+
self.current = nil
77+
self.buffer.removeAll(keepingCapacity: true)
78+
}
79+
/// Closes the current attribute (if any), and appends it to the list of
80+
/// complete attributes, making it available for encoding.
81+
mutating
82+
func flush(beginning next:MarkdownBytecode.Attribute?)
83+
{
84+
defer
85+
{
86+
self.current = next
87+
}
88+
if let instruction:MarkdownBytecode.Attribute = self.current
89+
{
90+
self.append(value: .init(decoding: self.buffer, as: Unicode.UTF8.self),
91+
as: instruction)
92+
self.buffer.removeAll(keepingCapacity: true)
93+
}
94+
}
95+
}
96+
extension MarkdownElementContext.AttributeContext:MarkdownAttributeContext
97+
{
98+
}
99+
extension MarkdownElementContext.AttributeContext
100+
{
101+
func encode(to attributes:inout HTML.AttributeEncoder)
102+
{
103+
var classes:[String] = []
104+
105+
for (name, value):(HTML.Attribute, String) in self.complete
106+
{
107+
switch name
108+
{
109+
case .class:
110+
classes.append(value)
111+
112+
default:
113+
attributes[name: name] = value
114+
}
115+
}
116+
if !classes.isEmpty
117+
{
118+
attributes.class = classes.joined(separator: " ")
119+
}
120+
}
121+
}

Sources/MarkdownRendering/MarkdownElementContext.swift renamed to Sources/MarkdownRendering/HTML/MarkdownElementContext.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,29 @@ enum MarkdownElementContext
2626
extension MarkdownElementContext
2727
{
2828
private static
29-
func highlight(_ type:MarkdownSyntaxHighlight,
30-
attributes:inout MarkdownAttributeContext) -> Self
29+
func highlight(_ type:MarkdownSyntaxHighlight, attributes:inout AttributeContext) -> Self
3130
{
3231
let container:HTML.ContainerElement = attributes.contains(.href) ? .a : .span
3332
attributes.append(class: type)
3433
return .highlight(.init(container: container, type: type))
3534
}
3635

3736
private static
38-
func section(_ type:Section, attributes:inout MarkdownAttributeContext) -> Self
37+
func section(_ type:Section, attributes:inout AttributeContext) -> Self
3938
{
4039
attributes.append(class: type)
4140
return .section(type)
4241
}
4342
private static
44-
func signage(_ type:Signage, attributes:inout MarkdownAttributeContext) -> Self
43+
func signage(_ type:Signage, attributes:inout AttributeContext) -> Self
4544
{
4645
attributes.append(class: type)
4746
return .signage(type)
4847
}
4948
}
5049
extension MarkdownElementContext
5150
{
52-
init(from markdown:MarkdownBytecode.Context, attributes:inout MarkdownAttributeContext)
51+
init(from markdown:MarkdownBytecode.Context, attributes:inout AttributeContext)
5352
{
5453
switch markdown
5554
{
Lines changed: 24 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,46 @@
1-
import HTML
21
import MarkdownABI
32

4-
struct MarkdownAttributeContext
3+
/// Common interface for ``MarkdownElementContext.AttributeContext`` and
4+
/// ``MarkdownTextContext.AttributeContext``.
5+
///
6+
/// We never actually dispatch through this protocol, but it is helpful for understanding how
7+
/// the markdown VM works.
8+
protocol MarkdownAttributeContext
59
{
6-
private(set)
7-
var complete:[(name:HTML.Attribute, value:String)]
8-
var current:(name:MarkdownBytecode.Attribute, utf8:[UInt8])?
9-
1010
init()
11-
{
12-
self.complete = []
13-
self.current = nil
14-
}
15-
}
16-
extension MarkdownAttributeContext
17-
{
18-
func contains(_ attribute:HTML.Attribute) -> Bool
19-
{
20-
self.complete.contains { $0.name == attribute }
21-
}
2211

2312
mutating
24-
func append(class enumerated:some RawRepresentable<String>)
25-
{
26-
self.complete.append((.class, enumerated.rawValue))
27-
}
28-
mutating
29-
func append(value:String, as instruction:MarkdownBytecode.Attribute)
30-
{
31-
switch instruction
32-
{
33-
case .language: self.complete.append((.class, "language-\(value)"))
13+
func append(value:__owned String, as instruction:MarkdownBytecode.Attribute)
3414

35-
case .checkbox: self.complete.append((.type, "checkbox"))
36-
37-
case .center: self.complete.append((.align, "center"))
38-
case .left: self.complete.append((.align, "left"))
39-
case .right: self.complete.append((.align, "right"))
15+
/// Appends a single UTF-8 code unit to the current attribute, returning `nil` if and only
16+
/// if there is no current attribute.
17+
mutating
18+
func buffer(utf8 codeunit:UInt8) -> Void?
4019

41-
case .alt: self.complete.append((.alt, value))
42-
case .class: self.complete.append((.class, value))
43-
case .checked: self.complete.append((.checked, value))
44-
case .disabled: self.complete.append((.disabled, value))
45-
case .href: self.complete.append((.href, value))
46-
case .src: self.complete.append((.src, value))
47-
case .title: self.complete.append((.title, value))
20+
/// Resets the attribute context to its initial state, possibly re-using existing array
21+
/// allocations.
22+
mutating
23+
func clear()
4824

49-
case .external:
50-
self.complete.append((.rel, "\(HTML.Attribute.Rel.nofollow)"))
51-
self.complete.append((.rel, "\(HTML.Attribute.Rel.noopener)"))
52-
self.complete.append((.rel, "\(HTML.Attribute.Rel.google_ugc)"))
53-
self.complete.append((.href, value))
54-
}
55-
}
25+
/// Terminates the current attribute, if any, and begins a new attribute if `next` is
26+
/// non-nil.
27+
mutating
28+
func flush(beginning next:MarkdownBytecode.Attribute?)
5629
}
5730
extension MarkdownAttributeContext
5831
{
59-
/// Remove all attributes from the attribute context.
32+
/// Replaces `self` with ``init``.
6033
mutating
6134
func clear()
6235
{
63-
self.complete.removeAll()
64-
self.current = nil
65-
}
66-
/// Closes the current attribute (if any), and appends it to the list of
67-
/// complete attributes, making it available for encoding.
68-
mutating
69-
func commit()
70-
{
71-
defer
72-
{
73-
self.current = nil
74-
}
75-
if let (instruction, utf8):(MarkdownBytecode.Attribute, [UInt8]) = self.current
76-
{
77-
self.append(value: .init(decoding: utf8, as: Unicode.UTF8.self), as: instruction)
78-
}
36+
self = .init()
7937
}
8038
}
8139
extension MarkdownAttributeContext
8240
{
83-
func encode(to attributes:inout HTML.AttributeEncoder)
41+
mutating
42+
func flush()
8443
{
85-
var classes:[String] = []
86-
87-
for (name, value):(HTML.Attribute, String) in self.complete
88-
{
89-
switch name
90-
{
91-
case .class:
92-
classes.append(value)
93-
94-
default:
95-
attributes[name: name] = value
96-
}
97-
}
98-
if !classes.isEmpty
99-
{
100-
attributes.class = classes.joined(separator: " ")
101-
}
44+
self.flush(beginning: nil)
10245
}
10346
}

0 commit comments

Comments
 (0)