Skip to content

Custom grammars #8

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,19 @@ try DeliciousRecipes().publish(using: [
...
])
```

You can also add your custom Splash Grammars like so:

```swift
...
.installPlugin(.splash(withClassPrefix: "classPrefix", withCustomGrammars: [(YourGrammar(), "name")])
...
```

where `name` is the name of the programming language that splash will use to look for in markdown.
You could use the grammar above like this:

```markdown
```name
...
```
16 changes: 12 additions & 4 deletions Sources/SplashPublishPlugin/SplashPublishPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,35 @@ import Splash
import Ink

public extension Plugin {
static func splash(withClassPrefix classPrefix: String) -> Self {
/// - withClassPrefix: A string to be appended to each HTML tag's class attribute
/// - withGrammars: A tuple containing the `Grammar` itself as well as the tag to look for inside of markdown.
static func splash(withClassPrefix classPrefix: String, withGrammars grammars: [(grammar: Grammar, name: String)] = [(SwiftGrammar(), "swift")]) -> Self {
Plugin(name: "Splash") { context in
context.markdownParser.addModifier(
.splashCodeBlocks(withFormat: HTMLOutputFormat(
classPrefix: classPrefix
))
), withGrammars: grammars)
)
}
}
}

public extension Modifier {
static func splashCodeBlocks(withFormat format: HTMLOutputFormat = .init()) -> Self {
let highlighter = SyntaxHighlighter(format: format)
static func splashCodeBlocks(withFormat format: HTMLOutputFormat = .init(), withGrammars grammars: [(grammar: Grammar, name: String)] = [(SwiftGrammar(), "swift")]) -> Self {
var highlighter = SyntaxHighlighter(format: format)

return Modifier(target: .codeBlocks) { html, markdown in
var markdown = markdown.dropFirst("```".count)

guard !markdown.hasPrefix("no-highlight") else {
return html
}

grammars.forEach({ grammar, name in
if markdown.hasPrefix(name) {
highlighter = SyntaxHighlighter(format: format, grammar: grammar)
}
})

markdown = markdown
.drop(while: { !$0.isNewline })
Expand Down
71 changes: 71 additions & 0 deletions Tests/SplashPublishPluginTests/SplashPublishPluginTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import XCTest
import SplashPublishPlugin
import Ink
import Splash

final class SplashPublishPluginTests: XCTestCase {
func testHighlightingMarkdown() {
Expand All @@ -28,8 +29,78 @@ final class SplashPublishPluginTests: XCTestCase {
</code></pre>
""")
}

func testHighlightingMultipleGrammarsMarkdown() {
let parser = MarkdownParser(modifiers: [.splashCodeBlocks(withGrammars: [(TestGrammar(), "test"), (SwiftGrammar(), "swift")])])
let html = parser.html(from: """
Some text
```test
some should be a keyword
```
here's some fake text
```swift
let int = 7
```
fake text
```no-highlight
not.highlighted()
```
""")
XCTAssertEqual(html, """
<p>Some text</p><pre><code><span class=\"keyword\">some</span> should be a keyword\n</code></pre><p>here\'s some fake text</p><pre><code><span class=\"keyword\">let</span> int = <span class=\"number\">7</span>\n</code></pre><p>fake text</p><pre><code class=\"language-no-highlight\">not.highlighted()\n</code></pre>
""")
}

static var allTests = [
("testHighlightingMarkdown", testHighlightingMarkdown)
]
}

public struct TestGrammar: Grammar {
public var delimiters: CharacterSet
public var syntaxRules: [SyntaxRule]

public init() {
var delimiters = CharacterSet.alphanumerics.inverted
delimiters.remove("_")
delimiters.remove("-")
delimiters.remove("\"")
delimiters.remove("#")
delimiters.remove("@")
delimiters.remove("$")
self.delimiters = delimiters

syntaxRules = [
KeywordRule(),
]
}

public func isDelimiter(_ delimiterA: Character, mergableWith delimiterB: Character) -> Bool {
switch (delimiterA, delimiterB) {
case (_, ":"):
return false
case (":", "/"):
return true
case (":", _):
return false
case ("-", _):
return false
case ("#", _):
return false
default:
return true
}
}

static let keywords = ([
"some", "fake", "keywords"
] as Set<String>)

struct KeywordRule: SyntaxRule {
var tokenType: TokenType { return .keyword }

func matches(_ segment: Segment) -> Bool {
return keywords.contains(segment.tokens.current)
}
}
}