Skip to content

Commit 43e4283

Browse files
committed
Add back the "intent" enum for command plugins, as described in the revised pitch document
1 parent 8f52852 commit 43e4283

File tree

9 files changed

+160
-50
lines changed

9 files changed

+160
-50
lines changed

Sources/Commands/Describe.swift

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -142,33 +142,56 @@ struct DescribedPackage: Encodable {
142142
/// Represents a plugin capability for the sole purpose of generating a description.
143143
struct DescribedPluginCapability: Encodable {
144144
let type: String
145-
let verb: String?
146-
let description: String?
145+
let intent: PluginCommandIntent?
147146
let permissions: [PluginPermission]?
148147

149148
init(from capability: PluginCapability, in package: Package) {
150149
switch capability {
151150
case .buildTool:
152151
self.type = "buildTool"
153-
self.verb = nil
154-
self.description = nil
152+
self.intent = nil
155153
self.permissions = nil
156-
case .command(let verb, let description, let permissions):
154+
case .command(let intent, let permissions):
157155
self.type = "command"
158-
self.verb = verb
159-
self.description = description
160-
self.permissions = permissions.map{
161-
switch $0 {
162-
case .packageWritability(let reason):
163-
return PluginPermission(type: "packageWritability", reason: reason)
164-
}
165-
}
156+
self.intent = .init(from: intent)
157+
self.permissions = permissions.map{ .init(from: $0) }
166158
}
167159
}
168160

161+
struct PluginCommandIntent: Encodable {
162+
let type: String
163+
let verb: String?
164+
let description: String?
165+
166+
init(from intent: PackageModel.PluginCommandIntent) {
167+
switch intent {
168+
case .documentationGeneration:
169+
self.type = "documentationGeneration"
170+
self.verb = nil
171+
self.description = nil
172+
case .sourceCodeFormatting:
173+
self.type = "sourceCodeFormatting"
174+
self.verb = nil
175+
self.description = nil
176+
case .custom(let verb, let description):
177+
self.type = "documentationGeneration"
178+
self.verb = verb
179+
self.description = description
180+
}
181+
}
182+
}
183+
169184
struct PluginPermission: Encodable {
170185
let type: String
171186
let reason: String
187+
188+
init(from permission: PackageModel.PluginPermission) {
189+
switch permission {
190+
case .packageWritability(let reason):
191+
self.type = "packageWritability"
192+
self.reason = reason
193+
}
194+
}
172195
}
173196
}
174197

Sources/PackageDescription/PackageDescriptionSerialization.swift

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ extension SystemPackageProvider: Encodable {
224224

225225
extension Target.PluginCapability: Encodable {
226226
private enum CodingKeys: CodingKey {
227-
case type, verb, description, permissions
227+
case type, intent, permissions
228228
}
229229

230230
private enum Capability: String, Encodable {
@@ -236,11 +236,34 @@ extension Target.PluginCapability: Encodable {
236236
switch self {
237237
case ._buildTool:
238238
try container.encode(Capability.buildTool, forKey: .type)
239-
case ._command(let verb, let description, let permissions):
239+
case ._command(let intent, let permissions):
240240
try container.encode(Capability.command, forKey: .type)
241+
try container.encode(intent, forKey: .intent)
242+
try container.encode(permissions, forKey: .permissions)
243+
}
244+
}
245+
}
246+
247+
extension PluginCommandIntent: Encodable {
248+
private enum CodingKeys: CodingKey {
249+
case type, verb, description
250+
}
251+
252+
private enum IntentType: String, Encodable {
253+
case documentationGeneration, sourceCodeFormatting, custom
254+
}
255+
256+
public func encode(to encoder: Encoder) throws {
257+
var container = encoder.container(keyedBy: CodingKeys.self)
258+
switch self {
259+
case .documentationGeneration:
260+
try container.encode(IntentType.documentationGeneration, forKey: .type)
261+
case .sourceCodeFormatting:
262+
try container.encode(IntentType.sourceCodeFormatting, forKey: .type)
263+
case .custom(let verb, let description):
264+
try container.encode(IntentType.custom, forKey: .type)
241265
try container.encode(verb, forKey: .verb)
242266
try container.encode(description, forKey: .description)
243-
try container.encode(permissions, forKey: .permissions)
244267
}
245268
}
246269
}

Sources/PackageDescription/Target.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public final class Target {
131131
/// this enum will be extended as new plugin capabilities are added.
132132
public enum PluginCapability {
133133
case _buildTool
134-
case _command(verb: String, description: String, permissions: [PluginPermission])
134+
case _command(intent: PluginCommandIntent, permissions: [PluginPermission])
135135
}
136136

137137
/// The target's C build settings.
@@ -1020,22 +1020,36 @@ extension Target.PluginCapability {
10201020
/// using the SwiftPM CLI (`swift package <verb>`), or in an IDE that supports
10211021
/// Swift Packages.
10221022
public static func command(
1023-
/// The `swift package` CLI verb through which the plugin can be invoked.
1024-
verb: String,
1025-
1026-
/// A description of the functionality of the custom command, suitable for
1027-
/// showing in help text output.
1028-
description: String,
1029-
1023+
/// The semantic intent of the plugin (either one of the predefined intents,
1024+
/// or a custom intent).
1025+
intent: PluginCommandIntent,
1026+
10301027
/// Any permissions needed by the command plugin. This affects what the
10311028
/// sandbox in which the plugin is run allows. Some permissions may require
10321029
/// approval by the user.
10331030
permissions: [PluginPermission] = []
10341031
) -> Target.PluginCapability {
1035-
return ._command(verb: verb, description: description, permissions: permissions)
1032+
return ._command(intent: intent, permissions: permissions)
10361033
}
10371034
}
10381035

1036+
public enum PluginCommandIntent {
1037+
/// The intent of the command is to generate documentation, either by parsing the
1038+
/// package contents directly or by using the build system support for generating
1039+
/// symbol graphs. Invoked by a `generate-documentation` verb to `swift package`.
1040+
case documentationGeneration
1041+
1042+
/// The intent of the command is to modify the source code in the package based
1043+
/// on a set of rules. Invoked by a `format-source-code` verb to `swift package`.
1044+
case sourceCodeFormatting
1045+
1046+
/// Any future enum cases should use @available()
1047+
1048+
/// An intent that doesn't fit into any of the other categories, with a custom
1049+
/// verb through which it can be invoked.
1050+
case custom(verb: String, description: String)
1051+
}
1052+
10391053
public enum PluginPermission {
10401054
/// The custom command plugin requests permission to modify the files inside the
10411055
/// package directory. The `reason` string is shown to the user at the time of

Sources/PackageLoading/ManifestJSONParser.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,10 +597,27 @@ extension TargetDescription.PluginCapability {
597597
case "buildTool":
598598
self = .buildTool
599599
case "command":
600+
let intent = try TargetDescription.PluginCommandIntent(v4: json.getJSON("intent"))
601+
let permissions = try json.getArray("permissions").map(TargetDescription.PluginPermission.init(v4:))
602+
self = .command(intent: intent, permissions: permissions)
603+
default:
604+
throw InternalError("invalid type \(type)")
605+
}
606+
}
607+
}
608+
609+
extension TargetDescription.PluginCommandIntent {
610+
fileprivate init(v4 json: JSON) throws {
611+
let type = try json.get(String.self, forKey: "type")
612+
switch type {
613+
case "documentationGeneration":
614+
self = .documentationGeneration
615+
case "sourceCodeFormatting":
616+
self = .sourceCodeFormatting
617+
case "custom":
600618
let verb = try json.get(String.self, forKey: "verb")
601619
let description = try json.get(String.self, forKey: "description")
602-
let permissions = try json.getArray("permissions").map(TargetDescription.PluginPermission.init(v4:))
603-
self = .command(verb: verb, description: description, permissions: permissions)
620+
self = .custom(verb: verb, description: description)
604621
default:
605622
throw InternalError("invalid type \(type)")
606623
}

Sources/PackageModel/Manifest/TargetDescription.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,15 @@ public struct TargetDescription: Equatable, Codable {
110110
/// Represents the declared capability of a package plugin.
111111
public enum PluginCapability: Equatable {
112112
case buildTool
113-
case command(verb: String, description: String, permissions: [PluginPermission])
113+
case command(intent: PluginCommandIntent, permissions: [PluginPermission])
114114
}
115115

116+
public enum PluginCommandIntent: Equatable, Codable {
117+
case documentationGeneration
118+
case sourceCodeFormatting
119+
case custom(verb: String, description: String)
120+
}
121+
116122
public enum PluginPermission: Equatable, Codable {
117123
case packageWritability(reason: String)
118124
}
@@ -272,11 +278,10 @@ extension TargetDescription.PluginCapability: Codable {
272278
switch self {
273279
case .buildTool:
274280
try container.encodeNil(forKey: .buildTool)
275-
case .command(let a1, let a2, let a3):
281+
case .command(let a1, let a2):
276282
var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .command)
277283
try unkeyedContainer.encode(a1)
278284
try unkeyedContainer.encode(a2)
279-
try unkeyedContainer.encode(a3)
280285
}
281286
}
282287

@@ -290,10 +295,9 @@ extension TargetDescription.PluginCapability: Codable {
290295
self = .buildTool
291296
case .command:
292297
var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key)
293-
let a1 = try unkeyedValues.decode(String.self)
294-
let a2 = try unkeyedValues.decode(String.self)
295-
let a3 = try unkeyedValues.decode([TargetDescription.PluginPermission].self)
296-
self = .command(verb: a1, description: a2, permissions: a3)
298+
let a1 = try unkeyedValues.decode(TargetDescription.PluginCommandIntent.self)
299+
let a2 = try unkeyedValues.decode([TargetDescription.PluginPermission].self)
300+
self = .command(intent: a1, permissions: a2)
297301
}
298302
}
299303
}

Sources/PackageModel/ManifestSourceGeneration.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,17 +415,32 @@ fileprivate extension SourceCodeFragment {
415415
switch capability {
416416
case .buildTool:
417417
self.init(enum: "buildTool", subnodes: [])
418-
case .command(let verb, let description, let permissions):
418+
case .command(let intent, let permissions):
419419
var params: [SourceCodeFragment] = []
420-
params.append(SourceCodeFragment(key: "verb", string: verb))
421-
params.append(SourceCodeFragment(key: "description", string: description))
420+
params.append(SourceCodeFragment(key: "intent", subnode: .init(from: intent)))
422421
if !permissions.isEmpty {
423422
params.append(SourceCodeFragment(key: "permissions", subnodes: permissions.map{ .init(from: $0) }))
424423
}
425424
self.init(enum: "command", subnodes: params)
426425
}
427426
}
428427

428+
/// Instantiates a SourceCodeFragment to represent a single plugin command intent.
429+
init(from intent: TargetDescription.PluginCommandIntent) {
430+
switch intent {
431+
case .documentationGeneration:
432+
self.init(enum: "documentationGeneration")
433+
case .sourceCodeFormatting:
434+
self.init(enum: "sourceCodeFormatting")
435+
case .custom(let verb, let description):
436+
let params = [
437+
SourceCodeFragment(key: "verb", string: verb),
438+
SourceCodeFragment(key: "description", string: description)
439+
]
440+
self.init(enum: "packageWritability", subnodes: params)
441+
}
442+
}
443+
429444
/// Instantiates a SourceCodeFragment to represent a single plugin permission.
430445
init(from permission: TargetDescription.PluginPermission) {
431446
switch permission {

Sources/PackageModel/Target.swift

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ public final class PluginTarget: Target {
664664

665665
public enum PluginCapability: Hashable, Codable {
666666
case buildTool
667-
case command(verb: String, description: String, permissions: [PluginPermission])
667+
case command(intent: PluginCommandIntent, permissions: [PluginPermission])
668668

669669
private enum CodingKeys: String, CodingKey {
670670
case buildTool, command
@@ -675,11 +675,10 @@ public enum PluginCapability: Hashable, Codable {
675675
switch self {
676676
case .buildTool:
677677
try container.encodeNil(forKey: .buildTool)
678-
case .command(let a1, let a2, let a3):
678+
case .command(let a1, let a2):
679679
var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .command)
680680
try unkeyedContainer.encode(a1)
681681
try unkeyedContainer.encode(a2)
682-
try unkeyedContainer.encode(a3)
683682
}
684683
}
685684

@@ -693,19 +692,35 @@ public enum PluginCapability: Hashable, Codable {
693692
self = .buildTool
694693
case .command:
695694
var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key)
696-
let a1 = try unkeyedValues.decode(String.self)
697-
let a2 = try unkeyedValues.decode(String.self)
698-
let a3 = try unkeyedValues.decode([PluginPermission].self)
699-
self = .command(verb: a1, description: a2, permissions: a3)
695+
let a1 = try unkeyedValues.decode(PluginCommandIntent.self)
696+
let a2 = try unkeyedValues.decode([PluginPermission].self)
697+
self = .command(intent: a1, permissions: a2)
700698
}
701699
}
702700

703701
public init(from desc: TargetDescription.PluginCapability) {
704702
switch desc {
705703
case .buildTool:
706704
self = .buildTool
707-
case .command(let verb, let description, let permissions):
708-
self = .command(verb: verb, description: description, permissions: permissions.map{ .init(from: $0) })
705+
case .command(let intent, let permissions):
706+
self = .command(intent: .init(from: intent), permissions: permissions.map{ .init(from: $0) })
707+
}
708+
}
709+
}
710+
711+
public enum PluginCommandIntent: Hashable, Codable {
712+
case documentationGeneration
713+
case sourceCodeFormatting
714+
case custom(verb: String, description: String)
715+
716+
public init(from desc: TargetDescription.PluginCommandIntent) {
717+
switch desc {
718+
case .documentationGeneration:
719+
self = .documentationGeneration
720+
case .sourceCodeFormatting:
721+
self = .sourceCodeFormatting
722+
case .custom(let verb, let description):
723+
self = .custom(verb: verb, description: description)
709724
}
710725
}
711726
}

Tests/FunctionalTests/PluginTests.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,8 @@ class PluginTests: XCTestCase {
167167
.plugin(
168168
name: "MyPlugin",
169169
capability: .command(
170-
verb: "mycmd",
171-
description: "What is mycmd anyway?",
172-
permissions: [ .packageWritability(reason: "YOLO") ]
170+
intent: .custom(verb: "mycmd", description: "What is mycmd anyway?"),
171+
permissions: [.packageWritability(reason: "YOLO")]
173172
)
174173
),
175174
]

Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests {
117117
XCTAssertNoDiagnostics(observability.diagnostics)
118118

119119
XCTAssertEqual(manifest.targets[0].type, .plugin)
120-
XCTAssertEqual(manifest.targets[0].pluginCapability, .command(verb: "mycmd", description: "helpful description of mycmd", permissions: [.packageWritability(reason: "YOLO")]))
120+
XCTAssertEqual(manifest.targets[0].pluginCapability, .command(intent: .custom(verb: "mycmd", description: "helpful description of mycmd"), permissions: [.packageWritability(reason: "YOLO")]))
121121
}
122122

123123
func testPluginTargetCustomization() throws {

0 commit comments

Comments
 (0)