17 Commits
dst ... main

Author SHA1 Message Date
56ffba1883 fix: template 2025-03-15 20:47:17 +07:00
8e7b0ab472 feat: search 2025-03-15 18:43:43 +07:00
b26de27165 fix: create method 2025-03-12 16:20:10 +07:00
50edeb07ca fix: readme 2025-03-12 16:15:37 +07:00
31c5c49589 fix: sorting method generation 2025-03-12 16:08:36 +07:00
51d8531792 upd: README.md 2025-03-12 16:01:53 +07:00
da19da2d63 update: README.md 2025-03-12 15:58:34 +07:00
e1012c234c feat: sorting 2025-03-12 15:57:30 +07:00
43c4fe36c0 upd: README 2025-03-12 13:42:21 +07:00
a51a2e063d fix: imports weren't added 2025-03-12 13:41:51 +07:00
ec87f02976 upd: README 2025-03-12 13:14:14 +07:00
c141b2a941 upd: README 2025-03-12 12:47:25 +07:00
6e9f5dd1eb feat: README 2025-03-12 12:46:02 +07:00
4a5ecc647d Merge pull request 'upd: template, imports' (#2) from dst into main
Reviewed-on: #2
2025-03-12 12:28:10 +07:00
c1b1d2d5f1 delete swap file 2025-02-16 21:50:49 +07:00
17047d2c71 feat: license 2025-02-16 21:50:23 +07:00
aed112a117 Merge pull request 'Migration to DST library' (#1) from dst into main
Reviewed-on: #1
2025-02-16 21:04:52 +07:00
8 changed files with 198 additions and 69 deletions

21
LICENSE.md Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Georgiy Derbenev
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.

13
README.md Normal file
View File

@@ -0,0 +1,13 @@
# Crudgen
> Generate crud's at speed of thought
## Install
```shell
go install git.gogacoder.ru/NTO/crudgen/cmd/crudgen@v1.0.16
```
## Run
Specify path to internal directory
```shell
crudgen -p internal
```

View File

@@ -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)
}

1
go.mod
View File

@@ -9,6 +9,5 @@ require github.com/dave/dst v0.27.3
require (
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/tools v0.31.0 // indirect
)

13
go.sum
View File

@@ -6,20 +6,9 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=

View File

@@ -2,7 +2,7 @@ package internal
const CreateRawTemplate = `func (service *{{.ServiceName}}) Create(item {{.EntityType}}) ({{.EntityType}}, error) {
utils.ReplaceEmptySlicesWithNil(&item)
err := dal.Author.Create(&item)
err := dal.{{.EntityType}}.Create(&item)
if err != nil {
return item, err
}
@@ -31,7 +31,7 @@ const GetByIdRawTemplate = `func (service *{{.ServiceName}}) GetById(id uint) (*
const UpdateRawTemplate = `func (service *{{.ServiceName}}) Update(item {{.EntityType}}) ({{.EntityType}}, error) {
utils.ReplaceEmptySlicesWithNil(&item)
_, err := dal.Author.Updates(&item)
_, err := dal.{{.EntityType}}.Updates(&item)
if err != nil {
return item, err
}
@@ -54,3 +54,13 @@ const CountRawTemplate = `func (service *{{.ServiceName}}) Count() (int64, error
amount, err := dal.{{.EntityType}}.Count()
return amount, err
}`
const SortedByOrderTemplate = `func (service *{{.ServiceName}}) SortedByOrder(fieldsSortingOrder []utils.SortField) ([]*{{.EntityType}}, error) {
return utils.SortByOrder(fieldsSortingOrder, {{.EntityType}}{})
}`
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}

View File

@@ -22,12 +22,15 @@ const GetByIdMethod = "GetById"
const UpdateMethod = "Update"
const DeleteMethod = "Delete"
const CountMethod = "Count"
const SortedByOrderMethod = "SortedByOrder"
var RawTemplates = map[string]string{
CreateMethod: CreateRawTemplate,
GetAllMethod: GetAllRawTemplate,
GetByIdMethod: GetByIdRawTemplate,
UpdateMethod: UpdateRawTemplate,
DeleteMethod: DeleteRawTemplate,
CountMethod: CountRawTemplate,
CreateMethod: CreateRawTemplate,
GetAllMethod: GetAllRawTemplate,
GetByIdMethod: GetByIdRawTemplate,
UpdateMethod: UpdateRawTemplate,
DeleteMethod: DeleteRawTemplate,
CountMethod: CountRawTemplate,
SortedByOrderMethod: SortedByOrderTemplate,
SearchByAllStringFields: SearchByAllStringFields,
}

View File

@@ -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,10 +177,32 @@ func importExists(file *dst.File, importPath string) bool {
return false
}
func MaintainImports(file *dst.File) error {
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
break
}
}
if importDecl == nil {
modified = true
importDecl = &dst.GenDecl{
Tok: token.IMPORT,
Specs: []dst.Spec{},
}
file.Decls = append([]dst.Decl{importDecl}, file.Decls...)
}
for _, importPath := range ServiceImports {
if !importExists(file, importPath) {
file.Imports = append(file.Imports, &dst.ImportSpec{
modified = true
importDecl.Specs = append(importDecl.Specs, &dst.ImportSpec{
Path: &dst.BasicLit{
Kind: token.STRING,
Value: `"` + importPath + `"`,
@@ -180,7 +210,8 @@ func MaintainImports(file *dst.File) error {
})
}
}
return nil
return modified, nil
}
func GenerateCrudMethodCode(methodName string, context CrudTemplatesContext) string {
@@ -220,14 +251,15 @@ 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 []string{CreateMethod, GetAllMethod, GetByIdMethod, UpdateMethod, DeleteMethod, CountMethod} {
for _, methodName := range implementedMethods {
methodCode := GenerateCrudMethodCode(methodName, templateContext)
methodDecl, err := MethodCodeToDeclaration(methodCode)
fmt.Printf("%s\n", methodCode)
@@ -235,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)
@@ -260,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
}
@@ -280,6 +316,7 @@ func ImplementMethod(file *dst.File, methodDecl *dst.FuncDecl, reimplement bool)
}
if reimplement || !methodImplemented {
modified = true
file.Decls = append(
decls,
&dst.FuncDecl{
@@ -293,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
}