Commit 505bd429eb

505bd429ebb57a21484c0a7054e8a5aadfda8269

parent: 8f8ad4e852

Unsigned

cmc <hello@cleberg.net> · 2026-03-30 21:44 UTC

feat(markdown): finalize migration to apple/swift-markdown renderer

Layout: unified · split

Hutch.xcodeproj/project.pbxproj +2 −6
@@ -11,7 +11,6 @@
1111 8B2F89672F69DEB900FC0253 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 8B2F89642F69DEB900FC0253 /* README.md */; };
1212 8B2F89682F69DEB900FC0253 /* SECURITY.md in Resources */ = {isa = PBXBuildFile; fileRef = 8B2F89652F69DEB900FC0253 /* SECURITY.md */; };
1313 8BE082012F80000100000001 /* HutchWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8BE081F62F80000100000001 /* HutchWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
14 8BE09E792F7B20CB00F88693 /* TEST_README.md in Resources */ = {isa = PBXBuildFile; fileRef = 8BE09E782F7B20CB00F88693 /* TEST_README.md */; };
1514 8BF100032F9A000100000001 /* Markdown in Frameworks */ = {isa = PBXBuildFile; productRef = 8BF100022F9A000100000001 /* Markdown */; };
1615/* End PBXBuildFile section */
1716
@@ -55,7 +54,6 @@
5554 8B8182C22F6B742B000AE049 /* HutchTests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = HutchTests; sourceTree = "<group>"; };
5655 8BDCA5272F6B76B20066AA29 /* HutchTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = HutchTests.xctestplan; sourceTree = "<group>"; };
5756 8BE081F62F80000100000001 /* HutchWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = HutchWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
58 8BE09E782F7B20CB00F88693 /* TEST_README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TEST_README.md; sourceTree = "<group>"; };
5957/* End PBXFileReference section */
6058
6159/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -143,7 +141,6 @@
143141 8BE081F52F80000100000001 /* HutchWidgetExtension */,
144142 8B8182B82F6B73F3000AE049 /* HutchTests */,
145143 8B4B28D22F6704280045FA19 /* Products */,
146 8BE09E782F7B20CB00F88693 /* TEST_README.md */,
147144 );
148145 sourceTree = "<group>";
149146 };
@@ -281,7 +278,6 @@
281278 buildActionMask = 2147483647;
282279 files = (
283280 8B2F89662F69DEB900FC0253 /* LICENSE in Resources */,
284 8BE09E792F7B20CB00F88693 /* TEST_README.md in Resources */,
285281 8B2F89672F69DEB900FC0253 /* README.md in Resources */,
286282 8B2F89682F69DEB900FC0253 /* SECURITY.md in Resources */,
287283 );
@@ -486,7 +482,7 @@
486482 "$(inherited)",
487483 "@executable_path/Frameworks",
488484 );
489 MARKETING_VERSION = 2.4.2;
485 MARKETING_VERSION = 2.5.0;
490486 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
491487 PRODUCT_NAME = "$(TARGET_NAME)";
492488 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -523,7 +519,7 @@
523519 "$(inherited)",
524520 "@executable_path/Frameworks",
525521 );
526 MARKETING_VERSION = 2.4.2;
522 MARKETING_VERSION = 2.5.0;
527523 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
528524 PRODUCT_NAME = "$(TARGET_NAME)";
529525 STRING_CATALOG_GENERATE_SYMBOLS = YES;
Hutch/Views/Repositories/MarkdownHTMLRenderer.swift +38 −4
@@ -10,7 +10,9 @@ private struct MarkdownHTMLRenderer: MarkupVisitor {
1010 typealias Result = String
1111
1212 nonisolated(unsafe) let imageURLResolver: ((String) -> String?)?
13 private var isRenderingTableHead = false
13 nonisolated(unsafe) private var isRenderingTableHead = false
14 nonisolated(unsafe) private var currentTableAlignments: [Markdown.Table.ColumnAlignment?] = []
15 nonisolated(unsafe) private var currentTableColumnIndex = 0
1416
1517 nonisolated init(imageURLResolver: ((String) -> String?)?) {
1618 self.imageURLResolver = imageURLResolver
@@ -140,7 +142,14 @@ private struct MarkdownHTMLRenderer: MarkupVisitor {
140142 }
141143
142144 nonisolated mutating func visitTable(_ table: Markdown.Table) -> String {
143 "<table>\n\(visitChildren(of: table))</table>\n"
145 let previousAlignments = currentTableAlignments
146 let previousColumnIndex = currentTableColumnIndex
147 currentTableAlignments = table.columnAlignments
148 currentTableColumnIndex = 0
149 let content = visitChildren(of: table)
150 currentTableAlignments = previousAlignments
151 currentTableColumnIndex = previousColumnIndex
152 return "<table>\n\(content)</table>\n"
144153 }
145154
146155 nonisolated mutating func visitTableHead(_ tableHead: Markdown.Table.Head) -> String {
@@ -160,12 +169,18 @@ private struct MarkdownHTMLRenderer: MarkupVisitor {
160169 }
161170
162171 nonisolated mutating func visitTableRow(_ tableRow: Markdown.Table.Row) -> String {
163 "<tr>\(visitChildren(of: tableRow))</tr>\n"
172 let previousColumnIndex = currentTableColumnIndex
173 currentTableColumnIndex = 0
174 let content = visitChildren(of: tableRow)
175 currentTableColumnIndex = previousColumnIndex
176 return "<tr>\(content)</tr>\n"
164177 }
165178
166179 nonisolated mutating func visitTableCell(_ tableCell: Markdown.Table.Cell) -> String {
167180 let tagName = isRenderingTableHead ? "th" : "td"
168 return "<\(tagName)>\(visitChildren(of: tableCell))</\(tagName)>"
181 let styleAttribute = alignmentStyleAttribute(forColumn: currentTableColumnIndex)
182 currentTableColumnIndex += 1
183 return "<\(tagName)\(styleAttribute)>\(visitChildren(of: tableCell))</\(tagName)>"
169184 }
170185
171186 nonisolated private mutating func visitChildren(of markup: Markup) -> String {
@@ -195,6 +210,25 @@ private struct MarkdownHTMLRenderer: MarkupVisitor {
195210 }
196211 }
197212
213 nonisolated private func alignmentStyleAttribute(forColumn column: Int) -> String {
214 guard column < currentTableAlignments.count,
215 let alignment = currentTableAlignments[column] else {
216 return ""
217 }
218
219 let textAlignment: String
220 switch alignment {
221 case .left:
222 textAlignment = "left"
223 case .center:
224 textAlignment = "center"
225 case .right:
226 textAlignment = "right"
227 }
228
229 return " style=\"text-align: \(textAlignment);\""
230 }
231
198232 nonisolated private func checkboxHTML(for checkbox: Checkbox) -> String {
199233 switch checkbox {
200234 case .checked:
HutchTests/ReadmeViewTests.swift +10
@@ -54,6 +54,16 @@ struct MarkdownRenderingTests {
5454 #expect(html.contains("<th>"))
5555 }
5656
57 @Test
58 func markdownTableAlignment() {
59 let input = "| Left | Center | Right |\n|:-----|:------:|------:|\n| a | b | c |"
60 let html = markdownToHTML(input)
61
62 #expect(html.contains("text-align: left;"))
63 #expect(html.contains("text-align: center;"))
64 #expect(html.contains("text-align: right;"))
65 }
66
5767 @Test
5868 func markdownStrikethrough() {
5969 let html = markdownToHTML("~~deleted~~")