Browse Source

Progress using Stencil to templatize code generation.

Tim Burks 9 years ago
parent
commit
08c5b62df3

+ 8 - 2
Examples/Echo/Swift/Echo/echo.client.pb.swift

@@ -1,3 +1,11 @@
+/*
+ * DO NOT EDIT.
+ *
+ * Generated by the protocol buffer compiler.
+ * Source: echo.proto
+ *
+ */
+
 /*
  *
  * Copyright 2016, Google Inc.
@@ -31,8 +39,6 @@
  *
  */
 
-// all code that follows is to-be-generated
-
 import Foundation
 import gRPC
 

+ 8 - 2
Examples/Echo/Swift/Echo/echo.server.pb.swift

@@ -1,3 +1,11 @@
+/*
+ * DO NOT EDIT.
+ *
+ * Generated by the protocol buffer compiler.
+ * Source: echo.proto
+ *
+ */
+
 /*
  *
  * Copyright 2016, Google Inc.
@@ -31,8 +39,6 @@
  *
  */
 
-// all code that follows is to-be-generated
-
 import Foundation
 import gRPC
 

+ 13 - 1
Plugin/Makefile

@@ -1,8 +1,20 @@
-test:
+
+all:	test
+
+build:
 	rm -f echo.client.pb.swift echo.server.pb.swift
 	swift build
 	cp .build/debug/protoc-gen-swiftgrpc .
+
+
+test:	build
 	protoc echo.proto --swiftgrpc_out=.
+	diff echo.client.pb.swift ../Examples/Echo/Swift/Echo/echo.client.pb.swift
+	diff echo.server.pb.swift ../Examples/Echo/Swift/Echo/echo.server.pb.swift
+
+deploy:
+	cp echo.client.pb.swift ../Examples/Echo/Swift/Echo/echo.client.pb.swift 
+	cp echo.server.pb.swift ../Examples/Echo/Swift/Echo/echo.server.pb.swift 
 
 clean :
 	rm -f protoc-gen-swiftgrpc 

+ 147 - 23
Plugin/Sources/main.swift

@@ -17,23 +17,140 @@ import Foundation
 import SwiftProtobuf
 import PluginLibrary
 
