Skip to content

Commit 8a086b6

Browse files
jrturtonRichard Turton
andauthored
Experimental markdown output (#1303)
* Add experimental markdown output flag and pass it through to the convert feature flags * Initial export of Markdown from Article * Initial processing of a type-level symbol * Adds symbol declarations and article reference links * Output tutorials to markdown * Be smarter about removing indentation from within block directives * Baseline for adding new tests for markdown output * Basic test infrastructure for markdown output * Adds symbol link tests to markdown output * Tutoorial code rendering markdown tests * Adding metadata to markdown output * Include package source for markdown output test catalog * Output metadata updates * Adds default availability for modules to markdown export * Move availability out of symbol and in to general metadata for markdown output * Refactor markdown output so the final node type is standalone * Add generated markdown flag to render node metadata * Only include unavailable in markdown header if it is true * Initial setup of manifest output, no references * Output of manifest / relationships * Manifest output format updates * Remove member symbol relationship * More compact availability, deal with metadata availability for symbols * Update tests so all inputs are locally defined * Remove print statements from unused visitors * Added snippet handling * Remove or _spi-hide new public API * Remove or _spi-hide new public API (part 2) * Prevent recursion when the abstract of a link contains a link and we are rendering links-with-abstracts * Test and temporary fix for colspan issue swiftlang/swift-markdown#238 * Remove separate SwiftDocCMarkdownOutput target * Bump swift-markdown, remove temporary bug fix * Updates to handle removed bundle parameter * Extract common writer code into a helper function * Clarify use of article.md in test catalog * Deal with internal links with anchor tags * Deal with unresolved symbol links * Updates to improve public API / clarity of MarkdownOutputNode * Improve public API of manifest, sort contents before writing to prevent unneccessary diffs on re-run * Don't use unstructured `Data` for WritableMarkdownOutputNode * Reduce public SPI surface around markdown writing * Make relationship subtype a specific type rather than a string * Make it clear that path component is a fallback title, harden linked list rendering * Add todos and missing tests * Move markdown output types from public SPI to package * don't include scheme or host in documentation links * Reformat multi-condition if statements * remove whitespace-only change * Remove unused declaration * Remove redundant return types * Remove MarkdownOutputNodeTranslator * Add TODO for alternate declarations * Add TODO for structural review * Add stacks todo, set writable type back to package * uri -> identifier * Use RelationshipsGroup.Kind instead of creating a mirror type * Lower access level of manifest, remove children method * Restrict package level declarations to those actually used at package level * Merging main --------- Co-authored-by: Richard Turton <r_turton@apple.com>
1 parent d89dfe7 commit 8a086b6

File tree

16 files changed

+2365
-5
lines changed

16 files changed

+2365
-5
lines changed

Package.resolved

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/DocCCommandLine/Action/Actions/Convert/ConvertFileWritingConsumer.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import Foundation
1212
import SwiftDocC
1313

14-
struct ConvertFileWritingConsumer: ConvertOutputConsumer, ExternalNodeConsumer {
14+
struct ConvertFileWritingConsumer: ConvertOutputConsumer, ExternalNodeConsumer, ConvertOutputMarkdownConsumer {
1515
var targetFolder: URL
1616
var bundleRootFolder: URL?
1717
var fileManager: any FileManagerProtocol
@@ -68,6 +68,21 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer, ExternalNodeConsumer {
6868
indexer?.index(renderNode)
6969
}
7070

71+
func consume(markdownNode: WritableMarkdownOutputNode) throws {
72+
try renderNodeWriter.write(markdownNode)
73+
}
74+
75+
func consume(markdownManifest: MarkdownOutputManifest) throws {
76+
let url = targetFolder.appendingPathComponent("\(markdownManifest.title)-markdown-manifest.json", isDirectory: false)
77+
let encoder = JSONEncoder()
78+
encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
79+
#if DEBUG
80+
encoder.outputFormatting.insert(.prettyPrinted)
81+
#endif
82+
let data = try encoder.encode(markdownManifest)
83+
try fileManager.createFile(at: url, contents: data)
84+
}
85+
7186
func consume(externalRenderNode: ExternalRenderNode) throws {
7287
// Index the external node, if indexing is enabled.
7388
indexer?.index(externalRenderNode)

Sources/DocCCommandLine/Action/Actions/Convert/JSONEncodingRenderNodeWriter.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,25 @@ class JSONEncodingRenderNodeWriter {
8989
}
9090
}
9191

92+
/// Writes a markdown node to a file at a location based on the node's relative URL.
93+
///
94+
/// If the target path to the markdown file includes intermediate folders that don't exist, the writer object will ask the file manager, with which it was created, to
95+
/// create those intermediate folders before writing the markdown file.
96+
///
97+
/// - Parameters:
98+
/// - markdownNode: The node which the writer object writes
99+
func write(_ markdownNode: WritableMarkdownOutputNode) throws {
100+
let fileSafePath = NodeURLGenerator.fileSafeReferencePath(
101+
markdownNode.identifier,
102+
lowercased: true
103+
)
104+
105+
try write(
106+
markdownNode.node.generateDataRepresentation(),
107+
toFileSafePath: "data/\(fileSafePath).md"
108+
)
109+
}
110+
92111
func write(_ data: Data, toFileSafePath fileSafePath: String) throws {
93112
// The path on disk to write the data at.
94113
let fileURL = targetFolder.appendingPathComponent(fileSafePath, isDirectory: false)

Sources/DocCCommandLine/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ extension ConvertAction {
2525
FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled = convert.enableExperimentalOverloadedSymbolPresentation
2626
FeatureFlags.current.isMentionedInEnabled = convert.enableMentionedIn
2727
FeatureFlags.current.isParametersAndReturnsValidationEnabled = convert.enableParametersAndReturnsValidation
28+
FeatureFlags.current.isExperimentalMarkdownOutputEnabled = convert.enableExperimentalMarkdownOutput
29+
FeatureFlags.current.isExperimentalMarkdownOutputManifestEnabled = convert.enableExperimentalMarkdownOutputManifest
2830

2931
// If the user-provided a URL for an external link resolver, attempt to
3032
// initialize an `OutOfProcessReferenceResolver` with the provided URL.

Sources/DocCCommandLine/ArgumentParsing/Subcommands/Convert.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,12 @@ extension Docc {
543543
@available(*, deprecated, message: "This flag is unused and only exist for backwards compatibility")
544544
var _unusedExperimentalMentionedInFlagForBackwardsCompatibility = false
545545

546+
@Flag(help: "Experimental: Create markdown versions of documents")
547+
var enableExperimentalMarkdownOutput = false
548+
549+
@Flag(help: "Experimental: Create manifest file of markdown outputs. Ignored if --enable-experimental-markdown-output is not set.")
550+
var enableExperimentalMarkdownOutputManifest = false
551+
546552
@Flag(
547553
name: .customLong("parameters-and-returns-validation"),
548554
inversion: .prefixedEnableDisable,
@@ -637,6 +643,18 @@ extension Docc {
637643
get { featureFlags.enableExperimentalOverloadedSymbolPresentation }
638644
set { featureFlags.enableExperimentalOverloadedSymbolPresentation = newValue }
639645
}
646+
647+
/// A user-provided value that is true if the user enables experimental markdown output
648+
public var enableExperimentalMarkdownOutput: Bool {
649+
get { featureFlags.enableExperimentalMarkdownOutput }
650+
set { featureFlags.enableExperimentalMarkdownOutput = newValue }
651+
}
652+
653+
/// A user-provided value that is true if the user enables experimental markdown output
654+
public var enableExperimentalMarkdownOutputManifest: Bool {
655+
get { featureFlags.enableExperimentalMarkdownOutputManifest }
656+
set { featureFlags.enableExperimentalMarkdownOutputManifest = newValue }
657+
}
640658

641659
/// A user-provided value that is true if the user enables experimental automatically generated "mentioned in"
642660
/// links on symbols.

Sources/SwiftDocC/Converter/DocumentationContextConverter.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,21 @@ public class DocumentationContextConverter {
113113
)
114114
return translator.visit(node.semantic) as? RenderNode
115115
}
116+
117+
/// Converts a documentation node to a markdown node.
118+
/// - Parameters:
119+
/// - node: The documentation node to convert.
120+
/// - Returns: The markdown node representation of the documentation node.
121+
internal func markdownOutput(for node: DocumentationNode) -> CollectedMarkdownOutput? {
122+
guard !node.isVirtual else {
123+
return nil
124+
}
125+
126+
var visitor = MarkdownOutputSemanticVisitor(context: context, node: node)
127+
128+
if let node = visitor.createOutput() {
129+
return CollectedMarkdownOutput(identifier: visitor.identifier, node: node, manifest: visitor.manifest)
130+
}
131+
return nil
132+
}
116133
}

Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ package enum ConvertActionConverter {
8080
var assets = [RenderReferenceType : [any RenderReference]]()
8181
var coverageInfo = [CoverageDataEntry]()
8282
let coverageFilterClosure = documentationCoverageOptions.generateFilterClosure()
83+
var markdownManifest = MarkdownOutputManifest(title: context.inputs.displayName, documents: [])
8384

8485
// An inner function to gather problems for errors encountered during the conversion.
8586
//
@@ -131,6 +132,21 @@ package enum ConvertActionConverter {
131132
return
132133
}
133134

135+
if FeatureFlags.current.isExperimentalMarkdownOutputEnabled,
136+
let markdownConsumer = outputConsumer as? (any ConvertOutputMarkdownConsumer),
137+
let markdownNode = converter.markdownOutput(for: entity)
138+
{
139+
try markdownConsumer.consume(markdownNode: markdownNode.writable)
140+
if FeatureFlags.current.isExperimentalMarkdownOutputManifestEnabled,
141+
let manifest = markdownNode.manifest
142+
{
143+
resultsGroup.async(queue: resultsSyncQueue) {
144+
markdownManifest.documents.formUnion(manifest.documents)
145+
markdownManifest.relationships.formUnion(manifest.relationships)
146+
}
147+
}
148+
}
149+
134150
try outputConsumer.consume(renderNode: renderNode)
135151

136152
switch documentationCoverageOptions.level {
@@ -235,6 +251,12 @@ package enum ConvertActionConverter {
235251
}
236252
}
237253
}
254+
255+
if FeatureFlags.current.isExperimentalMarkdownOutputManifestEnabled,
256+
let markdownConsumer = outputConsumer as? (any ConvertOutputMarkdownConsumer)
257+
{
258+
try markdownConsumer.consume(markdownManifest: markdownManifest)
259+
}
238260

239261
switch documentationCoverageOptions.level {
240262
case .detailed, .brief:

Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ public protocol ConvertOutputConsumer {
4848

4949
/// Consumes a file representation of the local link resolution information.
5050
func consume(linkResolutionInformation: SerializableLinkResolutionInformation) throws
51+
52+
}
53+
54+
package protocol ConvertOutputMarkdownConsumer {
55+
/// Consumes a markdown output node
56+
func consume(markdownNode: WritableMarkdownOutputNode) throws
57+
58+
/// Consumes a markdown output manifest
59+
func consume(markdownManifest: MarkdownOutputManifest) throws
5160
}
5261

5362
// Default implementations that discard the documentation conversion products, for consumers that don't need these

0 commit comments

Comments
 (0)