diff --git a/Sources/SwiftGtkGenCore/Config.swift b/Sources/SwiftGtkGenCore/Config.swift index c2dfef6..0237d00 100644 --- a/Sources/SwiftGtkGenCore/Config.swift +++ b/Sources/SwiftGtkGenCore/Config.swift @@ -474,19 +474,29 @@ public struct IncludeEntry { } extension MonorepoConfig { - /// Parses a monorepo TOML config file. + /// Parses a monorepo TOML config file using `[packages.Name]` section headers + /// (the TOML reader supports `[section]` headers but not `[[array-of-tables]]`). + /// + /// Example config: + /// ```toml + /// output_dir = "." + /// + /// [packages.GLib] + /// gir = "/usr/share/gir-1.0/GLib-2.0.gir" + /// + /// [packages.GObject] + /// gir = "/usr/share/gir-1.0/GObject-2.0.gir" + /// concurrency = "mainActor" + /// ``` public static func from(toml: [String: Any], defaultOutputDir: String) throws -> MonorepoConfig { let outputDir = toml["output_dir"] as? String ?? defaultOutputDir - guard let pkgArray = toml["packages"] as? [[String: Any]] else { - throw TOMLReaderError.parseError(line: 0, message: "monorepo config missing [[packages]] array") + guard let packagesDict = toml["packages"] as? [String: [String: Any]] else { + throw TOMLReaderError.parseError(line: 0, message: "monorepo config missing [packages.*] sections") } var packages: [PackageEntry] = [] - for (index, pkgDict) in pkgArray.enumerated() { - guard let name = pkgDict["name"] as? String else { - throw TOMLReaderError.parseError(line: 0, message: "packages[\(index)] missing 'name'") - } + for (name, pkgDict) in packagesDict.sorted(by: { $0.key < $1.key }) { guard let girPath = pkgDict["gir"] as? String else { - throw TOMLReaderError.parseError(line: 0, message: "packages[\(index)].\(name) missing 'gir'") + throw TOMLReaderError.parseError(line: 0, message: "packages.\(name) missing 'gir'") } let concurrency = (pkgDict["concurrency"] as? String).flatMap(ConcurrencyModel.init(rawValue:)) let objects = try parseMonorepoObjectConfigs(from: pkgDict) diff --git a/Tests/SwiftGtkGenCoreTests/MonorepoConfigTests.swift b/Tests/SwiftGtkGenCoreTests/MonorepoConfigTests.swift index 410158b..4b2ae07 100644 --- a/Tests/SwiftGtkGenCoreTests/MonorepoConfigTests.swift +++ b/Tests/SwiftGtkGenCoreTests/MonorepoConfigTests.swift @@ -8,7 +8,16 @@ struct MonorepoConfigTests { @Test func parsesValidMonorepoConfig() throws { let toml = """ output_dir = "." - packages = [{ name = "Gtk", gir = "/usr/share/gir-1.0/Gtk-4.0.gir" },{ name = "Gdk", gir = "/usr/share/gir-1.0/Gdk-4.0.gir", concurrency = "mainActor" },{ name = "GObject", gir = "/usr/share/gir-1.0/GObject-2.0.gir" }] + + [packages.Gtk] + gir = "/usr/share/gir-1.0/Gtk-4.0.gir" + + [packages.Gdk] + gir = "/usr/share/gir-1.0/Gdk-4.0.gir" + concurrency = "mainActor" + + [packages.GObject] + gir = "/usr/share/gir-1.0/GObject-2.0.gir" """ let dict = try TOMLReader.parse(toml) @@ -16,23 +25,22 @@ struct MonorepoConfigTests { #expect(config.outputDir == ".") #expect(config.packages.count == 3) - #expect(config.packages[0].name == "Gtk") - #expect(config.packages[0].girPath == "/usr/share/gir-1.0/Gtk-4.0.gir") + #expect(config.packages[0].name == "GObject") + #expect(config.packages[0].girPath == "/usr/share/gir-1.0/GObject-2.0.gir") #expect(config.packages[0].concurrency == nil) #expect(config.packages[1].name == "Gdk") #expect(config.packages[1].concurrency == .mainActor) - #expect(config.packages[2].name == "GObject") + #expect(config.packages[2].name == "Gtk") #expect(config.packages[2].concurrency == nil) } @Test func parsesPackagesWithObjectOverrides() throws { let dict: [String: Any] = [ "output_dir": "Sources", - "packages": [ - [ - "name": "Gtk", - "gir": "/usr/share/gir-1.0/Gtk-4.0.gir", - "object": [ + "packages": [ + "Gtk": [ + "gir": "/usr/share/gir-1.0/Gtk-4.0.gir", + "object": [ "Gtk.Widget": ["status": "generate", "concurrency": "mainActor"], "Gtk.Legacy": ["status": "ignore"], ], @@ -60,15 +68,17 @@ struct MonorepoConfigTests { } } - @Test func rejectsMissingName() { + @Test func rejectsMissingGir() throws { let dict: [String: Any] = [ "packages": [ - ["gir": "/usr/share/gir-1.0/Gtk-4.0.gir"], + "Broken": [ + "concurrency": "mainActor", + ], ], ] do { _ = try MonorepoConfig.from(toml: dict, defaultOutputDir: ".") - Issue.record("Expected error for missing name") + Issue.record("Expected error for missing gir") } catch { // Expected } @@ -77,7 +87,7 @@ struct MonorepoConfigTests { @Test func usesDefaultOutputDirWhenNotSpecified() throws { let dict: [String: Any] = [ "packages": [ - ["name": "Gtk", "gir": "/usr/share/gir-1.0/Gtk-4.0.gir"], + "Gtk": ["gir": "/usr/share/gir-1.0/Gtk-4.0.gir"], ], ] @@ -85,4 +95,19 @@ struct MonorepoConfigTests { #expect(config.outputDir == "/custom/output") } + + @Test func preservesSortedOrder() throws { + let dict: [String: Any] = [ + "packages": [ + "Zeta": ["gir": "/z"], + "Alpha": ["gir": "/a"], + ], + ] + + let config = try MonorepoConfig.from(toml: dict, defaultOutputDir: ".") + + #expect(config.packages.count == 2) + #expect(config.packages[0].name == "Alpha") + #expect(config.packages[1].name == "Zeta") + } } diff --git a/monorepo-config.toml b/monorepo-config.toml new file mode 100644 index 0000000..58c85ba --- /dev/null +++ b/monorepo-config.toml @@ -0,0 +1,10 @@ +output_dir = "/tmp/monorepo-gen" + +[packages.GLib] +gir = "/usr/share/gir-1.0/GLib-2.0.gir" + +[packages.GObject] +gir = "/usr/share/gir-1.0/GObject-2.0.gir" + +[packages.Gio] +gir = "/usr/share/gir-1.0/Gio-2.0.gir"