Skip to content

Commit f11ea83

Browse files
add @PageKind directive to set a page's role/title heading
rdar://69902322
1 parent e421b81 commit f11ea83

File tree

8 files changed

+187
-2
lines changed

8 files changed

+187
-2
lines changed

Sources/SwiftDocC/Model/Rendering/RenderNodeTranslator.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,11 @@ public struct RenderNodeTranslator: SemanticVisitor {
817817
node.metadata.platformsVariants = .init(defaultValue: renderAvailability)
818818
}
819819
}
820+
821+
if let pageKind = article.metadata?.pageKind {
822+
node.metadata.role = pageKind.kind.renderRole.rawValue
823+
node.metadata.roleHeading = pageKind.kind.titleHeading
824+
}
820825

821826
collectedTopicReferences.append(contentsOf: contentCompiler.collectedTopicReferences)
822827
node.references = createTopicRenderReferences()

Sources/SwiftDocC/Semantics/Metadata/Metadata.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import Markdown
2525
/// - ``PageImage``
2626
/// - ``CallToAction``
2727
/// - ``Availability``
28+
/// - ``PageKind``
2829
public final class Metadata: Semantic, AutomaticDirectiveConvertible {
2930
public let originalMarkup: BlockDirective
3031

@@ -52,6 +53,9 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
5253

5354
@ChildDirective(requirements: .zeroOrMore)
5455
var availability: [Availability]
56+
57+
@ChildDirective
58+
var pageKind: PageKind? = nil
5559

5660
static var keyPaths: [String : AnyKeyPath] = [
5761
"documentationOptions" : \Metadata._documentationOptions,
@@ -61,6 +65,7 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
6165
"customMetadata" : \Metadata._customMetadata,
6266
"callToAction" : \Metadata._callToAction,
6367
"availability" : \Metadata._availability,
68+
"pageKind" : \Metadata._pageKind,
6469
]
6570

6671
/// Creates a metadata object with a given markup, documentation extension, and technology root.
@@ -83,7 +88,7 @@ public final class Metadata: Semantic, AutomaticDirectiveConvertible {
8388

8489
func validate(source: URL?, for bundle: DocumentationBundle, in context: DocumentationContext, problems: inout [Problem]) -> Bool {
8590
// Check that something is configured in the metadata block
86-
if documentationOptions == nil && technologyRoot == nil && displayName == nil && pageImages.isEmpty && customMetadata.isEmpty && callToAction == nil && availability.isEmpty {
91+
if documentationOptions == nil && technologyRoot == nil && displayName == nil && pageImages.isEmpty && customMetadata.isEmpty && callToAction == nil && availability.isEmpty && pageKind == nil {
8792
let diagnostic = Diagnostic(
8893
source: source,
8994
severity: .information,
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2023 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+
extension Metadata {
15+
/// A directive that allows you to set a page's kind, which affects its default title heading and page icon.
16+
///
17+
/// The `@PageKind` directive tells Swift-DocC to treat a documentation page as a particular
18+
/// "kind". This is used to determine the page's default navigator icon, as well as the default
19+
/// title heading on the page itself.
20+
///
21+
/// The available page kinds are `article` and `sampleCode`.
22+
///
23+
/// This directive is only valid within a `@Metadata` directive:
24+
///
25+
/// ```markdown
26+
/// @Metadata {
27+
/// @PageKind(sampleCode)
28+
/// }
29+
/// ```
30+
public final class PageKind: Semantic, AutomaticDirectiveConvertible {
31+
/// The available kinds for use with the `@PageKind` directive.
32+
public enum Kind: String, CaseIterable, DirectiveArgumentValueConvertible {
33+
/// An article of free-form text; the default for standalone markdown files.
34+
case article
35+
/// A page describing a "sample code" project.
36+
case sampleCode
37+
38+
var renderRole: RenderMetadata.Role {
39+
switch self {
40+
case .article:
41+
return RenderMetadata.Role.article
42+
case .sampleCode:
43+
return RenderMetadata.Role.sampleCode
44+
}
45+
}
46+
47+
var titleHeading: String {
48+
switch self {
49+
case .article:
50+
return "Article"
51+
case .sampleCode:
52+
return "Sample Code"
53+
}
54+
}
55+
}
56+
57+
/// The page kind to apply to the page.
58+
@DirectiveArgumentWrapped(name: .unnamed)
59+
public var kind: Kind
60+
61+
static var keyPaths: [String : AnyKeyPath] = [
62+
"kind" : \PageKind._kind
63+
]
64+
65+
public let originalMarkup: Markdown.BlockDirective
66+
67+
@available(*, deprecated, message: "Do not call directly. Required for 'AutomaticDirectiveConvertible'.")
68+
init(originalMarkup: Markdown.BlockDirective) {
69+
self.originalMarkup = originalMarkup
70+
}
71+
}
72+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2023 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+
import XCTest
14+
@testable import SwiftDocC
15+
16+
class PageKindTests: XCTestCase {
17+
func testPageKindSampleCode() throws {
18+
let (bundle, context) = try testBundleAndContext(named: "SampleBundle")
19+
let reference = ResolvedTopicReference(
20+
bundleIdentifier: bundle.identifier,
21+
path: "/documentation/SampleBundle/MyLocalSample",
22+
sourceLanguage: .swift
23+
)
24+
let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article)
25+
var translator = RenderNodeTranslator(
26+
context: context,
27+
bundle: bundle,
28+
identifier: reference,
29+
source: nil
30+
)
31+
let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode)
32+
33+
XCTAssertEqual(renderNode.metadata.role, RenderMetadata.Role.sampleCode.rawValue)
34+
XCTAssertEqual(renderNode.metadata.roleHeading, Metadata.PageKind.Kind.sampleCode.titleHeading)
35+
}
36+
37+
func testPageKindArticle() throws {
38+
let (bundle, context) = try testBundleAndContext(named: "SampleBundle")
39+
let reference = ResolvedTopicReference(
40+
bundleIdentifier: bundle.identifier,
41+
path: "/documentation/SampleBundle/MySample",
42+
sourceLanguage: .swift
43+
)
44+
let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article)
45+
var translator = RenderNodeTranslator(
46+
context: context,
47+
bundle: bundle,
48+
identifier: reference,
49+
source: nil
50+
)
51+
let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode)
52+
53+
XCTAssertEqual(renderNode.metadata.role, RenderMetadata.Role.article.rawValue)
54+
XCTAssertEqual(renderNode.metadata.roleHeading, Metadata.PageKind.Kind.article.titleHeading)
55+
}
56+
57+
func testPageKindDefault() throws {
58+
let (bundle, context) = try testBundleAndContext(named: "AvailabilityBundle")
59+
let reference = ResolvedTopicReference(
60+
bundleIdentifier: bundle.identifier,
61+
path: "/documentation/AvailabilityBundle/ComplexAvailable",
62+
sourceLanguage: .swift
63+
)
64+
let article = try XCTUnwrap(context.entity(with: reference).semantic as? Article)
65+
var translator = RenderNodeTranslator(
66+
context: context,
67+
bundle: bundle,
68+
identifier: reference,
69+
source: nil
70+
)
71+
let renderNode = try XCTUnwrap(translator.visitArticle(article) as? RenderNode)
72+
73+
XCTAssertEqual(renderNode.metadata.role, "article")
74+
XCTAssertEqual(renderNode.metadata.roleHeading, "Article")
75+
}
76+
77+
func testValidMetadataWithOnlyPageKind() throws {
78+
let source = """
79+
@Metadata {
80+
@PageKind(article)
81+
}
82+
"""
83+
84+
let document = Document(parsing: source, options: .parseBlockDirectives)
85+
let directive = document.child(at: 0) as? BlockDirective
86+
XCTAssertNotNil(directive)
87+
88+
let (bundle, context) = try testBundleAndContext(named: "SampleBundle")
89+
90+
directive.map { directive in
91+
var problems = [Problem]()
92+
XCTAssertEqual(Metadata.directiveName, directive.name)
93+
let metadata = Metadata(from: directive, source: nil, for: bundle, in: context, problems: &problems)
94+
XCTAssertNotNil(metadata)
95+
XCTAssertNotNil(metadata?.pageKind)
96+
XCTAssertEqual(metadata?.pageKind?.kind, .article)
97+
XCTAssert(problems.isEmpty)
98+
}
99+
}
100+
}

Tests/SwiftDocCTests/Semantics/DirectiveInfrastructure/DirectiveIndexTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class DirectiveIndexTests: XCTestCase {
3737
"Metadata",
3838
"Options",
3939
"PageImage",
40+
"PageKind",
4041
"Redirected",
4142
"Row",
4243
"Small",

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, 7)
42+
XCTAssertEqual(reflectedDirective.childDirectives.count, 8)
4343

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

Tests/SwiftDocCTests/Test Bundles/SampleBundle.docc/MyLocalSample.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
@Metadata {
44
@CallToAction(file: "Downloads/plus.svg", purpose: download)
5+
@PageKind(sampleCode)
56
}
67

78
Here's a taste of what you can do with my cool framework.

Tests/SwiftDocCTests/Test Bundles/SampleBundle.docc/MySample.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
@Metadata {
44
@CallToAction(url: "https://example.com/sample.zip", purpose: download)
5+
@PageKind(article)
56
}
67

78
Here's a taste of what you can do with my cool framework.

0 commit comments

Comments
 (0)