Skip to content

Commit e079937

Browse files
committed
Refactor to JSON Output Format
1 parent 5d8b8fd commit e079937

File tree

4 files changed

+154
-22
lines changed

4 files changed

+154
-22
lines changed

Sources/Swiftly/OutputSchema.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import Foundation
2+
import SwiftlyCore
3+
4+
struct LocationInfo: OutputData {
5+
let path: String
6+
7+
init(path: String) {
8+
self.path = path
9+
}
10+
11+
var description: String {
12+
return self.path
13+
}
14+
}
15+
16+
struct ToolchainInfo: OutputData {
17+
let version: String
18+
let source: ToolchainSource?
19+
20+
init(version: String, source: ToolchainSource? = nil) {
21+
self.version = version
22+
self.source = source
23+
}
24+
25+
var description: String {
26+
var message = self.version
27+
if let source = source {
28+
message += " (\(source.description))"
29+
}
30+
return message
31+
}
32+
}
33+
34+
struct ToolchainSetInfo: OutputData {
35+
let version: String
36+
let previousVersion: String?
37+
let isGlobal: Bool
38+
let configFile: String?
39+
40+
init(
41+
version: String, previousVersion: String? = nil, isGlobal: Bool, configFile: String? = nil
42+
) {
43+
self.version = version
44+
self.previousVersion = previousVersion
45+
self.isGlobal = isGlobal
46+
self.configFile = configFile
47+
}
48+
49+
var description: String {
50+
var message = self.version
51+
if let previousVersion = previousVersion {
52+
message += " (previous: \(previousVersion))"
53+
}
54+
if isGlobal {
55+
message += " (global)"
56+
}
57+
if let configFile = configFile {
58+
message += " (config: \(configFile))"
59+
}
60+
return message
61+
}
62+
}
63+
64+
enum ToolchainSource: Encodable, CustomStringConvertible {
65+
case swiftVersionFile(String)
66+
case globalDefault
67+
68+
var description: String {
69+
switch self {
70+
case let .swiftVersionFile(path):
71+
return path
72+
case .globalDefault:
73+
return "default"
74+
}
75+
}
76+
}

Sources/Swiftly/Swiftly.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public struct GlobalOptions: ParsableArguments {
2727
@Flag(help: "Enable verbose reporting from swiftly")
2828
var verbose: Bool = false
2929

30+
@Option(name: .long, help: "Output format (text, json)")
31+
var format: SwiftlyCore.OutputFormat = .text
32+
3033
public init() {}
3134
}
3235

