Skip to content

Commit 4189720

Browse files
authored
Add support for custom aside titles (#303)
rdar://65856948
1 parent 0397c9d commit 4189720

File tree

8 files changed

+169
-158
lines changed

8 files changed

+169
-158
lines changed

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SwiftDocC/Model/Rendering/Content/RenderBlockContent.swift

Lines changed: 79 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -72,119 +72,93 @@ public enum RenderBlockContent: Equatable {
7272
}
7373
}
7474

75-
/// An aside style.
76-
public enum AsideStyle: String, Codable, Equatable, CaseIterable {
77-
/// A note aside.
78-
case note
79-
/// An important aside.
80-
case important
81-
/// A warning aside.
82-
case warning
83-
/// An experiment aside.
84-
case experiment
85-
/// A tip aside.
86-
case tip
87-
/// An attention aside.
88-
case attention
89-
/// An author aside.
90-
case author
91-
/// An authors aside.
92-
case authors
93-
/// A bug aside.
94-
case bug
95-
/// A complexity aside.
96-
case complexity
97-
/// A copyright aside.
98-
case copyright
99-
/// A date aside.
100-
case date
101-
/// An invariant aside.
102-
case invariant
103-
/// A mutatingVariant aside.
104-
case mutatingVariant
105-
/// A nonMutatingVariant aside.
106-
case nonMutatingVariant
107-
/// A postcondition aside.
108-
case postcondition
109-
/// A precondition aside.
110-
case precondition
111-
/// A remark aside.
112-
case remark
113-
/// A requires aside.
114-
case requires
115-
/// A since aside.
116-
case since
117-
/// A todo aside.
118-
case todo
119-
/// A version aside.
120-
case version
121-
/// A throws aside.
122-
case `throws`
75+
/// A type the describes an aside style.
76+
public struct AsideStyle: Codable, Equatable {
77+
private static let specialDisplayNames: [String: String] = [
78+
"nonmutatingvariant": "Non-Mutating Variant",
79+
"mutatingvariant": "Mutating Variant",
80+
"todo": "To Do",
81+
]
12382

124-
/// Creates a new aside style of the given kind.
125-
init(_ kind: Aside.Kind) {
126-
switch kind {
127-
case .note: self = .note
128-
case .important: self = .important
129-
case .warning: self = .warning
130-
case .experiment: self = .experiment
131-
case .tip: self = .tip
132-
case .attention: self = .attention
133-
case .author: self = .author
134-
case .authors: self = .authors
135-
case .bug: self = .bug
136-
case .complexity: self = .complexity
137-
case .copyright: self = .copyright
138-
case .date: self = .date
139-
case .invariant: self = .invariant
140-
case .mutatingVariant: self = .mutatingVariant
141-
case .nonMutatingVariant: self = .nonMutatingVariant
142-
case .postcondition: self = .postcondition
143-
case .precondition: self = .precondition
144-
case .remark: self = .remark
145-
case .requires: self = .requires
146-
case .since: self = .since
147-
case .todo: self = .todo
148-
case .version: self = .version
149-
case .throws: self = .throws
150-
}
83+
/// Returns a Boolean value indicating whether two aside styles are equal.
84+
///
85+
/// The comparison uses ``rawValue`` and is case-insensitive.
86+
///
87+
/// - Parameters:
88+
/// - lhs: An aside style to compare.
89+
/// - rhs: Another aside style to compare.
90+
public static func ==(lhs: AsideStyle, rhs: AsideStyle) -> Bool {
91+
lhs.rawValue.caseInsensitiveCompare(rhs.rawValue) == .orderedSame
15192
}
93+
94+
/// The underlying raw string value.
95+
public var rawValue: String
15296

153-
/// Create a new aside style by looking up its display name. Returns nil if no aside style matches.
154-
init?(displayName: String) {
155-
let casesAndDisplayNames = AsideStyle.allCases.map { (kind: $0, displayName: $0.displayName() )}
156-
guard let matchingCaseAndDisplayName = casesAndDisplayNames.first(where: { $0.displayName == displayName }) else {
157-
return nil
97+
/// The heading text to use when rendering this style of aside.
98+
public var displayName: String {
99+
if let value = Self.specialDisplayNames[rawValue.lowercased()] {
100+
return value
101+
} else if rawValue.contains(where: \.isUppercase) {
102+
// If any character is upper-cased, assume the content has
103+
// specific casing and return the raw value.
104+
return rawValue
105+
} else {
106+
return rawValue.capitalized
158107
}
159-
self = matchingCaseAndDisplayName.kind
160108
}
161109

162-
/// Returns the style of aside to use when rendering.
110+
/// The style of aside to use when rendering.
163111
///
164112
/// DocC Render currently has five styles of asides: Note, Tip, Experiment, Important, and Warning. Asides
165113
/// of these styles can emit their own style into the output, but other styles need to be rendered as one of
166-
/// these five styles. This function maps aside styles to the render style used in the output.
167-
func renderKind() -> Self {
168-
switch self {
169-
case .important, .warning, .experiment, .tip:
170-
return self
114+
/// these five styles. This property maps aside styles to the render style used in the output.
115+
var renderKind: String {
116+
switch rawValue.lowercased() {
117+
case let lowercasedRawValue
118+
where [
119+
"important",
120+
"warning",
121+
"experiment",
122+
"tip"
123+
].contains(lowercasedRawValue):
124+
return lowercasedRawValue
171125
default:
172-
return .note
126+
return "note"
173127
}
174128
}
175129

176-
/// The heading text to use when rendering this style of aside.
177-
public func displayName() -> String {
178-
switch self {
179-
case .mutatingVariant:
180-
return "Mutating Variant"
181-
case .nonMutatingVariant:
182-
return "Non-Mutating Variant"
183-
case .todo:
184-
return "To Do"
185-
default:
186-
return self.rawValue.capitalized
187-
}
130+
/// Creates an aside type for the specified aside kind.
131+
/// - Parameter asideKind: The aside kind that provides the display name.
132+
public init(asideKind: Aside.Kind) {
133+
self.rawValue = asideKind.rawValue
134+
}
135+
136+
/// Creates an aside style for the specified raw value.
137+
/// - Parameter rawValue: The heading text to use when rendering this style of aside.
138+
public init(rawValue: String) {
139+
self.rawValue = rawValue
140+
}
141+
142+
/// Creates an aside style with the specified display name.
143+
/// - Parameter displayName: The heading text to use when rendering this style of aside.
144+
public init(displayName: String) {
145+
self.rawValue = Self.specialDisplayNames.first(where: { $0.value == displayName })?.key ?? displayName
146+
}
147+
148+
/// Encodes the aside style into the specified encoder.
149+
/// - Parameter encoder: The encoder to write data to.
150+
public func encode(to encoder: Encoder) throws {
151+
// For backwards compatibility, encode only the display name and
152+
// not a key-value pair.
153+
var container = encoder.singleValueContainer()
154+
try container.encode(rawValue)
155+
}
156+
157+
/// Creates an aside style by decoding the specified decoder.
158+
/// - Parameter decoder: The decoder to read data from.
159+
public init(from decoder: Decoder) throws {
160+
let container = try decoder.singleValueContainer()
161+
self.rawValue = try container.decode(String.self)
188162
}
189163
}
190164

@@ -270,10 +244,8 @@ extension RenderBlockContent: Codable {
270244
self = try .paragraph(inlineContent: container.decode([RenderInlineContent].self, forKey: .inlineContent))
271245
case .aside:
272246
var style = try container.decode(AsideStyle.self, forKey: .style)
273-
if style == .note,
274-
let displayName = try container.decodeIfPresent(String.self, forKey: .name),
275-
let decodedKind = AsideStyle(displayName: displayName) {
276-
style = decodedKind
247+
if style.renderKind == "note", let displayName = try container.decodeIfPresent(String.self, forKey: .name) {
248+
style = AsideStyle(displayName: displayName)
277249
}
278250
self = try .aside(style: style, content: container.decode([RenderBlockContent].self, forKey: .content))
279251
case .codeListing:
@@ -337,16 +309,9 @@ extension RenderBlockContent: Codable {
337309
case .paragraph(let inlineContent):
338310
try container.encode(inlineContent, forKey: .inlineContent)
339311
case .aside(let style, let content):
340-
let renderKind = style.renderKind()
341-
if renderKind != style {
342-
// Aside styles after the first five render as notes with a special "name" field
343-
try container.encode(renderKind, forKey: .style)
344-
try container.encode(style.displayName(), forKey: .name)
345-
try container.encode(content, forKey: .content)
346-
} else {
347-
try container.encode(style, forKey: .style)
348-
try container.encode(content, forKey: .content)
349-
}
312+
try container.encode(style.renderKind, forKey: .style)
313+
try container.encode(style.displayName, forKey: .name)
314+
try container.encode(content, forKey: .content)
350315
case .codeListing(let syntax, let code, metadata: let metadata):
351316
try container.encode(syntax, forKey: .syntax)
352317
try container.encode(code, forKey: .code)

Sources/SwiftDocC/Model/Rendering/RenderContentCompiler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct RenderContentCompiler: MarkupVisitor {
3535

3636
mutating func visitBlockQuote(_ blockQuote: BlockQuote) -> [RenderContent] {
3737
let aside = Aside(blockQuote)
38-
return [RenderBlockContent.aside(style: RenderBlockContent.AsideStyle(aside.kind),
38+
return [RenderBlockContent.aside(style: RenderBlockContent.AsideStyle(asideKind: aside.kind),
3939
content: aside.content.reduce(into: [], { result, child in result.append(contentsOf: visit(child))}) as! [RenderBlockContent])]
4040
}
4141

Sources/SwiftDocC/Utility/MarkupExtensions/ListItemExtractor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ extension ListItem {
103103
}
104104

105105
/**
106-
Extract a "simple tag" from the liset of known list item tags.
106+
Extract a "simple tag" from the list of known list item tags.
107107

108108
Expected form:
109109

Tests/SwiftDocCTests/Indexing/IndexingTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class IndexingTests: XCTestCase {
181181
}
182182

183183
func testRenderBlockContentAside() {
184-
let aside = RenderBlockContent.aside(style: .experiment, content: [.paragraph(inlineContent: [.text("Hello, world!")])])
184+
let aside = RenderBlockContent.aside(style: .init(rawValue: "Experiment"), content: [.paragraph(inlineContent: [.text("Hello, world!")])])
185185
XCTAssertEqual([], aside.headings)
186186
XCTAssertEqual("Hello, world!", aside.rawIndexableTextContent(references: [:]))
187187
}

Tests/SwiftDocCTests/Model/RenderNodeSerializationTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class RenderNodeSerializationTests: XCTestCase {
3838

3939
let blockContent: [RenderBlockContent] = [
4040
.paragraph(inlineContent: inlines),
41-
.aside(style: .experiment, content: [
41+
.aside(style: .init(rawValue: "Experiment"), content: [
4242
.paragraph(inlineContent: [
4343
.text("Try running the project in the Simulator using the "),
4444
.strong(inlineContent: [.text("Project > Run")]),
@@ -53,7 +53,7 @@ class RenderNodeSerializationTests: XCTestCase {
5353
.step(content: [.paragraph(inlineContent: [.text("Lorem ipsum")])], caption: [.paragraph(inlineContent: [.text("Caption")])], media: .init("screenshot2.png"), code: nil, runtimePreview: nil),
5454
.step(content: [.paragraph(inlineContent: [.text("Lorem ipsum")])], caption: [], media: nil, code: .init("helloworld.swift"), runtimePreview: .init("screenshot2.png")),
5555
.step(content: [.paragraph(inlineContent: [.text("Lorem ipsum")])], caption: [], media: .init("screenshot3.png"), code: nil, runtimePreview: nil),
56-
.aside(style: .note, content: [.paragraph(inlineContent: [.text("Lorem ipsum dolor emit.")])]),
56+
.aside(style: .init(rawValue: "Note"), content: [.paragraph(inlineContent: [.text("Lorem ipsum dolor emit.")])]),
5757
.step(content: [.paragraph(inlineContent: [.text("Lorem ipsum")])], caption: [], media: .init("screenshot4.png"), code: nil, runtimePreview: nil),
5858
]
5959

0 commit comments

Comments
 (0)