Skip to content

Commit 117feb0

Browse files
Initial template work for docc catalogs (#719)
Int command added to DocC CLI. The `init` command generates a documentation catalog folder in the specified directory. There are two templates to generate the documentation catalog from: - `articleOnly` - `tutorial` rdar://115287607
1 parent 88fd578 commit 117feb0

File tree

10 files changed

+706
-4
lines changed

10 files changed

+706
-4
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2024 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 SwiftDocC
13+
14+
struct CatalogTemplate {
15+
16+
let files: [String: String]
17+
let additionalDirectories: [String]
18+
19+
/// Creates a Catalog Template using one of the provided template kinds.
20+
init(_ templateKind: CatalogTemplateKind, title: String) throws {
21+
switch templateKind {
22+
case .articleOnly:
23+
self.files = CatalogTemplateKind.articleOnlyTemplateFiles(title)
24+
self.additionalDirectories = ["Resources"]
25+
case .tutorial:
26+
self.files = CatalogTemplateKind.tutorialTemplateFiles(title)
27+
self.additionalDirectories = ["Resources", "Chapter01/Resources"]
28+
29+
}
30+
}
31+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2024 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 SwiftDocC
13+
14+
/// Specifies the different template kinds available for
15+
/// initializing the documentation catalog.
16+
public enum CatalogTemplateKind: String {
17+
18+
/// A template designed for authoring article-only reference documentation, consisting of a catalog that contains only one markdown file.
19+
case articleOnly
20+
21+
/// A template designed for authoring tutorials, consisting of a catalog that contains a table of contents and a chapter.
22+
case tutorial
23+
24+
}
25+
26+
/// Content of the different templates
27+
extension CatalogTemplateKind {
28+
29+
/// Content of the 'articleOnly' template
30+
static func articleOnlyTemplateFiles(_ title: String) -> [String: String] {
31+
[
32+
"\(title).md": """
33+
# \(title)
34+
35+
<!--- Metadata configuration to make appear this documentation page as a top-level page -->
36+
37+
@Metadata {
38+
@TechnologyRoot
39+
}
40+
41+
Add a single sentence or sentence fragment, which DocC uses as the page’s abstract or summary.
42+
43+
## Overview
44+
45+
Add one or more paragraphs that introduce your content overview.
46+
"""
47+
]
48+
}
49+
50+
/// Content of the 'tutorial' template
51+
static func tutorialTemplateFiles(_ title: String) -> [String: String] {
52+
[
53+
"table-of-contents.tutorial": """
54+
@Tutorials(name: "\(title)") {
55+
@Intro(title: "Tutorial Introduction") {
56+
Add one or more paragraphs that introduce your tutorial.
57+
}
58+
@Chapter(name: "Chapter Name") {
59+
@Image(source: "add-your-chapter-image-filename-here.jpg", alt: "Add an accessible description for your image here.")
60+
@TutorialReference(tutorial: "doc:page-01")
61+
}
62+
}
63+
""",
64+
"Chapter01/page-01.tutorial": """
65+
@Tutorial() {
66+
@Intro(title: "Tutorial Page Title") {
67+
Add one paragraph that introduce your tutorial.
68+
}
69+
@Section(title: "Section Name") {
70+
@ContentAndMedia {
71+
Add text that introduces the tasks that the reader needs to follow.
72+
@Image(source: "add-your-section-image-filename-here.jpg", alt: "Add an accessible description for your image here.")
73+
}
74+
@Steps {
75+
@Step {
76+
This is a step with code.
77+
@Code(name: "", file: "")
78+
}
79+
@Step {
80+
This is a step with an image.
81+
@Image(source: "", alt: "")
82+
}
83+
}
84+
}
85+
}
86+
"""
87+
]
88+
}
89+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2024 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 SwiftDocC
13+
14+
/// An action that generates a documentation catalog from a template seed.
15+
public struct InitAction: Action {
16+
17+
enum Error: DescribedError {
18+
case catalogAlreadyExists
19+
var errorDescription: String {
20+
switch self {
21+
case .catalogAlreadyExists: return "A documentation catalog with the same name already exists in the output directory."
22+
}
23+
}
24+
}
25+
26+
private var fileManager: FileManagerProtocol
27+
private let catalogOutputURL: URL
28+
private let catalogTemplateKind: CatalogTemplateKind
29+
private let documentationTitle: String
30+
31+
/// Creates a new Init action from the given parameters.
32+
///
33+
/// - Parameters:
34+
/// - catalogOutputDirectory: The URL to the directory where the catalog will be stored.
35+
/// - documentationTitle: The title of the used for the top-level root file.
36+
/// - catalogTemplate: The template used to initialize the catalog.
37+
/// - fileManager: A file persistence manager.
38+
init(
39+
catalogOutputDirectory: URL,
40+
documentationTitle: String,
41+
catalogTemplate: CatalogTemplateKind,
42+
fileManager: FileManagerProtocol
43+
) throws {
44+
self.catalogOutputURL = catalogOutputDirectory
45+
self.documentationTitle = documentationTitle
46+
self.catalogTemplateKind = catalogTemplate
47+
self.fileManager = fileManager
48+
}
49+
50+
/// Creates a new Init action from the given parameters.
51+
///
52+
/// - Parameters:
53+
/// - catalogOutputDirectory: The URL to the directory where the catalog will be stored.
54+
/// - documentationTitle: The title of the used for the top-level root file.
55+
/// - catalogTemplate: The template used to initialize the catalog.
56+
public init(
57+
catalogOutputDirectory: URL,
58+
documentationTitle: String,
59+
catalogTemplate: CatalogTemplateKind
60+
) throws {
61+
// Note: This public initializer recalls the internal initializer
62+
// and exists separately because the FileManagerProtocol type
63+
// we use to enable mocking in tests is internal to this framework.
64+
try self.init(
65+
catalogOutputDirectory: catalogOutputDirectory.appendingPathComponent("\(documentationTitle).docc"),
66+
documentationTitle: documentationTitle,
67+
catalogTemplate: catalogTemplate,
68+
fileManager: FileManager.default
69+
)
70+
}
71+
72+
/// Generates a documentation catalog from a catalog template.
73+
///
74+
/// - Parameter logHandle: The file handle that the convert and preview actions will print debug messages to.
75+
public mutating func perform(logHandle: SwiftDocC.LogHandle) throws -> ActionResult {
76+
77+
let diagnosticEngine: DiagnosticEngine = DiagnosticEngine(treatWarningsAsErrors: false)
78+
diagnosticEngine.filterLevel = .warning
79+
diagnosticEngine.add(DiagnosticConsoleWriter(formattingOptions: []))
80+
var logHandle = logHandle
81+
var directoryURLsList = [URL]()
82+
var resourceDocumentationLink: String {
83+
switch catalogTemplateKind {
84+
case .articleOnly:
85+
return "https://www.swift.org/documentation/docc/"
86+
case .tutorial:
87+
return "https://www.swift.org/documentation/docc/tutorial-syntax"
88+
}
89+
}
90+
91+
defer {
92+
diagnosticEngine.flush()
93+
}
94+
95+
do {
96+
// Create the directory where the catalog will be initialized.
97+
try fileManager.createDirectory(
98+
at: catalogOutputURL,
99+
withIntermediateDirectories: false,
100+
attributes: nil
101+
)
102+
} catch CocoaError.fileWriteFileExists {
103+
diagnosticEngine.emit(
104+
Problem(
105+
diagnostic: Diagnostic(
106+
severity: .error,
107+
identifier: "org.swift.DocumentationCatalogAlreadyExists",
108+
summary: Error.catalogAlreadyExists.errorDescription
109+
)
110+
)
111+
)
112+
return ActionResult(
113+
didEncounterError: !diagnosticEngine.problems.isEmpty,
114+
outputs: []
115+
)
116+
}
117+
118+
do {
119+
// Create a catalog template using the options passed through the CLI.
120+
let catalogTemplate = try CatalogTemplate(catalogTemplateKind, title: documentationTitle)
121+
// Store the catalog on disk.
122+
for (relativePath, content) in catalogTemplate.files {
123+
// Generate the directories for file storage
124+
// by adding the article path to the output URL and
125+
// excluding the file name.
126+
let fileURL = catalogOutputURL.appendingPathComponent(relativePath)
127+
try fileManager.createDirectory(
128+
at: fileURL.deletingLastPathComponent(),
129+
withIntermediateDirectories: true,
130+
attributes: nil
131+
)
132+
// Generate the article file at the specified URL path.
133+
try fileManager.createFile(at: fileURL, contents: Data(content.utf8))
134+
directoryURLsList.append(fileURL.relative(to: catalogOutputURL)!)
135+
}
136+
// Write additional directiories defined in the catalog.
137+
// Ex. `Resources`
138+
for relativePath in catalogTemplate.additionalDirectories {
139+
let directoryURL = catalogOutputURL.appendingPathComponent(relativePath)
140+
try fileManager.createDirectory(
141+
at: directoryURL,
142+
withIntermediateDirectories: true,
143+
attributes: nil
144+
)
145+
directoryURLsList.append(directoryURL.relative(to: catalogOutputURL)!)
146+
}
147+
print(
148+
"""
149+
A new documentation catalog has been generated at \(catalogOutputURL.path) with the following structure:
150+
151+
\(directoryURLsList.map {
152+
"""
153+
- \($0)
154+
"""
155+
}.joined(separator: "\n"))
156+
157+
To preview it, run the command:
158+
docc preview \(catalogOutputURL.path)
159+
160+
For additional resources on how to get started with DocC, please refer to \(resourceDocumentationLink).
161+
""",
162+
to: &logHandle
163+
)
164+
} catch {
165+
// If an error occurs, delete the generated output.
166+
if fileManager.fileExists(atPath: catalogOutputURL.path) {
167+
try fileManager.removeItem(at: catalogOutputURL)
168+
}
169+
diagnosticEngine.emit(
170+
Problem(
171+
diagnostic: Diagnostic(
172+
severity: .error,
173+
identifier: "org.swift.InitActionUnexpectedError",
174+
summary: error.localizedDescription
175+
)
176+
)
177+
)
178+
}
179+
180+
return ActionResult(
181+
didEncounterError: !diagnosticEngine.problems.isEmpty,
182+
outputs: [catalogOutputURL]
183+
)
184+
}
185+
186+
}
187+
188+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2024 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 SwiftDocC
12+
import Foundation
13+
14+
extension InitAction {
15+
/// Creates a init action from the options given in the init command.
16+
///
17+
/// - Parameter initOptions: The init options this `InitAction` will be based on.
18+
public init(
19+
fromInitOptions initOptions: InitOptions
20+
) throws {
21+
// Initialize the `InitAction` from the options provided by the `Init` command
22+
try self.init(
23+
catalogOutputDirectory: initOptions.providedCatalogOutputDirURL,
24+
documentationTitle: initOptions.name,
25+
catalogTemplate: initOptions.catalogTemplate
26+
)
27+
}
28+
}

0 commit comments

Comments
 (0)