diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..882d3cf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# Contributing + +## Styleguides + +- Use default `goimports` for code formatting. +- Use default `golint`, `golangci-lint` and `go vet` for linting. +- Do not use boolean flag in function arguments. diff --git a/go.mod b/go.mod index 1edb713..efec025 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,11 @@ require ( github.com/fatih/camelcase v1.0.0 github.com/golang/snappy v0.0.2 // indirect github.com/klauspost/compress v1.11.7 // indirect - go.mongodb.org/mongo-driver v1.4.6 + go.mongodb.org/mongo-driver v1.5.0 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect - golang.org/x/mod v0.4.1 // indirect + golang.org/x/mod v0.4.2 // indirect golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/sys v0.0.0-20210326220804-49726bf1d181 // indirect golang.org/x/text v0.3.5 // indirect golang.org/x/tools v0.1.0 ) diff --git a/go.sum b/go.sum index be08c7e..17f5883 100644 --- a/go.sum +++ b/go.sum @@ -87,21 +87,22 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.mongodb.org/mongo-driver v1.4.6 h1:rh7GdYmDrb8AQSkF8yteAus8qYOgOASWDOv1BWqBXkU= -go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.5.0 h1:REddm85e1Nl0JPXGGhgZkgJdG/yOe6xvpXUcYK5WLt0= +go.mongodb.org/mongo-driver v1.5.0/go.mod h1:boiGPFqyBs5R0R5qf2ErokGRekMfwn+MqKaUyHs7wy0= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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= @@ -126,11 +127,10 @@ golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210326220804-49726bf1d181 h1:64ChN/hjER/taL4YJuA+gpLfIMT+/NFherRZixbxOhg= +golang.org/x/sys v0.0.0-20210326220804-49726bf1d181/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/internal/spec/models.go b/internal/spec/models.go index 19f3a67..7bff286 100644 --- a/internal/spec/models.go +++ b/internal/spec/models.go @@ -63,57 +63,6 @@ const ( OrderingDescending = "DESC" ) -// UpdateOperation is a method specification for update operations -type UpdateOperation struct { - Update Update - Mode QueryMode - Query QuerySpec -} - -// Update is an interface of update operation type -type Update interface { - Name() string - NumberOfArguments() int -} - -// UpdateFields is a type of update operation that update specific fields -type UpdateFields []UpdateField - -// Name returns UpdateFields name 'Fields' -func (u UpdateFields) Name() string { - return "Fields" -} - -// NumberOfArguments returns number of update fields -func (u UpdateFields) NumberOfArguments() int { - return len(u) -} - -// UpdateModel is a type of update operation that update the whole model -type UpdateModel struct { -} - -// Name returns UpdateModel name 'Model' -func (u UpdateModel) Name() string { - return "Model" -} - -// NumberOfArguments returns 1 -func (u UpdateModel) NumberOfArguments() int { - return 1 -} - -// Name returns "Update" operation name -func (o UpdateOperation) Name() string { - return "Update" -} - -// UpdateField stores mapping between field name in the model and the parameter index -type UpdateField struct { - FieldReference FieldReference - ParamIndex int -} - // DeleteOperation is a method specification for delete operations type DeleteOperation struct { Mode QueryMode diff --git a/internal/spec/models_test.go b/internal/spec/models_test.go index 396d419..ba56c14 100644 --- a/internal/spec/models_test.go +++ b/internal/spec/models_test.go @@ -43,29 +43,3 @@ func TestOperationName(t *testing.T) { }) } } - -type UpdateTypeTestCase struct { - Update spec.Update - ExpectedName string -} - -func TestUpdateTypeName(t *testing.T) { - testTable := []UpdateTypeTestCase{ - { - Update: spec.UpdateModel{}, - ExpectedName: "Model", - }, - { - Update: spec.UpdateFields{}, - ExpectedName: "Fields", - }, - } - - for _, testCase := range testTable { - t.Run(testCase.ExpectedName, func(t *testing.T) { - if testCase.Update.Name() != testCase.ExpectedName { - t.Errorf("Expected = %v\nReceived = %v", testCase.ExpectedName, testCase.Update.Name()) - } - }) - } -} diff --git a/internal/spec/parser.go b/internal/spec/parser.go index dd7f481..1d74b1e 100644 --- a/internal/spec/parser.go +++ b/internal/spec/parser.go @@ -235,79 +235,6 @@ func (p interfaceMethodParser) extractModelOrSliceReturns(returns []code.Type) ( return "", UnsupportedReturnError } -func (p interfaceMethodParser) parseUpdateOperation(tokens []string) (Operation, error) { - mode, err := p.extractIntOrBoolReturns(p.Method.Returns) - if err != nil { - return nil, err - } - - if err := p.validateContextParam(); err != nil { - return nil, err - } - - updateTokens, queryTokens := p.splitUpdateAndQueryTokens(tokens) - - update, err := p.parseUpdate(updateTokens) - if err != nil { - return nil, err - } - - querySpec, err := p.parseQuery(queryTokens, 1+update.NumberOfArguments()) - if err != nil { - return nil, err - } - - if err := p.validateQueryFromParams(p.Method.Params[update.NumberOfArguments()+1:], querySpec); err != nil { - return nil, err - } - - return UpdateOperation{ - Update: update, - Mode: mode, - Query: querySpec, - }, nil -} - -func (p interfaceMethodParser) parseUpdate(tokens []string) (Update, error) { - if len(tokens) == 0 { - requiredType := code.PointerType{ContainedType: p.StructModel.ReferencedType()} - if len(p.Method.Params) <= 1 || p.Method.Params[1].Type != requiredType { - return nil, InvalidUpdateFieldsError - } - return UpdateModel{}, nil - } - - updateFieldTokens, ok := splitByAnd(tokens) - if !ok { - return nil, InvalidUpdateFieldsError - } - - var updateFields UpdateFields - - paramIndex := 1 - for _, updateFieldToken := range updateFieldTokens { - updateFieldReference, ok := p.fieldResolver.ResolveStructField(p.StructModel, updateFieldToken) - if !ok { - return nil, NewStructFieldNotFoundError(updateFieldToken) - } - - updateFields = append(updateFields, UpdateField{ - FieldReference: updateFieldReference, - ParamIndex: paramIndex, - }) - paramIndex++ - } - - for _, field := range updateFields { - if len(p.Method.Params) <= field.ParamIndex || - field.FieldReference.ReferencedField().Type != p.Method.Params[field.ParamIndex].Type { - return nil, InvalidUpdateFieldsError - } - } - - return updateFields, nil -} - func splitByAnd(tokens []string) ([][]string, bool) { var updateFieldTokens [][]string var aggregatedToken []string diff --git a/internal/spec/update.go b/internal/spec/update.go new file mode 100644 index 0000000..6be9552 --- /dev/null +++ b/internal/spec/update.go @@ -0,0 +1,127 @@ +package spec + +import "github.com/sunboyy/repogen/internal/code" + +// UpdateOperation is a method specification for update operations +type UpdateOperation struct { + Update Update + Mode QueryMode + Query QuerySpec +} + +// Name returns "Update" operation name +func (o UpdateOperation) Name() string { + return "Update" +} + +// Update is an interface of update operation type +type Update interface { + Name() string + NumberOfArguments() int +} + +// UpdateModel is a type of update operation that update the whole model +type UpdateModel struct { +} + +// Name returns UpdateModel name 'Model' +func (u UpdateModel) Name() string { + return "Model" +} + +// NumberOfArguments returns 1 +func (u UpdateModel) NumberOfArguments() int { + return 1 +} + +// UpdateFields is a type of update operation that update specific fields +type UpdateFields []UpdateField + +// Name returns UpdateFields name 'Fields' +func (u UpdateFields) Name() string { + return "Fields" +} + +// NumberOfArguments returns number of update fields +func (u UpdateFields) NumberOfArguments() int { + return len(u) +} + +// UpdateField stores mapping between field name in the model and the parameter index +type UpdateField struct { + FieldReference FieldReference + ParamIndex int +} + +func (p interfaceMethodParser) parseUpdateOperation(tokens []string) (Operation, error) { + mode, err := p.extractIntOrBoolReturns(p.Method.Returns) + if err != nil { + return nil, err + } + + if err := p.validateContextParam(); err != nil { + return nil, err + } + + updateTokens, queryTokens := p.splitUpdateAndQueryTokens(tokens) + + update, err := p.parseUpdate(updateTokens) + if err != nil { + return nil, err + } + + querySpec, err := p.parseQuery(queryTokens, 1+update.NumberOfArguments()) + if err != nil { + return nil, err + } + + if err := p.validateQueryFromParams(p.Method.Params[update.NumberOfArguments()+1:], querySpec); err != nil { + return nil, err + } + + return UpdateOperation{ + Update: update, + Mode: mode, + Query: querySpec, + }, nil +} + +func (p interfaceMethodParser) parseUpdate(tokens []string) (Update, error) { + if len(tokens) == 0 { + requiredType := code.PointerType{ContainedType: p.StructModel.ReferencedType()} + if len(p.Method.Params) <= 1 || p.Method.Params[1].Type != requiredType { + return nil, InvalidUpdateFieldsError + } + return UpdateModel{}, nil + } + + updateFieldTokens, ok := splitByAnd(tokens) + if !ok { + return nil, InvalidUpdateFieldsError + } + + var updateFields UpdateFields + + paramIndex := 1 + for _, updateFieldToken := range updateFieldTokens { + updateFieldReference, ok := p.fieldResolver.ResolveStructField(p.StructModel, updateFieldToken) + if !ok { + return nil, NewStructFieldNotFoundError(updateFieldToken) + } + + updateFields = append(updateFields, UpdateField{ + FieldReference: updateFieldReference, + ParamIndex: paramIndex, + }) + paramIndex++ + } + + for _, field := range updateFields { + if len(p.Method.Params) <= field.ParamIndex || + field.FieldReference.ReferencedField().Type != p.Method.Params[field.ParamIndex].Type { + return nil, InvalidUpdateFieldsError + } + } + + return updateFields, nil +} diff --git a/internal/spec/update_test.go b/internal/spec/update_test.go new file mode 100644 index 0000000..fcfab6a --- /dev/null +++ b/internal/spec/update_test.go @@ -0,0 +1,33 @@ +package spec_test + +import ( + "testing" + + "github.com/sunboyy/repogen/internal/spec" +) + +type UpdateTypeTestCase struct { + Update spec.Update + ExpectedName string +} + +func TestUpdateTypeName(t *testing.T) { + testTable := []UpdateTypeTestCase{ + { + Update: spec.UpdateModel{}, + ExpectedName: "Model", + }, + { + Update: spec.UpdateFields{}, + ExpectedName: "Fields", + }, + } + + for _, testCase := range testTable { + t.Run(testCase.ExpectedName, func(t *testing.T) { + if testCase.Update.Name() != testCase.ExpectedName { + t.Errorf("Expected = %v\nReceived = %v", testCase.ExpectedName, testCase.Update.Name()) + } + }) + } +} diff --git a/main.go b/main.go index f653fc5..64e589a 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,10 @@ package main import ( "errors" "flag" + "fmt" "go/parser" "go/token" + "log" "os" "path/filepath" @@ -13,7 +15,12 @@ import ( "github.com/sunboyy/repogen/internal/spec" ) +const version = "" + func main() { + flag.Usage = printUsage + + versionPtr := flag.Bool("version", false, "print repogen version") sourcePtr := flag.String("src", "", "source file") destPtr := flag.String("dest", "", "destination file") modelPtr := flag.String("model", "", "model struct name") @@ -21,14 +28,22 @@ func main() { flag.Parse() + if *versionPtr { + printVersion() + return + } + if *sourcePtr == "" { - panic("-source flag required") + printUsage() + log.Fatal("-source flag required") } if *modelPtr == "" { - panic("-model flag required") + printUsage() + log.Fatal("-model flag required") } if *repoPtr == "" { - panic("-repo flag required") + printUsage() + log.Fatal("-repo flag required") } code, err := generateFromRequest(*sourcePtr, *modelPtr, *repoPtr) @@ -54,6 +69,19 @@ func main() { } } +func printUsage() { + fmt.Println("Usage of repogen") + flag.PrintDefaults() +} + +func printVersion() { + if version != "" { + fmt.Println(version) + } else { + fmt.Println("(devel)") + } +} + func generateFromRequest(fileName, structModelName, repositoryInterfaceName string) (string, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)