-
Notifications
You must be signed in to change notification settings - Fork 14
Code generation command plugin #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
6d58cd4
e025438
68a2eb2
6c42243
5819616
efb4100
9536932
bdb7235
96da879
66261e7
3e8afb4
d8dd2b6
d95d00a
f088e0a
d657e93
4dd7da2
b87b3e5
1da570e
16d19d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
/* | ||
* Copyright 2024, gRPC Authors All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import Foundation | ||
import PackagePlugin | ||
|
||
struct CommandConfig { | ||
var common: GenerationConfig | ||
|
||
var verbose: Bool | ||
var dryRun: Bool | ||
|
||
static let defaults = Self( | ||
common: .init( | ||
accessLevel: .internal, | ||
servers: true, | ||
clients: true, | ||
messages: true, | ||
fileNaming: .fullPath, | ||
accessLevelOnImports: false, | ||
importPaths: [], | ||
outputPath: "" | ||
), | ||
verbose: false, | ||
dryRun: false | ||
) | ||
} | ||
|
||
extension CommandConfig { | ||
static func parse( | ||
argumentExtractor argExtractor: inout ArgumentExtractor, | ||
pluginWorkDirectory: URL | ||
) throws -> CommandConfig { | ||
var config = CommandConfig.defaults | ||
|
||
for flag in OptionsAndFlags.allCases { | ||
switch flag { | ||
case .accessLevel: | ||
if let value = argExtractor.extractSingleOption(named: flag.rawValue) { | ||
if let accessLevel = GenerationConfig.AccessLevel(rawValue: value) { | ||
config.common.accessLevel = accessLevel | ||
} else { | ||
throw CommandPluginError.unknownAccessLevel(value) | ||
} | ||
} | ||
|
||
case .noServers: | ||
// Handled by `.servers` | ||
continue | ||
case .servers: | ||
let servers = argExtractor.extractFlag(named: OptionsAndFlags.servers.rawValue) | ||
let noServers = argExtractor.extractFlag(named: OptionsAndFlags.noServers.rawValue) | ||
if servers > 0 && noServers > 0 { | ||
throw CommandPluginError.conflictingFlags( | ||
OptionsAndFlags.servers.rawValue, | ||
OptionsAndFlags.noServers.rawValue | ||
) | ||
} else if servers > 0 { | ||
config.common.servers = true | ||
} else if noServers > 0 { | ||
config.common.servers = false | ||
} | ||
|
||
case .noClients: | ||
// Handled by `.clients` | ||
continue | ||
case .clients: | ||
let clients = argExtractor.extractFlag(named: OptionsAndFlags.clients.rawValue) | ||
let noClients = argExtractor.extractFlag(named: OptionsAndFlags.noClients.rawValue) | ||
if clients > 0 && noClients > 0 { | ||
throw CommandPluginError.conflictingFlags( | ||
OptionsAndFlags.clients.rawValue, | ||
OptionsAndFlags.noClients.rawValue | ||
) | ||
} else if clients > 0 { | ||
config.common.clients = true | ||
} else if noClients > 0 { | ||
config.common.clients = false | ||
} | ||
|
||
case .noMessages: | ||
// Handled by `.messages` | ||
continue | ||
case .messages: | ||
let messages = argExtractor.extractFlag(named: OptionsAndFlags.messages.rawValue) | ||
let noMessages = argExtractor.extractFlag(named: OptionsAndFlags.noMessages.rawValue) | ||
if messages > 0 && noMessages > 0 { | ||
throw CommandPluginError.conflictingFlags( | ||
OptionsAndFlags.messages.rawValue, | ||
OptionsAndFlags.noMessages.rawValue | ||
) | ||
} else if messages > 0 { | ||
config.common.messages = true | ||
} else if noMessages > 0 { | ||
config.common.messages = false | ||
} | ||
|
||
case .fileNaming: | ||
|
||
if let value = argExtractor.extractSingleOption(named: flag.rawValue) { | ||
if let fileNaming = GenerationConfig.FileNaming(rawValue: value) { | ||
config.common.fileNaming = fileNaming | ||
} else { | ||
throw CommandPluginError.unknownFileNamingStrategy(value) | ||
} | ||
} | ||
|
||
case .accessLevelOnImports: | ||
if let value = argExtractor.extractSingleOption(named: flag.rawValue) { | ||
|
||
guard let accessLevelOnImports = Bool(value) else { | ||
throw CommandPluginError.invalidArgumentValue(name: flag.rawValue, value: value) | ||
|
||
} | ||
config.common.accessLevelOnImports = accessLevelOnImports | ||
} | ||
|
||
case .importPath: | ||
config.common.importPaths = argExtractor.extractOption(named: flag.rawValue) | ||
|
||
case .protocPath: | ||
config.common.protocPath = argExtractor.extractSingleOption(named: flag.rawValue) | ||
|
||
case .outputPath: | ||
config.common.outputPath = | ||
argExtractor.extractSingleOption(named: flag.rawValue) | ||
?? pluginWorkDirectory.absoluteStringNoScheme | ||
|
||
case .verbose: | ||
let verbose = argExtractor.extractFlag(named: flag.rawValue) | ||
config.verbose = verbose != 0 | ||
|
||
case .dryRun: | ||
let dryRun = argExtractor.extractFlag(named: flag.rawValue) | ||
config.dryRun = dryRun != 0 | ||
|
||
case .help: | ||
() // handled elsewhere | ||
} | ||
} | ||
|
||
if let argument = argExtractor.remainingArguments.first { | ||
throw CommandPluginError.unknownOption(argument) | ||
} | ||
|
||
return config | ||
} | ||
} | ||
|
||
extension ArgumentExtractor { | ||
mutating func extractSingleOption(named optionName: String) -> String? { | ||
let values = self.extractOption(named: optionName) | ||
if values.count > 1 { | ||
Diagnostics.warning( | ||
"'--\(optionName)' was unexpectedly repeated, the first value will be used." | ||
) | ||
} | ||
return values.first | ||
} | ||
} | ||
|
||
/// All valid input options/flags | ||
enum OptionsAndFlags: String, CaseIterable { | ||
case servers | ||
case noServers = "no-servers" | ||
case clients | ||
case noClients = "no-clients" | ||
case messages | ||
case noMessages = "no-messages" | ||
case fileNaming = "file-naming" | ||
case accessLevel = "access-level" | ||
case accessLevelOnImports = "access-level-on-imports" | ||
case importPath = "import-path" | ||
case protocPath = "protoc-path" | ||
case outputPath = "output-path" | ||
case verbose | ||
case dryRun = "dry-run" | ||
|
||
case help | ||
} | ||
|
||
extension OptionsAndFlags { | ||
func usageDescription() -> String { | ||
switch self { | ||
case .servers: | ||
return "Indicate that server code is to be generated. Generated by default." | ||
|
||
case .noServers: | ||
return "Indicate that server code is not to be generated. Generated by default." | ||
case .clients: | ||
return "Indicate that client code is to be generated. Generated by default." | ||
case .noClients: | ||
return "Indicate that client code is not to be generated. Generated by default." | ||
case .messages: | ||
return "Indicate that message code is to be generated. Generated by default." | ||
case .noMessages: | ||
return "Indicate that message code is not to be generated. Generated by default." | ||
case .fileNaming: | ||
return | ||
"The naming scheme for output files [fullPath/pathToUnderscores/dropPath]. Defaults to fullPath." | ||
case .accessLevel: | ||
return | ||
"The access level of the generated source [internal/public/package]. Defaults to internal." | ||
case .accessLevelOnImports: | ||
return "Whether imports should have explicit access levels. Defaults to false." | ||
case .importPath: | ||
return "The directory in which to search for imports." | ||
|
||
case .protocPath: | ||
return "The path to the protoc binary." | ||
case .dryRun: | ||
return "Print but do not execute the protoc commands." | ||
case .outputPath: | ||
return "The path into which the generated source files are created." | ||
rnro marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
case .verbose: | ||
return "Emit verbose output." | ||
case .help: | ||
return "Print this help." | ||
} | ||
} | ||
|
||
static func printHelp(requested: Bool) { | ||
let printMessage: (String) -> Void | ||
if requested { | ||
printMessage = { message in print(message) } | ||
} else { | ||
printMessage = Stderr.print | ||
} | ||
|
||
printMessage("Usage: swift package generate-grpc-code-from-protos [flags] [--] [input files]") | ||
printMessage("") | ||
printMessage("Flags:") | ||
printMessage("") | ||
|
||
let spacing = 3 | ||
let maxLength = | ||
(OptionsAndFlags.allCases.map(\.rawValue).max(by: { $0.count < $1.count })?.count ?? 0) | ||
+ spacing | ||
for flag in OptionsAndFlags.allCases { | ||
printMessage( | ||
" --\(flag.rawValue.padding(toLength: maxLength, withPad: " ", startingAt: 0))\(flag.usageDescription())" | ||
) | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.