+func protoMessageName(_ name :String?) -> String {
+  guard let name = name else {
+    return ""
+  }
+  let parts = name.components(separatedBy:".")
+  if parts.count == 3 {
+    return parts[1].capitalized + "_" + parts[2]
+  } else {
+    return name
+  }
+}
+
+
 func main() throws {
 
-    let fileSystemLoader = Stencil.FileSystemLoader(paths: ["templates/"])
-    let templateEnvironment = Environment(loader: fileSystemLoader)
-	
-  var response = Google_Protobuf_Compiler_CodeGeneratorResponse()
+  // initialize template engine
+  let fileSystemLoader = FileSystemLoader(paths: ["templates/"])
+  let ext = Extension()
+
+  ext.registerFilter("message") { (value: Any?) in
+    if let value = value as? String {
+      let parts = value.components(separatedBy:".")
+      if parts.count == 3 {
+        return parts[1].capitalized + "_" + parts[2]
+      }
+    }
+    throw TemplateSyntaxError("message: invalid argument \(value)")
+  }
+
+  ext.registerFilter("callname") { (value: Any?, arguments: [Any?]) in
+    if arguments.count != 3 {
+      throw TemplateSyntaxError("expects 3 arguments")
+    }
+    guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
+      else {
+        throw TemplateSyntaxError("tag must be called with a " +
+          "Google_Protobuf_FileDescriptorProto" +
+          " argument, received \(arguments[0])")
+    }
+    guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
+      else {
+        throw TemplateSyntaxError("tag must be called with a " +
+          "Google_Protobuf_ServiceDescriptorProto" +
+          " argument, received \(arguments[1])")
+    }
+    guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
+      else {
+        throw TemplateSyntaxError("tag must be called with a " +
+          "Google_Protobuf_MethodDescriptorProto" +
+          " argument, received \(arguments[2])")
+    }
+    return protoFile.package!.capitalized + "_" + service.name! + method.name! + "Call"
+  }
+
+  ext.registerFilter("callpath") { (value: Any?, arguments: [Any?]) in
+    if arguments.count != 3 {
+      throw TemplateSyntaxError("expects 3 arguments")
+    }
+    guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
+      else {
+        throw TemplateSyntaxError("tag must be called with a " +
+          "Google_Protobuf_FileDescriptorProto" +
+          " argument, received \(arguments[0])")
+    }
+    guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
+      else {
+        throw TemplateSyntaxError("tag must be called with a " +
+          "Google_Protobuf_ServiceDescriptorProto" +
+          " argument, received \(arguments[1])")
+    }
+    guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
+      else {
+        throw TemplateSyntaxError("tag must be called with a " +
+          "Google_Protobuf_MethodDescriptorProto" +
+          " argument, received \(arguments[2])")
+    }
+    return "/" + protoFile.package! + "." + service.name! + "/" + method.name!
+  }
 
+  ext.registerFilter("errorname") { (value: Any?, arguments: [Any?]) in
+    if arguments.count != 2 {
+      throw TemplateSyntaxError("expects 2 arguments")
+    }
+    guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
+      else {
+        throw TemplateSyntaxError("tag must be called with a " +
+          "Google_Protobuf_FileDescriptorProto" +
+          " argument, received \(arguments[0])")
+    }
+    guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
+      else {
+        throw TemplateSyntaxError("tag must be called with a " +
+          "Google_Protobuf_ServiceDescriptorProto" +
+          " argument, received \(arguments[1])")
+    }
+    return protoFile.package!.capitalized + "_" + service.name! + "ClientError"
+  }
+
+  ext.registerFilter("inputType") { (value: Any?) in
+    if let value = value as? Google_Protobuf_MethodDescriptorProto {
+      return protoMessageName(value.inputType)
+    }
+    throw TemplateSyntaxError("message: invalid argument \(value)")
+  }
+
+  ext.registerFilter("outputType") { (value: Any?) in
+    if let value = value as? Google_Protobuf_MethodDescriptorProto {
+      return protoMessageName(value.outputType)
+    }
+    throw TemplateSyntaxError("message: invalid argument \(value)")
+  }
+
+
+  let templateEnvironment = Environment(loader: fileSystemLoader,
+                                        extensions:[ext])
+
+  // initialize responses
+  var response = Google_Protobuf_Compiler_CodeGeneratorResponse()
   var log = ""
 
+  // read plugin input
   let rawRequest = try Stdin.readall()
   let request = try Google_Protobuf_Compiler_CodeGeneratorRequest(protobuf: rawRequest)
 
+  // process each .proto file separately
   for protoFile in request.protoFile {
-	
-	
-  	if let package = protoFile.package { 
-		  
+
+    // a package declaration is required
+    guard let package = protoFile.package else {
+      print("ERROR: no package")
+      continue
+    }
+
+    // log info about the service
     log += "File \(protoFile.name!)\n"
     for service in protoFile.service {
       log += "Service \(service.name!)\n"
@@ -46,31 +163,38 @@ func main() throws {
       }
       log += " Options \(service.options)\n"
     }
-	
+
+    // generate separate implementation files for client and server
     let context = ["protoFile": protoFile]
-    let clientcode = try templateEnvironment.renderTemplate(name: "PACKAGE.client.pb.swift", context: context)
-    let servercode = try templateEnvironment.renderTemplate(name: "PACKAGE.server.pb.swift", context: context)
 
-    var clientfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
-    clientfile.name = package + ".client.pb.swift"
-    clientfile.content = clientcode
-    response.file.append(clientfile)
+    do {
+      let clientcode = try templateEnvironment.renderTemplate(name:"client.pb.swift",
+                                                              context: context)
+      var clientfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
+      clientfile.name = package + ".client.pb.swift"
+      clientfile.content = clientcode
+      response.file.append(clientfile)
 
-    var serverfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
-    serverfile.name = package + ".server.pb.swift"
-    serverfile.content = servercode
-    response.file.append(serverfile)
-    } else {
-    	print("ERROR: no package")
+      let servercode = try templateEnvironment.renderTemplate(name:"server.pb.swift",
+                                                              context: context)
+      var serverfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
+      serverfile.name = package + ".server.pb.swift"
+      serverfile.content = servercode
+      response.file.append(serverfile)
+    } catch (let error) {
+      log += "ERROR: \(error)\n"
     }
   }
-  log += "\n\n\n\(request)"
 
+  // log the entire request proto
+  log += "\n\n\n\(request)"
+  // add the logfile to the code generation response
   var logfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
   logfile.name = "swiftgrpc.log"
   logfile.content = log
   response.file.append(logfile)
-  
+
+  // return everything to the caller
   let serializedResponse = try response.serializeProtobuf()
   Stdout.write(bytes: serializedResponse)
 }

+ 0 - 6
Plugin/TRYME.sh

@@ -1,6 +0,0 @@
-#!/bin/sh
-
-rm echo.client.pb.swift echo.server.pb.swift
-swift build
-cp .build/debug/protoc-gen-swiftgrpc .
-protoc echo.proto --swiftgrpc_out=.

+ 2 - 2
Plugin/echo.proto

@@ -17,9 +17,9 @@ package echo;
 
 service Echo {
   rpc Get(EchoRequest) returns (EchoResponse) {}
-  rpc Update(stream EchoRequest) returns (stream EchoResponse) {}
-  rpc Collect(stream EchoRequest) returns (EchoResponse) {}
   rpc Expand(EchoRequest) returns (stream EchoResponse) {}  
+  rpc Collect(stream EchoRequest) returns (EchoResponse) {}
+  rpc Update(stream EchoRequest) returns (stream EchoResponse) {}
 }
 
 message EchoRequest {

+ 0 - 341
Plugin/templates/PACKAGE.client.pb.swift

@@ -1,341 +0,0 @@
-/*
- * DO NOT EDIT.
- *
- * Generated by the protocol buffer compiler.
- * Source: {{ protoFile.name }}
- *
- */
-
-/*
- *
- * Copyright 2016, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-import Foundation
-import gRPC
-
-public enum Echo_EchoClientError : Error {
-  case endOfStream
-  case invalidMessageReceived
-  case error(c: CallResult)
-}
-
-//
-// Unary GET
-//
-public class Echo_EchoGetCall {
-  var call : Call
-
-  fileprivate init(_ channel: Channel) {
-    self.call = channel.makeCall("/echo.Echo/Get")
-  }
-
-  fileprivate func run(request: Echo_EchoRequest,
-                       metadata: Metadata) throws -> Echo_EchoResponse {
-    let done = NSCondition()
-    var callResult : CallResult!
-    var responseMessage : Echo_EchoResponse?
-    let requestMessageData = try! request.serializeProtobuf()
-    try! call.perform(message: requestMessageData,
-                      metadata: metadata)
-    {(_callResult) in
-      callResult = _callResult
-      if let messageData = callResult.resultData {
-        responseMessage = try? Echo_EchoResponse(protobuf:messageData)
-      }
-      done.lock()
-      done.signal()
-      done.unlock()
-    }
-    done.lock()
-    done.wait()
-    done.unlock()
-    if let responseMessage = responseMessage {
-      return responseMessage
-    } else {
-      throw Echo_EchoClientError.error(c: callResult)
-    }
-  }
-}
-
-//
-// Server-streaming EXPAND
-//
-public class Echo_EchoExpandCall {
-  var call : Call
-
-  fileprivate init(_ channel: Channel) {
-    self.call = channel.makeCall("/echo.Echo/Expand")
-  }
-
-  // Call this once with the message to send.
-  fileprivate func run(request: Echo_EchoRequest, metadata: Metadata) throws -> Echo_EchoExpandCall {
-    let requestMessageData = try! request.serializeProtobuf()
-    try! call.startServerStreaming(message: requestMessageData,
-                                   metadata: metadata,
-                                   completion:{(CallResult) in })
-    return self
-  }
-
-  // Call this to wait for a result. Blocks.
-  public func Receive() throws -> Echo_EchoResponse {
-    var returnError : Echo_EchoClientError?
-    var returnMessage : Echo_EchoResponse!
-    let done = NSCondition()
-    do {
-      try call.receiveMessage() {(data) in
-        if let data = data {
-          returnMessage = try? Echo_EchoResponse(protobuf:data)
-          if returnMessage == nil {
-            returnError = Echo_EchoClientError.invalidMessageReceived
-          }
-        } else {
-          returnError = Echo_EchoClientError.endOfStream
-        }
-        done.lock()
-        done.signal()
-        done.unlock()
-      }
-      done.lock()
-      done.wait()
-      done.unlock()
-    }
-    if let returnError = returnError {
-      throw returnError
-    }
-    return returnMessage
-  }
-}
-
-//
-// Client-streaming COLLECT
-//
-public class Echo_EchoCollectCall {
-  var call : Call
-
-  fileprivate init(_ channel: Channel) {
-    self.call = channel.makeCall("/echo.Echo/Collect")
-  }
-
-  // Call this to start a call.
-  fileprivate func run(metadata:Metadata) throws -> Echo_EchoCollectCall {
-    try self.call.start(metadata: metadata, completion:{})
-    return self
-  }
-
-  // Call this to send each message in the request stream.
-  public func Send(_ message: Echo_EchoRequest) {
-    let messageData = try! message.serializeProtobuf()
-    _ = call.sendMessage(data:messageData)
-  }
-
-  // Call this to close the connection and wait for a response. Blocks.
-  public func CloseAndReceive() throws -> Echo_EchoResponse {
-    var returnError : Echo_EchoClientError?
-    var returnMessage : Echo_EchoResponse!
-    let done = NSCondition()
-
-    do {
-      try self.receiveMessage() {(responseMessage) in
-        if let responseMessage = responseMessage {
-          returnMessage = responseMessage
-        } else {
-          returnError = Echo_EchoClientError.invalidMessageReceived
-        }
-        done.lock()
-        done.signal()
-        done.unlock()
-      }
-      try call.close(completion:{
-        print("closed")
-      })
-      done.lock()
-      done.wait()
-      done.unlock()
-    } catch (let error) {
-      print("ERROR B: \(error)")
-    }
-
-    if let returnError = returnError {
-      throw returnError
-    }
-    return returnMessage
-  }
-
-  // Call this to receive a message.
-  // The callback will be called when a message is received.
-  // call this again from the callback to wait for another message.
-  fileprivate func receiveMessage(callback:@escaping (Echo_EchoResponse?) throws -> Void)
-    throws {
-      try call.receiveMessage() {(data) in
-        guard let data = data else {
-          try callback(nil)
-          return
-        }
-        guard
-          let responseMessage = try? Echo_EchoResponse(protobuf:data)
-          else {
-            return
-        }
-        try callback(responseMessage)
-      }
-  }
-
-}
-
-//
-// Bidirectional-streaming UPDATE
-//
-public class Echo_EchoUpdateCall {
-  var call : Call
-
-  fileprivate init(_ channel: Channel) {
-    self.call = channel.makeCall("/echo.Echo/Update")
-  }
-
-  fileprivate func run(metadata:Metadata) throws -> Echo_EchoUpdateCall {
-    try self.call.start(metadata: metadata, completion:{})
-    return self
-  }
-
-  fileprivate func receiveMessage(callback:@escaping (Echo_EchoResponse?) throws -> Void) throws {
-    try call.receiveMessage() {(data) in
-      if let data = data {
-        if let responseMessage = try? Echo_EchoResponse(protobuf:data) {
-          try callback(responseMessage)
-        } else {
-          try callback(nil) // error, bad data
-        }
-      } else {
-        try callback(nil)
-      }
-    }
-  }
-
-  public func Receive() throws -> Echo_EchoResponse {
-    var returnError : Echo_EchoClientError?
-    var returnMessage : Echo_EchoResponse!
-    let done = NSCondition()
-    do {
-      try call.receiveMessage() {(data) in
-        if let data = data {
-          returnMessage = try? Echo_EchoResponse(protobuf:data)
-          if returnMessage == nil {
-            returnError = Echo_EchoClientError.invalidMessageReceived
-          }
-        } else {
-          returnError = Echo_EchoClientError.endOfStream
-        }
-        done.lock()
-        done.signal()
-        done.unlock()
-      }
-      done.lock()
-      done.wait()
-      done.unlock()
-    }
-    if let returnError = returnError {
-      throw returnError
-    }
-    return returnMessage
-  }
-
-  public func Send(_ message:Echo_EchoRequest) {
-    let messageData = try! message.serializeProtobuf()
-    _ = call.sendMessage(data:messageData)
-  }
-
-  public func CloseSend() {
-    let done = NSCondition()
-    try! call.close() {
-      done.lock()
-      done.signal()
-      done.unlock()
-    }
-    done.lock()
-    done.wait()
-    done.unlock()
-  }
-}
-
-// Call methods of this class to make API calls.
-public class Echo_EchoService {
-  private var channel: Channel
-
-  public var metadata : Metadata
-
-  public var host : String {
-    get {
-      return self.channel.host
-    }
-    set {
-      self.channel.host = newValue
-    }
-  }
-
-  public init(address: String) {
-    gRPC.initialize()
-    channel = Channel(address:address)
-    metadata = Metadata()
-  }
-
-  public init(address: String, certificates: String?, host: String?) {
-    gRPC.initialize()
-    channel = Channel(address:address, certificates:certificates, host:host)
-    metadata = Metadata()
-  }
-
-  // Synchronous. Unary.
-  public func get(_ request: Echo_EchoRequest) throws -> Echo_EchoResponse {
-    return try Echo_EchoGetCall(channel).run(request:request, metadata:metadata)
-  }
-
-  // Asynchronous. Server-streaming.
-  // Send the initial message.
-  // Use methods on the returned object to get streamed responses.
-  public func expand(_ request: Echo_EchoRequest) throws -> Echo_EchoExpandCall {
-    return try Echo_EchoExpandCall(channel).run(request:request, metadata:metadata)
-  }
-
-  // Asynchronous. Client-streaming.
-  // Use methods on the returned object to stream messages and
-  // to close the connection and wait for a final response.
-  public func collect() throws -> Echo_EchoCollectCall {
-    return try Echo_EchoCollectCall(channel).run(metadata:metadata)
-  }
-
-  // Asynchronous. Bidirectional-streaming.
-  // Use methods on the returned object to stream messages,
-  // to wait for replies, and to close the connection.
-  public func update() throws -> Echo_EchoUpdateCall {
-    return try Echo_EchoUpdateCall(channel).run(metadata:metadata)
-  }
-}

+ 74 - 0
Plugin/templates/client-call-bidistreaming.swift

@@ -0,0 +1,74 @@
+//
+// {{ method.name }} (Bidirectional streaming)
+//
+public class {{ .|callname:protoFile,service,method }} {
+  var call : Call
+
+  fileprivate init(_ channel: Channel) {
+    self.call = channel.makeCall("{{ .|callpath:protoFile,service,method }}")
+  }
+
+  fileprivate func run(metadata:Metadata) throws -> Echo_EchoUpdateCall {
+    try self.call.start(metadata: metadata, completion:{})
+    return self
+  }
+
+  fileprivate func receiveMessage(callback:@escaping (Echo_EchoResponse?) throws -> Void) throws {
+    try call.receiveMessage() {(data) in
+      if let data = data {
+        if let responseMessage = try? Echo_EchoResponse(protobuf:data) {
+          try callback(responseMessage)
+        } else {
+          try callback(nil) // error, bad data
+        }
+      } else {
+        try callback(nil)
+      }
+    }
+  }
+
+  public func Receive() throws -> Echo_EchoResponse {
+    var returnError : {{ .|errorname:protoFile,service }}?
+    var returnMessage : Echo_EchoResponse!
+    let done = NSCondition()
+    do {
+      try call.receiveMessage() {(data) in
+        if let data = data {
+          returnMessage = try? Echo_EchoResponse(protobuf:data)
+          if returnMessage == nil {
+            returnError = {{ .|errorname:protoFile,service }}.invalidMessageReceived
+          }
+        } else {
+          returnError = {{ .|errorname:protoFile,service }}.endOfStream
+        }
+        done.lock()
+        done.signal()
+        done.unlock()
+      }
+      done.lock()
+      done.wait()
+      done.unlock()
+    }
+    if let returnError = returnError {
+      throw returnError
+    }
+    return returnMessage
+  }
+
+  public func Send(_ message:Echo_EchoRequest) {
+    let messageData = try! message.serializeProtobuf()
+    _ = call.sendMessage(data:messageData)
+  }
+
+  public func CloseSend() {
+    let done = NSCondition()
+    try! call.close() {
+      done.lock()
+      done.signal()
+      done.unlock()
+    }
+    done.lock()
+    done.wait()
+    done.unlock()
+  }
+}

+ 75 - 0
Plugin/templates/client-call-clientstreaming.swift

@@ -0,0 +1,75 @@
+//
+// {{ method.name }} (Client streaming)
+//
+public class {{ .|callname:protoFile,service,method }} {
+  var call : Call
+
+  fileprivate init(_ channel: Channel) {
+    self.call = channel.makeCall("{{ .|callpath:protoFile,service,method }}")
+  }
+
+  // Call this to start a call.
+  fileprivate func run(metadata:Metadata) throws -> Echo_EchoCollectCall {
+    try self.call.start(metadata: metadata, completion:{})
+    return self
+  }
+
+  // Call this to send each message in the request stream.
+  public func Send(_ message: Echo_EchoRequest) {
+    let messageData = try! message.serializeProtobuf()
+    _ = call.sendMessage(data:messageData)
+  }
+
+  // Call this to close the connection and wait for a response. Blocks.
+  public func CloseAndReceive() throws -> Echo_EchoResponse {
+    var returnError : {{ .|errorname:protoFile,service }}?
+    var returnMessage : Echo_EchoResponse!
+    let done = NSCondition()
+
+    do {
+      try self.receiveMessage() {(responseMessage) in
+        if let responseMessage = responseMessage {
+          returnMessage = responseMessage
+        } else {
+          returnError = {{ .|errorname:protoFile,service }}.invalidMessageReceived
+        }
+        done.lock()
+        done.signal()
+        done.unlock()
+      }
+      try call.close(completion:{
+        print("closed")
+      })
+      done.lock()
+      done.wait()
+      done.unlock()
+    } catch (let error) {
+      print("ERROR B: \(error)")
+    }
+
+    if let returnError = returnError {
+      throw returnError
+    }
+    return returnMessage
+  }
+
+  // Call this to receive a message.
+  // The callback will be called when a message is received.
+  // call this again from the callback to wait for another message.
+  fileprivate func receiveMessage(callback:@escaping (Echo_EchoResponse?) throws -> Void)
+    throws {
+      try call.receiveMessage() {(data) in
+        guard let data = data else {
+          try callback(nil)
+          return
+        }
+        guard
+          let responseMessage = try? Echo_EchoResponse(protobuf:data)
+          else {
+            return
+        }
+        try callback(responseMessage)
+      }
+  }
+
+}

+ 48 - 0
Plugin/templates/client-call-serverstreaming.swift

@@ -0,0 +1,48 @@
+//
+// {{ method.name }} (Server streaming)
+//
+public class {{ .|callname:protoFile,service,method }} {
+  var call : Call
+
+  fileprivate init(_ channel: Channel) {
+    self.call = channel.makeCall("{{ .|callpath:protoFile,service,method }}")
+  }
+
+  // Call this once with the message to send.
+  fileprivate func run(request: Echo_EchoRequest, metadata: Metadata) throws -> Echo_EchoExpandCall {
+    let requestMessageData = try! request.serializeProtobuf()
+    try! call.startServerStreaming(message: requestMessageData,
+                                   metadata: metadata,
+                                   completion:{(CallResult) in })
+    return self
+  }
+
+  // Call this to wait for a result. Blocks.
+  public func Receive() throws -> Echo_EchoResponse {
+    var returnError : {{ .|errorname:protoFile,service }}?
+    var returnMessage : Echo_EchoResponse!
+    let done = NSCondition()
+    do {
+      try call.receiveMessage() {(data) in
+        if let data = data {
+          returnMessage = try? Echo_EchoResponse(protobuf:data)
+          if returnMessage == nil {
+            returnError = {{ .|errorname:protoFile,service }}.invalidMessageReceived
+          }
+        } else {
+          returnError = {{ .|errorname:protoFile,service }}.endOfStream
+        }
+        done.lock()
+        done.signal()
+        done.unlock()
+      }
+      done.lock()
+      done.wait()
+      done.unlock()
+    }
+    if let returnError = returnError {
+      throw returnError
+    }
+    return returnMessage
+  }
+}

+ 37 - 0
Plugin/templates/client-call-unary.swift

@@ -0,0 +1,37 @@
+//
+// {{ method.name }} (Unary)
+//
+public class {{ .|callname:protoFile,service,method }} {
+  var call : Call
+
+  fileprivate init(_ channel: Channel) {
+    self.call = channel.makeCall("{{ .|callpath:protoFile,service,method }}")
+  }
+
+  fileprivate func run(request: {{ method|inputType }},
+                       metadata: Metadata) throws -> {{ method|outputType }} {
+    let done = NSCondition()
+    var callResult : CallResult!
+    var responseMessage : {{ method|outputType }}?
+    let requestMessageData = try! request.serializeProtobuf()
+    try! call.perform(message: requestMessageData,
+                      metadata: metadata)
+    {(_callResult) in
+      callResult = _callResult
+      if let messageData = callResult.resultData {
+        responseMessage = try? {{ method|outputType }}(protobuf:messageData)
+      }
+      done.lock()
+      done.signal()
+      done.unlock()
+    }
+    done.lock()
+    done.wait()
+    done.unlock()
+    if let responseMessage = responseMessage {
+      return responseMessage
+    } else {
+      throw {{ .|errorname:protoFile,service }}.error(c: callResult)
+    }
+  }
+}

+ 126 - 0
Plugin/templates/client.pb.swift

@@ -0,0 +1,126 @@
+/*
+ * DO NOT EDIT.
+ *
+ * Generated by the protocol buffer compiler.
+ * Source: {{ protoFile.name }}
+ *
+ */
+
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+import Foundation
+import gRPC
+
+//{% for service in protoFile.service %}
+public enum {{ .|errorname:protoFile,service }} : Error {
+  case endOfStream
+  case invalidMessageReceived
+  case error(c: CallResult)
+}
+//{% for method in service.method %}
+//{% if not method.clientStreaming and not method.serverStreaming %}
+//{% include "client-call-unary.swift" %}
+//{% endif %}
+//{% if not method.clientStreaming and method.serverStreaming %}
+//{% include "client-call-serverstreaming.swift" %}
+//{% endif %}
+//{% if method.clientStreaming and not method.serverStreaming %}
+//{% include "client-call-clientstreaming.swift" %}
+//{% endif %}
+//{% if method.clientStreaming and method.serverStreaming %}
+//{% include "client-call-bidistreaming.swift" %}
+//{% endif %}
+//{% endfor %}
+
+// Call methods of this class to make API calls.
+public class {{ protoFile.package|capitalize }}_{{ service.name }}Service {
+  private var channel: Channel
+
+  public var metadata : Metadata
+
+  public var host : String {
+    get {
+      return self.channel.host
+    }
+    set {
+      self.channel.host = newValue
+    }
+  }
+
+  public init(address: String) {
+    gRPC.initialize()
+    channel = Channel(address:address)
+    metadata = Metadata()
+  }
+
+  public init(address: String, certificates: String?, host: String?) {
+    gRPC.initialize()
+    channel = Channel(address:address, certificates:certificates, host:host)
+    metadata = Metadata()
+  }
+
+  //{% for method in service.method %}
+  //{% if not method.clientStreaming and not method.serverStreaming %}
+  // Synchronous. Unary.
+  public func {{ method.name|lowercase }}(_ request: {{ method.inputType|message }}) throws -> {{ method.outputType|message }} {
+    return try {{ .|callname:protoFile,service,method }}(channel).run(request:request, metadata:metadata)
+  }
+  //{% endif %}
+  //{% if not method.clientStreaming and method.serverStreaming %}
+  // Asynchronous. Server-streaming.
+  // Send the initial message.
+  // Use methods on the returned object to get streamed responses.
+  public func {{ method.name|lowercase }}(_ request: {{ method.inputType|message }}) throws -> {{ .|callname:protoFile,service,method }} {
+    return try {{ .|callname:protoFile,service,method }}(channel).run(request:request, metadata:metadata)
+  }
+  //{% endif %}
+  //{% if method.clientStreaming and not method.serverStreaming %}
+  // Asynchronous. Client-streaming.
+  // Use methods on the returned object to stream messages and
+  // to close the connection and wait for a final response.
+  public func {{ method.name|lowercase }}() throws -> {{ .|callname:protoFile,service,method }} {
+    return try {{ .|callname:protoFile,service,method }}(channel).run(metadata:metadata)
+  }
+  //{% endif %}
+  //{% if method.clientStreaming and method.serverStreaming %}
+  // Asynchronous. Bidirectional-streaming.
+  // Use methods on the returned object to stream messages,
+  // to wait for replies, and to close the connection.
+  public func {{ method.name|lowercase }}() throws -> {{ .|callname:protoFile,service,method }} {
+    return try {{ .|callname:protoFile,service,method }}(channel).run(metadata:metadata)
+  }
+  //{% endif %}
+  //{% endfor %}
+}
+//{% endfor %}

+ 0 - 0
Plugin/templates/PACKAGE.server.pb.swift → Plugin/templates/server.pb.swift