/* * * Copyright 2015 gRPC authors. * * 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. * */ #include <cctype> #include <map> #include <vector> #include "src/compiler/config.h" #include "src/compiler/ruby_generator.h" #include "src/compiler/ruby_generator_helpers-inl.h" #include "src/compiler/ruby_generator_map-inl.h" #include "src/compiler/ruby_generator_string-inl.h" using grpc::protobuf::FileDescriptor; using grpc::protobuf::MethodDescriptor; using grpc::protobuf::ServiceDescriptor; using grpc::protobuf::io::Printer; using grpc::protobuf::io::StringOutputStream; using std::map; using std::vector; namespace grpc_ruby_generator { namespace { // Prints out the method using the ruby gRPC DSL. void PrintMethod(const MethodDescriptor* method, const grpc::string& package, Printer* out) { grpc::string input_type = RubyTypeOf(method->input_type()->full_name(), package); if (method->client_streaming()) { input_type = "stream(" + input_type + ")"; } grpc::string output_type = RubyTypeOf(method->output_type()->full_name(), package); if (method->server_streaming()) { output_type = "stream(" + output_type + ")"; } std::map<grpc::string, grpc::string> method_vars = ListToDict({ "mth.name", method->name(), "input.type", input_type, "output.type", output_type, }); out->Print(GetRubyComments(method, true).c_str()); out->Print(method_vars, "rpc :$mth.name$, $input.type$, $output.type$\n"); out->Print(GetRubyComments(method, false).c_str()); } // Prints out the service using the ruby gRPC DSL. void PrintService(const ServiceDescriptor* service, const grpc::string& package, Printer* out) { if (service->method_count() == 0) { return; } // Begin the service module std::map<grpc::string, grpc::string> module_vars = ListToDict({ "module.name", Modularize(service->name()), }); out->Print(module_vars, "module $module.name$\n"); out->Indent(); out->Print(GetRubyComments(service, true).c_str()); out->Print("class Service\n"); // Write the indented class body. out->Indent(); out->Print("\n"); out->Print("include GRPC::GenericService\n"); out->Print("\n"); out->Print("self.marshal_class_method = :encode\n"); out->Print("self.unmarshal_class_method = :decode\n"); std::map<grpc::string, grpc::string> pkg_vars = ListToDict({"service_full_name", service->full_name()}); out->Print(pkg_vars, "self.service_name = '$service_full_name$'\n"); out->Print("\n"); for (int i = 0; i < service->method_count(); ++i) { PrintMethod(service->method(i), package, out); } out->Outdent(); out->Print("end\n"); out->Print("\n"); out->Print("Stub = Service.rpc_stub_class\n"); // End the service module out->Outdent(); out->Print("end\n"); out->Print(GetRubyComments(service, false).c_str()); } } // namespace // The following functions are copied directly from the source for the protoc // ruby generator // to ensure compatibility (with the exception of int and string type changes). // See // https://github.com/google/protobuf/blob/master/src/google/protobuf/compiler/ruby/ruby_generator.cc#L250 // TODO: keep up to date with protoc code generation, though this behavior isn't // expected to change bool IsLower(char ch) { return ch >= 'a' && ch <= 'z'; } char ToUpper(char ch) { return IsLower(ch) ? (ch - 'a' + 'A') : ch; } // Package names in protobuf are snake_case by convention, but Ruby module // names must be PascalCased. // // foo_bar_baz -> FooBarBaz grpc::string PackageToModule(const grpc::string& name) { bool next_upper = true; grpc::string result; result.reserve(name.size()); for (grpc::string::size_type i = 0; i < name.size(); i++) { if (name[i] == '_') { next_upper = true; } else { if (next_upper) { result.push_back(ToUpper(name[i])); } else { result.push_back(name[i]); } next_upper = false; } } return result; } // end copying of protoc generator for ruby code grpc::string GetServices(const FileDescriptor* file) { grpc::string output; { // Scope the output stream so it closes and finalizes output to the string. StringOutputStream output_stream(&output); Printer out(&output_stream, '$'); // Don't write out any output if there no services, to avoid empty service // files being generated for proto files that don't declare any. if (file->service_count() == 0) { return output; } std::string package_name; if (file->options().has_ruby_package()) { package_name = file->options().ruby_package(); } else { package_name = file->package(); } // Write out a file header. std::map<grpc::string, grpc::string> header_comment_vars = ListToDict({ "file.name", file->name(), "file.package", package_name, }); out.Print("# Generated by the protocol buffer compiler. DO NOT EDIT!\n"); out.Print(header_comment_vars, "# Source: $file.name$ for package '$file.package$'\n"); grpc::string leading_comments = GetRubyComments(file, true); if (!leading_comments.empty()) { out.Print("# Original file comments:\n"); out.PrintRaw(leading_comments.c_str()); } out.Print("\n"); out.Print("require 'grpc'\n"); // Write out require statemment to import the separately generated file // that defines the messages used by the service. This is generated by the // main ruby plugin. std::map<grpc::string, grpc::string> dep_vars = ListToDict({ "dep.name", MessagesRequireName(file), }); out.Print(dep_vars, "require '$dep.name$'\n"); // Write out services within the modules out.Print("\n"); std::vector<grpc::string> modules = Split(package_name, '.'); for (size_t i = 0; i < modules.size(); ++i) { std::map<grpc::string, grpc::string> module_vars = ListToDict({ "module.name", PackageToModule(modules[i]), }); out.Print(module_vars, "module $module.name$\n"); out.Indent(); } for (int i = 0; i < file->service_count(); ++i) { auto service = file->service(i); PrintService(service, file->package(), &out); } for (size_t i = 0; i < modules.size(); ++i) { out.Outdent(); out.Print("end\n"); } out.Print(GetRubyComments(file, false).c_str()); } return output; } } // namespace grpc_ruby_generator