GeminiRenderer: Add Markdown renderer

This commit is contained in:
Shadowfacts 2021-10-02 10:28:36 -04:00
parent 806149ec1b
commit d6ff2141dc
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
3 changed files with 186 additions and 1 deletions

View File

@ -73,7 +73,7 @@ public class GeminiHTMLRenderer {
}
fileprivate extension Document.Line {
extension Document.Line {
var isListItem: Bool {
switch self {
case .unorderedListItem(_):

View File

@ -0,0 +1,82 @@
//
// GeminiMarkdownRenderer.swift
// GeminiRenderer
//
// Created by Shadowfacts on 10/1/21.
//
import Foundation
import GeminiFormat
import HTMLEntities
public class GeminiMarkdownRenderer {
public init() {
}
public func renderDocumentToMarkdown(_ doc: Document) -> String {
var str = ""
var inPreformatting = false
var inList = false
for line in doc.lines {
if inList && !line.isListItem {
str += "\n"
inList = false
}
switch line {
case let .text(text):
if !text.trimmingCharacters(in: .whitespaces).isEmpty {
str += text.htmlEscape()
str += "\n\n"
}
case let .link(url, text: maybeText):
let text = maybeText ?? url.absoluteString
// todo: do ] in the text need to be escaped?
str += "[\(text.htmlEscape())](\(url))"
str += "\n\n"
case let .preformattedToggle(alt: alt):
inPreformatting = !inPreformatting
if inPreformatting {
str += "```"
if let alt = alt {
str += alt
}
str += "\n"
} else {
str += "```"
str += "\n\n"
}
case let .preformattedText(text):
str += text
str += "\n"
case let .heading(text, level: level):
str += String(repeating: "#", count: level.rawValue)
str += " "
str += text.htmlEscape()
str += "\n\n"
case let .unorderedListItem(text):
if !inList {
inList = true
}
str += "* \(text.htmlEscape())"
str += "\n"
case let .quote(text):
str += "> "
str += text.htmlEscape()
str += "\n\n"
}
}
return str
}
}

View File

@ -0,0 +1,103 @@
//
// GeminiMarkdownRendererTests.swift
// GeminiRendererTests
//
// Created by Shadowfacts on 10/1/21.
//
import XCTest
import GeminiFormat
@testable import GeminiRenderer
class GeminiMarkdownRendererTests: XCTestCase {
private var doc: Document!
override func setUp() {
doc = Document(url: URL(string: "gemini://example.com/")!)
}
func testEscapeToEntities() {
doc.lines = [.text("<b>hello</b>")]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "&#x3C;b&#x3E;hello&#x3C;/b&#x3E;\n\n")
}
func testParagraph() {
doc.lines = [.text("Hello, world!")]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "Hello, world!\n\n")
}
func testLink() {
doc.lines = [.link(URL(string: "gemini://example.com/")!, text: "text")]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "[text](gemini://example.com/)\n\n")
doc.lines = [.link(URL(string: "gemini://example.com/")!, text: nil)]
let noText = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(noText, "[gemini://example.com/](gemini://example.com/)\n\n")
}
func testLinksAfterList() {
doc.lines = [
.unorderedListItem("a"),
.unorderedListItem("b"),
.link(URL(string: "gemini://example.com")!, text: "one"),
.link(URL(string: "gemini://example.com")!, text: "two"),
]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "* a\n* b\n\n[one](gemini://example.com)\n\n[two](gemini://example.com)\n\n")
}
func testPreformatting() {
doc.lines = [
.preformattedToggle(alt: nil),
.preformattedText("foo"),
.preformattedText("* bar"),
.preformattedToggle(alt: nil),
]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "```\nfoo\n* bar\n```\n\n")
}
func testHeading() {
doc.lines = [
.heading("One", level: .h1),
.heading("Two", level: .h2),
]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "# One\n\n## Two\n\n")
}
func testUnorderedList() {
doc.lines = [
.text("before"),
.unorderedListItem("a"),
.unorderedListItem("b"),
.unorderedListItem("c"),
.text("after"),
]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "before\n\n* a\n* b\n* c\n\nafter\n\n")
}
func testQuote() {
doc.lines = [
.quote("quoted")
]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "> quoted\n\n")
}
func testSkipBlankLines() {
doc.lines = [
.heading("Hello", level: .h1),
.text(""),
.text("World"),
]
let markdown = GeminiMarkdownRenderer().renderDocumentToMarkdown(doc)
XCTAssertEqual(markdown, "# Hello\n\nWorld\n\n")
}
}