Skip to content

Commit

Permalink
Enable metadata keys and values to be modified (#39)
Browse files Browse the repository at this point in the history
This change introduces two new `Modifier.Target` instances: `metadataKeys`
and `metadataValues`, which can be used to customize how Ink parses metadata.
This in turn enables Publish plugins to be written that bridges the gap
between Publish’s built-in metadata format and that of external tools, such
as other static site generators or CMSs.
  • Loading branch information
JohnSundell committed Jan 13, 2020
1 parent c88bbce commit a9a1a01
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Sources/Ink/API/MarkdownParser.swift
Expand Up @@ -53,7 +53,7 @@ public struct MarkdownParser {
do {
if metadata == nil, fragments.isEmpty, reader.currentCharacter == "-" {
if let parsedMetadata = try? Metadata.readOrRewind(using: &reader) {
metadata = parsedMetadata
metadata = parsedMetadata.applyingModifiers(modifiers)
continue
}
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/Ink/API/Modifier.swift
Expand Up @@ -16,7 +16,8 @@
public struct Modifier {
/// The type of input that each modifier is given, which both
/// contains the HTML that was generated for a fragment, and
/// its raw Markdown representation.
/// its raw Markdown representation. Note that for metadata
/// targets, the two input arguments will be equivalent.
public typealias Input = (html: String, markdown: Substring)
/// The type of closure that Modifiers are based on. Each
/// modifier is given a set of input, and is expected to return
Expand All @@ -39,6 +40,8 @@ public struct Modifier {

public extension Modifier {
enum Target {
case metadataKeys
case metadataValues
case blockquotes
case codeBlocks
case headings
Expand Down
20 changes: 20 additions & 0 deletions Sources/Ink/Internal/Metadata.swift
Expand Up @@ -42,6 +42,26 @@ internal struct Metadata: Readable {

throw Reader.Error()
}

func applyingModifiers(_ modifiers: ModifierCollection) -> Self {
var modified = self

modifiers.applyModifiers(for: .metadataKeys) { modifier in
for (key, value) in modified.values {
let newKey = modifier.closure((key, Substring(key)))
modified.values[key] = nil
modified.values[newKey] = value
}
}

modifiers.applyModifiers(for: .metadataValues) { modifier in
modified.values = modified.values.mapValues { value in
modifier.closure((value, Substring(value)))
}
}

return modified
}
}

private extension Metadata {
Expand Down
24 changes: 24 additions & 0 deletions Tests/InkTests/MarkdownTests.swift
Expand Up @@ -67,6 +67,29 @@ final class MarkdownTests: XCTestCase {
XCTAssertEqual(markdown.html, "<h1>Title</h1>")
}

func testMetadataModifiers() {
let parser = MarkdownParser(modifiers: [
Modifier(target: .metadataKeys) { key, _ in
"ModifiedKey-" + key
},
Modifier(target: .metadataValues) { value, _ in
"ModifiedValue-" + value
}
])

let markdown = parser.parse("""
---
keyA: valueA
keyB: valueB
---
""")

XCTAssertEqual(markdown.metadata, [
"ModifiedKey-keyA" : "ModifiedValue-valueA",
"ModifiedKey-keyB" : "ModifiedValue-valueB"
])
}

func testPlainTextTitle() {
let markdown = MarkdownParser().parse("""
# Hello, world!
Expand Down Expand Up @@ -115,6 +138,7 @@ extension MarkdownTests {
("testDiscardingEmptyMetadataValues", testDiscardingEmptyMetadataValues),
("testMergingOrphanMetadataValueIntoPreviousOne", testMergingOrphanMetadataValueIntoPreviousOne),
("testMissingMetadata", testMissingMetadata),
("testMetadataModifiers", testMetadataModifiers),
("testPlainTextTitle", testPlainTextTitle),
("testRemovingTrailingMarkersFromTitle", testRemovingTrailingMarkersFromTitle),
("testConvertingFormattedTitleTextToPlainText", testConvertingFormattedTitleTextToPlainText),
Expand Down

1 comment on commit a9a1a01

@Goosse
Copy link

@Goosse Goosse commented on a9a1a01 Jan 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this, but as I get a better grasp of what's going on here I wonder if this is the best, most powerful way to deal with decoding different formats of metadata. I sure wouldn't want to create a plugin that parses YAML formatted metadata only to regurgitate it into strings that the Publish MarkdownMetadataDecoder will understand. Wouldn't it be cool if we could designate the decoder to use, possibly as a custom publish pipeline parameter? The custom decoder would then simply be fed the full string for all the custom metadata. This way, it would be trivial (or at least it appears that it would be) to use a decoder like Yams for YAML metadata.

What do you think?

Please sign in to comment.