twirp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +141 -62
- data/lib/twirp.rb +1 -0
- data/lib/twirp/client.rb +147 -0
- data/lib/twirp/error.rb +12 -8
- data/lib/twirp/service.rb +42 -76
- data/lib/twirp/service_dsl.rb +61 -0
- data/lib/twirp/version.rb +1 -1
- data/test/client_test.rb +203 -0
- data/test/fake_services.rb +21 -1
- data/test/service_test.rb +27 -28
- data/twirp.gemspec +6 -6
- metadata +30 -15
- data/.gitignore +0 -1
- data/Gemfile.lock +0 -20
- data/example/Gemfile +0 -7
- data/example/Gemfile.lock +0 -17
- data/example/gen/haberdasher_pb.rb +0 -18
- data/example/gen/haberdasher_twirp.rb +0 -10
- data/example/haberdasher.proto +0 -14
- data/example/main.rb +0 -14
- data/protoc-gen-twirp_ruby/main.go +0 -259
data/example/haberdasher.proto
DELETED
data/example/main.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'rack'
|
2
|
-
require_relative 'gen/haberdasher_pb.rb'
|
3
|
-
require_relative 'gen/haberdasher_twirp.rb'
|
4
|
-
|
5
|
-
class HaberdasherHandler
|
6
|
-
def hello_world(req, env)
|
7
|
-
{message: "Hello #{req.name}"}
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
handler = HaberdasherHandler.new()
|
12
|
-
service = Example::HaberdasherService.new(handler)
|
13
|
-
|
14
|
-
Rack::Handler::WEBrick.run service
|
@@ -1,259 +0,0 @@
|
|
1
|
-
package main
|
2
|
-
|
3
|
-
import (
|
4
|
-
"bytes"
|
5
|
-
"flag"
|
6
|
-
"fmt"
|
7
|
-
"io"
|
8
|
-
"io/ioutil"
|
9
|
-
"log"
|
10
|
-
"os"
|
11
|
-
"path"
|
12
|
-
"strings"
|
13
|
-
"unicode"
|
14
|
-
|
15
|
-
"github.com/golang/protobuf/proto"
|
16
|
-
"github.com/golang/protobuf/protoc-gen-go/descriptor"
|
17
|
-
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
|
18
|
-
)
|
19
|
-
|
20
|
-
const Version = "v5.2.0"
|
21
|
-
|
22
|
-
func main() {
|
23
|
-
versionFlag := flag.Bool("version", false, "print version and exit")
|
24
|
-
flag.Parse()
|
25
|
-
if *versionFlag {
|
26
|
-
fmt.Println(Version)
|
27
|
-
os.Exit(0)
|
28
|
-
}
|
29
|
-
|
30
|
-
g := newGenerator()
|
31
|
-
Main(g)
|
32
|
-
}
|
33
|
-
|
34
|
-
type Generator interface {
|
35
|
-
Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse
|
36
|
-
}
|
37
|
-
|
38
|
-
func Main(g Generator) {
|
39
|
-
req := readGenRequest(os.Stdin)
|
40
|
-
resp := g.Generate(req)
|
41
|
-
writeResponse(os.Stdout, resp)
|
42
|
-
}
|
43
|
-
|
44
|
-
type generator struct {
|
45
|
-
output *bytes.Buffer
|
46
|
-
}
|
47
|
-
|
48
|
-
func newGenerator() *generator {
|
49
|
-
return &generator{output: new(bytes.Buffer)}
|
50
|
-
}
|
51
|
-
|
52
|
-
func (g *generator) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse {
|
53
|
-
|
54
|
-
resp := new(plugin.CodeGeneratorResponse)
|
55
|
-
for _, name := range in.FileToGenerate {
|
56
|
-
for _, f := range in.ProtoFile {
|
57
|
-
if f.GetName() == name {
|
58
|
-
respFile := g.generateFile(f)
|
59
|
-
if respFile != nil {
|
60
|
-
resp.File = append(resp.File, respFile)
|
61
|
-
}
|
62
|
-
continue
|
63
|
-
}
|
64
|
-
}
|
65
|
-
}
|
66
|
-
|
67
|
-
return resp
|
68
|
-
}
|
69
|
-
|
70
|
-
func (g *generator) generateFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File {
|
71
|
-
indent := ""
|
72
|
-
pkgName := pkgName(file)
|
73
|
-
g.P(`# Code generated by protoc-gen-twirp_ruby, DO NOT EDIT.`)
|
74
|
-
g.P(`require 'twirp'`)
|
75
|
-
g.P(``)
|
76
|
-
if pkgName != "" {
|
77
|
-
g.P(fmt.Sprintf("module %s", CamelCase(pkgName)))
|
78
|
-
indent = indent + " "
|
79
|
-
}
|
80
|
-
for i, service := range file.Service {
|
81
|
-
serviceName := serviceName(service)
|
82
|
-
g.P(fmt.Sprintf("%sclass %sService < Twirp::Service", indent, CamelCase(serviceName)))
|
83
|
-
if pkgName != "" {
|
84
|
-
g.P(fmt.Sprintf(`%s package "%s"`, indent, pkgName))
|
85
|
-
}
|
86
|
-
g.P(fmt.Sprintf(`%s service "%s"`, indent, serviceName))
|
87
|
-
for _, method := range service.GetMethod() {
|
88
|
-
methName := methodName(method)
|
89
|
-
inputName := methodInputName(method)
|
90
|
-
outputName := methodOutputName(method)
|
91
|
-
g.P(fmt.Sprintf(`%s rpc :%s, %s, %s, :handler_method => :%s`,
|
92
|
-
indent, methName, inputName, outputName, SnakeCase(methName)))
|
93
|
-
}
|
94
|
-
g.P(fmt.Sprintf(`%send`, indent))
|
95
|
-
if i < len(file.Service)-1 {
|
96
|
-
g.P(``)
|
97
|
-
}
|
98
|
-
}
|
99
|
-
if pkgName != "" {
|
100
|
-
g.P(`end`)
|
101
|
-
}
|
102
|
-
|
103
|
-
resp := new(plugin.CodeGeneratorResponse_File)
|
104
|
-
resp.Name = proto.String(rubyFileName(file))
|
105
|
-
resp.Content = proto.String(g.output.String())
|
106
|
-
g.output.Reset()
|
107
|
-
|
108
|
-
return resp
|
109
|
-
}
|
110
|
-
|
111
|
-
func (g *generator) P(args ...string) {
|
112
|
-
for _, v := range args {
|
113
|
-
g.output.WriteString(v)
|
114
|
-
}
|
115
|
-
g.output.WriteByte('\n')
|
116
|
-
}
|
117
|
-
|
118
|
-
func rubyFileName(f *descriptor.FileDescriptorProto) string {
|
119
|
-
name := *f.Name
|
120
|
-
if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" {
|
121
|
-
name = name[:len(name)-len(ext)]
|
122
|
-
}
|
123
|
-
name += "_twirp.rb"
|
124
|
-
return name
|
125
|
-
}
|
126
|
-
|
127
|
-
func pkgName(file *descriptor.FileDescriptorProto) string {
|
128
|
-
return file.GetPackage()
|
129
|
-
}
|
130
|
-
|
131
|
-
func serviceName(service *descriptor.ServiceDescriptorProto) string {
|
132
|
-
return service.GetName()
|
133
|
-
}
|
134
|
-
|
135
|
-
func methodName(method *descriptor.MethodDescriptorProto) string {
|
136
|
-
return method.GetName()
|
137
|
-
}
|
138
|
-
|
139
|
-
// methodInputName returns the basename of the input type of a method in snake
|
140
|
-
// case.
|
141
|
-
func methodInputName(meth *descriptor.MethodDescriptorProto) string {
|
142
|
-
fullName := meth.GetInputType()
|
143
|
-
split := strings.Split(fullName, ".")
|
144
|
-
return split[len(split)-1]
|
145
|
-
}
|
146
|
-
|
147
|
-
// methodInputName returns the basename of the input type of a method in snake
|
148
|
-
// case.
|
149
|
-
func methodOutputName(meth *descriptor.MethodDescriptorProto) string {
|
150
|
-
fullName := meth.GetOutputType()
|
151
|
-
split := strings.Split(fullName, ".")
|
152
|
-
return split[len(split)-1]
|
153
|
-
}
|
154
|
-
|
155
|
-
func Fail(msgs ...string) {
|
156
|
-
s := strings.Join(msgs, " ")
|
157
|
-
log.Print("error:", s)
|
158
|
-
os.Exit(1)
|
159
|
-
}
|
160
|
-
|
161
|
-
// SnakeCase converts a string from CamelCase to snake_case.
|
162
|
-
func SnakeCase(s string) string {
|
163
|
-
var buf bytes.Buffer
|
164
|
-
for i, r := range s {
|
165
|
-
if unicode.IsUpper(r) && i > 0 {
|
166
|
-
fmt.Fprintf(&buf, "_")
|
167
|
-
}
|
168
|
-
r = unicode.ToLower(r)
|
169
|
-
fmt.Fprintf(&buf, "%c", r)
|
170
|
-
}
|
171
|
-
return buf.String()
|
172
|
-
}
|
173
|
-
|
174
|
-
func readGenRequest(r io.Reader) *plugin.CodeGeneratorRequest {
|
175
|
-
data, err := ioutil.ReadAll(os.Stdin)
|
176
|
-
if err != nil {
|
177
|
-
Fail(err.Error(), "reading input")
|
178
|
-
}
|
179
|
-
|
180
|
-
req := new(plugin.CodeGeneratorRequest)
|
181
|
-
if err = proto.Unmarshal(data, req); err != nil {
|
182
|
-
Fail(err.Error(), "parsing input proto")
|
183
|
-
}
|
184
|
-
|
185
|
-
if len(req.FileToGenerate) == 0 {
|
186
|
-
Fail("no files to generate")
|
187
|
-
}
|
188
|
-
|
189
|
-
return req
|
190
|
-
}
|
191
|
-
|
192
|
-
func writeResponse(w io.Writer, resp *plugin.CodeGeneratorResponse) {
|
193
|
-
data, err := proto.Marshal(resp)
|
194
|
-
if err != nil {
|
195
|
-
Fail(err.Error(), "marshaling response")
|
196
|
-
}
|
197
|
-
_, err = w.Write(data)
|
198
|
-
if err != nil {
|
199
|
-
Fail(err.Error(), "writing response")
|
200
|
-
}
|
201
|
-
}
|
202
|
-
|
203
|
-
// CamelCase converts a string from snake_case to CamelCased.
|
204
|
-
//
|
205
|
-
// If there is an interior underscore followed by a lower case letter, drop the
|
206
|
-
// underscore and convert the letter to upper case. There is a remote
|
207
|
-
// possibility of this rewrite causing a name collision, but it's so remote
|
208
|
-
// we're prepared to pretend it's nonexistent - since the C++ generator
|
209
|
-
// lowercases names, it's extremely unlikely to have two fields with different
|
210
|
-
// capitalizations. In short, _my_field_name_2 becomes XMyFieldName_2.
|
211
|
-
func CamelCase(s string) string {
|
212
|
-
if s == "" {
|
213
|
-
return ""
|
214
|
-
}
|
215
|
-
t := make([]byte, 0, 32)
|
216
|
-
i := 0
|
217
|
-
if s[0] == '_' {
|
218
|
-
// Need a capital letter; drop the '_'.
|
219
|
-
t = append(t, 'X')
|
220
|
-
i++
|
221
|
-
}
|
222
|
-
// Invariant: if the next letter is lower case, it must be converted
|
223
|
-
// to upper case.
|
224
|
-
//
|
225
|
-
// That is, we process a word at a time, where words are marked by _ or upper
|
226
|
-
// case letter. Digits are treated as words.
|
227
|
-
for ; i < len(s); i++ {
|
228
|
-
c := s[i]
|
229
|
-
if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) {
|
230
|
-
continue // Skip the underscore in s.
|
231
|
-
}
|
232
|
-
if isASCIIDigit(c) {
|
233
|
-
t = append(t, c)
|
234
|
-
continue
|
235
|
-
}
|
236
|
-
// Assume we have a letter now - if not, it's a bogus identifier. The next
|
237
|
-
// word is a sequence of characters that must start upper case.
|
238
|
-
if isASCIILower(c) {
|
239
|
-
c ^= ' ' // Make it a capital letter.
|
240
|
-
}
|
241
|
-
t = append(t, c) // Guaranteed not lower case.
|
242
|
-
// Accept lower case sequence that follows.
|
243
|
-
for i+1 < len(s) && isASCIILower(s[i+1]) {
|
244
|
-
i++
|
245
|
-
t = append(t, s[i])
|
246
|
-
}
|
247
|
-
}
|
248
|
-
return string(t)
|
249
|
-
}
|
250
|
-
|
251
|
-
// Is c an ASCII lower-case letter?
|
252
|
-
func isASCIILower(c byte) bool {
|
253
|
-
return 'a' <= c && c <= 'z'
|
254
|
-
}
|
255
|
-
|
256
|
-
// Is c an ASCII digit?
|
257
|
-
func isASCIIDigit(c byte) bool {
|
258
|
-
return '0' <= c && c <= '9'
|
259
|
-
}
|