diff --git a/.gitignore b/.gitignore index 2bdc15b..735b1a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.idea/ /dist/ +/gtag # Created by https://www.gitignore.io/api/go,intellij,visualstudiocode,vim,macos # Edit at https://www.gitignore.io/?templates=go,intellij,visualstudiocode,vim,macos @@ -120,7 +121,7 @@ fabric.properties .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* diff --git a/cmd/gtag/main.go b/cmd/gtag/main.go index e429f45..e76d794 100644 --- a/cmd/gtag/main.go +++ b/cmd/gtag/main.go @@ -5,20 +5,33 @@ import ( "flag" "fmt" "os" + "strings" "github.com/gochore/tag/internal/gtag" ) var ( - file = flag.String("file", "", "source file") - name = flag.String("name", "", "struct type name") + types = flag.String("types", "", "struct types") ) func main() { flag.Parse() - _, err := gtag.Generate(context.Background(), *file, *name) + + args := flag.Args() + if *types == "" || len(args) != 1 { + printUsages() + return + } + dir := args[0] + + _, err := gtag.Generate(context.Background(), dir, strings.Split(*types, ",")) if err != nil { fmt.Println(err) os.Exit(1) } } + +func printUsages() { + fmt.Println(`gtag -types A,B dir`) + flag.PrintDefaults() +} diff --git a/go.mod b/go.mod index 823193d..2394529 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ module github.com/gochore/tag go 1.14 + +require ( + github.com/gochore/uniq v1.0.0 + github.com/pmezard/go-difflib v1.0.0 + golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 +) diff --git a/go.sum b/go.sum index e69de29..acdb4ac 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,23 @@ +github.com/gochore/uniq v1.0.0 h1:qBA8Jm9GhSojZ8vsCI2h5FJGonmItjY9NLBPUaciOYk= +github.com/gochore/uniq v1.0.0/go.mod h1:zxZBO4/6PcgNuYMrlJCUKOEEpxSejbD6R53GUpSHelk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 h1:BMFHd4OFnFtWX46Xj4DN6vvT1btiBxyq+s0orYBqcQY= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/gtag/gtag.go b/internal/gtag/gtag.go index 507c4b6..923bf52 100644 --- a/internal/gtag/gtag.go +++ b/internal/gtag/gtag.go @@ -10,6 +10,9 @@ import ( "io/ioutil" "os" "strings" + + "github.com/gochore/uniq" + "golang.org/x/tools/go/packages" ) type GenerateResult struct { @@ -17,13 +20,51 @@ type GenerateResult struct { Output string } -func Generate(ctx context.Context, file, name string) (*GenerateResult, error) { - f, err := loadFile(file) +func (r *GenerateResult) String() string { + return fmt.Sprintf("%s\n%s", r.Output, r.Content) +} + +func Generate(ctx context.Context, dir string, types []string) ([]*GenerateResult, error) { + cmd := fmt.Sprintf("gtag -types %s %s", strings.Join(types, ","), dir) + + types = types[:uniq.Strings(types)] + + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedFiles, + Context: ctx, + Dir: dir, + Env: os.Environ(), + Fset: token.NewFileSet(), + }) if err != nil { return nil, err } - fields, err := parseStructField(f, name) + var files []string + for _, pkg := range pkgs { + files = append(files, pkg.GoFiles...) + } + + var ret []*GenerateResult + for _, file := range files { + result, err := generateFile(ctx, cmd, file, types) + if err != nil { + return nil, err + } + if result != nil { + ret = append(ret, result) + } + } + for _, v := range ret { + if err := v.Commit(); err != nil { + return nil, err + } + } + return ret, nil +} + +func generateFile(ctx context.Context, cmd, file string, types []string) (*GenerateResult, error) { + f, err := loadFile(file) if err != nil { return nil, err } @@ -33,10 +74,30 @@ func Generate(ctx context.Context, file, name string) (*GenerateResult, error) { } pkg := f.Name.Name + fieldM := map[string][]string{} + for _, typ := range types { + fields, ok := parseStructField(f, typ) + if ok { + fieldM[typ] = fields + } + } + + if len(fieldM) == 0 { + return nil, nil + } + data := templateData{ Package: pkg, - Type: name, - Fields: fields, + Command: cmd, + } + + for _, typ := range types { + if fields, ok := fieldM[typ]; ok { + data.Types = append(data.Types, templateDataType{ + Name: typ, + Fields: fields, + }) + } } src, err := format.Source(execute(data)) @@ -49,10 +110,6 @@ func Generate(ctx context.Context, file, name string) (*GenerateResult, error) { Output: strings.TrimSuffix(file, ".go") + "_tag.go", } - if err := ret.Commit(); err != nil { - return nil, err - } - return ret, nil } @@ -73,7 +130,7 @@ func loadFile(name string) (*ast.File, error) { return parser.ParseFile(token.NewFileSet(), name, f, 0) } -func parseStructField(f *ast.File, name string) ([]string, error) { +func parseStructField(f *ast.File, name string) ([]string, bool) { var fields []*ast.Field found := false @@ -96,7 +153,7 @@ func parseStructField(f *ast.File, name string) ([]string, error) { }) if !found { - return nil, fmt.Errorf("can not find struct %q", name) + return nil, found } var ret []string @@ -106,5 +163,5 @@ func parseStructField(f *ast.File, name string) ([]string, error) { } } - return ret, nil + return ret, found } diff --git a/internal/gtag/gtag_test.go b/internal/gtag/gtag_test.go index 3442d61..db07e89 100644 --- a/internal/gtag/gtag_test.go +++ b/internal/gtag/gtag_test.go @@ -9,32 +9,22 @@ const testDir = "../../test/internal/" func TestGenerate(t *testing.T) { type args struct { - ctx context.Context - file string - name string + ctx context.Context + dir string + types []string } tests := []struct { name string args args - want []byte + want []*GenerateResult wantErr bool }{ { - name: "regular", + name: "reguler", args: args{ - ctx: context.Background(), - file: testDir + "regular/user.go", - name: "User", - }, - want: nil, - wantErr: false, - }, - { - name: "empty", - args: args{ - ctx: context.Background(), - file: testDir + "regular/empty.go", - name: "Empty", + ctx: context.Background(), + dir: testDir + "regular/", + types: []string{"User", "Empty", "UserName", "UserName"}, }, want: nil, wantErr: false, @@ -42,17 +32,16 @@ func TestGenerate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Generate(tt.args.ctx, tt.args.file, tt.args.name) + got, err := Generate(tt.args.ctx, tt.args.dir, tt.args.types) if (err != nil) != tt.wantErr { t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) return } //if !reflect.DeepEqual(got, tt.want) { - // t.Errorf("Generate() got = %s, want %s", got, tt.want) + // t.Errorf("Generate() got = %v, want %v", got, tt.want) //} - t.Logf("%s", got) - if err := got.Commit(); err != nil { - t.Error(err) + for _, result := range got { + t.Log(result) } }) } diff --git a/internal/gtag/template.go b/internal/gtag/template.go index 4cae6ed..8cb15f3 100644 --- a/internal/gtag/template.go +++ b/internal/gtag/template.go @@ -7,24 +7,31 @@ import ( type templateData struct { Package string - Type string - Fields []string + Command string + Types []templateDataType +} + +type templateDataType struct { + Name string + Fields []string } const templateLayout = ` // Code generated by gtag. DO NOT EDIT. // See: https://github.com/wolfogre/gtag -//go:generate gtag +//go:generate {{.Command}} package {{.Package}} import "reflect" +{{- range .Types}} + var ( - valueOf{{.Type}} = {{.Type}}{} - typeOf{{.Type}} = reflect.TypeOf(valueOf{{.Type}}) + valueOf{{.Name}} = {{.Name}}{} + typeOf{{.Name}} = reflect.TypeOf(valueOf{{.Name}}) -{{$type := .Type}} +{{$type := .Name}} {{- range .Fields}} _ = valueOf{{$type}}.{{.}} fieldOf{{$type}}{{.}}, _ = typeOf{{$type}}.FieldByName("{{.}}") @@ -46,6 +53,8 @@ func ({{$type}}) Tags(tag string) {{$type}}Tags { } } +{{- end}} + ` func execute(data templateData) []byte { diff --git a/internal/gtag/template_test.go b/internal/gtag/template_test.go index 35afcd8..42fd5be 100644 --- a/internal/gtag/template_test.go +++ b/internal/gtag/template_test.go @@ -2,6 +2,8 @@ package gtag import ( "testing" + + "github.com/pmezard/go-difflib/difflib" ) func Test_execute(t *testing.T) { @@ -18,43 +20,47 @@ func Test_execute(t *testing.T) { args: args{ data: templateData{ Package: "test", - Type: "Test", - Fields: []string{"A", "b"}, + Types: []templateDataType{ + { + Name: "test", + Fields: []string{"A", "b"}, + }, + }, }, }, want: ` // Code generated by gtag. DO NOT EDIT. // See: https://github.com/wolfogre/gtag -//go:generate gtag +//go:generate package test import "reflect" var ( - valueOfTest = Test{} - typeOfTest = reflect.TypeOf(valueOfTest) + valueOftest = test{} + typeOftest = reflect.TypeOf(valueOftest) - _ = valueOfTest.A - fieldOfTestA, _ = typeOfTest.FieldByName("A") - tagOfTestA = fieldOfTestA.Tag + _ = valueOftest.A + fieldOftestA, _ = typeOftest.FieldByName("A") + tagOftestA = fieldOftestA.Tag - _ = valueOfTest.b - fieldOfTestb, _ = typeOfTest.FieldByName("b") - tagOfTestb = fieldOfTestb.Tag + _ = valueOftest.b + fieldOftestb, _ = typeOftest.FieldByName("b") + tagOftestb = fieldOftestb.Tag ) -type TestTags struct { +type testTags struct { A string b string } -func (Test) Tags(tag string) TestTags { - return TestTags{ - A: tagOfTestA.Get(tag), - b: tagOfTestb.Get(tag), +func (test) Tags(tag string) testTags { + return testTags{ + A: tagOftestA.Get(tag), + b: tagOftestb.Get(tag), } } @@ -63,8 +69,16 @@ func (Test) Tags(tag string) TestTags { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := execute(tt.args.data); string(got) != tt.want { + if got := string(execute(tt.args.data)); got != tt.want { t.Errorf("execute() = %v, want %v", got, tt.want) + diff := difflib.UnifiedDiff{ + A: difflib.SplitLines(got), + B: difflib.SplitLines(tt.want), + FromFile: "got", + ToFile: "want", + } + text, _ := difflib.GetUnifiedDiffString(diff) + t.Log(text) } }) } diff --git a/test/internal/regular/empty_tag.go b/test/internal/regular/empty_tag.go index bdd7c1b..cc47358 100644 --- a/test/internal/regular/empty_tag.go +++ b/test/internal/regular/empty_tag.go @@ -1,7 +1,7 @@ // Code generated by gtag. DO NOT EDIT. // See: https://github.com/wolfogre/gtag -//go:generate gtag +//go:generate gtag -types User,Empty,UserName,UserName ../../test/internal/regular/ package regular import "reflect" diff --git a/test/internal/regular/user.go b/test/internal/regular/user.go index d254cd3..3af0409 100644 --- a/test/internal/regular/user.go +++ b/test/internal/regular/user.go @@ -1,8 +1,13 @@ package regular type User struct { - Id int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` + Id int `json:"id"` + Name UserName `json:"name"` + Email string `json:"email"` age int } + +type UserName struct { + First string `json:"first"` + Last string `json:"last"` +} diff --git a/test/internal/regular/user_tag.go b/test/internal/regular/user_tag.go index e58505a..f5f7172 100644 --- a/test/internal/regular/user_tag.go +++ b/test/internal/regular/user_tag.go @@ -1,7 +1,7 @@ // Code generated by gtag. DO NOT EDIT. // See: https://github.com/wolfogre/gtag -//go:generate gtag +//go:generate gtag -types User,Empty,UserName,UserName ../../test/internal/regular/ package regular import "reflect" @@ -42,3 +42,28 @@ func (User) Tags(tag string) UserTags { age: tagOfUserage.Get(tag), } } + +var ( + valueOfUserName = UserName{} + typeOfUserName = reflect.TypeOf(valueOfUserName) + + _ = valueOfUserName.First + fieldOfUserNameFirst, _ = typeOfUserName.FieldByName("First") + tagOfUserNameFirst = fieldOfUserNameFirst.Tag + + _ = valueOfUserName.Last + fieldOfUserNameLast, _ = typeOfUserName.FieldByName("Last") + tagOfUserNameLast = fieldOfUserNameLast.Tag +) + +type UserNameTags struct { + First string + Last string +} + +func (UserName) Tags(tag string) UserNameTags { + return UserNameTags{ + First: tagOfUserNameFirst.Get(tag), + Last: tagOfUserNameLast.Get(tag), + } +} diff --git a/test/internal/regular/user_tag_test.go b/test/internal/regular/user_tag_test.go index 8495b78..37dc436 100644 --- a/test/internal/regular/user_tag_test.go +++ b/test/internal/regular/user_tag_test.go @@ -8,7 +8,7 @@ import ( func TestUser_Tags(t *testing.T) { type fields struct { Id int - Name string + Name UserName Email string age int } @@ -24,8 +24,11 @@ func TestUser_Tags(t *testing.T) { { name: "regular", fields: fields{ - Id: 0, - Name: "", + Id: 0, + Name: UserName{ + First: "", + Last: "", + }, Email: "", age: 0, }, @@ -54,3 +57,45 @@ func TestUser_Tags(t *testing.T) { }) } } + +func TestUserName_Tags(t *testing.T) { + type fields struct { + First string + Last string + } + type args struct { + tag string + } + tests := []struct { + name string + fields fields + args args + want UserNameTags + }{ + { + name: "regular", + fields: fields{ + First: "", + Last: "", + }, + args: args{ + tag: "json", + }, + want: UserNameTags{ + First: "first", + Last: "last", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + us := UserName{ + First: tt.fields.First, + Last: tt.fields.Last, + } + if got := us.Tags(tt.args.tag); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Tags() = %v, want %v", got, tt.want) + } + }) + } +}