Add CLI support for model generation
This commit is contained in:
parent
4e34a6d21b
commit
1295b207bb
205
cli/generator.go
Normal file
205
cli/generator.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FieldInfo struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateModelEntity membuat GORM-ready model, entity, dan file migrasi
|
||||||
|
func GenerateModelEntity(modelName string, fields []FieldInfo, baseFolder string) error {
|
||||||
|
if modelName == "" || len(fields) == 0 {
|
||||||
|
return fmt.Errorf("modelName and fields must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
modelsFolder := fmt.Sprintf("%s/internal/models", baseFolder)
|
||||||
|
entityFolder := fmt.Sprintf("%s/internal/entity", baseFolder)
|
||||||
|
migrationsFolder := fmt.Sprintf("%s/internal/migrations", baseFolder)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(modelsFolder, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(entityFolder, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(migrationsFolder, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tulis model
|
||||||
|
modelFile := fmt.Sprintf("%s/%s.go", modelsFolder, strings.ToLower(modelName))
|
||||||
|
if err := writeFile(modelFile, modelName, fields, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tulis entity
|
||||||
|
entityFile := fmt.Sprintf("%s/%s.go", entityFolder, strings.ToLower(modelName))
|
||||||
|
if err := writeFile(entityFile, modelName, fields, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleName, err := getModuleName(baseFolder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := createMigrationFile(modelName, migrationsFolder, moduleName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err:= updateMasterMigration(migrationsFolder, modelName);err !=nil{
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(filename, modelName string, fields []FieldInfo, isModel bool) error {
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var lines []string
|
||||||
|
for _, field := range fields {
|
||||||
|
lines = append(lines, fmt.Sprintf("\t%s %s `gorm:\"column:%s\"`", capitalize(field.Name), mapType(field.Type), field.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
var content string
|
||||||
|
if isModel {
|
||||||
|
content = fmt.Sprintf(`package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type %s struct {
|
||||||
|
ID uint `+"`gorm:\"primaryKey\"`"+`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `+"`gorm:\"index\"`"+`
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
`, modelName, joinLines(lines))
|
||||||
|
} else {
|
||||||
|
content = fmt.Sprintf(`package entity
|
||||||
|
|
||||||
|
type %s struct {
|
||||||
|
ID uint
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
`, modelName, joinLines(lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.WriteString(content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Create file:", filename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMigrationFile(modelName, migrationsFolder, moduleName string) error {
|
||||||
|
timestamp := time.Now().Format("20060102_150405")
|
||||||
|
filename := fmt.Sprintf("%s/%s_create_%s.go", migrationsFolder, timestamp, strings.ToLower(modelName))
|
||||||
|
|
||||||
|
content := fmt.Sprintf(`package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"%s/configs"
|
||||||
|
"%s/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Up migrates table %s
|
||||||
|
func Up%s() {
|
||||||
|
configs.DB.AutoMigrate(&models.%s{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down rolls back table %s
|
||||||
|
func Down%s() {
|
||||||
|
configs.DB.Migrator().DropTable(&models.%s{})
|
||||||
|
}
|
||||||
|
`, moduleName, moduleName, modelName, modelName, modelName, modelName, modelName, modelName)
|
||||||
|
|
||||||
|
return os.WriteFile(filename, []byte(content), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMasterMigration(migrationsFolder, modelName string) error {
|
||||||
|
masterFile := fmt.Sprintf("%s/migrate.go", migrationsFolder)
|
||||||
|
|
||||||
|
// Jika belum ada, buat file baru
|
||||||
|
if _, err := os.Stat(masterFile); os.IsNotExist(err) {
|
||||||
|
content := `package migrations
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func MigrateAll() {
|
||||||
|
fmt.Println("Running migrations...")
|
||||||
|
Up` + modelName + `()
|
||||||
|
// Add other migrations here
|
||||||
|
fmt.Println("Migrations completed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RollbackAll() {
|
||||||
|
fmt.Println("Rolling back migrations...")
|
||||||
|
Down` + modelName + `()
|
||||||
|
// Add other rollbacks here
|
||||||
|
fmt.Println("Rollback completed!")
|
||||||
|
}
|
||||||
|
`
|
||||||
|
return os.WriteFile(masterFile, []byte(content), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika sudah ada, append import Up/Down baru jika belum ada
|
||||||
|
data, err := os.ReadFile(masterFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := string(data)
|
||||||
|
if !strings.Contains(text, "Up"+modelName+"()") {
|
||||||
|
text = strings.Replace(text, "// Add other migrations here", "Up"+modelName+"()\n\t// Add other migrations here", 1)
|
||||||
|
}
|
||||||
|
if !strings.Contains(text, "Down"+modelName+"()") {
|
||||||
|
text = strings.Replace(text, "// Add other rollbacks here", "Down"+modelName+"()\n\t// Add other rollbacks here", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(masterFile, []byte(text), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapType(t string) string {
|
||||||
|
switch t {
|
||||||
|
case "string":
|
||||||
|
return "string"
|
||||||
|
case "int":
|
||||||
|
return "int"
|
||||||
|
case "float":
|
||||||
|
return "float64"
|
||||||
|
case "bool":
|
||||||
|
return "bool"
|
||||||
|
default:
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func capitalize(s string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.ToUpper(s[:1]) + s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinLines(lines []string) string {
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "\n" + strings.Join(lines, "\n")
|
||||||
|
}
|
60
cli/generatorDbConfig.go
Normal file
60
cli/generatorDbConfig.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateDBConfig membuat file configs/db.go untuk berbagai DB
|
||||||
|
func GenerateDBConfig(baseFolder, dbType string) error {
|
||||||
|
configsFolder := fmt.Sprintf("%s/configs", baseFolder)
|
||||||
|
if err := os.MkdirAll(configsFolder, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbFile := fmt.Sprintf("%s/db.go", configsFolder)
|
||||||
|
|
||||||
|
dbType = strings.ToLower(dbType)
|
||||||
|
var importLine, openLine string
|
||||||
|
|
||||||
|
switch dbType {
|
||||||
|
case "postgres":
|
||||||
|
importLine = `"gorm.io/driver/postgres"`
|
||||||
|
openLine = `gorm.Open(postgres.Open(dsn), &gorm.Config{})`
|
||||||
|
case "mysql":
|
||||||
|
importLine = `"gorm.io/driver/mysql"`
|
||||||
|
openLine = `gorm.Open(mysql.Open(dsn), &gorm.Config{})`
|
||||||
|
case "sqlite":
|
||||||
|
importLine = `"gorm.io/driver/sqlite"`
|
||||||
|
openLine = `gorm.Open(sqlite.Open(dsn), &gorm.Config{})`
|
||||||
|
case "sqlserver":
|
||||||
|
importLine = `"gorm.io/driver/sqlserver"`
|
||||||
|
openLine = `gorm.Open(sqlserver.Open(dsn), &gorm.Config{})`
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unsupported DB type: %s", dbType)
|
||||||
|
}
|
||||||
|
|
||||||
|
content := fmt.Sprintf(`package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
%s
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
func InitDB(dsn string) {
|
||||||
|
var err error
|
||||||
|
DB, err = %s
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to connect to database: %%v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Database connected via GORM")
|
||||||
|
}
|
||||||
|
`, importLine, openLine)
|
||||||
|
|
||||||
|
return os.WriteFile(dbFile, []byte(content), 0644)
|
||||||
|
}
|
31
cli/getmodule.go
Normal file
31
cli/getmodule.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func getModuleName(baseFolder string) (string, error) {
|
||||||
|
file, err := os.Open(baseFolder + "/go.mod")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("cannot open go.mod: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if strings.HasPrefix(line, "module ") {
|
||||||
|
return strings.TrimSpace(strings.TrimPrefix(line, "module ")), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return "", fmt.Errorf("error reading go.mod: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("module name not found in go.mod")
|
||||||
|
}
|
47
cli/relation.go
Normal file
47
cli/relation.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RelationType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
One2Many RelationType = "one2many"
|
||||||
|
Many2Many RelationType = "many2many"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RelationInfo struct {
|
||||||
|
FieldName string
|
||||||
|
Target string
|
||||||
|
Type RelationType
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRelation menambahkan relasi ke file model
|
||||||
|
func AddRelation(modelFile string, relations []RelationInfo, modelName string) error {
|
||||||
|
content, err := os.ReadFile(modelFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(content), "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
if strings.TrimSpace(line) == "}" {
|
||||||
|
for _, r := range relations {
|
||||||
|
var relLine string
|
||||||
|
if r.Type == One2Many {
|
||||||
|
relLine = fmt.Sprintf("\t%s []%s `gorm:\"foreignKey:%sID\"`", r.FieldName, r.Target, modelName)
|
||||||
|
} else if r.Type == Many2Many {
|
||||||
|
relLine = fmt.Sprintf("\t%s []%s `gorm:\"many2many:%s_%s\"`", r.FieldName, r.Target, strings.ToLower(modelName), strings.ToLower(r.Target))
|
||||||
|
}
|
||||||
|
lines = append(lines[:i], append([]string{relLine}, lines[i:]...)...)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(modelFile, []byte(strings.Join(lines, "\n")), 0644)
|
||||||
|
}
|
77
cmd/cli.go
Normal file
77
cmd/cli.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// --- Flags ---
|
||||||
|
modelName := flag.String("name", "", "Model name, e.g.: User")
|
||||||
|
attributes := flag.String("attributes", "", "Model attributes, e.g.: name:string,email:string")
|
||||||
|
baseFolder := flag.String("folder", ".", "Base folder of the project")
|
||||||
|
relations := flag.String("relations", "", "Relations, e.g.: Products:Product:one2many,Tags:Tag:many2many")
|
||||||
|
initDB := flag.Bool("init", false, "Generate configs/db.go for supported databases")
|
||||||
|
dbType := flag.String("db", "postgres", "Database type: postgres, mysql, sqlite, sqlserver")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *initDB {
|
||||||
|
if err := cli.GenerateDBConfig(*baseFolder, *dbType); err != nil {
|
||||||
|
log.Fatal("Failed to create db.go:", err)
|
||||||
|
}
|
||||||
|
fmt.Println("configs/db.go created successfully for", *dbType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *modelName == "" || *attributes == "" {
|
||||||
|
fmt.Println("Use : go run main.go --name User --attributes name:string,email:string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := parseFields(*attributes)
|
||||||
|
|
||||||
|
if err := cli.GenerateModelEntity(*modelName, fields, *baseFolder); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *relations != "" {
|
||||||
|
rels := parseRelations(*relations)
|
||||||
|
modelFile := fmt.Sprintf("%s/internal/models/%s.go", *baseFolder, strings.ToLower(*modelName))
|
||||||
|
if err := cli.AddRelation(modelFile, rels, *modelName); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Helpers ---
|
||||||
|
func parseFields(attr string) []cli.FieldInfo {
|
||||||
|
var fields []cli.FieldInfo
|
||||||
|
for _, a := range strings.Split(attr, ",") {
|
||||||
|
parts := strings.Split(a, ":")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
log.Fatalf("Attribute format is invalid: %s", a)
|
||||||
|
}
|
||||||
|
fields = append(fields, cli.FieldInfo{Name: parts[0], Type: parts[1]})
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRelations(rel string) []cli.RelationInfo {
|
||||||
|
var rels []cli.RelationInfo
|
||||||
|
for _, r := range strings.Split(rel, ",") {
|
||||||
|
parts := strings.Split(r, ":")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
log.Fatalf("Relation format is invalid: %s", r)
|
||||||
|
}
|
||||||
|
rt := cli.RelationType(parts[2])
|
||||||
|
rels = append(rels, cli.RelationInfo{FieldName: parts[0], Target: parts[1], Type: rt})
|
||||||
|
}
|
||||||
|
return rels
|
||||||
|
}
|
8
internal/entity/user.go
Normal file
8
internal/entity/user.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID uint
|
||||||
|
|
||||||
|
Name string `gorm:"column:name"`
|
||||||
|
Email string `gorm:"column:email"`
|
||||||
|
}
|
16
internal/migrations/20250821_051926_create_user.go
Normal file
16
internal/migrations/20250821_051926_create_user.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm/configs"
|
||||||
|
"gorm.io/gorm/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Up migrates table User
|
||||||
|
func UpUser() {
|
||||||
|
configs.DB.AutoMigrate(&models.User{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down rolls back table User
|
||||||
|
func DownUser() {
|
||||||
|
configs.DB.Migrator().DropTable(&models.User{})
|
||||||
|
}
|
17
internal/migrations/migrate.go
Normal file
17
internal/migrations/migrate.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func MigrateAll() {
|
||||||
|
fmt.Println("Running migrations...")
|
||||||
|
UpUser()
|
||||||
|
// Add other migrations here
|
||||||
|
fmt.Println("Migrations completed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RollbackAll() {
|
||||||
|
fmt.Println("Rolling back migrations...")
|
||||||
|
DownUser()
|
||||||
|
// Add other rollbacks here
|
||||||
|
fmt.Println("Rollback completed!")
|
||||||
|
}
|
16
internal/models/user.go
Normal file
16
internal/models/user.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID uint `gorm:"primaryKey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
|
||||||
|
Name string `gorm:"column:name"`
|
||||||
|
Email string `gorm:"column:email"`
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user