A Swift Jekyll Assistant
Well, this has been a fascinating project: I was looking back at my AppleScript Jekyll Assistant and thinking that “this is so much more complicated than it neeeds to be”.
I had used the Swift Argument Parser before and had toyed with writing command line scripts in Swift but was a bit daunted. By the documentation. So ChatGPT came to the rescue and helped me write the following. So AI has definitely been a “productivity enhancer” in this case. I know my way around but am not an expert in the APIs that I rarely use. And getting the regular expressions just right is something that usually would have taken me a lot of time. So ChatGPT helped me with the important and unfamiliar detail and I think you’ll agree we came up with a pretty elegant solution.
#!/usr/bin/env swift
import Foundation
import ArgumentParser
struct CLI: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "A command-line tool that processes a category and description and generates a markdown file."
)
@Option(name: [.short, .long], help: "The category of the item.")
var category: String
@Option(name: [.short, .long], help: "The description of the item.")
var description: String
@Option(name: [.short, .long], help: "Optional: directory to write output markdown file. Overrides and sets persistent path.")
var outputPath: String?
func run() throws {
let fileManager = FileManager.default
let homeDir = fileManager.homeDirectoryForCurrentUser
let configURL = homeDir.appendingPathComponent(".mycli_config")
// Determine target directory for output
let targetDirectory: String
if let providedPath = outputPath {
// Expand ~ and create directory
let expanded = (providedPath as NSString).expandingTildeInPath
try fileManager.createDirectory(atPath: expanded, withIntermediateDirectories: true, attributes: nil)
// Save persistent path
try expanded.write(to: configURL, atomically: true, encoding: .utf8)
targetDirectory = expanded
} else if fileManager.fileExists(atPath: configURL.path) {
// Load saved path
let savedPath = try String(contentsOf: configURL, encoding: .utf8)
.trimmingCharacters(in: .whitespacesAndNewlines)
targetDirectory = savedPath
} else {
throw ValidationError("No output path provided and no persistent path found. Use --output-path to set one.")
}
// Print received inputs
print("Category: \(category)")
print("Description: \(description)")
print("Output directory: \(targetDirectory)")
// Prepare date strings
let fileDateFormatter = DateFormatter()
fileDateFormatter.dateFormat = "yyyy-MM-dd"
let dateString = fileDateFormatter.string(from: Date())
let timestampFormatter = DateFormatter()
timestampFormatter.dateFormat = "yyyy-MM-dd HH-mm-ss"
let timestampString = timestampFormatter.string(from: Date())
// Sanitize description for filename
let sanitizedDescription = description
.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: "\\s+", with: "_", options: .regularExpression)
.replacingOccurrences(of: "[^A-Za-z0-9_-]", with: "", options: .regularExpression)
// Build filename and paths
let baseFilename = "\(dateString)-\(sanitizedDescription)"
let outputFilename = "\(baseFilename).markdown"
let fileURL = URL(fileURLWithPath: targetDirectory)
.appendingPathComponent(outputFilename)
// Prepare title for front matter (retain spaces)
let titleString = description.replacingOccurrences(of: "_", with: " ")
// Build markdown content
let markdownContent = """
---
title: \(titleString)
date: \(timestampString)
layout: post
comments: true
categories: ['\(category)']
---
"""
// Write the markdown file
try markdownContent.write(to: fileURL, atomically: true, encoding: .utf8)
print("Created markdown file: \(outputFilename) at \(targetDirectory)")
// Open the file in Visual Studio Code
do {
let openProcess = Process()
openProcess.executableURL = URL(fileURLWithPath: "/usr/bin/open")
openProcess.arguments = ["-a", "/Applications/Visual Studio Code.app", fileURL.path]
try openProcess.run()
} catch {
print("Failed to open file in VS Code: \(error)")
}
}
}
// Entry point
CLI.main()