Skip to content

Commit

Permalink
do
Browse files Browse the repository at this point in the history
  • Loading branch information
anjmao committed Mar 20, 2019
0 parents commit 0ca8439
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
.idea
go2proto
21 changes: 21 additions & 0 deletions 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.
9 changes: 9 additions & 0 deletions 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
45 changes: 45 additions & 0 deletions 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
}
36 changes: 36 additions & 0 deletions 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;
}

5 changes: 5 additions & 0 deletions go.mod
@@ -0,0 +1,5 @@
module github.com/anjmao/go2proto

go 1.12

require golang.org/x/tools v0.0.0-20190319232107-3f1ed9edd1b4
6 changes: 6 additions & 0 deletions 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=
180 changes: 180 additions & 0 deletions 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)
}

0 comments on commit 0ca8439

Please sign in to comment.