| /* | |
| * | |
| * 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 |