Skip to content

Commit 74a23a7

Browse files
authored
Support for custommetadata (#405)
rdar://97739617 (Add a directive for custom page metadata (@CustomMetadata))
1 parent ff8663c commit 74a23a7

File tree

10 files changed

+114
-4
lines changed

10 files changed

+114
-4
lines changed

Sources/SwiftDocC/Model/Rendering/RenderNode/RenderMetadata.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ public struct RenderMetadata: VariantContainer {
7777
/// Authors can use the `@PageImage` metadata directive to provide custom icon and card images for a page.
7878
public var images: [TopicImage] = []
7979

80+
/// Author provided custom metadata describing the page.
81+
public var customMetadata: [String: String] = [:]
82+
8083
/// The title of the page.
8184
public var title: String? {
8285
get { getVariantDefaultValue(keyPath: \.titleVariants) }
@@ -228,6 +231,7 @@ extension RenderMetadata: Codable {
228231
public static let remoteSource = CodingKeys(stringValue: "remoteSource")
229232
public static let tags = CodingKeys(stringValue: "tags")
230233
public static let images = CodingKeys(stringValue: "images")
234+
public static let customMetadata = CodingKeys(stringValue: "customMetadata")
231235
}
232236

233237
public init(from decoder: Decoder) throws {
@@ -243,6 +247,7 @@ extension RenderMetadata: Codable {
243247
requiredVariants = try container.decodeVariantCollectionIfPresent(ofValueType: Bool.self, forKey: .required) ?? .init(defaultValue: false)
244248
roleHeadingVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .roleHeading)
245249
images = try container.decodeIfPresent([TopicImage].self, forKey: .images) ?? []
250+
customMetadata = try container.decodeIfPresent([String: String].self, forKey: .customMetadata) ?? [:]
246251
let rawRole = try container.decodeIfPresent(String.self, forKey: .role)
247252
role = rawRole == "tutorial" ? Role.tutorial.rawValue : rawRole
248253
titleVariants = try container.decodeVariantCollectionIfPresent(ofValueType: String?.self, forKey: .title)
@@ -316,5 +321,6 @@ extension RenderMetadata: Codable {
316321
}
317322

318323
try container.encodeIfNotEmpty(images, forKey: .images)
324+
try container.encodeIfNotEmpty(customMetadata, forKey: .customMetadata)
319325
}
320326
}

Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ public struct RenderNodeTranslator: SemanticVisitor {
424424
imageReferences[imageReference.identifier.identifier] = imageReference
425425
}
426426

427+
427428
for dependencyReference in dependencies.topicReferences {
428429
var dependencyRenderReference: TopicRenderReference
429430
if let renderContext = renderContext, let prerendered = renderContext.store.content(for: dependencyReference)?.renderReference as? TopicRenderReference {
@@ -729,6 +730,14 @@ public struct RenderNodeTranslator: SemanticVisitor {
729730
}
730731
}
731732

733+
var metadataCustomDictionary : [String: String] = [:]
734+
if let customMetadatas = documentationNode.metadata?.customMetadata {
735+
for elem in customMetadatas {
736+
metadataCustomDictionary[elem.key] = elem.value
737+
}
738+
}
739+
740+
node.metadata.customMetadata = metadataCustomDictionary
732741
node.seeAlsoSectionsVariants = VariantCollection<[TaskGroupRenderSection]>(
733742
from: documentationNode.availableVariantTraits,
734743
fallbackDefaultValue: []
@@ -1171,7 +1180,15 @@ public struct RenderNodeTranslator: SemanticVisitor {
11711180
)
11721181
}
11731182
}
1174-
1183+
1184+
var metadataCustomDictionary : [String: String] = [:]
1185+
if let customMetadatas = documentationNode.metadata?.customMetadata {
1186+
for elem in customMetadatas {
1187+
metadataCustomDictionary[elem.key] = elem.value
1188+
}
1189+
}
1190+
1191+
node.metadata.customMetadata = metadataCustomDictionary
11751192
node.variants = variants(for: documentationNode)
11761193

11771194
collectedTopicReferences.append(identifier)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2022 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Foundation
12+
import Markdown
13+
14+
/// A directive that accepts an arbitary key/valye pair and emits it into the metadata of the page
15+
///
16+
/// It accepts following parameters:
17+
///
18+
/// key- a key to identify a piece of metadata
19+
/// value- the value for the metadata
20+
///
21+
/// This directive is only valid within a ``Metadata`` directive:
22+
/// ```
23+
/// @Metadata {
24+
/// @CustomMetadata(key: "country", value: "Belgium")
25+
/// }
26+
/// ```
27+
public final class CustomMetadata: Semantic, AutomaticDirectiveConvertible {
28+
public let originalMarkup: BlockDirective
29+
30+
/// A key to identify a piece of metadata
31+
@DirectiveArgumentWrapped
32+
public var key: String
33+
34+
/// value of the metadata
35+
@DirectiveArgumentWrapped
36+
public var value: String
37+
38+
static var keyPaths: [String : AnyKeyPath] = [
39+
"key" : \CustomMetadata._key,
40+
"value" : \CustomMetadata._value,
41+
]
42+
43+
44+
@available(*, deprecated, message: "Do not call directly. Required for 'AutomaticDirectiveConvertible'.")
45+
init(originalMarkup: BlockDirective) {
46+
self.originalMarkup = originalMarkup
47+
}
48+
}
49+

Sources/SwiftDocC/Semantics/Metadata/Metadata.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,15 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
4040
@ChildDirective(requirements: .zeroOrMore)
4141
var pageImages: [PageImage]
4242

43+
@ChildDirective(requirements: .zeroOrMore)
44+
var customMetadata: [CustomMetadata]
45+
4346
static var keyPaths: [String : AnyKeyPath] = [
4447
"documentationOptions" : \Metadata._documentationOptions,
4548
"technologyRoot" : \Metadata._technologyRoot,
4649
"displayName" : \Metadata._displayName,
4750
"pageImages" : \Metadata._pageImages,
51+
"customMetadata" : \Metadata._customMetadata,
4852
]
4953

5054
/// Creates a metadata object with a given markup, documentation extension, and technology root.
@@ -67,7 +71,7 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
6771

6872
func validate(source: URL?, for bundle: DocumentationBundle, in context: DocumentationContext, problems: inout [Problem]) -> Bool {
6973
// Check that something is configured in the metadata block
70-
if documentationOptions == nil && technologyRoot == nil && displayName == nil && pageImages.isEmpty {
74+
if documentationOptions == nil && technologyRoot == nil && displayName == nil && pageImages.isEmpty && customMetadata.isEmpty {
7175
let diagnostic = Diagnostic(
7276
source: source,
7377
severity: .information,

Sources/SwiftDocC/SwiftDocC.docc/Resources/RenderNode.spec.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,6 +2248,13 @@
22482248
"$ref": "#/components/schemas/TopicImage"
22492249
}
22502250
},
2251+
"customMetadata": {
2252+
"type": "object",
2253+
"description" : "A dictionary containing arbitrary, user-provided metadata key/value pairs.\n\nThese values are authored using the `@CustomMetadata(key:value:)` directive.",
2254+
"additionalProperties": {
2255+
"type": "string"
2256+
}
2257+
},
22512258
"modules": {
22522259
"type": "array",
22532260
"items": {

Tests/SwiftDocCTests/Rendering/RenderNodeTranslatorTests.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1169,7 +1169,8 @@ class RenderNodeTranslatorTests: XCTestCase {
11691169
let roundTrippedArticle = try JSONDecoder().decode(RenderNode.self, from: encodedArticle)
11701170

11711171
XCTAssertEqual(roundTrippedArticle.icon?.identifier, "plus.svg")
1172-
1172+
1173+
XCTAssertEqual(renderNode.metadata.customMetadata.count, 1)
11731174
XCTAssertEqual(
11741175
roundTrippedArticle.references["figure1.png"] as? ImageReference,
11751176
ImageReference(
@@ -1213,5 +1214,11 @@ class RenderNodeTranslatorTests: XCTestCase {
12131214
TopicImage(type: .card, identifier: RenderReferenceIdentifier("figure1.png"))
12141215
]
12151216
)
1217+
1218+
XCTAssertEqual(roundTrippedArticle.metadata.customMetadata.count, 1)
1219+
XCTAssertEqual(roundTrippedArticle.metadata.customMetadata.keys.count, 1)
1220+
XCTAssertEqual(roundTrippedArticle.metadata.customMetadata.keys.first, "country")
1221+
XCTAssertEqual(roundTrippedArticle.metadata.customMetadata.values.count, 1)
1222+
XCTAssertEqual(roundTrippedArticle.metadata.customMetadata.values.first, "Belgium")
12161223
}
12171224
}

Tests/SwiftDocCTests/Semantics/DirectiveInfrastructure/DirectiveIndexTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class DirectiveIndexTests: XCTestCase {
2323
"Chapter",
2424
"Choice",
2525
"Column",
26+
"CustomMetadata",
2627
"DeprecationSummary",
2728
"DisplayName",
2829
"DocumentationExtension",

Tests/SwiftDocCTests/Semantics/DirectiveInfrastructure/DirectiveMirrorTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class DirectiveMirrorTests: XCTestCase {
3939
XCTAssertFalse(reflectedDirective.allowsMarkup)
4040
XCTAssert(reflectedDirective.arguments.isEmpty)
4141

42-
XCTAssertEqual(reflectedDirective.childDirectives.count, 4)
42+
XCTAssertEqual(reflectedDirective.childDirectives.count, 5)
4343

4444
XCTAssertEqual(
4545
reflectedDirective.childDirectives["DocumentationExtension"]?.propertyLabel,

Tests/SwiftDocCTests/Semantics/MetadataTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,23 @@ class MetadataTests: XCTestCase {
131131
XCTAssertEqual(metadata?.displayName?.name, "Custom Name")
132132
}
133133

134+
func testCustomMetadataSupport() throws {
135+
let source = """
136+
@Metadata {
137+
@CustomMetadata(key: "country", value: "Belgium")
138+
@CustomMetadata(key: "continent", value: "Europe")
139+
}
140+
"""
141+
let document = Document(parsing: source, options: .parseBlockDirectives)
142+
let directive = document.child(at: 0)! as! BlockDirective
143+
let (bundle, context) = try testBundleAndContext(named: "TestBundle")
144+
var problems = [Problem]()
145+
let metadata = Metadata(from: directive, source: nil, for: bundle, in: context, problems: &problems)
146+
XCTAssertNotNil(metadata)
147+
XCTAssertEqual(metadata?.customMetadata.count, 2)
148+
XCTAssertEqual(problems.count, 0)
149+
}
150+
134151
// MARK: - Metadata Support
135152

136153
func testArticleSupportsMetadata() throws {
@@ -319,3 +336,4 @@ class MetadataTests: XCTestCase {
319336
return (problemIDs, metadata)
320337
}
321338
}
339+

Tests/SwiftDocCTests/Test Bundles/BookLikeContent.docc/MyArticle.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This is the abstract of my article. Nice!
55
@Metadata {
66
@PageImage(source: "plus", alt: "A plus icon.", purpose: icon)
77
@PageImage(source: "figure1", alt: "An example figure.", purpose: card)
8+
@CustomMetadata(key: "country", value: "Belgium")
89
}
910

1011
@Row(numberOfColumns: 8) {

0 commit comments

Comments
 (0)