diff --git a/example/hello_world/service_twirp.rb b/example/hello_world/service_twirp.rb index c07f07d..71a7225 100644 --- a/example/hello_world/service_twirp.rb +++ b/example/hello_world/service_twirp.rb @@ -1,14 +1,16 @@ # Code generated by protoc-gen-twirp_ruby, DO NOT EDIT. require 'twirp' -module Example.helloWorld - class HelloWorldService < Twirp::Service - package "example.hello_world" - service "HelloWorld" - rpc :Hello, HelloRequest, HelloResponse, :ruby_method => :hello - end +module Example + module HelloWorld + class HelloWorldService < Twirp::Service + package "example.hello_world" + service "HelloWorld" + rpc :Hello, HelloRequest, HelloResponse, :ruby_method => :hello + end - class HelloWorldClient < Twirp::Client - client_for HelloWorldService + class HelloWorldClient < Twirp::Client + client_for HelloWorldService + end end end diff --git a/protoc-gen-twirp_ruby/main.go b/protoc-gen-twirp_ruby/main.go index c38169a..6368a2d 100644 --- a/protoc-gen-twirp_ruby/main.go +++ b/protoc-gen-twirp_ruby/main.go @@ -80,16 +80,28 @@ func (g *generator) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGenera return resp } +// indentation represents the level of Ruby indentation for a block of code. It +// implements the fmt.Stringer interface to output the correct number of spaces +// for the given level of indentation +type indentation int + +func (i indentation) String() string { + return strings.Repeat(" ", int(i)) +} + func (g *generator) generateFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File { - indent := "" + indent := indentation(0) pkgName := pkgName(file) g.P(`# Code generated by protoc-gen-twirp_ruby, DO NOT EDIT.`) g.P(`require 'twirp'`) g.P(``) - if pkgName != "" { - g.P(fmt.Sprintf("module %s", CamelCase(pkgName))) - indent = indent + " " + + modules := PackageToModules(pkgName) + for _, m := range modules { + g.P(fmt.Sprintf("%smodule %s", indent, m)) + indent += 1 } + for i, service := range file.Service { serviceName := serviceName(service) g.P(fmt.Sprintf("%sclass %sService < Twirp::Service", indent, CamelCase(serviceName))) @@ -113,8 +125,9 @@ func (g *generator) generateFile(file *descriptor.FileDescriptorProto) *plugin.C g.P(``) } } - if pkgName != "" { - g.P(`end`) + for range modules { + indent -= 1 + g.P(fmt.Sprintf("%send", indent)) } resp := new(plugin.CodeGeneratorResponse_File) @@ -217,6 +230,24 @@ func writeResponse(w io.Writer, resp *plugin.CodeGeneratorResponse) { } } +// Modules converts protobuf package name to a list of Ruby module names to +// represent it. +// +// Example +// +// PackageToModules("twitch.twirp.example.helloworld") => ["Twitch", "Twirp", "Example", "Helloworld] +func PackageToModules(pkgName string) []string { + if pkgName == "" { + return nil + } + + var parts []string + for _, p := range strings.Split(pkgName, ".") { + parts = append(parts, CamelCase(p)) + } + return parts +} + // CamelCase converts a string from snake_case to CamelCased. // // If there is an interior underscore followed by a lower case letter, drop the diff --git a/protoc-gen-twirp_ruby/main_test.go b/protoc-gen-twirp_ruby/main_test.go new file mode 100644 index 0000000..ff88138 --- /dev/null +++ b/protoc-gen-twirp_ruby/main_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestPackageToModules(t *testing.T) { + tests := []struct { + pkgName string + modules []string + }{ + {"", nil}, + {"example", []string{"Example"}}, + {"twitch.twirp.example.helloworld", []string{"Twitch", "Twirp", "Example", "Helloworld"}}, + } + + for _, tt := range tests { + t.Run(tt.pkgName, func(t *testing.T) { + modules := PackageToModules(tt.pkgName) + if got, want := modules, tt.modules; !reflect.DeepEqual(got, want) { + t.Errorf("expected %v; got %v", want, got) + } + }) + } +}