Skip to content

Commit 030529a

Browse files
Merge pull request #73 from samueldasilvadev/generating-struct-from-db
Generating struct from db
2 parents debd70e + 4b65183 commit 030529a

File tree

15 files changed

+342
-43
lines changed

15 files changed

+342
-43
lines changed

cmd/cli/generator/generator.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ func NewGenerator() *Generator {
2525
}
2626

2727
func (g *Generator) DeclareCommands(cmd *cobra.Command) {
28-
g.initFlags(cmd)
2928
createDomain := &cobra.Command{
3029
Use: "create-domain",
3130
Short: "Create a new domain service",
@@ -51,6 +50,19 @@ func (g *Generator) DeclareCommands(cmd *cobra.Command) {
5150
)
5251
}
5352

53+
func (g *Generator) DeclareDomainCreatorFromSchema(cmd *cobra.Command) {
54+
generateFromDb := &cobra.Command{
55+
Use: "create-domain-from-schema",
56+
Short: "create-domain-from-schema <schema name> <table name>",
57+
Long: "this command will read the chosen schema and create a crud for each table (if you pass a specific table it will generate only this)",
58+
PreRun: g.BootGenerator,
59+
Run: g.GenerateFromDb,
60+
}
61+
cmd.AddCommand(
62+
generateFromDb,
63+
)
64+
}
65+
5466
func (g *Generator) CreateDomain(_ *cobra.Command, args []string) {
5567
if len(args) == 0 {
5668
g.Logger.Error(errors.New("empty args"))
@@ -76,6 +88,21 @@ func (g *Generator) DestroyDomain(_ *cobra.Command, args []string) {
7688
)
7789
}
7890

91+
func (g *Generator) GenerateFromDb(_ *cobra.Command, args []string) {
92+
if len(args) == 0 {
93+
g.Logger.Error(errors.New("empty args"))
94+
}
95+
table := ""
96+
if len(args) > 1 {
97+
table = args[1]
98+
}
99+
generator.NewCodeGenerator(
100+
g.Logger,
101+
g.Flags.validator,
102+
g.Flags.domainType,
103+
).ReadFromSchema(args[0], table)
104+
}
105+
79106
func (g *Generator) BootGenerator(_ *cobra.Command, _ []string) {
80107
conf := config.NewConfig()
81108
err := conf.LoadEnvs()
@@ -92,8 +119,3 @@ func (g *Generator) BootGenerator(_ *cobra.Command, _ []string) {
92119
l.Boot()
93120
g.Logger = l
94121
}
95-
96-
func (g *Generator) initFlags(cmd *cobra.Command) {
97-
cmd.PersistentFlags().StringVar(&g.Flags.domain, "domain", "", "Describe name to New Domain")
98-
cmd.MarkFlagsMutuallyExclusive("domain")
99-
}

cmd/cli/migrator/migrator.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ func (m *Migrator) DeclareCommands(cmd *cobra.Command) {
3434
Run: m.Inspect,
3535
},
3636
&cobra.Command{
37-
Use: "generate",
38-
Short: "generate HCL from database",
37+
Use: "generate-schema-from-db",
38+
Short: "generate-schema-from-db <schema name>",
39+
Long: "generate HCL schema from database connected on env",
3940
PreRun: m.BootMigrator,
4041
Run: m.Generate,
4142
},
@@ -52,9 +53,13 @@ func (m *Migrator) Inspect(_ *cobra.Command, _ []string) {
5253
migratorInstance.Inspect()
5354
}
5455

55-
func (m *Migrator) Generate(_ *cobra.Command, _ []string) {
56+
func (m *Migrator) Generate(_ *cobra.Command, args []string) {
57+
schema := ""
58+
if len(args) > 1 {
59+
schema = args[1]
60+
}
5661
migratorInstance := migrator.NewMigrator(m.dsn, m.dsnTest)
57-
migratorInstance.Generate()
62+
migratorInstance.Generate(schema)
5863
}
5964

6065
func (m *Migrator) BootMigrator(_ *cobra.Command, _ []string) {

tools/generator/config.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ table "{{domain}}" {
3737
//tableEnd
3838
3939
$repeat$"""
40+
"{{domainType}}" = """type {{domainPascalCase}} struct {
41+
ID string `db:"id"`
42+
}"""
43+
"{{pkType}}" = "ID string `param:\"id\"`"
44+
"{{pkName}}" = "ID"
45+
"{{pkDbName}}" = "id"
46+
"{{dataType}}" = ""
47+
"{{createServiceData}}" = "ID: s.idCreator.Create(),"
48+
"{{editServiceData}}" = "ID: id,"
49+
"{{optionalImports}}" = ""
50+
"{{idVar}}" = ""
4051

4152
[stubs.crud.domain]
4253
toPath = "internal/application/domain/{{domain}}/"

tools/generator/generateFromDb.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package generator
2+
3+
import (
4+
"fmt"
5+
"github.com/hashicorp/hcl/v2"
6+
"github.com/hashicorp/hcl/v2/hclwrite"
7+
"os"
8+
"strings"
9+
)
10+
11+
func (cg *CodeGenerator) ReadFromSchema(schema string, table string) {
12+
file, hclErr := cg.getHclFile(schema)
13+
if hclErr != nil {
14+
fmt.Println("Error validating files:", hclErr)
15+
return
16+
}
17+
for _, block := range file.Body().Blocks() {
18+
if table != "" && block.Labels()[0] != table {
19+
continue
20+
}
21+
err := cg.handleHclBlock(block)
22+
if err != nil {
23+
fmt.Println("Error validating files:", err)
24+
return
25+
}
26+
}
27+
}
28+
29+
func (cg *CodeGenerator) getHclFile(schema string) (*hclwrite.File, error) {
30+
filepath := fmt.Sprintf("tools/migrator/schema/%s.my.hcl", schema)
31+
content, err := os.ReadFile(filepath)
32+
if err != nil {
33+
fmt.Println("Error reading file:", err)
34+
return nil, err
35+
}
36+
file, _ := hclwrite.ParseConfig(content, filepath, hcl.Pos{Line: 1, Column: 1})
37+
return file, err
38+
}
39+
40+
func (cg *CodeGenerator) handleHclBlock(block *hclwrite.Block) error {
41+
if block.Type() == "schema" {
42+
return nil
43+
}
44+
if len(block.Labels()) == 0 {
45+
return nil
46+
}
47+
48+
tableName := block.Labels()[0]
49+
replacers := cg.generateDomainFromHclBlock(block, tableName)
50+
validateErr := cg.validateFiles(tableName)
51+
if validateErr != nil {
52+
return validateErr
53+
}
54+
stubs := GetStubsConfig(cg.Logger, cg.config, cg.domainType)
55+
cg.GenerateFilesFromStubs(stubs, replacers)
56+
return nil
57+
}
58+
59+
func (cg *CodeGenerator) generateDomainFromHclBlock(block *hclwrite.Block, tableName string) map[string]string {
60+
cg.needImportTime = new(bool)
61+
cg.primaryKey = new(string)
62+
cg.pkType = new(string)
63+
cg.isIntId = new(bool)
64+
*cg.needImportTime = false
65+
*cg.isIntId = false
66+
domain := cg.generateDomainStruct(block.Body().Blocks(), tableName, cg.primaryKey, cg.pkType)
67+
dataType := cg.generateStruct(block.Body().Blocks(), nil, nil, cg.generateDeclarationLine)
68+
createAttrData := cg.generateStruct(block.Body().Blocks(), nil, nil, cg.generateCreateAttributionLine)
69+
editAttrData := cg.generateStruct(block.Body().Blocks(), nil, nil, cg.generateEditAttributionLine)
70+
replacers := GetReplacersConfig(cg.config, cg.domainType, []string{tableName})
71+
replacers["{{domainType}}"] = domain
72+
replacers["{{dataType}}"] = dataType
73+
replacers["{{pkDbName}}"] = *cg.primaryKey
74+
replacers["{{pkName}}"] = PascalCase(*cg.primaryKey)
75+
replacers["{{pkType}}"] = *cg.pkType
76+
replacers["{{createServiceData}}"] = createAttrData
77+
replacers["{{editServiceData}}"] = editAttrData
78+
if *cg.needImportTime {
79+
replacers["{{optionalImports}}"] = "\"time\""
80+
}
81+
if !*cg.isIntId {
82+
replacers["{{idVar}}"] = "id := s.idCreator.Create()"
83+
}
84+
return replacers
85+
}
86+
87+
func (cg *CodeGenerator) generateDomainStruct(blocks []*hclwrite.Block, tableName string, pk, pkType *string) string {
88+
*pk = cg.findPkOnBlocks(blocks)
89+
structString := "type " + PascalCase(tableName) + " struct {\n"
90+
structString += cg.generateStruct(blocks, pk, pkType, cg.generateDeclarationLine)
91+
structString += "}"
92+
return structString
93+
}
94+
95+
func (cg *CodeGenerator) generateStruct(blocks []*hclwrite.Block, pk, pkType *string, strFormationFunc func(string, string, string, string) string) string {
96+
declarationString := "\n"
97+
for _, block := range blocks {
98+
if block.Type() == "column" {
99+
token, ok := block.Body().Attributes()["type"]
100+
if !ok {
101+
continue
102+
}
103+
tokenStr := string(token.Expr().BuildTokens(nil).Bytes())
104+
goType := cg.dbTypesToGoTypes(tokenStr)
105+
106+
if pk != nil && block.Labels()[0] == *pk {
107+
*pkType = fmt.Sprintf("%s string `param:\"id\"`", PascalCase(*pk))
108+
}
109+
110+
declarationString = strFormationFunc(
111+
declarationString,
112+
PascalCase(block.Labels()[0]),
113+
goType,
114+
block.Labels()[0],
115+
)
116+
}
117+
}
118+
return declarationString
119+
}
120+
121+
func (cg *CodeGenerator) generateDeclarationLine(str, name, goType, dbTag string) string {
122+
if name == PascalCase(*cg.primaryKey) && strings.Contains(goType, "int") {
123+
return fmt.Sprintf(
124+
"%s %s %s `db:\"%s\"`\n",
125+
str,
126+
name,
127+
"*string",
128+
dbTag,
129+
)
130+
}
131+
return fmt.Sprintf(
132+
"%s %s %s `db:\"%s\"`\n",
133+
str,
134+
name,
135+
goType,
136+
dbTag,
137+
)
138+
}
139+
140+
func (cg *CodeGenerator) generateCreateAttributionLine(str, name, goType, _ string) string {
141+
if name == PascalCase(*cg.primaryKey) {
142+
if strings.Contains(goType, "int") {
143+
*cg.isIntId = true
144+
return str
145+
}
146+
return fmt.Sprintf(
147+
"%s %s: &id,\n",
148+
str,
149+
name,
150+
)
151+
}
152+
return fmt.Sprintf(
153+
"%s %s: data.%s,\n",
154+
str,
155+
name,
156+
name,
157+
)
158+
}
159+
160+
func (cg *CodeGenerator) generateEditAttributionLine(str, name, goType, _ string) string {
161+
if name == PascalCase(*cg.primaryKey) {
162+
if strings.Contains(goType, "int") {
163+
return str
164+
}
165+
return fmt.Sprintf(
166+
"%s %s: &id,\n",
167+
str,
168+
name,
169+
)
170+
}
171+
return fmt.Sprintf(
172+
"%s %s: data.%s,\n",
173+
str,
174+
name,
175+
name,
176+
)
177+
}
178+
179+
func (cg *CodeGenerator) findPkOnBlocks(blocks []*hclwrite.Block) string {
180+
str := ""
181+
for _, block := range blocks {
182+
if block.Type() == "primary_key" {
183+
token, ok := block.Body().Attributes()["columns"]
184+
if !ok {
185+
return ""
186+
}
187+
pkAttr := string(token.Expr().BuildTokens(nil).Bytes())
188+
str = cg.getColumnFromAttrString(pkAttr)
189+
}
190+
}
191+
return str
192+
}
193+
194+
func (cg *CodeGenerator) getColumnFromAttrString(attrStr string) string {
195+
str := strings.Replace(attrStr, "[", "", -1)
196+
str = strings.Replace(str, "]", "", -1)
197+
str = strings.Split(str, ".")[1]
198+
return str
199+
}
200+
201+
func (cg *CodeGenerator) dbTypesToGoTypes(typo string) string {
202+
dbTypesMap := map[string]string{
203+
" bigint": "*int64",
204+
" bit": "* ",
205+
" char": "*string",
206+
" decimal": "*float64",
207+
" float": "*float32",
208+
" double": "*float64",
209+
" int": "*int",
210+
" longtext": "*string",
211+
" mediumint": "*int",
212+
" mediumtext": "*string",
213+
" smallint": "*int16",
214+
" text": "*string",
215+
" time": "*string",
216+
" timestamp": "*string",
217+
" datetime": "*time.Time",
218+
" date": "*string",
219+
" tinyint": "*int8",
220+
" tinytext": "*string",
221+
" varbinary": "*string",
222+
" varchar": "*string",
223+
" json": "*string",
224+
}
225+
226+
GolangType, ok := dbTypesMap[typo]
227+
if ok {
228+
if GolangType == "*time.Time" {
229+
*cg.needImportTime = true
230+
}
231+
return GolangType
232+
}
233+
234+
if strings.Contains(typo, "char") {
235+
return "*string"
236+
}
237+
238+
if strings.Contains(typo, "double") {
239+
return "*float64"
240+
}
241+
242+
if strings.Contains(typo, "year") {
243+
return "*string"
244+
}
245+
246+
if strings.Contains(typo, "decimal") {
247+
return "*float64"
248+
}
249+
250+
return typo
251+
}

0 commit comments

Comments
 (0)