main.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /*
  2. *
  3. * Copyright 2017, Google Inc.
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are
  8. * met:
  9. *
  10. * * Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * * Redistributions in binary form must reproduce the above
  13. * copyright notice, this list of conditions and the following disclaimer
  14. * in the documentation and/or other materials provided with the
  15. * distribution.
  16. * * Neither the name of Google Inc. nor the names of its
  17. * contributors may be used to endorse or promote products derived from
  18. * this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. *
  32. */
  33. import Foundation
  34. import SwiftProtobuf
  35. import PluginLibrary
  36. import Stencil
  37. import PathKit
  38. extension String {
  39. var undotted : String {
  40. return self.replacingOccurrences(of:".", with:"_")
  41. }
  42. var uppercasedFirst : String {
  43. var out = self.characters
  44. if let first = out.popFirst() {
  45. return String(first).uppercased() + String(out)
  46. } else {
  47. return self
  48. }
  49. }
  50. }
  51. func protoMessageName(_ name :String?) -> String {
  52. guard let name = name else {
  53. return ""
  54. }
  55. let parts = name.undotted.components(separatedBy:"_")
  56. var capitalizedParts : [String] = []
  57. for part in parts {
  58. if part != "" {
  59. capitalizedParts.append(part.uppercasedFirst)
  60. }
  61. }
  62. return capitalizedParts.joined(separator:"_")
  63. }
  64. func pathName(_ arguments: [Any?]) throws -> String {
  65. if arguments.count != 3 {
  66. throw TemplateSyntaxError("path expects 3 arguments")
  67. }
  68. guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
  69. else {
  70. throw TemplateSyntaxError("tag must be called with a " +
  71. "Google_Protobuf_FileDescriptorProto" +
  72. " argument, received \(arguments[0])")
  73. }
  74. guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
  75. else {
  76. throw TemplateSyntaxError("tag must be called with a " +
  77. "Google_Protobuf_ServiceDescriptorProto" +
  78. " argument, received \(arguments[1])")
  79. }
  80. guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
  81. else {
  82. throw TemplateSyntaxError("tag must be called with a " +
  83. "Google_Protobuf_MethodDescriptorProto" +
  84. " argument, received \(arguments[2])")
  85. }
  86. return "/" + protoFile.package! + "." + service.name! + "/" + method.name!
  87. }
  88. func packageServiceMethodName(_ arguments: [Any?]) throws -> String {
  89. if arguments.count != 3 {
  90. throw TemplateSyntaxError("tag expects 3 arguments")
  91. }
  92. guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
  93. else {
  94. throw TemplateSyntaxError("tag must be called with a " +
  95. "Google_Protobuf_FileDescriptorProto" +
  96. " argument, received \(arguments[0])")
  97. }
  98. guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
  99. else {
  100. throw TemplateSyntaxError("tag must be called with a " +
  101. "Google_Protobuf_ServiceDescriptorProto" +
  102. " argument, received \(arguments[1])")
  103. }
  104. guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
  105. else {
  106. throw TemplateSyntaxError("tag must be called with a " +
  107. "Google_Protobuf_MethodDescriptorProto" +
  108. " argument, received \(arguments[2])")
  109. }
  110. return protoFile.package!.capitalized.undotted + "_" + service.name! + method.name!
  111. }
  112. func packageServiceName(_ arguments: [Any?]) throws -> String {
  113. if arguments.count != 2 {
  114. throw TemplateSyntaxError("tag expects 2 arguments")
  115. }
  116. guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
  117. else {
  118. throw TemplateSyntaxError("tag must be called with a " +
  119. "Google_Protobuf_FileDescriptorProto" +
  120. " argument, received \(arguments[0])")
  121. }
  122. guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
  123. else {
  124. throw TemplateSyntaxError("tag must be called with a " +
  125. "Google_Protobuf_ServiceDescriptorProto" +
  126. " argument, received \(arguments[1])")
  127. }
  128. return protoFile.package!.capitalized.undotted + "_" + service.name!
  129. }
  130. // Code templates use "//-" prefixes to comment-out template operators
  131. // to keep them from interfering with Swift code formatting tools.
  132. // Use this to remove them after templates have been expanded.
  133. func stripMarkers(_ code:String) -> String {
  134. let inputLines = code.components(separatedBy:"\n")
  135. var outputLines : [String] = []
  136. for line in inputLines {
  137. if line.contains("//-") {
  138. let removed = line.replacingOccurrences(of:"//-", with:"")
  139. if (removed.trimmingCharacters(in:CharacterSet.whitespaces) != "") {
  140. outputLines.append(removed)
  141. }
  142. } else {
  143. outputLines.append(line)
  144. }
  145. }
  146. return outputLines.joined(separator:"\n")
  147. }
  148. func Log(_ message : String) {
  149. FileHandle.standardError.write((message + "\n").data(using:.utf8)!)
  150. }
  151. func main() throws {
  152. // initialize template engine and add custom filters
  153. let ext = Extension()
  154. ext.registerFilter("call") { (value: Any?, arguments: [Any?]) in
  155. return try packageServiceMethodName(arguments) + "Call"
  156. }
  157. ext.registerFilter("session") { (value: Any?, arguments: [Any?]) in
  158. return try packageServiceMethodName(arguments) + "Session"
  159. }
  160. ext.registerFilter("path") { (value: Any?, arguments: [Any?]) in
  161. return try pathName(arguments)
  162. }
  163. ext.registerFilter("provider") { (value: Any?, arguments: [Any?]) in
  164. return try packageServiceName(arguments) + "Provider"
  165. }
  166. ext.registerFilter("clienterror") { (value: Any?, arguments: [Any?]) in
  167. return try packageServiceName(arguments) + "ClientError"
  168. }
  169. ext.registerFilter("serviceclass") { (value: Any?, arguments: [Any?]) in
  170. return try packageServiceName(arguments) + "Service"
  171. }
  172. ext.registerFilter("servererror") { (value: Any?, arguments: [Any?]) in
  173. return try packageServiceName(arguments) + "ServerError"
  174. }
  175. ext.registerFilter("server") { (value: Any?, arguments: [Any?]) in
  176. return try packageServiceName(arguments) + "Server"
  177. }
  178. ext.registerFilter("service") { (value: Any?, arguments: [Any?]) in
  179. return try packageServiceName(arguments)
  180. }
  181. ext.registerFilter("input") { (value: Any?) in
  182. if let value = value as? Google_Protobuf_MethodDescriptorProto {
  183. return protoMessageName(value.inputType)
  184. }
  185. throw TemplateSyntaxError("message: invalid argument \(value)")
  186. }
  187. ext.registerFilter("output") { (value: Any?) in
  188. if let value = value as? Google_Protobuf_MethodDescriptorProto {
  189. return protoMessageName(value.outputType)
  190. }
  191. throw TemplateSyntaxError("message: invalid argument \(value)")
  192. }
  193. let templateEnvironment = Environment(loader: InternalLoader(),
  194. extensions:[ext])
  195. // initialize responses
  196. var response = Google_Protobuf_Compiler_CodeGeneratorResponse()
  197. var log = ""
  198. // read plugin input
  199. let rawRequest = try Stdin.readall()
  200. let request = try Google_Protobuf_Compiler_CodeGeneratorRequest(protobuf: rawRequest)
  201. var generatedFileNames = Set<String>()
  202. // process each .proto file separately
  203. for protoFile in request.protoFile {
  204. // a package declaration is required
  205. guard let package = protoFile.package else {
  206. print("ERROR: no package for \(protoFile.name)")
  207. continue
  208. }
  209. // log info about the service
  210. log += "File \(protoFile.name!)\n"
  211. for service in protoFile.service {
  212. log += "Service \(service.name!)\n"
  213. for method in service.method {
  214. log += " Method \(method.name!)\n"
  215. log += " input \(method.inputType!)\n"
  216. log += " output \(method.outputType!)\n"
  217. log += " client_streaming \(method.clientStreaming!)\n"
  218. log += " server_streaming \(method.serverStreaming!)\n"
  219. }
  220. log += " Options \(service.options)\n"
  221. }
  222. if protoFile.service.count > 0 {
  223. // generate separate implementation files for client and server
  224. let context = ["protoFile": protoFile]
  225. do {
  226. let clientFileName = package + ".client.pb.swift"
  227. if !generatedFileNames.contains(clientFileName) {
  228. generatedFileNames.insert(clientFileName)
  229. let clientcode = try templateEnvironment.renderTemplate(name:"client.pb.swift",
  230. context: context)
  231. var clientfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
  232. clientfile.name = clientFileName
  233. clientfile.content = stripMarkers(clientcode)
  234. response.file.append(clientfile)
  235. }
  236. let serverFileName = package + ".server.pb.swift"
  237. if !generatedFileNames.contains(serverFileName) {
  238. generatedFileNames.insert(serverFileName)
  239. let servercode = try templateEnvironment.renderTemplate(name:"server.pb.swift",
  240. context: context)
  241. var serverfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
  242. serverfile.name = serverFileName
  243. serverfile.content = stripMarkers(servercode)
  244. response.file.append(serverfile)
  245. }
  246. } catch (let error) {
  247. log += "ERROR: \(error)\n"
  248. }
  249. }
  250. }
  251. log += "\(request)"
  252. // add the logfile to the code generation response
  253. var logfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
  254. logfile.name = "swiftgrpc.log"
  255. logfile.content = log
  256. response.file.append(logfile)
  257. // return everything to the caller
  258. let serializedResponse = try response.serializeProtobuf()
  259. Stdout.write(bytes: serializedResponse)
  260. }
  261. do {
  262. try main()
  263. } catch (let error) {
  264. Log("ERROR: \(error)")
  265. }