Add SplashMarkdown (#57)

This change adds a new command line tool to the Splash family:
`SplashMarkdown`.

It’s an adapted version of the tool that I’ve been using for months to
publish every article on Swift by Sundell, and works by replacing all
code blocks within a Markdown file.

Adding this will hopefully make Splash even easier to use, without the
need for writing custom tooling, or to manually replace each code block
within a Markdown file with a “splashed” version.
This commit is contained in:
John Sundell 2019-03-15 20:24:53 +01:00 committed by GitHub
parent 60c06cc385
commit 8e7599150f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 195 additions and 2 deletions

View File

@ -2,5 +2,6 @@ install:
swift package update
swift build -c release -Xswiftc -static-stdlib
install .build/Release/SplashHTMLGen /usr/local/bin/SplashHTMLGen
install .build/Release/SplashMarkdown /usr/local/bin/SplashMarkdown
install .build/Release/SplashImageGen /usr/local/bin/SplashImageGen
install .build/Release/SplashTokenizer /usr/local/bin/SplashTokenizer

View File

@ -15,6 +15,10 @@ let package = Package(
],
targets: [
.target(name: "Splash"),
.target(
name: "SplashMarkdown",
dependencies: ["Splash"]
),
.target(
name: "SplashHTMLGen",
dependencies: ["Splash"]

View File

@ -51,7 +51,7 @@ You'll get the following output back:
To be as flexible as possible, Splash doesn't hardcode any colors or other CSS attributes in the HTML it generates. Instead it simply assigns a CSS class to each token. For an example of a CSS file that can be used to style Splash-generated HTML, see [Examples/sundellsColors.css](https://github.com/JohnSundell/Splash/blob/master/Examples/sundellsColors.css).
When rendering your outputted html, make sure to wrap your output code in the `<pre>` and `<code> ` tags and properly link to your `.css` file. Like this:
When rendering your outputted html, make sure to wrap your output code in the `<pre>` and `<code>` tags and properly link to your `.css` file. Like this:
```html
<!DOCTYPE html>
@ -69,6 +69,22 @@ When rendering your outputted html, make sure to wrap your output code in the `<
For more information about HTML generation with Splash and how to customize it, see `HTMLOutputFormat` [here](https://github.com/JohnSundell/Splash/blob/master/Sources/Splash/Output/HTMLOutputFormat.swift).
#### SplashMarkdown
`SplashMarkdown` builds on top of `SplashHTMLGen` to enable easy Splash decoration of any Markdown file. Pass it a path to a Markdown file, and it will iterate through all code blocks within that file and convert them into Splash-highlighted HTML.
Just like the HTML generated by `SplashHTMLGen` itself, a CSS file should also be added to any page serving the processed Markdown, since Splash only adds CSS classes to tokens — rather than hardcoding styles inline. See the above `SplashHTMLGen` documentation for more information.
Heres an example call to decorate a Markdown file at the path `~/Documents/Article.md`:
```
$ SplashMarkdown ~/Documents/Article.md
```
The decorated Markdown will be returned as standard output.
Highlighting can be skipped for any code block by adding `no-highlight` next to the blocks opening row of backticks — like this: *“```no-highlight”*.
#### SplashImageGen
`SplashImageGen` uses Splash to generate an `NSAttributedString` from Swift code, then draws that attributed string into a graphics context to turn it into an image, which is then written to disk.
@ -168,6 +184,7 @@ That will install the following three tools in your `/usr/local/bin` folder:
```
SplashHTMLGen
SplashMarkdown
SplashImageGen
SplashTokenizer
```

View File

@ -0,0 +1,49 @@
/**
* Splash
* Copyright (c) John Sundell 2019
* MIT license - see LICENSE.md
*/
import Foundation
/// Type used to decorate a Markdown file with Splash-highlighted code blocks
public struct MarkdownDecorator {
private let highlighter = SyntaxHighlighter(format: HTMLOutputFormat())
private let skipHighlightingPrefix = "no-highlight"
public init() {}
/// Decorate all code blocks within a given Markdown string. This API assumes
/// that the passed Markdown is valid. Each code block will be replaced by
/// Splash-highlighted HTML for that block's code. To skip highlighting for
/// any given code block, add "no-highlight" next to the opening row of
/// backticks for that block.
public func decorate(_ markdown: String) -> String {
let components = markdown.components(separatedBy: "```")
var output = ""
for (index, component) in components.enumerated() {
guard index % 2 != 0 else {
output.append(component)
continue
}
var code = component.trimmingCharacters(in: .whitespacesAndNewlines)
if code.hasPrefix(skipHighlightingPrefix) {
let charactersToDrop = skipHighlightingPrefix + "\n"
code = String(code.dropFirst(charactersToDrop.count))
} else {
code = highlighter.highlight(code)
}
output.append("""
<pre class="splash"><code>
\(code)
</code></pre>
""")
}
return output
}
}

View File

@ -0,0 +1,33 @@
/**
* Splash
* Copyright (c) John Sundell 2019
* MIT license - see LICENSE.md
*/
import Foundation
import Splash
guard CommandLine.arguments.count > 1 else {
print("⚠️ Please supply the path to a Markdown file to process as an argument")
exit(1)
}
let markdown: String = {
let path = CommandLine.arguments[1]
do {
let path = (path as NSString).expandingTildeInPath
return try String(contentsOfFile: path)
} catch {
print("""
🛑 Failed to open Markdown file at '\(path)':
---
\(error.localizedDescription)
---
""")
exit(1)
}
}()
let decorator = MarkdownDecorator()
print(decorator.decorate(markdown))

View File

@ -1,3 +1,9 @@
/**
* Splash
* Copyright (c) John Sundell 2019
* MIT license - see LICENSE.md
*/
import Foundation
import XCTest

View File

@ -18,7 +18,8 @@ public func makeLinuxTests() -> [XCTestCaseEntry] {
testCase(LiteralTests.allTests),
testCase(OptionalTests.allTests),
testCase(PreprocessorTests.allTests),
testCase(StatementTests.allTests)
testCase(StatementTests.allTests),
testCase(MarkdownTests.allTests)
]
}

View File

@ -0,0 +1,82 @@
/**
* Splash
* Copyright (c) John Sundell 2019
* MIT license - see LICENSE.md
*/
import XCTest
import Splash
final class MarkdownTests: SplashTestCase {
private var decorator: MarkdownDecorator!
override func setUp() {
super.setUp()
decorator = MarkdownDecorator()
}
func testConvertingCodeBlock() {
let markdown = """
# Title
Text text text `inline.code.shouldNotBeHighlighted()`.
```
struct Hello: Protocol {}
```
Text.
"""
let expectedResult = """
# Title
Text text text `inline.code.shouldNotBeHighlighted()`.
<pre class="splash"><code>
<span class="keyword">struct</span> Hello: <span class="type">Protocol</span> {}
</code></pre>
Text.
"""
XCTAssertEqual(decorator.decorate(markdown), expectedResult)
}
func testSkippingHighlightingForCodeBlock() {
let markdown = """
Text text.
```no-highlight
struct Hello: Protocol {}
```
Text.
"""
let expectedResult = """
Text text.
<pre class="splash"><code>
struct Hello: Protocol {}
</code></pre>
Text.
"""
XCTAssertEqual(decorator.decorate(markdown), expectedResult)
}
func testAllTestsRunOnLinux() {
XCTAssertTrue(TestCaseVerifier.verifyLinuxTests((type(of: self)).allTests))
}
}
extension MarkdownTests {
static var allTests: [(String, TestClosure<MarkdownTests>)] {
return [
("testConvertingCodeBlock", testConvertingCodeBlock),
("testSkippingHighlightingForCodeBlock", testSkippingHighlightingForCodeBlock)
]
}
}