Skip to content

Commit e7a3212

Browse files
committed
Step 2 of better plugin support.
Extend `CodeGenerator` to allow easy customization and generally provide what's needed for a `main()` so plugins don't have to reimplement everything.
1 parent 4ee05eb commit e7a3212

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

Sources/SwiftProtobufPluginLibrary/CodeGenerator.swift

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import Foundation
1717
/// A protocol that generator should conform to then get easy support for
1818
/// being a protocol buffer compiler pluign.
1919
public protocol CodeGenerator {
20+
init()
21+
2022
/// Generates code for the given proto files.
2123
///
2224
/// - Parameters:
@@ -42,6 +44,107 @@ public protocol CodeGenerator {
4244
/// The list of features this CodeGenerator support to be reported back to
4345
/// the protocol buffer compiler.
4446
var supportedFeatures: [Google_Protobuf_Compiler_CodeGeneratorResponse.Feature] { get }
47+
48+
/// If provided, the argument parsing will support `--version` and report
49+
/// this value.
50+
var version: String? { get }
51+
52+
/// If provided and `printHelp` isn't provide, this value will be including in
53+
/// default output for the `--help` output.
54+
var projectURL: String? { get }
55+
56+
/// If provided and `printHelp` isn't provide, this value will be including in
57+
/// default output for the `--help` output.
58+
var copyrightLine: String? { get }
59+
60+
/// Will be called for `-h` or `--help`, should `print()` out whatever is
61+
/// desired; there is a default implementation that uses the above info
62+
/// when provided.
63+
func printHelp()
64+
}
65+
66+
extension CodeGenerator {
67+
var programName: String {
68+
guard let name = CommandLine.arguments.first?.split(separator: "/").last else {
69+
return "<plugin>"
70+
}
71+
return String(name)
72+
}
73+
74+
/// Runs as a protocol buffer compiler plugin based on the given arguments
75+
/// or falls back to `CommandLine.arguments`.
76+
public func main(_ args: [String]?) {
77+
let args = args ?? Array(CommandLine.arguments.dropFirst())
78+
79+
for arg in args {
80+
if arg == "--version", let version = version {
81+
print("\(programName) \(version)")
82+
return
83+
}
84+
if arg == "-h" || arg == "--help" {
85+
printHelp()
86+
return
87+
}
88+
// Could look at bringing back the support for recorded requests, but
89+
// haven't needed it in a long time.
90+
var stderr = StandardErrorOutputStream()
91+
print("Unknown argument: \(arg)", to: &stderr)
92+
return
93+
}
94+
95+
let response: Google_Protobuf_Compiler_CodeGeneratorResponse
96+
do {
97+
let request = try Google_Protobuf_Compiler_CodeGeneratorRequest(
98+
serializedData: FileHandle.standardInput.readDataToEndOfFile())
99+
response = generateCode(request: request, generator: self)
100+
} catch let e {
101+
response = Google_Protobuf_Compiler_CodeGeneratorResponse(
102+
error: "Received an unparsable request from the compiler: \(e)")
103+
}
104+
105+
let serializedResponse: Data
106+
do {
107+
serializedResponse = try response.serializedBytes()
108+
} catch let e {
109+
var stderr = StandardErrorOutputStream()
110+
print("\(programName): Failure while serializing response: \(e)", to: &stderr)
111+
return
112+
}
113+
FileHandle.standardOutput.write(serializedResponse)
114+
}
115+
116+
/// Runs as a protocol buffer compiler plugin; reading the generation request
117+
/// off stdin and sending the response on stdout.
118+
///
119+
/// Instead of calling this, just add `@main` to your `CodeGenerator`.
120+
public static func main() {
121+
let generator = Self()
122+
generator.main(nil)
123+
}
124+
}
125+
126+
// Provide default implementation for things so `CodeGenerator`s only have to
127+
// provide them if they wish too.
128+
extension CodeGenerator {
129+
public var version: String? { return nil }
130+
public var projectURL: String? { return nil }
131+
public var copyrightLine: String? { return nil }
132+
133+
public func printHelp() {
134+
print("\(programName): A plugin for protoc and should not normally be run directly.")
135+
if let copyright = copyrightLine {
136+
print("\(copyright)")
137+
}
138+
if let projectURL = projectURL {
139+
print(
140+
"""
141+
142+
For more information on the usage of this plugin, please see:
143+
\(projectURL)
144+
145+
""")
146+
}
147+
}
45148
}
46149

47150
/// Uses the given `Google_Protobuf_Compiler_CodeGeneratorRequest` and
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Sources/SwiftProtobufPluginLibrary/StandardErrorOutputStream.swift
2+
//
3+
// Copyright (c) 2014 - 2023 Apple Inc. and the project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See LICENSE.txt for license information:
7+
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
8+
//
9+
10+
import Foundation
11+
12+
class StandardErrorOutputStream: TextOutputStream {
13+
func write(_ string: String) {
14+
if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
15+
try! FileHandle.standardError.write(contentsOf: Data(string.utf8))
16+
} else {
17+
FileHandle.standardError.write(Data(string.utf8))
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)