Skip to content

Commit 5722e87

Browse files
committed
Configuration file
1 parent c53497d commit 5722e87

14 files changed

Lines changed: 142 additions & 54 deletions

Package.resolved

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ let package = Package(
2222
.package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.0-latest"),
2323
.package(url: "https://github.com/apple/swift-collections", from: "1.1.4"),
2424
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
25+
.package(url: "https://github.com/jpsim/Yams", from: "6.0.2"),
2526
],
2627
targets: [
2728
.macro(
@@ -55,6 +56,7 @@ let package = Package(
5556
dependencies: [
5657
"Compiler",
5758
.product(name: "ArgumentParser", package: "swift-argument-parser"),
59+
"Yams",
5860
]
5961
),
6062

Sources/Compiler/Driver.swift

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,8 @@ public actor Driver {
4949
reporters.append(reporter)
5050
}
5151

52-
public func compile(path: Path) async throws {
52+
public func compile(migrationsPath: Path, queriesPath: Path) async throws {
5353
try await measure("Compilation") {
54-
let migrationsPath = migrationsPath(at: path)
55-
let queriesPath = queriesPath(at: path)
56-
5754
let migrationFiles = try fileSystem.files(atPath: migrationsPath)
5855
let queriesFiles = try fileSystem.files(atPath: queriesPath)
5956

@@ -163,16 +160,6 @@ public actor Driver {
163160
}
164161
}
165162

166-
/// The migrations path relative to the base path
167-
private func migrationsPath(at base: Path) -> Path {
168-
"\(base)/Migrations"
169-
}
170-
171-
/// The queries path relative to the base path
172-
private func queriesPath(at base: Path) -> Path {
173-
"\(base)/Queries"
174-
}
175-
176163
/// Sorts the migration files
177164
private func sortMigrations(
178165
fileNames: [String]

Sources/Compiler/Gen/Language.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,10 @@ public struct GenerationOptions: Sendable {
326326
public var imports: [String]
327327

328328
public init(
329-
databaseName: String? = nil,
329+
databaseName: String,
330330
imports: [String] = []
331331
) {
332-
self.databaseName = databaseName ?? "DB"
332+
self.databaseName = databaseName
333333
self.imports = imports
334334
}
335335
}

Sources/Compiler/Gen/SwiftLanguage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public struct SwiftLanguage: Language {
4343
}
4444

4545
public func interpolatedQuestionMarks(for param: String) -> String {
46-
return "\\(\(param).sqlQuestionMarks)"
46+
return "\\(\(param).sqlQuestionMarks)"
4747
}
4848

4949
public func typeName(for type: GenerationType) -> String {

Sources/Compiler/Project.swift

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,34 @@ import Foundation
99

1010
/// Small object to make interacting with the overall project structure easier.
1111
public struct Project {
12-
public let url: URL
12+
public let generatedOutputFile: URL
1313
public let migrationsDirectory: URL
1414
public let queriesDirectory: URL
1515
private let fileSystem: FileSystem
1616

17-
public init(url: URL) {
18-
self = Project(url: url, fileSystem: FileManager.default)
17+
public init(
18+
generatedOutputFile: URL,
19+
migrationsDirectory: URL,
20+
queriesDirectory: URL
21+
) {
22+
self = Project(
23+
generatedOutputFile: generatedOutputFile,
24+
migrationsDirectory: migrationsDirectory,
25+
queriesDirectory: queriesDirectory,
26+
fileSystem: FileManager.default
27+
)
1928
}
2029

21-
init(url: URL, fileSystem: FileSystem) {
22-
self.url = url
30+
init(
31+
generatedOutputFile: URL,
32+
migrationsDirectory: URL,
33+
queriesDirectory: URL,
34+
fileSystem: FileSystem
35+
) {
36+
self.generatedOutputFile = generatedOutputFile
37+
self.migrationsDirectory = migrationsDirectory
38+
self.queriesDirectory = queriesDirectory
2339
self.fileSystem = fileSystem
24-
self.migrationsDirectory = url.appendingPathComponent("Migrations")
25-
self.queriesDirectory = url.appendingPathComponent("Queries")
26-
}
27-
28-
public static func inWorkingDir() -> Project {
29-
Project(url: URL(fileURLWithPath: FileManager.default.currentDirectoryPath))
3040
}
3141

3242
public var doesMigrationsExist: Bool {

Sources/OtterCLI/Config.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// Config.swift
3+
// Otter
4+
//
5+
// Created by Wes Wickwire on 8/5/25.
6+
//
7+
8+
import Foundation
9+
import Yams
10+
import Compiler
11+
12+
struct Config: Codable {
13+
var queries: String
14+
var migrations: String
15+
var output: String?
16+
var databaseName: String?
17+
var additionalImports: [String]?
18+
19+
func project(at path: String) -> Project {
20+
let url = URL(fileURLWithPath: path)
21+
22+
return Project(
23+
generatedOutputFile: url.appendingPathComponent(output ?? "Queries.swift"),
24+
migrationsDirectory: url.appendingPathComponent(migrations),
25+
queriesDirectory: url.appendingPathComponent(queries)
26+
)
27+
}
28+
29+
static func load(at path: String) throws -> Config {
30+
let url = URL(fileURLWithPath: path)
31+
.appendingPathComponent("otter.yaml")
32+
33+
guard FileManager.default.fileExists(atPath: url.path) else {
34+
throw OtterError.configDoesNotExist
35+
}
36+
37+
let data = try Data(contentsOf: url)
38+
39+
let decoder = YAMLDecoder()
40+
return try decoder.decode(Config.self, from: data)
41+
}
42+
}

Sources/OtterCLI/GenerateCommand.swift

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,35 @@ import SwiftSyntax
1313
struct GenerateCommand: AsyncParsableCommand {
1414
static let configuration = CommandConfiguration(commandName: "generate")
1515

16-
@Option(name: .shortAndLong, help: "The root directory of the Otter sources")
16+
@Option(name: .shortAndLong, help: "The directory containing the otter.yaml")
1717
var path: String = FileManager.default.currentDirectoryPath
1818

19-
@Option(name: .shortAndLong, help: "The output file path. Default is to stdout")
20-
var output: String? = nil
21-
22-
@Option(name: .shortAndLong, help: "The database name")
23-
var databaseName: String = "DB"
24-
25-
@Option(name: .shortAndLong, help: "Comma separated list of additional imports to add")
26-
var additionalImports: String?
27-
2819
@Flag(help: "If set, any diagnostic message will not be colorized")
2920
var dontColorize = false
3021

3122
@Flag(help: "If set, core parts of the compilation will be timed")
3223
var time = false
3324

3425
mutating func run() async throws {
26+
let config = try Config.load(at: path)
27+
let project = config.project(at: path)
28+
3529
let options = GenerationOptions(
36-
databaseName: databaseName,
37-
imports: additionalImports?.split(separator: ",").map(\.description) ?? []
30+
databaseName: config.databaseName ?? "DB",
31+
imports: config.additionalImports ?? []
3832
)
3933

40-
try await generate(language: SwiftLanguage.self, options: options)
34+
try await generate(
35+
language: SwiftLanguage.self,
36+
options: options,
37+
project: project
38+
)
4139
}
4240

4341
private func generate<Lang: Language>(
4442
language: Lang.Type,
45-
options: GenerationOptions
43+
options: GenerationOptions,
44+
project: Project
4645
) async throws {
4746
let driver = Driver()
4847
await driver.logTimes(time)
@@ -51,12 +50,17 @@ struct GenerateCommand: AsyncParsableCommand {
5150
reporter: StdoutDiagnosticReporter(dontColorize: dontColorize)
5251
)
5352

54-
try await driver.compile(path: path)
53+
try await driver.compile(
54+
migrationsPath: project.migrationsDirectory.path,
55+
queriesPath: project.queriesDirectory.path
56+
)
5557

5658
try await driver.generate(
5759
language: Lang.self,
58-
to: output,
60+
to: project.generatedOutputFile.path,
5961
options: options
6062
)
63+
64+
print("Generated output to \(project.generatedOutputFile.path)")
6165
}
6266
}

Sources/OtterCLI/InitCommand.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,26 @@ struct InitCommand: ParsableCommand {
1313
static let configuration = CommandConfiguration(commandName: "init")
1414

1515
func run() throws {
16-
let project = Project.inWorkingDir()
17-
try project.setup()
18-
try project.addMigration()
16+
let defaultConfig = """
17+
# The type name of the generated struct for the database
18+
databaseName: DB
19+
# Path to the directory containing the migrations
20+
migrations: ProjectName/Migrations
21+
# Path to the directory containing the queries
22+
queries: ProjectName/Queries
23+
# The path of the file to generate the Swift code into
24+
output: ProjectName/Queries.swift
25+
# Uncomment to add any custom imports that may be needed
26+
# additionalImports:
27+
# - MyModule
28+
"""
29+
30+
try defaultConfig.write(
31+
toFile: FileManager.default.currentDirectoryPath.appending("/otter.yaml"),
32+
atomically: true,
33+
encoding: .utf8
34+
)
35+
36+
print("Created otter.yaml configration")
1937
}
2038
}

Sources/OtterCLI/MigrateCommand.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ struct MigrateCommand: ParsableCommand {
1616
)
1717

1818
struct Add: ParsableCommand {
19+
@Option(name: .shortAndLong, help: "The directory containing the otter.yaml")
20+
var path: String = FileManager.default.currentDirectoryPath
21+
1922
static let configuration = CommandConfiguration(commandName: "add")
2023

2124
func run() throws {
22-
let project = Project.inWorkingDir()
25+
let config = try Config.load(at: path)
26+
let project = config.project(at: path)
2327

2428
guard project.doesMigrationsExist else {
2529
throw OtterError.sourcesNotFound

0 commit comments

Comments
 (0)