feat: skeleton
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
9
.idea/crudgen.iml
generated
Normal file
9
.idea/crudgen.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/crudgen.iml" filepath="$PROJECT_DIR$/.idea/crudgen.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/cmd/crudgen" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
34
cmd/crudgen/main.go
Normal file
34
cmd/crudgen/main.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crudgen/internal"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ImplementServices(mainPkgDir string, reimplement bool) {
|
||||||
|
modelsNames, err := internal.GetStructNames(filepath.Join(mainPkgDir, "models"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found models: %#v\n", modelsNames)
|
||||||
|
|
||||||
|
for _, modelName := range modelsNames {
|
||||||
|
log.Print(modelName)
|
||||||
|
err := internal.ImplementService(mainPkgDir, modelName, reimplement)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error implement service for model %s: %s\n", modelName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
projectPath := flag.String("p", ".", "project path")
|
||||||
|
reimplement := flag.Bool("f", false, "pass -f to allow tool to overwrite exist functions and service structure")
|
||||||
|
flag.Parse()
|
||||||
|
ImplementServices(*projectPath, *reimplement)
|
||||||
|
}
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module crudgen
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
require golang.org/x/tools v0.28.0
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||||
|
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||||
55
internal/parser.go
Normal file
55
internal/parser.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStructNames(modelsDir string) ([]string, error) {
|
||||||
|
var structs []string
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
|
||||||
|
files, err := os.ReadDir(modelsDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if !strings.HasSuffix(file.Name(), ".go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(modelsDir, file.Name())
|
||||||
|
fileAst, err := parser.ParseFile(fset, filePath, nil, parser.AllErrors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, decl := range fileAst.Decls {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if genDecl.Tok != token.TYPE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, spec := range genDecl.Specs {
|
||||||
|
typeSpec, ok := spec.(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := typeSpec.Type.(*ast.StructType); ok {
|
||||||
|
if typeSpec.Name != nil {
|
||||||
|
structs = append(structs, typeSpec.Name.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return structs, nil
|
||||||
|
|
||||||
|
}
|
||||||
30
internal/templates.go
Normal file
30
internal/templates.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CrudTemplateContext struct {
|
||||||
|
ServiceName string
|
||||||
|
EntityType string
|
||||||
|
EntityPlural string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ServiceImports = []string{
|
||||||
|
"app/internal/dal",
|
||||||
|
"app/internal/models",
|
||||||
|
//"errors"
|
||||||
|
"gorm.io/gen/field",
|
||||||
|
//"gorm.io/gorm"
|
||||||
|
}
|
||||||
|
|
||||||
|
var GetAllRawTemplate = `func (service *{{.ServiceName}}) GetAll() ([]*{{.EntityType}}, error) {
|
||||||
|
var {{.EntityPlurar}} []*{{.EntityType}}
|
||||||
|
{{.EntityPlural}}, err := dal.{{.EntityType}}.Preload(field.Associations).Find()
|
||||||
|
return {{.EntityPlural}}, err
|
||||||
|
}`
|
||||||
|
|
||||||
|
|
||||||
|
func ToPlural(entityName string) string {
|
||||||
|
return strings.ToLower(entityName) + "s"
|
||||||
|
}
|
||||||
151
internal/writer.go
Normal file
151
internal/writer.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/printer"
|
||||||
|
"go/token"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ImplementServiceStruct(entityName string, file *ast.File, reimplement bool) {
|
||||||
|
serviceName := entityName + "Service"
|
||||||
|
isServiceStructDeclarated := false
|
||||||
|
var insertPos int
|
||||||
|
|
||||||
|
for i, decl := range file.Decls {
|
||||||
|
genDecl, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if genDecl.Tok == token.IMPORT {
|
||||||
|
insertPos = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if genDecl.Tok != token.TYPE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, spec := range genDecl.Specs {
|
||||||
|
typeSpec, ok := spec.(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := typeSpec.Type.(*ast.StructType); ok {
|
||||||
|
if typeSpec.Name != nil && typeSpec.Name.Name == serviceName {
|
||||||
|
isServiceStructDeclarated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isServiceStructDeclarated && !reimplement {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceStruct := &ast.GenDecl{
|
||||||
|
Tok: token.TYPE,
|
||||||
|
Specs: []ast.Spec{
|
||||||
|
&ast.TypeSpec{
|
||||||
|
Name: ast.NewIdent(serviceName),
|
||||||
|
Type: &ast.StructType{
|
||||||
|
Fields: &ast.FieldList{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Decls = append(file.Decls[:insertPos], append([]ast.Decl{serviceStruct}, file.Decls[insertPos:]...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importExists(fset *token.FileSet, file *ast.File, importPath string) bool {
|
||||||
|
for _, group := range astutil.Imports(fset, file) {
|
||||||
|
for _, imp := range group {
|
||||||
|
if imp.Name == nil && imp.Path.Value == `"`+importPath+`"` {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func MaintainImports(fileSet *token.FileSet, file *ast.File) error {
|
||||||
|
for _, importPath := range ServiceImports {
|
||||||
|
if !importExists(fileSet, file, importPath) {
|
||||||
|
if !astutil.AddImport(fileSet, file, importPath) {
|
||||||
|
err := fmt.Sprintf("%s: Failed to add import: %s", fileSet.Position(file.Pos()), importPath)
|
||||||
|
return errors.New(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImplementMethods(structName string, methodName string, template string, node ast.Node) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateServiceFileIfNotExists(filePath string) error {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write([]byte("package services\n"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to write file: %s", filePath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImplementService(mainPkgPath string, modelName string, reimplement bool) error {
|
||||||
|
serviceRelativePath := fmt.Sprintf("services/%s.go", strings.ToLower(modelName))
|
||||||
|
filePath := filepath.Join(mainPkgPath, serviceRelativePath)
|
||||||
|
|
||||||
|
err := CreateServiceFileIfNotExists(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
serviceFile, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Parsing error: %s: %v", mainPkgPath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = MaintainImports(fset, serviceFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ImplementServiceStruct(modelName, serviceFile, reimplement)
|
||||||
|
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error occured to open `%s` service file: %v", modelName, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = printer.Fprint(file, fset, serviceFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error occurred to writing changes in `%s` service file: %v", modelName, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user