diff --git a/README.md b/README.md index f16ce19..7702fbf 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Install ```shell -go install git.gogacoder.ru/NTO/crudgen/cmd/crudgen@v1.0.14 +go install git.gogacoder.ru/NTO/crudgen/cmd/crudgen@v1.0.15 ``` ## Run diff --git a/cmd/crudgen/main.go b/cmd/crudgen/main.go index e5e6681..06c573f 100644 --- a/cmd/crudgen/main.go +++ b/cmd/crudgen/main.go @@ -4,30 +4,66 @@ import ( "flag" "git.gogacoder.ru/NTO/crudgen/internal" "log" + "os/exec" "path/filepath" + "strings" ) -func ImplementServices(mainPkgDir string, reimplement bool) { +func ImplementServices(mainPkgDir string, reimplement bool) (modified bool) { modelsNames, err := internal.GetStructNames(filepath.Join(mainPkgDir, "models")) if err != nil { log.Printf("Error: %s\n", err) return } + var wasModified bool log.Printf("Found models: %v\n", modelsNames) for _, modelName := range modelsNames { - err := internal.ImplementService(mainPkgDir, modelName, reimplement) + codeModified, err := internal.ImplementService(mainPkgDir, modelName, reimplement) + if codeModified { + wasModified = true + } if err != nil { log.Printf("Error implement service for model %s: %s\n", modelName, err) } } + return wasModified +} + +func runPostHook(postHook *string, wasModified bool) { + if wasModified && postHook != nil && *postHook != "" { + log.Printf("Running post hook %s\n", *postHook) + args := strings.Fields(*postHook) + + var cmd *exec.Cmd + + if len(args) == 0 { + log.Printf("Empty post hook %s\n", *postHook) + return + } + if len(args) == 1 { + cmd = exec.Command(args[0]) + } else { + cmd = exec.Command(args[0], args[1:]...) + } + + output, err := cmd.CombinedOutput() + if err != nil { + log.Fatalf("Error running post hook for %s: %s\n", *postHook, err) + } else { + log.Printf("Post hook output: %s\n", string(output)) + } + } } func main() { log.SetFlags(0) projectPath := flag.String("p", ".", "project path") reimplement := flag.Bool("f", false, "pass -f to allow tool to overwrite exist functions and service structure") + postHook := flag.String("h", "", "post hook to run command after code modifications") + flag.Parse() - ImplementServices(*projectPath, *reimplement) + wasModified := ImplementServices(*projectPath, *reimplement) + runPostHook(postHook, wasModified) } diff --git a/internal/raw_templates.go b/internal/raw_templates.go index c2d5ddb..451799b 100644 --- a/internal/raw_templates.go +++ b/internal/raw_templates.go @@ -55,8 +55,12 @@ const CountRawTemplate = `func (service *{{.ServiceName}}) Count() (int64, error return amount, err }` -const SortedByOrderTemplate = `func (service *{{.ServiceName}}) SortedByOrder(fieldsSortOrder map[string]string) ([]*{{.EntityType}}, error) { - return utils.SortByOrder(fieldsSortOrder, {{.EntityType}}{}) +const SortedByOrderTemplate = `func (service *{{.ServiceName}}) SortedByOrder(fieldsSortingOrder []utils.SortField) ([]*{{.EntityType}}, error) { + return utils.SortByOrder(fieldsSortingOrder, {{.EntityType}}{}) }` -var implementedMethods = []string{CreateMethod, GetAllMethod, GetByIdMethod, UpdateMethod, DeleteMethod, CountMethod, SortedByOrderMethod} +const SearchByAllStringFields = `func (service *{{.ServiceName}}) SearchByAllTextFields(phrase string) ([]*{{.EntityType}}, error) { + return utils.FindPhraseByStringFields[{{.EntityType}}](phrase, {{.EntityType}}{}) +}` + +var implementedMethods = []string{CreateMethod, GetAllMethod, GetByIdMethod, UpdateMethod, DeleteMethod, CountMethod, SortedByOrderMethod, SearchByAllStringFields} diff --git a/internal/templates.go b/internal/templates.go index c7a15c5..d0338cb 100644 --- a/internal/templates.go +++ b/internal/templates.go @@ -25,11 +25,12 @@ const CountMethod = "Count" const SortedByOrderMethod = "SortedByOrder" var RawTemplates = map[string]string{ - CreateMethod: CreateRawTemplate, - GetAllMethod: GetAllRawTemplate, - GetByIdMethod: GetByIdRawTemplate, - UpdateMethod: UpdateRawTemplate, - DeleteMethod: DeleteRawTemplate, - CountMethod: CountRawTemplate, - SortedByOrderMethod: SortedByOrderTemplate, + CreateMethod: CreateRawTemplate, + GetAllMethod: GetAllRawTemplate, + GetByIdMethod: GetByIdRawTemplate, + UpdateMethod: UpdateRawTemplate, + DeleteMethod: DeleteRawTemplate, + CountMethod: CountRawTemplate, + SortedByOrderMethod: SortedByOrderTemplate, + SearchByAllStringFields: SearchByAllStringFields, } diff --git a/internal/writer.go b/internal/writer.go index 3fe4a9f..a05b680 100644 --- a/internal/writer.go +++ b/internal/writer.go @@ -16,11 +16,13 @@ import ( "github.com/dave/dst/decorator" ) -func ImplementServiceStruct(modelName string, file *dst.File, reimplement bool) { +func ImplementServiceStruct(modelName string, file *dst.File, reimplement bool) (wasModified bool) { serviceName := modelName + "Service" - isServiceStructDefined := false - var insertPos int - var decls []dst.Decl + var ( + isServiceStructDefined bool + insertPos int + decls []dst.Decl + ) for i, decl := range file.Decls { genDecl, ok := decl.(*dst.GenDecl) @@ -53,7 +55,7 @@ func ImplementServiceStruct(modelName string, file *dst.File, reimplement bool) } if isServiceStructDefined && !reimplement { - return + return false } serviceStruct := &dst.GenDecl{ @@ -69,13 +71,16 @@ func ImplementServiceStruct(modelName string, file *dst.File, reimplement bool) } file.Decls = append(decls[:insertPos], append([]dst.Decl{serviceStruct}, decls[insertPos:]...)...) + return true } -func ImplementModelAlias(modelName string, file *dst.File) { - isAliasDefined := false +func ImplementModelAlias(modelName string, file *dst.File) (wasModified bool) { aliasTypeStandard := fmt.Sprintf("models.%s", CapitalizeFirst(modelName)) - var insertPos int - var decls []dst.Decl + var ( + insertPos int + decls []dst.Decl + isAliasDefined bool + ) for i, decl := range file.Decls { genDecl, ok := decl.(*dst.GenDecl) @@ -147,7 +152,7 @@ func ImplementModelAlias(modelName string, file *dst.File) { Name: &dst.Ident{ Name: modelName, }, - Assign: true, // quick and dirty + Assign: true, Type: &dst.Ident{ Name: aliasTypeStandard, }, @@ -157,6 +162,9 @@ func ImplementModelAlias(modelName string, file *dst.File) { if !isAliasDefined { file.Decls = append(decls[:insertPos], append([]dst.Decl{&typeAlias}, decls[insertPos:]...)...) + return true + } else { + return false } } @@ -169,8 +177,12 @@ func importExists(file *dst.File, importPath string) bool { return false } -func MaintainImports(file *dst.File) error { - var importDecl *dst.GenDecl +func MaintainImports(file *dst.File) (wasModified bool, e error) { + var ( + modified bool + importDecl *dst.GenDecl + ) + for _, decl := range file.Decls { if genDecl, ok := decl.(*dst.GenDecl); ok && genDecl.Tok == token.IMPORT { importDecl = genDecl @@ -179,6 +191,7 @@ func MaintainImports(file *dst.File) error { } if importDecl == nil { + modified = true importDecl = &dst.GenDecl{ Tok: token.IMPORT, Specs: []dst.Spec{}, @@ -188,6 +201,7 @@ func MaintainImports(file *dst.File) error { for _, importPath := range ServiceImports { if !importExists(file, importPath) { + modified = true importDecl.Specs = append(importDecl.Specs, &dst.ImportSpec{ Path: &dst.BasicLit{ Kind: token.STRING, @@ -197,7 +211,7 @@ func MaintainImports(file *dst.File) error { } } - return nil + return modified, nil } func GenerateCrudMethodCode(methodName string, context CrudTemplatesContext) string { @@ -237,12 +251,13 @@ func MethodCodeToDeclaration(methodCode string) (*dst.FuncDecl, error) { return methodDecl, nil } -func ImplementCrudMethods(modelName string, serviceName string, file *dst.File, reimplement bool) error { +func ImplementCrudMethods(modelName string, serviceName string, file *dst.File, reimplement bool) (wasModified bool, e error) { templateContext := CrudTemplatesContext{ ServiceName: serviceName, EntityType: modelName, EntityPlural: ToPlural(modelName), } + modified := reimplement for _, methodName := range implementedMethods { methodCode := GenerateCrudMethodCode(methodName, templateContext) @@ -252,23 +267,27 @@ func ImplementCrudMethods(modelName string, serviceName string, file *dst.File, fmt.Println(methodDecl) panic(err) } - err = ImplementMethod(file, methodDecl, reimplement) + + methodModified, err := ImplementMethod(file, methodDecl, reimplement) if err != nil { - return err + return false, err + } + if methodModified { + modified = true } } - return nil + return modified, nil } -func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool) error { - var decls []dst.Decl - methodImplemented := false - +func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool) (wasModified bool, e error) { + var ( + decls []dst.Decl + methodImplemented bool + ) + modified := reimplement methodStructure := methodDecl.Recv.List[0].Names[0].Name methodName := methodDecl.Name.Name - log.Printf("Standard method structure: %s\n", methodStructure) - log.Printf("Standard method name: %s\n", methodName) for _, decl := range file.Decls { decls = append(decls, decl) @@ -277,14 +296,14 @@ func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool) continue } if len(funcDecl.Recv.List) > 0 && len(funcDecl.Recv.List[0].Names) > 0 { - fmt.Printf("Method structure: %s\n", funcDecl.Recv.List[0].Names[0].Name) - fmt.Printf("Method name: %s\n", funcDecl.Name.Name) + log.Printf("Method structure: %s\n", funcDecl.Recv.List[0].Names[0].Name) + log.Printf("Method name: %s\n", funcDecl.Name.Name) if funcDecl.Recv.List[0].Names[0].Name == methodStructure { if funcDecl.Name != nil && funcDecl.Name.Name == methodName { if methodImplemented { err := fmt.Sprintf("`%s` method redeclarated for struct `%s`", methodName, methodStructure) log.Println(err) - return errors.New(err) + return false, errors.New(err) } else { methodImplemented = true } @@ -297,6 +316,7 @@ func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool) } if reimplement || !methodImplemented { + modified = true file.Decls = append( decls, &dst.FuncDecl{ @@ -310,72 +330,93 @@ func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool) ) } - return nil + return modified, nil } -func CreateServiceFileIfNotExists(filePath string) error { +func CreateServiceFileIfNotExists(filePath string) (wasModified bool, e error) { + var modified bool if _, err := os.Stat(filePath); err != nil { // file wasn't created f, err := os.Create(filePath) if err != nil { log.Fatalf("Failed to create file: %s", filePath) - return err + return false, err } _, err = f.Write([]byte("package services\n")) if err != nil { log.Fatalf("Failed to write file: %s", filePath) - return err + return false, err } + modified = true defer f.Close() } - return nil + return modified, nil } -func ImplementService(mainPkgPath string, modelName string, reimplement bool) error { +func ImplementService(mainPkgPath string, modelName string, reimplement bool) (wasModified bool, e error) { serviceRelativePath := fmt.Sprintf("services/%s.go", strings.ToLower(modelName)) filePath := filepath.Join(mainPkgPath, serviceRelativePath) serviceName := modelName + "Service" + modified := reimplement - err := CreateServiceFileIfNotExists(filePath) + fileModified, err := CreateServiceFileIfNotExists(filePath) if err != nil { - return err + return false, err + } + if fileModified { + modified = true } fset := token.NewFileSet() serviceFile, err := decorator.ParseFile(fset, filePath, nil, parser.ParseComments) if err != nil { log.Fatalf("Parsing error: %s: %v", mainPkgPath, err) - return err + return false, err } - err = MaintainImports(serviceFile) + importsModified, err := MaintainImports(serviceFile) if err != nil { - return err + return false, err + } + if importsModified { + modified = true } - ImplementModelAlias(modelName, serviceFile) - ImplementServiceStruct(modelName, serviceFile, reimplement) - err = ImplementCrudMethods(modelName, serviceName, serviceFile, reimplement) + aliasAdded := ImplementModelAlias(modelName, serviceFile) + if aliasAdded { + modified = true + } + serviceStructModified := ImplementServiceStruct(modelName, serviceFile, reimplement) + if serviceStructModified { + modified = true + } + + methodsModified, err := ImplementCrudMethods(modelName, serviceName, serviceFile, reimplement) if err != nil { - return err + return false, err + } + if methodsModified { + modified = true } file, err := os.Create(filePath) if err != nil { - return errors.New(fmt.Sprintf("Error occured to open `%s` service file: %v", modelName, err)) + errMessage := errors.New(fmt.Sprintf("Error occured to open `%s` service file: %v", modelName, err)) + return false, errMessage } err = decorator.Fprint(file, serviceFile) if err != nil { - return errors.New( + errMessage := errors.New( fmt.Sprintf("Error occurred to writing changes in `%s` service file: %v", modelName, err), ) + return false, errMessage } defer file.Close() - return nil + return modified, nil }