Compare commits
4 Commits
e153f15d43
...
29e35ae386
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29e35ae386 | ||
|
|
672c4b85f2 | ||
|
|
295b701be1 | ||
|
|
984b0dd29c |
1
.spi.yml
1
.spi.yml
@@ -2,4 +2,3 @@ version: 1
|
||||
builder:
|
||||
configs:
|
||||
- documentation_targets: [Inotify, TaskCLI]
|
||||
platform: Linux
|
||||
|
||||
@@ -47,7 +47,6 @@ let package = Package(
|
||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
|
||||
.product(name: "Logging", package: "swift-log"),
|
||||
.product(name: "_NIOFileSystem", package: "swift-nio"),
|
||||
.product(name: "Subprocess", package: "swift-subprocess"),
|
||||
.product(name: "Noora", package: "Noora")
|
||||
]
|
||||
|
||||
@@ -4,6 +4,6 @@ import ArgumentParser
|
||||
struct Command: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "Project tasks of Astzweig's Swift Inotify project.",
|
||||
subcommands: [TestCommand.self, GenerateDocumentationCommand.self]
|
||||
subcommands: [TestCommand.self, GenerateDocsCommand.self]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import _NIOFileSystem
|
||||
|
||||
public struct DoccFinder {
|
||||
static let fileManager = FileSystem.shared
|
||||
|
||||
public static func getTargetsWithDocumentation(at paths: String...) async throws -> [String] {
|
||||
try await Self.getTargetsWithDocumentation(at: paths)
|
||||
}
|
||||
|
||||
static func getTargetsWithDocumentation(at paths: [String]) async throws -> [String] {
|
||||
var resolved: [String] = []
|
||||
|
||||
for path in paths {
|
||||
let itemPath = FilePath(path)
|
||||
|
||||
try await withSubdirectories(at: itemPath) { targetPath in
|
||||
print("Target path is", targetPath.description)
|
||||
try await withSubdirectories(at: targetPath) { subdirectory in
|
||||
guard subdirectory.description.hasSuffix(".docc") else { return }
|
||||
guard let target = targetPath.lastComponent?.description else { return }
|
||||
resolved.append(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolved
|
||||
}
|
||||
|
||||
private static func withSubdirectories(at path: FilePath, body: (FilePath) async throws -> Void) async throws {
|
||||
let directoryHandle = try await fileManager.openDirectory(atPath: path)
|
||||
for try await childContent in directoryHandle.listContents() {
|
||||
guard childContent.type == .directory else { continue }
|
||||
try await body(childContent.path)
|
||||
}
|
||||
try await directoryHandle.close()
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ import Logging
|
||||
import Noora
|
||||
import Subprocess
|
||||
|
||||
struct GenerateDocumentationCommand: AsyncParsableCommand {
|
||||
struct GenerateDocsCommand: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "generate-documentation",
|
||||
abstract: "Generate DocC documentation of all targets inside a Linux container.",
|
||||
commandName: "generate-docs",
|
||||
abstract: "Generate DocC documentation inside a Linux container.",
|
||||
aliases: ["gd"],
|
||||
)
|
||||
|
||||
@@ -16,20 +16,20 @@ struct GenerateDocumentationCommand: AsyncParsableCommand {
|
||||
|
||||
private static let doccPluginURL = "https://github.com/apple/swift-docc-plugin.git"
|
||||
private static let doccPluginMinVersion = "1.4.0"
|
||||
private static let targets = ["Inotify", "TaskCLI"]
|
||||
private static let hostingBasePath = "swift-inotify"
|
||||
private static let skipItems: Set<String> = [".git", ".build", ".swiftpm", "public"]
|
||||
|
||||
// MARK: - Run
|
||||
|
||||
func run() async throws {
|
||||
let noora = Noora()
|
||||
let logger = global.makeLogger(labeled: "swift-inotify.cli.task.generate-documentation")
|
||||
let logger = global.makeLogger(labeled: "swift-inotify.cli.task.generate-docs")
|
||||
let fileManager = FileManager.default
|
||||
let projectDirectory = URL(fileURLWithPath: fileManager.currentDirectoryPath)
|
||||
|
||||
let targets = try await Self.targets(for: projectDirectory)
|
||||
|
||||
noora.info("Generating DocC documentation on Linux.")
|
||||
logger.debug("Current directory", metadata: ["current-directory": "\(projectDirectory.path(percentEncoded: false))", "targets": "\(targets.joined(separator: ", "))"])
|
||||
logger.debug("Current directory", metadata: ["current-directory": "\(projectDirectory.path(percentEncoded: false))"])
|
||||
|
||||
let tempDirectory = try copyProject(from: projectDirectory)
|
||||
logger.info("Copied project to temporary directory.", metadata: ["path": "\(tempDirectory.path(percentEncoded: false))"])
|
||||
@@ -40,7 +40,7 @@ struct GenerateDocumentationCommand: AsyncParsableCommand {
|
||||
}
|
||||
|
||||
try await injectDoccPluginDependency(in: tempDirectory, logger: logger)
|
||||
let script = Self.makeRunScript(for: targets)
|
||||
let script = Self.makeRunScript()
|
||||
|
||||
logger.debug("Container script", metadata: ["script": "\(script)"])
|
||||
let dockerResult = try await Subprocess.run(
|
||||
@@ -70,59 +70,46 @@ struct GenerateDocumentationCommand: AsyncParsableCommand {
|
||||
}
|
||||
|
||||
try copyResults(from: tempDirectory, to: projectDirectory)
|
||||
try Self.generateIndexHTML(
|
||||
templateURL: projectDirectory.appending(path: ".github/workflows/index.tpl.html"),
|
||||
outputURL: projectDirectory.appending(path: "public/index.html")
|
||||
)
|
||||
|
||||
noora.success(
|
||||
.alert("Documentation generated successfully.",
|
||||
takeaways: targets.map {
|
||||
takeaways: Self.targets.map {
|
||||
"./public/\($0.lowercased())/"
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private static func generateIndexHTML(templateURL: URL, outputURL: URL) throws {
|
||||
var content = try String(contentsOf: templateURL, encoding: .utf8)
|
||||
|
||||
let replacements: [(String, String)] = [
|
||||
("{{project.name}}", "Swift Inotify"),
|
||||
("{{project.tagline}}", "🗂️ Monitor filesystem events on Linux using modern Swift concurrency"),
|
||||
("{{project.links}}", """
|
||||
<li><a href="inotify/documentation/inotify/">Inotify</a>: The actual library.</li>\
|
||||
<li><a href="taskcli/documentation/taskcli/">TaskCLI</a>: The project build command.</li>
|
||||
"""),
|
||||
]
|
||||
|
||||
for (placeholder, value) in replacements {
|
||||
content = content.replacingOccurrences(of: placeholder, with: value)
|
||||
}
|
||||
|
||||
try FileManager.default.createDirectory(
|
||||
at: outputURL.deletingLastPathComponent(),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
try content.write(to: outputURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
private static func targets(for projectDirectory: URL) async throws -> [String] {
|
||||
let sourcesDirectory = projectDirectory.appending(path: "Sources").path
|
||||
let testsDirectory = projectDirectory.appending(path: "Tests").path
|
||||
return try await DoccFinder.getTargetsWithDocumentation(at: sourcesDirectory, testsDirectory)
|
||||
}
|
||||
|
||||
private static func makeRunScript(for targets: [String]) -> String {
|
||||
targets.map {
|
||||
"mkdir -p \"./public/\($0.localizedLowercase)\" && " +
|
||||
"swift package --allow-writing-to-directory \"\($0.localizedLowercase)\" " +
|
||||
"generate-documentation --disable-indexing --transform-for-static-hosting " +
|
||||
"--target \"\($0)\" " +
|
||||
"--hosting-base-path \"\($0.localizedLowercase)\" " +
|
||||
"--output-path \"./public/\($0.localizedLowercase)\""
|
||||
private static func generateDocsCommand() -> String {
|
||||
Self.targets.map {
|
||||
"swift package generate-documentation --target \($0)"
|
||||
}.joined(separator: " && ")
|
||||
}
|
||||
|
||||
private static func transformDocsForStaticHostingCommand() -> String {
|
||||
Self.targets.map { target in
|
||||
let lowercased = target.lowercased()
|
||||
return "docc process-archive transform-for-static-hosting" +
|
||||
" $OUTPUTS/\(target).doccarchive" +
|
||||
" --output-path ./public/\(lowercased)" +
|
||||
" --hosting-base-path \(Self.hostingBasePath)/\(lowercased)"
|
||||
}.joined(separator: " && ")
|
||||
}
|
||||
|
||||
private static func outputPathsForDocTargets() -> String {
|
||||
Self.targets.map { "./public/\($0.lowercased())" }.joined(separator: " ")
|
||||
}
|
||||
|
||||
private static func makeRunScript() -> String {
|
||||
let generateDocs = Self.generateDocsCommand()
|
||||
let transformDocs = Self.transformDocsForStaticHostingCommand()
|
||||
let outputDirs = Self.outputPathsForDocTargets()
|
||||
|
||||
return "set -euo pipefail && \(generateDocs)" +
|
||||
" && OUTPUTS=.build/plugins/Swift-DocC/outputs" +
|
||||
" && mkdir -p \(outputDirs)" +
|
||||
" && \(transformDocs)"
|
||||
}
|
||||
|
||||
// MARK: - Project Copy
|
||||
|
||||
@@ -154,23 +141,32 @@ struct GenerateDocumentationCommand: AsyncParsableCommand {
|
||||
// MARK: - Dependency Injection
|
||||
|
||||
private func injectDoccPluginDependency(in directory: URL, logger: Logger) async throws {
|
||||
let packageSwiftURL = directory.appending(path: "Package.swift")
|
||||
let contents = try String(contentsOf: packageSwiftURL, encoding: .utf8)
|
||||
|
||||
guard !contents.contains("swift-docc-plugin") else {
|
||||
logger.info("swift-docc-plugin dependency already present.")
|
||||
return
|
||||
}
|
||||
|
||||
let result = try await Subprocess.run(
|
||||
.name("swift"),
|
||||
arguments: [
|
||||
"package", "--package-path", directory.path(percentEncoded: false),
|
||||
"add-dependency", "--from", Self.doccPluginMinVersion, Self.doccPluginURL
|
||||
"add-dependency", Self.doccPluginURL,
|
||||
"--from", Self.doccPluginMinVersion,
|
||||
],
|
||||
) { _ in }
|
||||
|
||||
guard result.terminationStatus.isSuccess else {
|
||||
throw GenerateDocumentationError.dependencyInjectionFailed
|
||||
throw GenerateDocsError.dependencyInjectionFailed
|
||||
}
|
||||
|
||||
logger.info("Injected swift-docc-plugin dependency.")
|
||||
}
|
||||
}
|
||||
|
||||
enum GenerateDocumentationError: Error, CustomStringConvertible {
|
||||
enum GenerateDocsError: Error, CustomStringConvertible {
|
||||
case dependencyInjectionFailed
|
||||
|
||||
var description: String {
|
||||
@@ -22,7 +22,7 @@ The container is started with `--security-opt systempaths=unconfined` so that th
|
||||
### Generating Documentation
|
||||
|
||||
```bash
|
||||
swift run task generate-documentation
|
||||
swift run task generate-docs
|
||||
```
|
||||
|
||||
This copies the project to a temporary directory, injects the `swift-docc-plugin` dependency via `swift package add-dependency` (if absent), and runs documentation generation inside a `swift:latest` Docker container. The resulting static sites are written to `./public/inotify/` and `./public/taskcli/`, ready for deployment to GitHub Pages.
|
||||
@@ -50,7 +50,7 @@ Docker must be installed and running on the host machine. The container uses the
|
||||
|
||||
- ``Command``
|
||||
- ``TestCommand``
|
||||
- ``GenerateDocumentationCommand``
|
||||
- ``GenerateDocsCommand``
|
||||
|
||||
### Configuration
|
||||
|
||||
|
||||
Reference in New Issue
Block a user