Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SplashMarkdown #57

Merged
merged 5 commits into from
Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add SplashMarkdown
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.
  • Loading branch information
JohnSundell committed Mar 15, 2019
commit 04e2cbe27bcd4b43e70d681bbfd30736fa473552
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 6 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ let package = Package(
],
targets: [
.target(name: "Splash"),
.target(
name: "SplashMarkdown",
dependencies: ["Splash"]
),
.target(
name: "SplashHTMLGen",
dependencies: ["Splash"]
Expand All @@ -29,7 +33,7 @@ let package = Package(
),
.testTarget(
name: "SplashTests",
dependencies: ["Splash"]
)
dependencies: ["Splash", "SplashMarkdown"]
),
]
)
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -69,6 +69,20 @@ 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.

Here’s 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.

#### 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.
Expand Down Expand Up @@ -168,6 +182,7 @@ That will install the following three tools in your `/usr/local/bin` folder:

```
SplashHTMLGen
SplashMarkdown
SplashImageGen
SplashTokenizer
```
Expand Down
42 changes: 42 additions & 0 deletions Sources/SplashMarkdown/MarkdownDecorator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Splash
* Copyright (c) John Sundell 2019
* MIT license - see LICENSE.md
*/

import Foundation
import Splash

public struct MarkdownDecorator {
private let highlighter = SyntaxHighlighter(format: HTMLOutputFormat())
private let skipHighlightingPrefix = "no-highlight"

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
}
}
33 changes: 33 additions & 0 deletions Sources/SplashMarkdown/main.swift
Original file line number Diff line number Diff line change
@@ -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))
6 changes: 6 additions & 0 deletions Tests/SplashTests/Core/SplashTestCase.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* Splash
* Copyright (c) John Sundell 2019
* MIT license - see LICENSE.md
*/

import Foundation
import XCTest

Expand Down
3 changes: 2 additions & 1 deletion Tests/SplashTests/Core/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
]
}

Expand Down
82 changes: 82 additions & 0 deletions Tests/SplashTests/Tests/MarkdownTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Splash
* Copyright (c) John Sundell 2019
* MIT license - see LICENSE.md
*/

import XCTest
import SplashMarkdown

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)
]
}
}