diff --git a/Cli/App.swift b/Cli/App.swift
index 75a932b..bb29d38 100644
--- a/Cli/App.swift
+++ b/Cli/App.swift
@@ -16,6 +16,14 @@ struct Cli: ParsableCommand {
@Argument
var inputFilePath: String
+ enum OutputFormat: String, ExpressibleByArgument {
+ case html
+ case raw
+ }
+
+ @Option(name: .shortAndLong)
+ var outputFormat: OutputFormat = .raw
+
@Flag
var debug: Bool = false
@@ -29,11 +37,19 @@ struct Cli: ParsableCommand {
contents += chunk
}
}
- let md = try Markdown(contents: contents)
+ let pathComponents = inputFilePath.split(separator: "/")
+ let title = String(pathComponents.last!)
+ let md = try Markdown(title: title, contents: contents)
if debug {
print(md.debugDescription)
} else {
- print(md.description)
+ switch outputFormat {
+ case .html:
+ let renderer = HTMLRenderer(markdown: md)
+ print(renderer.render())
+ case .raw:
+ print(md.description)
+ }
}
}
}
diff --git a/Markdown/HTMLRenderer.swift b/Markdown/HTMLRenderer.swift
new file mode 100644
index 0000000..1a1f214
--- /dev/null
+++ b/Markdown/HTMLRenderer.swift
@@ -0,0 +1,57 @@
+public struct HTMLRenderer {
+ private let markdown: Markdown
+
+ public init(markdown: Markdown) {
+ self.markdown = markdown
+ }
+
+ public func render() -> String {
+ """
+
+
+
+
+ \(markdown.title)
+
+
+
+ \(markdown.blocks.map { render($0) }.joined(separator: "\n"))
+
+
+ """
+ }
+
+ private func render(_ block: Block) -> String {
+ switch block {
+ case let .p(text):
+ "\(text.map { render($0) }.joined())
"
+ case let .text(value, style):
+ "\(value)"
+ case let .list(blocks):
+ "\(blocks.map { "- \(render($0))
" }.joined())
"
+ case let .code(value):
+ "\(value)
"
+ case let .h(level, blocks):
+ "\(blocks.map { render($0) }.joined())"
+ }
+ }
+}
diff --git a/Markdown/Lexer.swift b/Markdown/Lexer.swift
index 01236a3..e460ef1 100644
--- a/Markdown/Lexer.swift
+++ b/Markdown/Lexer.swift
@@ -4,7 +4,24 @@
internal enum MarkdownToken {
case newline
case line(String)
+ case list
case header(HeaderLevel)
+ case codeBlock(lang: String?)
+
+ var rawValue: String {
+ switch self {
+ case .newline:
+ return "\n"
+ case let .line(value):
+ return value
+ case .list:
+ return "_"
+ case let .header(level):
+ return Array(repeating: "#", count: level.rawValue).joined()
+ case let .codeBlock(lang):
+ return "```\(lang ?? "")"
+ }
+ }
}
internal struct Lexer {
@@ -30,6 +47,24 @@ internal struct Lexer {
return .newline
case "#":
return header(start: start)
+ case "-":
+ lastPos += 1
+ return .list
+ case "`":
+ // consume 3 backticks
+ let start = lastPos
+ while lastPos < contents.count && lastPos - start < 3 && contents[lastPos] == "`" {
+ lastPos += 1
+ }
+ var lang: String? = nil
+ /*
+ while lastPos < contents.count && (contents[lastPos] != "\n" || contents[lastPos] != " ") {
+ lastPos += 1
+ }
+ lang = String(contents[start + 3.. [Block] {
setup()
while let tok = lexer.nextTok() {
+ if case .line = tok {
+ // do nothing
+ } else {
+ checkParagraph()
+ }
switch tok {
case .newline:
- checkParagraph()
+ break
case let .line(value):
parseLine(value: value)
case let .header(level):
- checkParagraph()
parseHeader(level: level)
+ case .list:
+ parseList()
+ case .codeBlock:
+ parseCodeBlock()
}
}
checkParagraph()
@@ -52,14 +60,43 @@ internal struct Parser {
private mutating func parseHeader(level: HeaderLevel) {
let nextTok = lexer.nextTok()
- var headerValue: Block = .p([.text("", .regular)])
+ var components: [Block] = []
if case let .line(value) = nextTok {
- headerValue = .p([.text(value, .regular)])
+ components.append(.text(value, .regular))
}
- blocks.append(.h(level, headerValue))
+ blocks.append(.h(level, components))
_ = lexer.nextTok() // consume newline
}
+ private mutating func parseList() {
+ var items: [Block] = []
+ var consequentiveLines = 0
+ while consequentiveLines < 2, let tok = lexer.nextTok() {
+ switch tok {
+ case .list:
+ consequentiveLines = 0
+ case let .line(value):
+ items.append(.text(value, .regular))
+ default:
+ consequentiveLines += 1
+ }
+ }
+ blocks.append(.list(items))
+ }
+
+ private mutating func parseCodeBlock() {
+ var value = ""
+ while let tok = lexer.nextTok() {
+ switch tok {
+ case .codeBlock:
+ blocks.append(.code(value))
+ return
+ default:
+ value += tok.rawValue
+ }
+ }
+ }
+
private mutating func checkParagraph() {
if readingParagraph {
blocks.append(.p([.text(paragraphValue, .regular)]))