A Rudimentary Blog in Swift with Build Tool Plugins
April 19, 2025
Swift is fun to write in. Wouldn't it be fun to write a markdown.md file, put it in the Sources
folder, push the change, and almost have a live blog post?
It would, and it doesn't take much code at all!
For a primer on how to get past the Xcode ceremonies, see Apple's Create Swift Package Plugins.
With that out of the way, here's how to collect all .md
files from your app sources:
@main
struct MarkdownHTML: BuildToolPlugin {
func createBuildCommands(
context: PackagePlugin.PluginContext,
target: any PackagePlugin.Target
) async throws -> [PackagePlugin.Command] {
enum MarkdownHTMLError: Error {
case failedToReadInputDirectory
}
guard let target = target as? SourceModuleTarget else {
Diagnostics.emit(.error, "🔴 GenerateSwiftMarkdown failed")
throw MarkdownHTMLError.failedToReadInputDirectory
}
return try target.sourceFiles(withSuffix: "md").map { file in
let base = file.url.deletingPathExtension().lastPathComponent
let input = file.url
let output = context.pluginWorkDirectoryURL.appending(path: "\(base).swift")
return .buildCommand(
displayName: "Generating HTML from Markdown \(base)",
executable: try context.tool(named: "MarkdownHTMLExec").url,
arguments: [input.path, output.path],
inputFiles: [input],
outputFiles: [output]
)
}
}
}
And here's the executable that runs Ink to parse Markdown into HTML:
let fileManager = FileManager.default
let arguments = ProcessInfo.processInfo.arguments
if arguments.count < 3 {
throw MarkdownHTMLExec.insufficientArguments
}
let (input, output) = (arguments[1], arguments[2])
let markdown = try String(contentsOfFile: input)
let parser = MarkdownParser()
let html = parser.html(from: markdown)
...
What's left is to grab that variable and render it. That's it!
While this post focused on the plugin part, the complete source code of this website is open source.