diff --git a/Package.resolved b/Package.resolved index 8bb57398..c0f51697 100644 --- a/Package.resolved +++ b/Package.resolved @@ -253,6 +253,15 @@ "version" : "2.0.0" } }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "deaf82e867fa2cbd3cd865978b079bfcf384ac28", + "version" : "6.2.1" + } + }, { "identity" : "zstd", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index e3cdcf6a..17ecc14f 100644 --- a/Package.swift +++ b/Package.swift @@ -60,6 +60,7 @@ let package = Package( .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.20.1"), .package(url: "https://github.com/swiftlang/swift-docc-plugin.git", from: "1.1.0"), .package(url: "https://github.com/mattt/swift-toml.git", from: "2.0.0"), + .package(url: "https://github.com/jpsim/Yams.git", from: "6.2.1"), ], targets: [ .executableTarget( @@ -82,6 +83,7 @@ let package = Package( "ContainerBuild", "ContainerLog", "ContainerResource", + "Yams", ], path: "Tests/CLITests" ), @@ -104,6 +106,7 @@ let package = Package( "ContainerVersion", "ContainerXPC", "TerminalProgress", + "Yams", ], path: "Sources/ContainerCommands" ), diff --git a/Sources/ContainerCommands/ListFormat.swift b/Sources/ContainerCommands/ListFormat.swift index 6083acbc..6b7bd866 100644 --- a/Sources/ContainerCommands/ListFormat.swift +++ b/Sources/ContainerCommands/ListFormat.swift @@ -19,4 +19,5 @@ import ArgumentParser public enum ListFormat: String, CaseIterable, ExpressibleByArgument, Sendable { case json case table + case yaml } diff --git a/Sources/ContainerCommands/OutputRendering.swift b/Sources/ContainerCommands/OutputRendering.swift index ef08f9ca..fb84bac5 100644 --- a/Sources/ContainerCommands/OutputRendering.swift +++ b/Sources/ContainerCommands/OutputRendering.swift @@ -15,6 +15,7 @@ //===----------------------------------------------------------------------===// import Foundation +import Yams /// Options for JSON rendering, wrapping the knobs on `JSONEncoder`. public struct JSONOptions: Sendable { @@ -48,6 +49,12 @@ public enum Output { return String(decoding: data, as: UTF8.self) } + public static func renderYAML(_ value: T) throws -> String { + let encoder = YAMLEncoder() + let data = try encoder.encode(value) + return data + } + /// Renders a list of displayable items as a table (with header) or quiet-mode identifiers. public static func renderList(_ items: [T], quiet: Bool) -> String { if quiet { @@ -74,6 +81,7 @@ public enum Output { ) throws { switch format { case .json: try emit(renderJSON(json)) + case .yaml: try emit(renderYAML(json)) case .table: emit(renderList(display, quiet: quiet)) } } diff --git a/Sources/ContainerCommands/System/SystemVersion.swift b/Sources/ContainerCommands/System/SystemVersion.swift index 58f9cd91..8cc87704 100644 --- a/Sources/ContainerCommands/System/SystemVersion.swift +++ b/Sources/ContainerCommands/System/SystemVersion.swift @@ -63,6 +63,8 @@ extension Application { printVersionTable(versions: versions) case .json: try Output.emit(Output.renderJSON(versions)) + case .yaml: + try Output.emit(Output.renderYAML(versions)) } } diff --git a/Tests/CLITests/Subcommands/System/TestCLIVersion.swift b/Tests/CLITests/Subcommands/System/TestCLIVersion.swift index f3985f0e..27161247 100644 --- a/Tests/CLITests/Subcommands/System/TestCLIVersion.swift +++ b/Tests/CLITests/Subcommands/System/TestCLIVersion.swift @@ -16,6 +16,7 @@ import Foundation import Testing +import Yams /// Tests for `container system version` output formats and build type detection. final class TestCLIVersion: CLITest { @@ -26,7 +27,7 @@ final class TestCLIVersion: CLITest { let appName: String } - struct VersionJSON: Codable { + struct VersionOutput: Codable { let version: String let buildType: String let commit: String @@ -68,7 +69,21 @@ final class TestCLIVersion: CLITest { #expect(status == 0, "system version --format json should succeed, stderr: \(err)") #expect(!out.isEmpty) - let decoded = try JSONDecoder().decode([VersionJSON].self, from: data) + let decoded = try JSONDecoder().decode([VersionOutput].self, from: data) + #expect(decoded[0].appName == "container") + #expect(!decoded[0].version.isEmpty) + #expect(!decoded[0].commit.isEmpty) + + let expected = try expectedBuildType() + #expect(decoded[0].buildType == expected) + } + + @Test func yamlFormat() throws { + let (data, out, err, status) = try run(arguments: ["system", "version", "--format", "yaml"]) + #expect(status == 0, "system version --format yaml should succeed, stderr: \(err)") + #expect(!out.isEmpty) + + let decoded = try YAMLDecoder().decode([VersionOutput].self, from: data) #expect(decoded[0].appName == "container") #expect(!decoded[0].version.isEmpty) #expect(!decoded[0].commit.isEmpty) @@ -92,7 +107,7 @@ final class TestCLIVersion: CLITest { // Validate build type via JSON to avoid parsing table text loosely let (data, _, err, status) = try run(arguments: ["system", "version", "--format", "json"]) #expect(status == 0, "version --format json should succeed, stderr: \(err)") - let decoded = try JSONDecoder().decode([VersionJSON].self, from: data) + let decoded = try JSONDecoder().decode([VersionOutput].self, from: data) let expected = try expectedBuildType() #expect(decoded[0].buildType == expected, "Expected build type \(expected) but got \(decoded[0].buildType)") diff --git a/Tests/ContainerCommandsTests/ListFormattingTests.swift b/Tests/ContainerCommandsTests/ListFormattingTests.swift index 65613eaa..7d870c01 100644 --- a/Tests/ContainerCommandsTests/ListFormattingTests.swift +++ b/Tests/ContainerCommandsTests/ListFormattingTests.swift @@ -250,10 +250,11 @@ struct PrintableNetworkDisplayTests { struct ListFormatTests { @Test - func hasJsonAndTableCases() { - #expect(ListFormat.allCases.count == 2) + func hasAllOutputFormatCases() { + #expect(ListFormat.allCases.count == 3) #expect(ListFormat.json.rawValue == "json") #expect(ListFormat.table.rawValue == "table") + #expect(ListFormat.yaml.rawValue == "yaml") } } diff --git a/docs/command-reference.md b/docs/command-reference.md index f3730d06..9254e35d 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -1062,7 +1062,7 @@ container system version [--format ] **Options** -* `--format `: Output format (values: json, table; default: table) +* `--format `: Output format (values: json, table, yaml; default: table) **Table Output** @@ -1099,6 +1099,21 @@ Backward-compatible with previous CLI-only output. Top-level fields describe the } ``` +**YAML Output** + +Equivalent to the JSON output but in YAML format. Each entry in the array represents a component. + +```yaml +- version: 1.2.3 + buildType: debug + commit: abcdef1 + appName: container +- version: 1.2.3 + buildType: release + commit: 1234abc + appName: container-apiserver +``` + ### `container system logs` Displays logs from the container services. You can specify a time interval or follow new logs in real time.