diff --git a/Cli/App.swift b/Cli/App.swift index fdf5278..fd5cce3 100644 --- a/Cli/App.swift +++ b/Cli/App.swift @@ -1,11 +1,32 @@ +// Marker CLI entry point. +// (c) Kyrylo Khlopko + +#if canImport(Darwin) + import Darwin +#else + import Glibc +#endif + +import ArgumentParser + import DotMd @main -struct App { - static func main() throws { - //let path = "Examples/markdown/bench.md" - let path = "README.md" - let md = try Markdown(path: path) +struct Cli: ParsableCommand { + @Argument + var inputFilePath: String + + mutating func run() throws { + let file = fopen(inputFilePath, "r") + defer { fclose(file) } + var contents = "" + var buf = Array(repeating: CChar(0), count: 1024) + while fgets(&buf, Int32(1024), file) != nil { + if let chunk = String(validatingUTF8: buf) { + contents += chunk + } + } + let md = try Markdown(contents: contents) print(md) } } diff --git a/Markdown/Lexer.swift b/Markdown/Lexer.swift new file mode 100644 index 0000000..01236a3 --- /dev/null +++ b/Markdown/Lexer.swift @@ -0,0 +1,64 @@ +// Markdown lexer implementation +// (c) Kyrylo Khlopko + +internal enum MarkdownToken { + case newline + case line(String) + case header(HeaderLevel) +} + +internal struct Lexer { + private let contents: [Character] + private var lastPos = 0 + + init(contents: String) { + self.contents = Array(contents) + } + + init(contents: [Character]) { + self.contents = contents + } + + mutating func nextTok() -> MarkdownToken? { + guard lastPos < contents.count else { + return nil + } + let start = lastPos + switch contents[lastPos] { + case "\n": + lastPos += 1 + return .newline + case "#": + return header(start: start) + default: + return line(start: start) + } + } + + private mutating func header(start: Int) -> MarkdownToken { + var level = 0 + while lastPos < contents.count && contents[lastPos] == "#" { + level += 1 + lastPos += 1 + } + guard + let headerLevel = HeaderLevel(rawValue: level), + contents[lastPos] == " " + else { + return line(start: start) + } + lastPos += 1 + return .header(headerLevel) + } + + private mutating func line(start: Int) -> MarkdownToken { + guard start < contents.count else { + return .line(String(contents[start...])) + } + while lastPos < contents.count && contents[lastPos] != "\n" { + lastPos += 1 + } + return .line(String(contents[start.. [Block] { - setup() - while let tok = lexer.nextTok() { - switch tok { - case .newline: - checkParagraph() - case let .line(value): - parseLine(value: value) - case let .header(level): - checkParagraph() - parseHeader(level: level) - } - } - checkParagraph() - return blocks - } - - private mutating func setup() { - blocks = [] - paragraphValue = "" - readingParagraph = false - } - - private mutating func parseLine(value: String) { - if readingParagraph { - paragraphValue += "\n" - paragraphValue += value - } else { - readingParagraph = true - paragraphValue = value - } - _ = lexer.nextTok() // consume newline - } - - private mutating func parseHeader(level: HeaderLevel) { - let nextTok = lexer.nextTok() - var headerValue: Block = .p([.text("", .regular)]) - if case let .line(value) = nextTok { - headerValue = .p([.text(value, .regular)]) - } - blocks.append(.h(level, headerValue)) - _ = lexer.nextTok() // consume newline - } - - private mutating func checkParagraph() { - if readingParagraph { - blocks.append(.p([.text(paragraphValue, .regular)])) - readingParagraph = false - } - } -} - -enum MarkdownToken { - case newline - case line(String) - case header(HeaderLevel) -} - -struct MarkdownLexer { - private let contents: [Character] - private var lastPos = 0 - - init(contents: String) { - self.contents = Array(contents) - } - - init(contents: [Character]) { - self.contents = contents - } - - mutating func nextTok() -> MarkdownToken? { - guard lastPos < contents.count else { - return nil - } - let start = lastPos - switch contents[lastPos] { - case "\n": - lastPos += 1 - return .newline - case "#": - return header(start: start) - default: - return line(start: start) - } - } - - private mutating func header(start: Int) -> MarkdownToken { - var level = 0 - while lastPos < contents.count && contents[lastPos] == "#" { - level += 1 - lastPos += 1 - } - guard - let headerLevel = HeaderLevel(rawValue: level), - contents[lastPos] == " " - else { - return line(start: start) - } - lastPos += 1 - return .header(headerLevel) - } - - private mutating func line(start: Int) -> MarkdownToken { - guard start < contents.count else { - return .line(String(contents[start...])) - } - while lastPos < contents.count && contents[lastPos] != "\n" { - lastPos += 1 - } - return .line(String(contents[start.. [Block] { + setup() + while let tok = lexer.nextTok() { + switch tok { + case .newline: + checkParagraph() + case let .line(value): + parseLine(value: value) + case let .header(level): + checkParagraph() + parseHeader(level: level) + } + } + checkParagraph() + return blocks + } + + private mutating func setup() { + blocks = [] + paragraphValue = "" + readingParagraph = false + } + + private mutating func parseLine(value: String) { + if readingParagraph { + paragraphValue += "\n" + paragraphValue += value + } else { + readingParagraph = true + paragraphValue = value + } + _ = lexer.nextTok() // consume newline + } + + private mutating func parseHeader(level: HeaderLevel) { + let nextTok = lexer.nextTok() + var headerValue: Block = .p([.text("", .regular)]) + if case let .line(value) = nextTok { + headerValue = .p([.text(value, .regular)]) + } + blocks.append(.h(level, headerValue)) + _ = lexer.nextTok() // consume newline + } + + private mutating func checkParagraph() { + if readingParagraph { + blocks.append(.p([.text(paragraphValue, .regular)])) + readingParagraph = false + } + } +} diff --git a/Package.resolved b/Package.resolved index b7df665..a8b3698 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { - "originHash" : "ccfcb9545e17b683fc71f240b434fd9e699c7ad431a4508c9f6f5aef1185ef39", + "originHash" : "8d3c5bcf931d8172b0afa33fe2a5e54c7b37c0c93c050aa2b4f6a78e0b00dc1d", "pins" : [ + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "46989693916f56d1186bd59ac15124caef896560", + "version" : "1.3.1" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 7648ede..cac0c37 100644 --- a/Package.swift +++ b/Package.swift @@ -12,12 +12,14 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-testing.git", branch: "main"), + .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0"), ], targets: [ .executableTarget( name: "Marker", dependencies: [ .target(name: "DotMd"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), ], path: "Cli" ), diff --git a/README.md b/README.md index 7dc4a5a..d2c3467 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -# Marker [![tests](https://github.com/khlopko/marker/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/khlopko/marker/actions/workflows/test.yml) - -A static web site generator, written in Swift, with a focus on simplicity and ease of use. +# Marker +❤️ A static web site generator, written in Swift, with a focus on simplicity and ease of use. ## (Upcoming) Features