From 0ca8439a6939868b180321c14ebc96b7cc20ea16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?And=C5=BEej=20Maciusovi=C4=8D?= Date: Wed, 20 Mar 2019 14:34:42 +0200 Subject: [PATCH] do --- .gitignore | 2 + LICENSE | 21 +++++ README.md | 9 ++ example/in/model.go | 45 ++++++++++ example/out/output.proto | 36 ++++++++ go.mod | 5 ++ go.sum | 6 ++ main.go | 180 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 304 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 example/in/model.go create mode 100644 example/out/output.proto create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8eb0ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +go2proto \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f4c9a51 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Andžej Maciusovič + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b609ca --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# go2proto + +Generate protobuf messages from given go structs. + +### Example + +git clone github.com/anjmao/go2proto +cd go2proto +go run main.go -f /YourPWD/example/out -p github.com/anjmao/go2proto/example/in \ No newline at end of file diff --git a/example/in/model.go b/example/in/model.go new file mode 100644 index 0000000..3dbdaea --- /dev/null +++ b/example/in/model.go @@ -0,0 +1,45 @@ +package in + +type EventSubForm struct { + Id string + + Caption string + + Rank int32 + + Fields *ArrayOfEventField +} + +type ArrayOfEventField struct { + EventField []*EventField +} + +type EventField struct { + Id string + + Name string + + FieldType string + + IsMandatory bool + + Rank int32 + + Tag string + + Items *ArrayOfEventFieldItem + + CustomFieldOrder int32 +} + +type ArrayOfEventFieldItem struct { + EventFieldItem []*EventFieldItem +} + +type EventFieldItem struct { + Id string + + Text string + + Rank int32 +} diff --git a/example/out/output.proto b/example/out/output.proto new file mode 100644 index 0000000..20d29fa --- /dev/null +++ b/example/out/output.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; +package proto; + + +message ArrayOfEventFieldItem { + repeated EventFieldItem eventFieldItem = 1; +} + +message EventSubForm { + string id = 1; + string caption = 2; + int32 rank = 3; + ArrayOfEventField fields = 4; +} + +message EventField { + string id = 1; + string name = 2; + string fieldType = 3; + bool isMandatory = 4; + int32 rank = 5; + string tag = 6; + ArrayOfEventFieldItem items = 7; + int32 customFieldOrder = 8; +} + +message ArrayOfEventField { + repeated EventField eventField = 1; +} + +message EventFieldItem { + string id = 1; + string text = 2; + int32 rank = 3; +} + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f4594ba --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/anjmao/go2proto + +go 1.12 + +require golang.org/x/tools v0.0.0-20190319232107-3f1ed9edd1b4 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..57e4efc --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190319232107-3f1ed9edd1b4 h1:4oAPsdy/MJIeaCzEMEhYwYBU/gHkXH52Xa4M+0GBHfA= +golang.org/x/tools v0.0.0-20190319232107-3f1ed9edd1b4/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/main.go b/main.go new file mode 100644 index 0000000..4c7ae5a --- /dev/null +++ b/main.go @@ -0,0 +1,180 @@ +package main + +import ( + "flag" + "go/token" + "go/types" + "golang.org/x/tools/go/packages" + "log" + "os" + "path/filepath" + "strings" + "text/template" + "unicode" + "unicode/utf8" +) + +type arrFlags []string + +func (i *arrFlags) String() string { + return "" +} + +func (i *arrFlags) Set(value string) error { + *i = append(*i, value) + return nil +} + +var ( + protoFolder = flag.String("f", "", "Proto output path.") + pkgFlags arrFlags +) + +func main() { + flag.Var(&pkgFlags, "p", "Go source packages.") + flag.Parse() + + if len(pkgFlags) == 0 || protoFolder == nil { + flag.PrintDefaults() + os.Exit(1) + } + + if err := checkOutFolder(*protoFolder); err != nil { + log.Fatal(err) + } + + pwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + + pkgs, err := loadPackages(pwd, pkgFlags) + if err != nil { + log.Fatal(err) + } + + msgs := getMessages(pkgs) + + if err := writeOutput(msgs, *protoFolder); err != nil { + log.Fatal(err) + } +} + +func checkOutFolder(path string) error { + _, err := os.Stat(path) + return err +} + +func loadPackages(pwd string, pkgs []string) ([]*packages.Package, error) { + fset := token.NewFileSet() + cfg := &packages.Config{ + Dir: pwd, + Mode: packages.LoadSyntax, + Fset: fset, + } + return packages.Load(cfg, pkgs...) +} + +type message struct { + Name string + Fields []*field +} + +type field struct { + Name string + TypeName string + Order int + IsRepeated bool +} + +func getMessages(pkgs []*packages.Package) []*message { + out := []*message{} + for _, p := range pkgs { + for _, t := range p.TypesInfo.Defs { + if t == nil { + continue + } + if !t.Exported() { + continue + } + if s, ok := t.Type().Underlying().(*types.Struct); ok { + out = appendMessage(out, t, s) + } + } + + } + return out +} + +func appendMessage(out []*message, t types.Object, s *types.Struct) []*message { + msg := &message{ + Name: t.Name(), + Fields: []*field{}, + } + + for i := 0; i < s.NumFields(); i++ { + f := s.Field(i) + if !f.Exported() { + continue + } + newField := &field{ + Name: toProtoFieldName(f.Name()), + TypeName: toProtoFieldTypeName(f), + IsRepeated: isRepeated(f), + Order: i + 1, + } + msg.Fields = append(msg.Fields, newField) + } + out = append(out, msg) + return out +} + +func toProtoFieldTypeName(f *types.Var) string { + switch f.Type().Underlying().(type) { + case *types.Basic: + return f.Type().String() + case *types.Slice, *types.Pointer: + parts := strings.Split(f.Type().String(), ".") + return parts[len(parts)-1] + } + return f.Type().String() +} + +func isRepeated(f *types.Var) bool { + _, ok := f.Type().Underlying().(*types.Slice) + return ok +} + +func toProtoFieldName(name string) string { + r, n := utf8.DecodeRuneInString(name) + return string(unicode.ToLower(r)) + name[n:] +} + +func writeOutput(msgs []*message, path string) error { + msgTemplate := `syntax = "proto3"; +package proto; + +{{range .}} +message {{.Name}} { +{{- range .Fields}} +{{- if .IsRepeated}} + repeated {{.TypeName}} {{.Name}} = {{.Order}}; +{{- else}} + {{.TypeName}} {{.Name}} = {{.Order}}; +{{- end}} +{{- end}} +} +{{end}} +` + tmpl, err := template.New("test").Parse(msgTemplate) + if err != nil { + panic(err) + } + + f, err := os.Create(filepath.Join(path, "output.proto")) + if err != nil { + return err + } + + return tmpl.Execute(f, msgs) +}