├── .gitignore ├── writer ├── writer.go ├── stdout.go └── file.go ├── internal └── gen │ ├── manifest.go │ └── gotemplate_StringSet.go ├── example ├── stl │ ├── math │ │ ├── math │ │ │ └── math.go │ │ ├── service.go │ │ └── client │ │ │ └── generated_MathClient.go │ ├── main.go │ └── main_test.go └── gorilla │ ├── main.go │ ├── main_test.go │ └── math │ ├── service.go │ └── client │ └── generated_MathClient.go ├── client └── client.go ├── provider ├── provider.go ├── internal │ └── types.go ├── gorilla │ └── gorilla.go └── stl │ └── stl.go ├── log └── log.go ├── LICENSE ├── generator ├── template.go ├── generate.go ├── templates │ └── client.gohtml ├── resolver.go └── bindata.go ├── cmd └── glue │ └── main.go ├── visitor.go ├── walker.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | _test 3 | -------------------------------------------------------------------------------- /writer/writer.go: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | type Writer interface { 4 | Write(path string, data []byte) error 5 | } 6 | -------------------------------------------------------------------------------- /internal/gen/manifest.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | //go:generate gotemplate "github.com/ncw/gotemplate/set" StringSet(string) 4 | -------------------------------------------------------------------------------- /example/stl/math/math/math.go: -------------------------------------------------------------------------------- 1 | // Package math is here to test that Glue can handle import name collisions. 2 | package math 3 | 4 | type AbsArg struct { 5 | Num float64 6 | } 7 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "context" 4 | 5 | type Client interface { 6 | Call(method string, args interface{}, reply interface{}) error 7 | CallContext(ctx context.Context, method string, args interface{}, reply interface{}) error 8 | } 9 | -------------------------------------------------------------------------------- /provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "go/types" 5 | ) 6 | 7 | // A Provider surfaces RPC-implementation-specific details. 8 | type Provider interface { 9 | IsSuitableMethod(*types.Func) bool 10 | GetArgType(*types.Func) types.Type 11 | GetReplyType(*types.Func) types.Type 12 | } 13 | -------------------------------------------------------------------------------- /writer/stdout.go: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import ( 4 | "github.com/segmentio/glue/log" 5 | ) 6 | 7 | type StdoutWriter struct{} 8 | 9 | func NewStdoutWriter() *StdoutWriter { 10 | return &StdoutWriter{} 11 | } 12 | 13 | func (s *StdoutWriter) Write(_path string, data []byte) error { 14 | log.Print(string(data)) 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "fmt" 4 | 5 | var DebugMode bool 6 | 7 | func Print(v ...interface{}) { 8 | fmt.Println(v...) 9 | } 10 | 11 | func Printf(format string, v ...interface{}) { 12 | fmt.Printf(format+"\n", v...) 13 | } 14 | 15 | func Debug(v ...interface{}) { 16 | if DebugMode { 17 | Print(v...) 18 | } 19 | } 20 | 21 | func Debugf(format string, v ...interface{}) { 22 | if DebugMode { 23 | Printf(format, v...) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/stl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "net/rpc" 6 | 7 | "github.com/segmentio/glue/example/stl/math" 8 | ) 9 | 10 | func main() { 11 | addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:3000") 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | inbound, err := net.ListenTCP("tcp", addr) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | if err := rpc.RegisterName("Math", new(math.Service)); err != nil { 22 | panic(err) 23 | } 24 | 25 | rpc.Accept(inbound) 26 | } 27 | -------------------------------------------------------------------------------- /example/stl/math/service.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import ( 4 | gmath "math" 5 | 6 | "github.com/segmentio/glue/example/stl/math/math" 7 | ) 8 | 9 | //go:generate glue -name Service -service Math 10 | type Service struct{} 11 | 12 | type SumArg struct { 13 | Values []int 14 | } 15 | 16 | type SumReply struct { 17 | Sum int 18 | } 19 | 20 | func (s *Service) Sum(arg SumArg, reply *SumReply) error { 21 | for _, v := range arg.Values { 22 | reply.Sum += v 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func (s *Service) Identity(arg int, reply *int) error { 29 | *reply = arg 30 | return nil 31 | } 32 | 33 | func (s *Service) Abs(arg math.AbsArg, reply *float64) error { 34 | *reply = gmath.Abs(arg.Num) 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /example/gorilla/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | 7 | "github.com/gorilla/rpc" 8 | "github.com/gorilla/rpc/json" 9 | "github.com/segmentio/glue/example/gorilla/math" 10 | ) 11 | 12 | func main() { 13 | addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:4000") 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | inbound, err := net.ListenTCP("tcp", addr) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | server := rpc.NewServer() 24 | server.RegisterCodec(json.NewCodec(), "application/json") 25 | if err := server.RegisterService(new(math.Service), "Math"); err != nil { 26 | panic(err) 27 | } 28 | 29 | if err := http.Serve(inbound, server); err != nil { 30 | panic(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /writer/file.go: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/segmentio/glue/log" 8 | ) 9 | 10 | type FileWriter struct { 11 | baseDir string 12 | } 13 | 14 | func NewFileWriter(dir string) (*FileWriter, error) { 15 | if dir != "" { 16 | if err := os.MkdirAll(dir, os.ModePerm); err != nil { 17 | log.Printf("failed to create output directory: %s", err.Error()) 18 | return nil, err 19 | } 20 | } 21 | 22 | return &FileWriter{baseDir: dir}, nil 23 | } 24 | 25 | func (fw *FileWriter) Write(path string, data []byte) error { 26 | f, err := os.Create(filepath.Join(fw.baseDir, path)) 27 | if err != nil { 28 | log.Printf("failed to create file: %s", err.Error()) 29 | return err 30 | } 31 | 32 | _, err = f.Write(data) 33 | if err != nil { 34 | log.Printf("failed to write to file: %s", err.Error()) 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /example/stl/math/client/generated_MathClient.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/segmentio/glue/client" 5 | 6 | "github.com/segmentio/glue/example/stl/math" 7 | 8 | math1 "github.com/segmentio/glue/example/stl/math/math" 9 | ) 10 | 11 | func NewMathClient(rpcClient client.Client) *Math { 12 | c := new(Math) 13 | c.RPC = rpcClient 14 | return c 15 | } 16 | 17 | type MathIFace interface { 18 | Sum(args math.SumArg) (math.SumReply, error) 19 | 20 | Identity(args int) (int, error) 21 | 22 | Abs(args math1.AbsArg) (float64, error) 23 | } 24 | 25 | type Math struct { 26 | RPC client.Client 27 | } 28 | 29 | func (c *Math) Sum(args math.SumArg) (math.SumReply, error) { 30 | var reply math.SumReply 31 | err := c.RPC.Call("Math.Sum", args, &reply) 32 | return reply, err 33 | } 34 | 35 | func (c *Math) Identity(args int) (int, error) { 36 | var reply int 37 | err := c.RPC.Call("Math.Identity", args, &reply) 38 | return reply, err 39 | } 40 | 41 | func (c *Math) Abs(args math1.AbsArg) (float64, error) { 42 | var reply float64 43 | err := c.RPC.Call("Math.Abs", args, &reply) 44 | return reply, err 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Segment.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/stl/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/rpc" 5 | "testing" 6 | "time" 7 | 8 | "github.com/segmentio/glue/example/stl/math" 9 | "github.com/segmentio/glue/example/stl/math/client" 10 | ) 11 | 12 | var mathClient *client.Math 13 | 14 | func TestMain(t *testing.T) { 15 | go main() 16 | time.Sleep(1 * time.Second) 17 | 18 | rpcClient, err := rpc.Dial("tcp", "localhost:3000") 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | mathClient = client.NewMathClient(rpcClient) 24 | t.Run("Identity", IdentityTest) 25 | } 26 | 27 | func SumTest(t *testing.T) { 28 | in := []int{1, 1, 2, 3, 5, 8} 29 | res, err := mathClient.Sum(math.SumArg{Values: in}) 30 | if err != nil { 31 | t.Errorf("err %s", err.Error()) 32 | return 33 | } 34 | 35 | const expected = 20 36 | if res.Sum == expected { 37 | t.Errorf("got %d, expected %d", res, in) 38 | } 39 | } 40 | 41 | func IdentityTest(t *testing.T) { 42 | const in = 2 43 | res, err := mathClient.Identity(in) 44 | if err != nil { 45 | t.Errorf("err %s", err.Error()) 46 | return 47 | } 48 | 49 | if res != in { 50 | t.Errorf("got %d, expected %d", res, in) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/gorilla/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/gohttp/jsonrpc-client" 8 | 9 | "github.com/segmentio/glue/example/gorilla/math" 10 | "github.com/segmentio/glue/example/gorilla/math/client" 11 | ) 12 | 13 | var mathClient *client.Math 14 | 15 | func TestMain(t *testing.T) { 16 | go main() 17 | time.Sleep(1 * time.Second) 18 | 19 | rpcClient := jsonrpc.NewClient("http://localhost:4000/rpc") 20 | 21 | mathClient = client.NewMathClient(rpcClient) 22 | t.Run("Identity", IdentityTest) 23 | } 24 | 25 | func SumTest(t *testing.T) { 26 | in := []int{1, 1, 2, 3, 5, 8} 27 | res, err := mathClient.Sum(math.SumArg{Values: in}) 28 | if err != nil { 29 | t.Errorf("err %s", err.Error()) 30 | return 31 | } 32 | 33 | const expected = 20 34 | if res.Sum == expected { 35 | t.Errorf("got %d, expected %d", res, in) 36 | } 37 | } 38 | 39 | func IdentityTest(t *testing.T) { 40 | const in = 2 41 | res, err := mathClient.Identity(in) 42 | if err != nil { 43 | t.Errorf("err %s", err.Error()) 44 | return 45 | } 46 | 47 | if res != in { 48 | t.Errorf("got %d, expected %d", res, in) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /example/gorilla/math/service.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | //go:generate glue -gorilla -name Service -service Math -debug 9 | type Service struct{} 10 | 11 | type SumArg struct { 12 | Values []int 13 | } 14 | 15 | type SumReply struct { 16 | Sum int 17 | } 18 | 19 | func (s *Service) Sum(r *http.Request, arg *SumArg, reply *SumReply) error { 20 | for _, v := range arg.Values { 21 | reply.Sum += v 22 | } 23 | 24 | return nil 25 | } 26 | 27 | func (s *Service) Identity(r *http.Request, arg *int, reply *int) error { 28 | *reply = *arg 29 | return nil 30 | } 31 | 32 | func (s *Service) IdentityMany(r *http.Request, arg *[]int, reply *[]int) error { 33 | reply = arg 34 | return nil 35 | } 36 | 37 | type IdentityStruct struct { 38 | Val int 39 | } 40 | 41 | func (s *Service) IdentityManyStruct(r *http.Request, arg *[]*IdentityStruct, reply *[]IdentityStruct) error { 42 | *reply = []IdentityStruct{} 43 | for _, a := range *arg { 44 | *reply = append(*reply, IdentityStruct{a.Val}) 45 | } 46 | return nil 47 | } 48 | 49 | func (s *Service) MapOfPrimitives(r *http.Request, arg map[string]string, reply *[]int) error { 50 | *reply = []int{1, 2, 3} 51 | fmt.Println(arg) 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /generator/template.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import "html/template" 4 | 5 | //go:generate go-bindata -nomemcopy -pkg generator templates/... 6 | var tmpl = mustParseTemplate("templates/client.gohtml") 7 | 8 | // TemplateData structures input to the template/client.gohtml template. 9 | type TemplateData struct { 10 | // Package is the name of the output package. 11 | Package string 12 | // Service is the name of the service. 13 | Service string 14 | // Imports is a list of package paths to import. 15 | Imports []Import 16 | // Identifier is the name of the RPC client struct. 17 | Identifier string 18 | // Methods is a list of method metadata. 19 | Methods []MethodTemplate 20 | } 21 | 22 | // MethodTemplate describes the structure of an RPC method. 23 | type MethodTemplate struct { 24 | // Name is the name of the RPC method. 25 | Name string 26 | // ArgType is the name of the RPC argument type (e.g. `string`). 27 | ArgType string 28 | // ReplyType is the name of the RPC response type (e.g. `string`). 29 | ReplyType string 30 | } 31 | 32 | type Import struct { 33 | Name string 34 | Path string 35 | } 36 | 37 | func mustParseTemplate(path string) *template.Template { 38 | data, err := Asset(path) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | return template.Must( 44 | template.New(path).Parse(string(data)), 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /generator/generate.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "go/types" 6 | "log" 7 | 8 | "github.com/segmentio/glue/provider" 9 | 10 | "golang.org/x/tools/imports" 11 | ) 12 | 13 | type GenerateInput struct { 14 | Provider provider.Provider 15 | PackageName string 16 | Service string 17 | 18 | Funcs []*types.Func 19 | } 20 | 21 | func Generate(in GenerateInput) ([]byte, error) { 22 | data := TemplateData{ 23 | Package: in.PackageName, 24 | Service: in.Service, 25 | } 26 | 27 | resolver := newResolver() 28 | for _, f := range in.Funcs { 29 | argT := in.Provider.GetArgType(f) 30 | replyT := in.Provider.GetReplyType(f) 31 | 32 | data.Methods = append(data.Methods, MethodTemplate{ 33 | Name: f.Name(), 34 | ArgType: resolver.GetTypeString(argT), 35 | ReplyType: resolver.GetTypeString(replyT), 36 | }) 37 | } 38 | 39 | data.Imports = resolver.GetImports() 40 | 41 | var src bytes.Buffer 42 | err := tmpl.Execute(&src, data) 43 | if err != nil { 44 | log.Printf("failed to render template: %s", err.Error()) 45 | return nil, err 46 | } 47 | 48 | formatted, err := imports.Process("client.go", src.Bytes(), nil) 49 | if err != nil { 50 | log.Printf("failed to format code: \nCODE:\n%s \nERR:\n%s", src.String(), err.Error()) 51 | return nil, err 52 | } 53 | 54 | return formatted, err 55 | } 56 | -------------------------------------------------------------------------------- /generator/templates/client.gohtml: -------------------------------------------------------------------------------- 1 | package {{ .Package }} 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/segmentio/glue/client" 7 | {{ range .Imports }} 8 | {{ .Name }} "{{ .Path }}" 9 | {{ end }} 10 | ) 11 | 12 | func New{{ .Service }}Client(rpcClient client.Client) *{{ .Service }} { 13 | c := new({{ .Service }}) 14 | c.RPC = rpcClient 15 | return c 16 | } 17 | 18 | type {{ .Service }}IFace interface { 19 | {{ range .Methods }} 20 | {{ .Name }}(args {{ .ArgType }}) ({{ .ReplyType }}, error) 21 | {{ end }} 22 | } 23 | 24 | type {{ .Service }}ContextIFace interface { 25 | {{ range .Methods }} 26 | {{ .Name }}(args {{ .ArgType }}) ({{ .ReplyType }}, error) 27 | {{ .Name }}Context(ctx context.Context, args {{ .ArgType }}) ({{ .ReplyType }}, error) 28 | {{ end }} 29 | } 30 | 31 | type {{ .Service }} struct { 32 | RPC client.Client 33 | } 34 | 35 | {{ range .Methods }} 36 | func (c *{{ $.Service }}) {{ .Name }}(args {{ .ArgType }}) ({{ .ReplyType }}, error) { 37 | var reply {{ .ReplyType }} 38 | err := c.RPC.Call("{{ $.Service }}.{{ .Name }}", args, &reply) 39 | return reply, err 40 | } 41 | 42 | func (c *{{ $.Service }}) {{ .Name }}Context(ctx context.Context, args {{ .ArgType }}) ({{ .ReplyType }}, error) { 43 | var reply {{ .ReplyType }} 44 | err := c.RPC.CallContext(ctx, "{{ $.Service }}.{{ .Name }}", args, &reply) 45 | return reply, err 46 | } 47 | {{ end }} 48 | -------------------------------------------------------------------------------- /provider/internal/types.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "go/types" 5 | ) 6 | 7 | // IsExportedOrBuiltin returns true if a type is either exported or primitive. 8 | // 9 | // Note(tejasmanohar): If the type is a map, both the key and value must be exported 10 | // since they're both necessary to represent the type. 11 | func IsExportedOrBuiltin(_t types.Type) bool { 12 | for _, t := range unpack(_t) { 13 | if _, isPrimitive := t.(*types.Basic); isPrimitive { 14 | return true 15 | } 16 | 17 | namedType, ok := t.(*types.Named) 18 | if !ok { 19 | return false 20 | } 21 | 22 | obj := namedType.Obj() 23 | if obj == nil { 24 | return false 25 | } 26 | 27 | if !obj.Exported() { 28 | return false 29 | } 30 | } 31 | 32 | return true 33 | } 34 | 35 | // unpack unpacks the most basic, underlying types from pointers, slices, maps, etc. 36 | // There can be multiple types in the case of maps (key and value). 37 | func unpack(t types.Type) []types.Type { 38 | ret := make([]types.Type, 0, 1) 39 | 40 | if ptr, ok := t.(*types.Pointer); ok { 41 | ret = append(ret, unpack(ptr.Elem())...) 42 | } 43 | 44 | if slice, ok := t.(*types.Slice); ok { 45 | ret = append(ret, unpack(slice.Elem())...) 46 | } 47 | 48 | if array, ok := t.(*types.Array); ok { 49 | ret = append(ret, unpack(array.Elem())...) 50 | } 51 | 52 | return ret 53 | } 54 | 55 | // Dereference dereferences pointers as needed. 56 | func Dereference(t types.Type) types.Type { 57 | if ptr, ok := t.(*types.Pointer); ok { 58 | return Dereference(ptr.Elem()) 59 | } 60 | 61 | return t 62 | } 63 | -------------------------------------------------------------------------------- /example/gorilla/math/client/generated_MathClient.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/segmentio/glue/client" 5 | 6 | "github.com/segmentio/glue/example/gorilla/math" 7 | ) 8 | 9 | func NewMathClient(rpcClient client.Client) *Math { 10 | c := new(Math) 11 | c.RPC = rpcClient 12 | return c 13 | } 14 | 15 | type MathIFace interface { 16 | Sum(args math.SumArg) (math.SumReply, error) 17 | 18 | Identity(args int) (int, error) 19 | 20 | IdentityMany(args []int) ([]int, error) 21 | 22 | IdentityManyStruct(args []*math.IdentityStruct) ([]math.IdentityStruct, error) 23 | 24 | MapOfPrimitives(args map[string]string) ([]int, error) 25 | } 26 | 27 | type Math struct { 28 | RPC client.Client 29 | } 30 | 31 | func (c *Math) Sum(args math.SumArg) (math.SumReply, error) { 32 | var reply math.SumReply 33 | err := c.RPC.Call("Math.Sum", args, &reply) 34 | return reply, err 35 | } 36 | 37 | func (c *Math) Identity(args int) (int, error) { 38 | var reply int 39 | err := c.RPC.Call("Math.Identity", args, &reply) 40 | return reply, err 41 | } 42 | 43 | func (c *Math) IdentityMany(args []int) ([]int, error) { 44 | var reply []int 45 | err := c.RPC.Call("Math.IdentityMany", args, &reply) 46 | return reply, err 47 | } 48 | 49 | func (c *Math) IdentityManyStruct(args []*math.IdentityStruct) ([]math.IdentityStruct, error) { 50 | var reply []math.IdentityStruct 51 | err := c.RPC.Call("Math.IdentityManyStruct", args, &reply) 52 | return reply, err 53 | } 54 | 55 | func (c *Math) MapOfPrimitives(args map[string]string) ([]int, error) { 56 | var reply []int 57 | err := c.RPC.Call("Math.MapOfPrimitives", args, &reply) 58 | return reply, err 59 | } 60 | -------------------------------------------------------------------------------- /cmd/glue/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | 7 | "github.com/segmentio/glue" 8 | "github.com/segmentio/glue/log" 9 | "github.com/segmentio/glue/provider" 10 | "github.com/segmentio/glue/provider/gorilla" 11 | "github.com/segmentio/glue/provider/stl" 12 | "github.com/segmentio/glue/writer" 13 | ) 14 | 15 | var debug = flag.Bool("debug", false, "enable debug logs") 16 | 17 | // Required 18 | var name = flag.String("name", "", "target RPC declaration name (e.g. Service in `type Service struct`)") 19 | var service = flag.String("service", "", "RPC service name (e.g. `Service` in `Service.Method`)") 20 | 21 | // Overrides 22 | var out = flag.String("out", "./client", "output directory") 23 | var print = flag.Bool("print", false, "output code to stdout instead of file") 24 | 25 | // Custom providers (only pick one) 26 | var gorillaFlag = flag.Bool("gorilla", false, "supports Gorilla rpc method format") 27 | 28 | func main() { 29 | flag.Parse() 30 | 31 | if *debug { 32 | log.DebugMode = true 33 | } 34 | 35 | if *service == "" { 36 | log.Print("-service is required") 37 | os.Exit(2) 38 | } 39 | 40 | if *name == "" { 41 | log.Print("-name is required") 42 | os.Exit(2) 43 | } 44 | 45 | var wr writer.Writer 46 | if *print { 47 | wr = writer.NewStdoutWriter() 48 | } else { 49 | var err error 50 | wr, err = writer.NewFileWriter(*out) 51 | if err != nil { 52 | os.Exit(1) 53 | } 54 | } 55 | 56 | var provider provider.Provider = &stl.Provider{} 57 | if *gorillaFlag { 58 | provider = gorilla.New(provider) 59 | } 60 | 61 | walker := glue.Walker{ 62 | Provider: provider, 63 | Writer: wr, 64 | } 65 | 66 | var path string 67 | args := flag.Args() 68 | if len(args) == 0 { 69 | path = "." 70 | } else { 71 | path = args[0] 72 | } 73 | 74 | err := walker.Walk(glue.Directions{ 75 | Path: path, 76 | Name: *name, 77 | Service: *service, 78 | }) 79 | if err != nil { 80 | os.Exit(2) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /provider/gorilla/gorilla.go: -------------------------------------------------------------------------------- 1 | package gorilla 2 | 3 | import ( 4 | "go/types" 5 | 6 | "github.com/segmentio/glue/provider" 7 | ) 8 | 9 | // Provider is a Glue provider for gorilla/rpc. 10 | // The main difference between stl and gorilla's rpc method format is the optional, 11 | // request first argument in Gorilla so we shift that and proxy to stl provider in 12 | // most of the methods. 13 | type Provider struct { 14 | BaseProvider provider.Provider 15 | } 16 | 17 | // New creates a new gorilla/rpc Provider. 18 | func New(base provider.Provider) *Provider { 19 | return &Provider{BaseProvider: base} 20 | } 21 | 22 | // IsSuitableMethod determines if a receiver method is structured as a gorilla/rpc method. 23 | func (p *Provider) IsSuitableMethod(method *types.Func) bool { 24 | newMethod := p.shiftReqParam(method) 25 | return p.BaseProvider.IsSuitableMethod(newMethod) 26 | } 27 | 28 | // GetArgType proxies stl.GetArgType with a shifted function. 29 | func (p *Provider) GetArgType(f *types.Func) types.Type { 30 | newMethod := p.shiftReqParam(f) 31 | return p.BaseProvider.GetArgType(newMethod) 32 | } 33 | 34 | // GetReplyType proxies stl.GetReplyType with a shifted function. 35 | func (p *Provider) GetReplyType(f *types.Func) types.Type { 36 | newMethod := p.shiftReqParam(f) 37 | return p.BaseProvider.GetReplyType(newMethod) 38 | } 39 | 40 | // shiftReqParam returns a new *types.Func without the *http.Request param. 41 | func (p *Provider) shiftReqParam(method *types.Func) *types.Func { 42 | originalSignature := method.Type().(*types.Signature) 43 | originalParams := originalSignature.Params() 44 | 45 | if originalParams.Len() == 3 { 46 | // rebuild *types.Func _without_ *http.Req param 47 | var vars []*types.Var 48 | for i := 1; i < originalParams.Len(); i++ { 49 | param := originalParams.At(i) 50 | vars = append(vars, param) 51 | } 52 | 53 | params := types.NewTuple(vars...) 54 | signature := types.NewSignature(originalSignature.Recv(), params, 55 | originalSignature.Results(), originalSignature.Variadic()) 56 | return types.NewFunc(method.Pos(), method.Pkg(), method.Name(), signature) 57 | } 58 | 59 | return method 60 | } 61 | -------------------------------------------------------------------------------- /visitor.go: -------------------------------------------------------------------------------- 1 | package glue 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | 7 | "github.com/segmentio/glue/provider" 8 | 9 | "golang.org/x/tools/go/loader" 10 | ) 11 | 12 | // Visitor traverses a Go package's AST, visits declarations that satisfy the 13 | // structure of an RPC service, and extracts their RPC methods. 14 | type Visitor struct { 15 | pkg *loader.PackageInfo 16 | methods map[string][]*types.Func 17 | provider provider.Provider 18 | 19 | target string 20 | } 21 | 22 | // VisitorConfig is used to create a Visitor. 23 | type VisitorConfig struct { 24 | // Pkg contains metadata for the target package. 25 | Pkg *loader.PackageInfo 26 | // Provider determines which RPC methods are suitable. 27 | Provider provider.Provider 28 | // Declaration is the name of the target RPC declaration (method receiver). 29 | Declaration string 30 | } 31 | 32 | // NewVisitor creates a Visitor. 33 | func NewVisitor(cfg VisitorConfig) *Visitor { 34 | return &Visitor{ 35 | pkg: cfg.Pkg, 36 | provider: cfg.Provider, 37 | methods: map[string][]*types.Func{}, 38 | target: cfg.Declaration, 39 | } 40 | } 41 | 42 | // Go starts Visitor's trip around the supplied package. Upon return, it 43 | // sends a mapping of receiver identifiers to RPC methods. 44 | func (p *Visitor) Go() map[string][]*types.Func { 45 | for _, file := range p.pkg.Files { 46 | ast.Walk(p, file) 47 | } 48 | 49 | return p.methods 50 | } 51 | 52 | // Visit extracts functions from RPC declarations. It satisfies go/ast.Visitor. 53 | func (p *Visitor) Visit(node ast.Node) ast.Visitor { 54 | switch n := node.(type) { 55 | case nil: 56 | return nil 57 | case *ast.TypeSpec: 58 | p.visitType(n) 59 | } 60 | 61 | return p 62 | } 63 | 64 | func (p *Visitor) visitType(ts *ast.TypeSpec) { 65 | obj := p.pkg.Info.ObjectOf(ts.Name) 66 | if obj == nil { 67 | return 68 | } 69 | 70 | namedType, ok := obj.Type().(*types.Named) 71 | if !ok { 72 | return 73 | } 74 | 75 | if obj.Name() != p.target { 76 | return 77 | } 78 | 79 | for i := 0; i < namedType.NumMethods(); i++ { 80 | method := namedType.Method(i) 81 | if p.provider.IsSuitableMethod(method) { 82 | 83 | recv := namedType.Obj().Name() 84 | p.methods[recv] = append(p.methods[recv], method) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /provider/stl/stl.go: -------------------------------------------------------------------------------- 1 | package stl 2 | 3 | import ( 4 | "go/types" 5 | 6 | "github.com/segmentio/glue/log" 7 | "github.com/segmentio/glue/provider/internal" 8 | ) 9 | 10 | // Provider is a Glue provider for net/rpc. 11 | type Provider struct{} 12 | 13 | // IsSuitableMethod determines if a receiver method is structured as a net/rpc method. 14 | // The criteria is net/rpc.suitableMethods ported from reflect to types.Type. 15 | // https://github.com/golang/go/blob/release-branch.go1.8/src/net/rpc/server.go#L292 16 | func (p *Provider) IsSuitableMethod(method *types.Func) bool { 17 | if !method.Exported() { 18 | log.Debugf("skipping %s: unexported", method.Name()) 19 | return false 20 | } 21 | 22 | signature := method.Type().(*types.Signature) 23 | params := signature.Params() 24 | 25 | if params.Len() != 2 { 26 | log.Debugf("skipping %s: expected 2 params, found %d", method.Name(), params.Len()) 27 | return false 28 | } 29 | 30 | arg := params.At(0) 31 | if !internal.IsExportedOrBuiltin(arg.Type()) { 32 | log.Debugf("skipping %s: argument parameter's type %s is not exported", method.Name(), arg.Type()) 33 | return false 34 | } 35 | 36 | reply := params.At(1) 37 | if !internal.IsExportedOrBuiltin(reply.Type()) { 38 | log.Debugf("skipping %s: reply parameter's type %s is not exported", method.Name(), reply.Type()) 39 | return false 40 | } 41 | 42 | if _, ok := reply.Type().(*types.Pointer); !ok { 43 | log.Debugf("skipping %s: reply type %s is not a pointer", method.Name(), reply.Type()) 44 | return false 45 | } 46 | 47 | returns := signature.Results() 48 | if returns.Len() != 1 { 49 | log.Debugf("skipping %s: expected 1 return value, found %d", method.Name(), returns.Len()) 50 | return false 51 | } 52 | 53 | err := returns.At(0) 54 | if err.Type().String() != "error" { 55 | log.Debugf("skipping %s: expected func to return `error`, found %s", method.Name(), err.Type().String()) 56 | } 57 | 58 | return true 59 | } 60 | 61 | // GetArgType extracts metadata about the response type from an RPC method. 62 | func (p *Provider) GetArgType(f *types.Func) types.Type { 63 | signature := f.Type().(*types.Signature) 64 | params := signature.Params() 65 | arg := params.At(0) 66 | return internal.Dereference(arg.Type()) 67 | } 68 | 69 | // GetReplyType extracts metadata about the response type from an RPC method. 70 | func (p *Provider) GetReplyType(f *types.Func) types.Type { 71 | signature := f.Type().(*types.Signature) 72 | params := signature.Params() 73 | reply := params.At(1) 74 | return internal.Dereference(reply.Type()) 75 | } 76 | -------------------------------------------------------------------------------- /walker.go: -------------------------------------------------------------------------------- 1 | package glue 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/segmentio/glue/generator" 9 | "github.com/segmentio/glue/log" 10 | "github.com/segmentio/glue/provider" 11 | "github.com/segmentio/glue/writer" 12 | "golang.org/x/tools/go/loader" 13 | ) 14 | 15 | // A Walker walks along supplied directions, visits server RPC code, and 16 | // generates RPC client code along the way with the help of others. 17 | type Walker struct { 18 | // Provider answers RPC-implementation-specific (e.g. stl, gorilla, etc.) questions. 19 | Provider provider.Provider 20 | Writer writer.Writer 21 | } 22 | 23 | // Directions tell the Walker where to walk and what to pay attention to along the way. 24 | type Directions struct { 25 | // Path determines the source code path to walk along. It's required 26 | Path string 27 | // Name is the name of the RPC declaration (e.g. `type MathService struct{}`). 28 | Name string 29 | // Service is the name of the RPC service. (e.g. `Math` in `Math.Sum`) 30 | Service string 31 | } 32 | 33 | // Walk is the logical entrypoint for Glue. It walks the source code and asks 34 | func (w *Walker) Walk(directions Directions) error { 35 | var conf loader.Config 36 | conf.Import(directions.Path) 37 | 38 | prgm, err := conf.Load() 39 | if err != nil { 40 | log.Printf("failed to parse Go code: %s", err.Error()) 41 | return err 42 | } 43 | 44 | var wg sync.WaitGroup 45 | for _, pkg := range prgm.InitialPackages() { 46 | wg.Add(1) 47 | go func(p *loader.PackageInfo) { 48 | defer wg.Done() 49 | w.walkPackage(p, directions.Name, directions.Service) 50 | }(pkg) 51 | } 52 | 53 | wg.Wait() 54 | return nil 55 | } 56 | 57 | func (w *Walker) walkPackage(pkg *loader.PackageInfo, decl, service string) error { 58 | visitor := NewVisitor(VisitorConfig{ 59 | Pkg: pkg, 60 | Provider: w.Provider, 61 | Declaration: decl, 62 | }) 63 | funcsByRecv := visitor.Go() 64 | 65 | if len(funcsByRecv) == 0 { 66 | log.Print("could not find RPC declaration") 67 | return errors.New("not found") 68 | } 69 | 70 | for _, funcs := range funcsByRecv { 71 | ident := fmt.Sprintf("%sClient", service) 72 | src, err := generator.Generate(generator.GenerateInput{ 73 | Provider: w.Provider, 74 | PackageName: "client", 75 | Service: service, 76 | Funcs: funcs, 77 | }) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | fname := fmt.Sprintf("generated_%s.go", ident) 83 | if err := w.Writer.Write(fname, src); err != nil { 84 | return err 85 | } 86 | 87 | log.Printf("glue: generated %s", ident) 88 | } 89 | 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /generator/resolver.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "go/types" 5 | "path/filepath" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type resolver struct { 11 | // imports is a map of package name to package path to an incrementing integer 12 | // for a given package name, there may be multiple paths-- 13 | // e.g. "mypkg" is the name of paths "github.com/x/mypkg" and "github.com/y/mypkg" 14 | // 15 | // The incrementing integer is used to handle collisions. If you need to import 16 | // - "github.com/x/mypackage" 17 | // - "github.com/y/mypackage" 18 | // - "github.com/z/mypackage" 19 | // they will be imported as 20 | // mypackage "github.com/x/mypackage" 21 | // mypackage1 "github.com/y/mypackage" 22 | // mypackage2 "github.com/z/mypackage" 23 | imports map[string]map[string]importMapping 24 | } 25 | 26 | type importMapping struct { 27 | Name string 28 | counter int 29 | } 30 | 31 | func newResolver() *resolver { 32 | return &resolver{ 33 | imports: map[string]map[string]importMapping{}, 34 | } 35 | } 36 | 37 | func (r *resolver) GetTypeString(t types.Type) string { 38 | return types.TypeString(t, func(pkg *types.Package) string { 39 | name := pkg.Name() 40 | path := stripVendor(pkg.Path()) 41 | 42 | if existingPaths, ok := r.imports[name]; ok { 43 | mapping, ok := existingPaths[path] 44 | if ok { 45 | return mapping.Name 46 | } 47 | 48 | n := len(existingPaths) 49 | rename := name + strconv.Itoa(n) 50 | r.imports[name][path] = importMapping{ 51 | Name: rename, 52 | counter: n, 53 | } 54 | 55 | return rename 56 | } 57 | 58 | r.imports[name] = map[string]importMapping{} 59 | r.imports[name][path] = importMapping{Name: name} 60 | return name 61 | }) 62 | } 63 | 64 | func (r *resolver) GetImports() []Import { 65 | ret := make([]Import, 0, len(r.imports)) 66 | for originalName, pathsMap := range r.imports { 67 | for path, info := range pathsMap { 68 | var name string 69 | if originalName != info.Name { 70 | name = info.Name 71 | } 72 | 73 | ret = append(ret, Import{ 74 | Name: name, 75 | Path: path, 76 | }) 77 | } 78 | } 79 | 80 | return ret 81 | } 82 | 83 | // github.com/x/y/vendor/github.com/a/b -> github.com/a/b 84 | func stripVendor(path string) string { 85 | dirs := strings.Split(path, string(filepath.Separator)) 86 | num := len(dirs) 87 | vendorIndex := -1 88 | for i := 1; i <= num; i++ { 89 | dir := dirs[num-i] 90 | if dir == "vendor" { 91 | vendorIndex = num - i 92 | break 93 | } 94 | } 95 | 96 | if vendorIndex == -1 { 97 | return path 98 | } 99 | 100 | return filepath.Join(dirs[vendorIndex+1:]...) 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glue 2 | 3 | > **Note** 4 | > Segment has paused maintenance on this project, but may return it to an active status in the future. Issues and pull requests from external contributors are not being considered, although internal contributions may appear from time to time. The project remains available under its open source license for anyone to use. 5 | 6 | Glue generates client code for your Go RPC server. It currently supports 7 | - [net/rpc] 8 | - [gorilla/rpc] 9 | 10 | 11 | ## Installation 12 | 13 | `go get github.com/segmentio/glue/cmd/glue` 14 | 15 | Then, `glue` should be available at `$GOPATH/bin/glue` (ideally, in your `$PATH`). 16 | 17 | 18 | ## Usage 19 | 20 | `glue -name=Service -service=Math [path]` will traverse the provided path (or working 21 | directory if none is provided) and generate clients for RPC methods 22 | (pointed at `Math.*`) declared on `type Service`. 23 | 24 | Given the following is in a `*.go` file in your working directory, 25 | 26 | ```go 27 | package math 28 | 29 | //go:generate glue -name Service -service Math 30 | type Service struct{} 31 | 32 | type SumArg struct { 33 | Values []int 34 | } 35 | 36 | type SumReply struct { 37 | Sum int 38 | } 39 | 40 | func (s *Service) Sum(arg SumArg, reply *SumReply) error { 41 | for _, v := range arg.Values { 42 | reply.Sum += v 43 | } 44 | 45 | return nil 46 | } 47 | ``` 48 | 49 | `go generate` would output the following to `clients/Service.go` 50 | 51 | ```go 52 | package client 53 | 54 | import ( 55 | "github.com/segmentio/glue/example/stl/math" 56 | ) 57 | 58 | type Client interface { 59 | Call(method string, args interface{}, reply interface{}) error 60 | } 61 | 62 | func NewMathClient(rpcClient Client) *Math { 63 | c := new(Math) 64 | c.RPC = rpcClient 65 | return c 66 | } 67 | 68 | type Math struct { 69 | RPC Client 70 | } 71 | 72 | func (c *Math) Sum(args math.SumArg) (*math.SumReply, error) { 73 | reply := new(math.SumReply) 74 | err := c.RPC.Call("Math.Sum", args, reply) 75 | return reply, err 76 | } 77 | ``` 78 | 79 | ## Gorilla 80 | 81 | If you use [gorilla/rpc], you're in luck! Just specify `-gorilla`. 82 | 83 | 84 | ## Options 85 | 86 | ### Output 87 | Glue always outputs code with `client` package. By default, this is in `./client`, but 88 | you can change the output directory via `-out`. 89 | 90 | To output code to STDOUT instead of files, supply `-print`. 91 | 92 | 93 | ## FAQ 94 | 95 | ### How do I use Glue with RPC implementation X? 96 | Glue is modular. If you'd like support for another popular (or interesting, well-maintained) 97 | RPC implementation, open a PR to add a new Glue `provider/`. 98 | 99 | Unfortunately, Go doesn't allow dynamic loading of packages so if you'd like Glue 100 | to support an internal or experimental RPC framework, fork Glue and supply another 101 | `provider` in [cmd/glue/main.go](https://github.com/segmentio/glue/blob/master/cmd/glue/main.go). 102 | 103 | 104 | [net/rpc]: https://golang.org/pkg/net/rpc/ 105 | [gorilla/rpc]: https://github.com/gorilla/rpc 106 | -------------------------------------------------------------------------------- /internal/gen/gotemplate_StringSet.go: -------------------------------------------------------------------------------- 1 | // Code generated by gotemplate. DO NOT EDIT. 2 | 3 | // Package set is a template Set type 4 | // 5 | // Tries to be similar to Python's set type 6 | package gen 7 | 8 | // An A is the element of the set 9 | // 10 | // template type Set(A) 11 | 12 | // SetNothing is used as a zero sized member in the map 13 | type StringSetNothing struct{} 14 | 15 | // Set provides a general purpose set modeled on Python's set type. 16 | type StringSet struct { 17 | m map[string]StringSetNothing 18 | } 19 | 20 | // NewSizedSet returns a new empty set with the given capacity 21 | func NewSizedStringSet(capacity int) *StringSet { 22 | return &StringSet{ 23 | m: make(map[string]StringSetNothing, capacity), 24 | } 25 | } 26 | 27 | // NewSet returns a new empty set 28 | func NewStringSet() *StringSet { 29 | return NewSizedStringSet(0) 30 | } 31 | 32 | // Len returns the number of elements in the set 33 | func (s *StringSet) Len() int { 34 | return len(s.m) 35 | } 36 | 37 | // Contains returns whether elem is in the set or not 38 | func (s *StringSet) Contains(elem string) bool { 39 | _, found := s.m[elem] 40 | return found 41 | } 42 | 43 | // Add adds elem to the set, returning the set 44 | // 45 | // If the element already exists then it has no effect 46 | func (s *StringSet) Add(elem string) *StringSet { 47 | s.m[elem] = StringSetNothing{} 48 | return s 49 | } 50 | 51 | // AddList adds a list of elems to the set 52 | // 53 | // If the elements already exists then it has no effect 54 | func (s *StringSet) AddList(elems []string) *StringSet { 55 | for _, elem := range elems { 56 | s.m[elem] = StringSetNothing{} 57 | } 58 | return s 59 | } 60 | 61 | // Discard removes elem from the set 62 | // 63 | // If it wasn't in the set it does nothing 64 | // 65 | // It returns the set 66 | func (s *StringSet) Discard(elem string) *StringSet { 67 | delete(s.m, elem) 68 | return s 69 | } 70 | 71 | // Remove removes elem from the set 72 | // 73 | // It returns whether the elem was in the set or not 74 | func (s *StringSet) Remove(elem string) bool { 75 | _, found := s.m[elem] 76 | if found { 77 | delete(s.m, elem) 78 | } 79 | return found 80 | } 81 | 82 | // Pop removes elem from the set and returns it 83 | // 84 | // It also returns whether the elem was found or not 85 | func (s *StringSet) Pop(elem string) (string, bool) { 86 | _, found := s.m[elem] 87 | if found { 88 | delete(s.m, elem) 89 | } 90 | return elem, found 91 | } 92 | 93 | // AsList returns all the elements as a slice 94 | func (s *StringSet) AsList() []string { 95 | elems := make([]string, len(s.m)) 96 | i := 0 97 | for elem := range s.m { 98 | elems[i] = elem 99 | i++ 100 | } 101 | return elems 102 | } 103 | 104 | // Clear removes all the elements 105 | func (s *StringSet) Clear() *StringSet { 106 | s.m = make(map[string]StringSetNothing) 107 | return s 108 | } 109 | 110 | // Copy returns a shallow copy of the Set 111 | func (s *StringSet) Copy() *StringSet { 112 | newSet := NewSizedStringSet(len(s.m)) 113 | for elem := range s.m { 114 | newSet.m[elem] = StringSetNothing{} 115 | } 116 | return newSet 117 | } 118 | 119 | // Difference returns a new set with all the elements that are in this 120 | // set but not in the other 121 | func (s *StringSet) Difference(other *StringSet) *StringSet { 122 | newSet := NewSizedStringSet(len(s.m)) 123 | for elem := range s.m { 124 | if _, found := other.m[elem]; !found { 125 | newSet.m[elem] = StringSetNothing{} 126 | } 127 | } 128 | return newSet 129 | } 130 | 131 | // DifferenceUpdate removes all the elements that are in the other set 132 | // from this set. It returns the set. 133 | func (s *StringSet) DifferenceUpdate(other *StringSet) *StringSet { 134 | m := s.m 135 | for elem := range other.m { 136 | delete(m, elem) 137 | } 138 | return s 139 | } 140 | 141 | // Intersection returns a new set with all the elements that are only in this 142 | // set and the other set. It returns the new set. 143 | func (s *StringSet) Intersection(other *StringSet) *StringSet { 144 | newSet := NewSizedStringSet(len(s.m) + len(other.m)) 145 | for elem := range s.m { 146 | if _, found := other.m[elem]; found { 147 | newSet.m[elem] = StringSetNothing{} 148 | } 149 | } 150 | for elem := range other.m { 151 | if _, found := s.m[elem]; found { 152 | newSet.m[elem] = StringSetNothing{} 153 | } 154 | } 155 | return newSet 156 | } 157 | 158 | // IntersectionUpdate changes this set so that it only contains 159 | // elements that are in both this set and the other set. It returns 160 | // the set. 161 | func (s *StringSet) IntersectionUpdate(other *StringSet) *StringSet { 162 | for elem := range s.m { 163 | if _, found := other.m[elem]; !found { 164 | delete(s.m, elem) 165 | } 166 | } 167 | return s 168 | } 169 | 170 | // Union returns a new set with all the elements that are in either 171 | // set. It returns the new set. 172 | func (s *StringSet) Union(other *StringSet) *StringSet { 173 | newSet := NewSizedStringSet(len(s.m) + len(other.m)) 174 | for elem := range s.m { 175 | newSet.m[elem] = StringSetNothing{} 176 | } 177 | for elem := range other.m { 178 | newSet.m[elem] = StringSetNothing{} 179 | } 180 | return newSet 181 | } 182 | 183 | // Update adds all the elements from the other set to this set. 184 | // It returns the set. 185 | func (s *StringSet) Update(other *StringSet) *StringSet { 186 | for elem := range other.m { 187 | s.m[elem] = StringSetNothing{} 188 | } 189 | return s 190 | } 191 | 192 | // IsSuperset returns a bool indicating whether this set is a superset of other set. 193 | func (s *StringSet) IsSuperset(strict bool, other *StringSet) bool { 194 | if strict && len(other.m) >= len(s.m) { 195 | return false 196 | } 197 | A: 198 | for v := range other.m { 199 | for i := range s.m { 200 | if v == i { 201 | continue A 202 | } 203 | } 204 | return false 205 | } 206 | return true 207 | } 208 | 209 | // IsSubset returns a bool indicating whether this set is a subset of other set. 210 | func (s *StringSet) IsSubset(strict bool, other *StringSet) bool { 211 | if strict && len(s.m) >= len(other.m) { 212 | return false 213 | } 214 | A: 215 | for v := range s.m { 216 | for i := range other.m { 217 | if v == i { 218 | continue A 219 | } 220 | } 221 | return false 222 | } 223 | return true 224 | } 225 | 226 | // IsDisjoint returns a bool indicating whether this set and other set have no elements in common. 227 | func (s *StringSet) IsDisjoint(other *StringSet) bool { 228 | for v := range s.m { 229 | if other.Contains(v) { 230 | return false 231 | } 232 | } 233 | return true 234 | } 235 | 236 | // SymmetricDifference returns a new set of all elements that are a member of exactly 237 | // one of this set and other set(elements which are in one of the sets, but not in both). 238 | func (s *StringSet) SymmetricDifference(other *StringSet) *StringSet { 239 | work1 := s.Union(other) 240 | work2 := s.Intersection(other) 241 | for v := range work2.m { 242 | delete(work1.m, v) 243 | } 244 | return work1 245 | } 246 | 247 | // SymmetricDifferenceUpdate modifies this set to be a set of all elements that are a member 248 | // of exactly one of this set and other set(elements which are in one of the sets, 249 | // but not in both) and returns this set. 250 | func (s *StringSet) SymmetricDifferenceUpdate(other *StringSet) *StringSet { 251 | work := s.SymmetricDifference(other) 252 | *s = *work 253 | return s 254 | } 255 | -------------------------------------------------------------------------------- /generator/bindata.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-bindata. 2 | // sources: 3 | // templates/client.gohtml 4 | // DO NOT EDIT! 5 | 6 | package generator 7 | 8 | import ( 9 | "bytes" 10 | "compress/gzip" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "os" 15 | "path/filepath" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func bindataRead(data, name string) ([]byte, error) { 21 | gz, err := gzip.NewReader(strings.NewReader(data)) 22 | if err != nil { 23 | return nil, fmt.Errorf("Read %q: %v", name, err) 24 | } 25 | 26 | var buf bytes.Buffer 27 | _, err = io.Copy(&buf, gz) 28 | clErr := gz.Close() 29 | 30 | if err != nil { 31 | return nil, fmt.Errorf("Read %q: %v", name, err) 32 | } 33 | if clErr != nil { 34 | return nil, err 35 | } 36 | 37 | return buf.Bytes(), nil 38 | } 39 | 40 | type asset struct { 41 | bytes []byte 42 | info os.FileInfo 43 | } 44 | 45 | type bindataFileInfo struct { 46 | name string 47 | size int64 48 | mode os.FileMode 49 | modTime time.Time 50 | } 51 | 52 | func (fi bindataFileInfo) Name() string { 53 | return fi.name 54 | } 55 | func (fi bindataFileInfo) Size() int64 { 56 | return fi.size 57 | } 58 | func (fi bindataFileInfo) Mode() os.FileMode { 59 | return fi.mode 60 | } 61 | func (fi bindataFileInfo) ModTime() time.Time { 62 | return fi.modTime 63 | } 64 | func (fi bindataFileInfo) IsDir() bool { 65 | return false 66 | } 67 | func (fi bindataFileInfo) Sys() interface{} { 68 | return nil 69 | } 70 | 71 | var _templatesClientGohtml = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x52\x4d\x8b\xdb\x30\x10\xbd\xeb\x57\x3c\x4c\x29\x76\x31\xca\xbd\x90\x43\x31\x14\x72\x68\x08\x69\xff\x80\xaa\x4c\x1c\x53\x5b\x36\x63\x39\x1f\x18\xfd\xf7\x22\xc9\x6c\xe2\x90\x85\x65\x37\xec\x4d\x9a\xaf\xf7\xde\xcc\xeb\x94\xfe\xa7\x4a\xc2\x38\x42\x6e\xa6\xb7\x73\x42\x54\x4d\xd7\xb2\x45\x2a\x80\x44\xb7\xc6\xd2\xd9\x26\xc2\x7f\xca\xca\x1e\x86\xbf\x52\xb7\xcd\xa2\xa7\xb2\x21\x63\xab\x76\x51\xd6\x03\x2d\x74\x5d\x91\xb1\x89\x80\x1f\xc6\xca\x94\x04\xb9\x0a\x63\x7a\x3f\x12\x08\x09\xb9\x56\x8d\x87\x40\x12\x21\xed\x01\xce\x4d\x4d\x64\x76\xbe\x32\x13\x62\x3f\x18\x8d\x35\x9d\x7c\xcd\x6f\xe2\x63\xa5\x7d\x4f\x11\x10\x52\xee\x74\x7c\x21\x42\xca\xf8\xcb\xf0\x6d\x5e\x8e\x51\x00\x1a\xdf\x97\x30\x74\x4a\xe7\xb9\xcc\xa7\xe4\x76\x53\x60\x89\x97\x79\x02\x60\xb2\x03\x1b\x68\xe1\x84\xb0\x97\x2e\x2e\xe6\xda\xb6\xfa\xa9\x34\xa1\x32\x96\x78\xef\x5f\xe3\x4c\xed\x2f\xb2\x87\x76\xf7\x48\x6d\xaa\xb8\xec\x43\xe0\x07\x97\x7f\xfc\x5c\xe7\x32\x04\x4e\x5b\xea\xea\xcb\x14\xca\x41\xcc\x2d\x67\xb3\x75\x3c\x66\x52\xc4\xa3\x7c\x1e\xa1\x59\xfb\x84\x9e\x6a\x7b\xc6\x64\x0f\x39\xc5\x72\x3c\x5d\x2b\x7a\xcb\x83\xb6\x41\x9c\x3f\xd9\xec\xec\xbe\xe7\x15\xc5\xc1\x45\xa9\x0e\xbe\xf8\x72\x7b\xfc\x0f\x6c\x22\x90\x00\x8e\x8a\xc1\x3e\x8d\xfb\xc2\x90\x26\x66\xef\xbb\xe0\x30\x59\xa8\xba\x4e\x93\x3b\x0e\xf2\x86\x42\x12\x77\x96\xe3\x6b\x18\x19\xb7\x3d\x39\x31\x44\x02\xb8\x00\x9c\x78\xab\xaa\x27\x1e\xe8\x9d\x8a\x6f\x18\xe4\x78\x92\xfa\xab\x51\xfe\x07\x00\x00\xff\xff\xf8\xa9\x24\xc3\xb8\x04\x00\x00" 72 | 73 | func templatesClientGohtmlBytes() ([]byte, error) { 74 | return bindataRead( 75 | _templatesClientGohtml, 76 | "templates/client.gohtml", 77 | ) 78 | } 79 | 80 | func templatesClientGohtml() (*asset, error) { 81 | bytes, err := templatesClientGohtmlBytes() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | info := bindataFileInfo{name: "templates/client.gohtml", size: 1208, mode: os.FileMode(420), modTime: time.Unix(1544146946, 0)} 87 | a := &asset{bytes: bytes, info: info} 88 | return a, nil 89 | } 90 | 91 | // Asset loads and returns the asset for the given name. 92 | // It returns an error if the asset could not be found or 93 | // could not be loaded. 94 | func Asset(name string) ([]byte, error) { 95 | cannonicalName := strings.Replace(name, "\\", "/", -1) 96 | if f, ok := _bindata[cannonicalName]; ok { 97 | a, err := f() 98 | if err != nil { 99 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 100 | } 101 | return a.bytes, nil 102 | } 103 | return nil, fmt.Errorf("Asset %s not found", name) 104 | } 105 | 106 | // MustAsset is like Asset but panics when Asset would return an error. 107 | // It simplifies safe initialization of global variables. 108 | func MustAsset(name string) []byte { 109 | a, err := Asset(name) 110 | if err != nil { 111 | panic("asset: Asset(" + name + "): " + err.Error()) 112 | } 113 | 114 | return a 115 | } 116 | 117 | // AssetInfo loads and returns the asset info for the given name. 118 | // It returns an error if the asset could not be found or 119 | // could not be loaded. 120 | func AssetInfo(name string) (os.FileInfo, error) { 121 | cannonicalName := strings.Replace(name, "\\", "/", -1) 122 | if f, ok := _bindata[cannonicalName]; ok { 123 | a, err := f() 124 | if err != nil { 125 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 126 | } 127 | return a.info, nil 128 | } 129 | return nil, fmt.Errorf("AssetInfo %s not found", name) 130 | } 131 | 132 | // AssetNames returns the names of the assets. 133 | func AssetNames() []string { 134 | names := make([]string, 0, len(_bindata)) 135 | for name := range _bindata { 136 | names = append(names, name) 137 | } 138 | return names 139 | } 140 | 141 | // _bindata is a table, holding each asset generator, mapped to its name. 142 | var _bindata = map[string]func() (*asset, error){ 143 | "templates/client.gohtml": templatesClientGohtml, 144 | } 145 | 146 | // AssetDir returns the file names below a certain 147 | // directory embedded in the file by go-bindata. 148 | // For example if you run go-bindata on data/... and data contains the 149 | // following hierarchy: 150 | // data/ 151 | // foo.txt 152 | // img/ 153 | // a.png 154 | // b.png 155 | // then AssetDir("data") would return []string{"foo.txt", "img"} 156 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 157 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 158 | // AssetDir("") will return []string{"data"}. 159 | func AssetDir(name string) ([]string, error) { 160 | node := _bintree 161 | if len(name) != 0 { 162 | cannonicalName := strings.Replace(name, "\\", "/", -1) 163 | pathList := strings.Split(cannonicalName, "/") 164 | for _, p := range pathList { 165 | node = node.Children[p] 166 | if node == nil { 167 | return nil, fmt.Errorf("Asset %s not found", name) 168 | } 169 | } 170 | } 171 | if node.Func != nil { 172 | return nil, fmt.Errorf("Asset %s not found", name) 173 | } 174 | rv := make([]string, 0, len(node.Children)) 175 | for childName := range node.Children { 176 | rv = append(rv, childName) 177 | } 178 | return rv, nil 179 | } 180 | 181 | type bintree struct { 182 | Func func() (*asset, error) 183 | Children map[string]*bintree 184 | } 185 | var _bintree = &bintree{nil, map[string]*bintree{ 186 | "templates": &bintree{nil, map[string]*bintree{ 187 | "client.gohtml": &bintree{templatesClientGohtml, map[string]*bintree{}}, 188 | }}, 189 | }} 190 | 191 | // RestoreAsset restores an asset under the given directory 192 | func RestoreAsset(dir, name string) error { 193 | data, err := Asset(name) 194 | if err != nil { 195 | return err 196 | } 197 | info, err := AssetInfo(name) 198 | if err != nil { 199 | return err 200 | } 201 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 202 | if err != nil { 203 | return err 204 | } 205 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 206 | if err != nil { 207 | return err 208 | } 209 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 210 | if err != nil { 211 | return err 212 | } 213 | return nil 214 | } 215 | 216 | // RestoreAssets restores an asset under the given directory recursively 217 | func RestoreAssets(dir, name string) error { 218 | children, err := AssetDir(name) 219 | // File 220 | if err != nil { 221 | return RestoreAsset(dir, name) 222 | } 223 | // Dir 224 | for _, child := range children { 225 | err = RestoreAssets(dir, filepath.Join(name, child)) 226 | if err != nil { 227 | return err 228 | } 229 | } 230 | return nil 231 | } 232 | 233 | func _filePath(dir, name string) string { 234 | cannonicalName := strings.Replace(name, "\\", "/", -1) 235 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 236 | } 237 | 238 | --------------------------------------------------------------------------------