Sources/Swiftly/Use.swift

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ struct Use: SwiftlyCommand {
6969

7070
try await validateLinked(ctx)
7171

72+
let formatter: OutputFormatter = self.root.format == .json ? JSONOutputFormatter() : TextOutputFormatter()
73+
7274
// This is the bare use command where we print the selected toolchain version (or the path to it)
7375
guard let toolchain = self.toolchain else {
7476
let (selectedVersion, result) = try await selectToolchain(ctx, config: &config, globalDefault: self.globalDefault)
@@ -84,21 +86,22 @@ struct Use: SwiftlyCommand {
8486
}
8587

8688
if self.printLocation {
87-
// Print the toolchain location and exit
88-
await ctx.message("\(Swiftly.currentPlatform.findToolchainLocation(ctx, selectedVersion))")
89+
let location = "\(Swiftly.currentPlatform.findToolchainLocation(ctx, selectedVersion))"
90+
let output = formatter.format(LocationInfo(path: location))
91+
await ctx.print(output)
8992
return
9093
}
9194

92-
var message = "\(selectedVersion)"
93-
94-
switch result {
95+
let source: ToolchainSource? = switch result {
9596
case let .swiftVersionFile(versionFile, _, _):
96-
message += " (\(versionFile))"
97+
.swiftVersionFile("\(versionFile)")
9798
case .globalDefault:
98-
message += " (default)"
99+
.globalDefault
99100
}
100101

101-
await ctx.message(message)
102+
let toolchainInfo = ToolchainInfo(version: "\(selectedVersion)", source: source)
103+
let output = formatter.format(toolchainInfo)
104+
await ctx.print(output)
102105

103106
return
104107
}
@@ -110,24 +113,25 @@ struct Use: SwiftlyCommand {
110113
let selector = try ToolchainSelector(parsing: toolchain)
111114

112115
guard let toolchain = config.listInstalledToolchains(selector: selector).max() else {
113-
await ctx.message("No installed toolchains match \"\(toolchain)\"")
114-
return
116+
throw SwiftlyError(message: "No installed toolchains match \"\(toolchain)\"")
115117
}
116118

117-
try await Self.execute(ctx, toolchain, globalDefault: self.globalDefault, assumeYes: self.root.assumeYes, &config)
119+
try await Self.execute(ctx, toolchain, globalDefault: self.globalDefault, assumeYes: self.root.assumeYes, formatter: formatter, &config)
118120
}
119121

120122
/// Use a toolchain. This method can modify and save the input config and also create/modify a `.swift-version` file.
121-
static func execute(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, globalDefault: Bool, assumeYes: Bool = true, _ config: inout Config) async throws {
123+
static func execute(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, globalDefault: Bool, assumeYes: Bool = true, formatter: OutputFormatter? = nil, _ config: inout Config) async throws {
122124
let (selectedVersion, result) = try await selectToolchain(ctx, config: &config, globalDefault: globalDefault)
123125

124-
var message: String
126+
let outputFormatter = formatter ?? TextOutputFormatter()
127+
let isGlobal: Bool
128+
let configFile: String?
125129

126130
if case let .swiftVersionFile(versionFile, _, _) = result {
127131
// We don't care in this case if there were any problems with the swift version files, just overwrite it with the new value
128132
try toolchain.name.write(to: versionFile, atomically: true)
129-
130-
message = "The file `\(versionFile)` has been set to `\(toolchain)`"
133+
isGlobal = false
134+
configFile = "\(versionFile)"
131135
} else if let newVersionFile = try await findNewVersionFile(ctx), !globalDefault {
132136
if !assumeYes {
133137
await ctx.message("A new file `\(newVersionFile)` will be created to set the new in-use toolchain for this project. Alternatively, you can set your default globally with the `--global-default` flag. Proceed with creating this file?")
@@ -139,19 +143,24 @@ struct Use: SwiftlyCommand {
139143
}
140144

141145
try toolchain.name.write(to: newVersionFile, atomically: true)
142-
143-
message = "The file `\(newVersionFile)` has been set to `\(toolchain)`"
146+
isGlobal = false
147+
configFile = "\(newVersionFile)"
144148
} else {
145149
config.inUse = toolchain
146150
try config.save(ctx)
147-
message = "The global default toolchain has been set to `\(toolchain)`"
151+
isGlobal = true
152+
configFile = nil
148153
}
149154

150-
if let selectedVersion {
151-
message += " (was \(selectedVersion.name))"
152-
}
155+
let setInfo = ToolchainSetInfo(
156+
version: "\(toolchain)",
157+
previousVersion: selectedVersion?.name,
158+
isGlobal: isGlobal,
159+
configFile: configFile
160+
)
153161

154-
await ctx.message(message)
162+
let output = outputFormatter.format(setInfo)
163+
await ctx.print(output)
155164
}
156165

157166
static func findNewVersionFile(_ ctx: SwiftlyCoreContext) async throws -> FilePath? {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import ArgumentParser
2+
import Foundation
3+
4+
public enum OutputFormat: String, CaseIterable, ExpressibleByArgument {
5+
case text
6+
case json
7+
8+
public var description: String {
9+
self.rawValue
10+
}
11+
}
12+
13+
public protocol OutputFormatter {
14+
func format(_ data: OutputData) -> String
15+
}
16+
17+
public protocol OutputData: Encodable, CustomStringConvertible {
18+
var description: String { get }
19+
}
20+
21+
public struct TextOutputFormatter: OutputFormatter {
22+
public init() {}
23+
24+
public func format(_ data: OutputData) -> String {
25+
data.description
26+
}
27+
}
28+
29+
public struct JSONOutputFormatter: OutputFormatter {
30+
public init() {}
31+
32+
public func format(_ data: OutputData) -> String {
33+
let encoder = JSONEncoder()
34+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
35+
36+
let jsonData = try? encoder.encode(data)
37+
38+
guard let jsonData = jsonData, let result = String(data: jsonData, encoding: .utf8) else {
39+
return "{}"
40+
}
41+
42+
return result
43+
}
44+
}

0 commit comments

Comments
 (0)