diff --git a/example.yml b/example.yml index 8b547fd..fb5ad02 100644 --- a/example.yml +++ b/example.yml @@ -5,4 +5,11 @@ db: pk: uuid fields: - name: f1 + - name: test2 + pk: int + fields: + - name: f1 + - name: f2 + fks: + - name: test1 \ No newline at end of file diff --git a/lib/backend.go b/lib/backend.go index d92daf3..cb78ab7 100644 --- a/lib/backend.go +++ b/lib/backend.go @@ -11,8 +11,8 @@ func Backend() { generateDB() } +// Генерирование структуры БД func generateDB() { - // TODO if err := os.MkdirAll(filepath.Join(AppConfig.OutdirBackend, "db", "model"), 0775); err != nil { log.Fatal(err) } @@ -30,6 +30,9 @@ func generateModelBase() { if err := WriteTmplFile("tmpl/backend/db/transaction.tmpl", filepath.Join(AppConfig.OutdirBackend, "db", "transaction.go")); err != nil { log.Fatal(err) } + if err := PrepareTmplFile("tmpl/backend/go.tmpl", Project, filepath.Join(AppConfig.OutdirBackend, "go.mod")); err != nil { + log.Fatal(err) + } if err := PrepareTmplFile("tmpl/backend/db/db.tmpl", Project, filepath.Join(AppConfig.OutdirBackend, "db", "db.go")); err != nil { log.Fatal(err) } diff --git a/lib/config.go b/lib/config.go index 10e2c6a..3115d12 100644 --- a/lib/config.go +++ b/lib/config.go @@ -11,6 +11,7 @@ import ( var AppConfig struct { IsYaml bool IsJson bool + IsHelp bool Help bool // Вывести справку на экран Filename string // Файл с метоописанием в формате YAML или JSON @@ -28,20 +29,13 @@ func parseArgs() { flag.StringVar(&AppConfig.Filename, "metafile", "", "Путь к файлу с описанием для генерации кода") flag.StringVar(&AppConfig.OutdirBackend, "outdir-backend", "", "Путь к директории для сохранения генерируемого кода сервера") flag.StringVar(&AppConfig.OutdirFrontend, "outdir-frontend", "", "Путь к директории для сохранения генерируемого кода клиенат") - help := flag.Bool("help", false, "Справка") + flag.BoolVar(&AppConfig.IsHelp, "help", false, "Справка") flag.Parse() flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0]) flag.PrintDefaults() } - if help != nil && *help { - flag.Usage() - os.Exit(0) - } - if AppConfig.Filename == "" { - flag.Usage() - os.Exit(0) - } + } func HelpPrint() { diff --git a/lib/frontend.go b/lib/frontend.go index cfbca77..8de56de 100644 --- a/lib/frontend.go +++ b/lib/frontend.go @@ -1,5 +1,6 @@ package lib +// Генерирование исходного кода для клиента func Frontend() { // TODO } diff --git a/lib/lib_test.go b/lib/lib_test.go new file mode 100644 index 0000000..0d70124 --- /dev/null +++ b/lib/lib_test.go @@ -0,0 +1,31 @@ +package lib + +import ( + "testing" + + "git.ymnuktech.ru/ymnuk/yt-gen-app/structs" +) + +func TestFieldType(t *testing.T) { + if fieldDBName(&structs.Field{Name: "Field"}) != "field" { + t.Fail() + } + if fieldDBName(&structs.Field{Name: "Field_NAME"}) != "field_name" { + t.Fail() + } + + if fieldDBName(&structs.Field{Name: "fIELD_nAMe"}) != "field_name" { + t.Fail() + } +} + +func TestDBModelAttrib(t *testing.T) { + str := fieldDescript(&structs.Field{ + Name: "Field_naME1", + Type: "int", + Description: "Hello world", + }) + if str != "`gorm:\"column:field_name1;type:INT;comment:Hello world\"`" { + t.Fail() + } +} diff --git a/lib/prepare-metadata.go b/lib/prepare-metadata.go index a47c6ce..810bc22 100644 --- a/lib/prepare-metadata.go +++ b/lib/prepare-metadata.go @@ -1,7 +1,9 @@ package lib import ( + "fmt" "log" + "strings" "git.ymnuktech.ru/ymnuk/yt-gen-app/structs" uuid "github.com/satori/go.uuid" @@ -24,9 +26,32 @@ func PrepareMetadata(project *structs.Project) { } if len(project.DB.Tables[i].Fields) > 0 { for j := range project.DB.Tables[i].Fields { + project.DB.Tables[i].Fields[j].Name = fieldDBName(&project.DB.Tables[i].Fields[j]) if project.DB.Tables[i].Fields[j].ID == uuid.Nil { project.DB.Tables[i].Fields[j].ID = uuid.NewV4() } + project.DB.Tables[i].Fields[j].Type = strings.Trim(project.DB.Tables[i].Fields[j].Type, " ") + project.DB.Tables[i].Fields[j].Type = strings.ToLower(project.DB.Tables[i].Fields[j].Type) + if project.DB.Tables[i].Fields[j].Type == "" { + project.DB.Tables[i].Fields[j].Type = "text" + } + + } + } + if project.DB.Tables[i].Recursive { + project.DB.Tables[i].FkFields = append(project.DB.Tables[i].FkFields, structs.Field{ + Name: "parent_id", + Description: "Recursive foreign key for self table", + }) + switch project.DB.Tables[i].Pk { + case "uuid": + project.DB.Tables[i].FkFields[len(project.DB.Tables[i].FkFields)-1].Type = "uuid" + case "int": + project.DB.Tables[i].FkFields[len(project.DB.Tables[i].FkFields)-1].Type = "bigint" + case "bigintint": + project.DB.Tables[i].FkFields[len(project.DB.Tables[i].FkFields)-1].Type = "bigint" + default: + log.Fatalf("Error primary key type '%s' in table '%s'", project.DB.Tables[i].Pk, project.DB.Tables[i].Name) } } if len(project.DB.Tables[i].FKs) > 0 { @@ -48,6 +73,13 @@ func PrepareMetadata(project *structs.Project) { } } } + if project.DB.Tables[i].FKs[j].TableName == "" { + log.Fatalf("Error foreign key for '%s' table", project.DB.Tables[i].FKs[j].TableID.String()) + } + project.DB.Tables[i].FkFields = append(project.DB.Tables[i].FkFields, structs.Field{ + Name: fmt.Sprintf("%s_id", strings.ToLower(project.DB.Tables[i].FKs[j].TableName)), + Description: fmt.Sprintf("Foreign key for \"%s\" table", project.DB.Tables[i].FKs[j].TableName), + }) } } } diff --git a/lib/template.go b/lib/template.go index a81a4e3..786fb12 100644 --- a/lib/template.go +++ b/lib/template.go @@ -4,39 +4,117 @@ import ( "bufio" "bytes" "embed" + "fmt" "log" "os" "strings" "text/template" + + "git.ymnuktech.ru/ymnuk/yt-gen-app/structs" ) //go:embed tmpl/* var content embed.FS var funcMap = template.FuncMap{ - "fieldName": fieldName, + "fieldName": fieldName, + "fieldType": fieldType, + "fieldDescript": fieldDescript, } -func fieldName(name string) (res string) { - res = strings.Trim(name, " ") - if res == "" { +func fieldName(field *structs.Field) string { + if field == nil { + log.Fatal("Field is null") + } + field.Name = strings.Trim(field.Name, " ") + if field.Name == "" { log.Fatal("Shoudl be set name for field") } - asRunes := []rune(res) + asRunes := []rune(field.Name) - asRunes[0] = []rune(strings.ToUpper(string([]rune(res)[0])))[0] - res = string(asRunes) - i := strings.Index(res, "_") + asRunes[0] = []rune(strings.ToUpper(string([]rune(field.Name)[0])))[0] + field.Name = string(asRunes) + i := strings.Index(field.Name, "_") for i != -1 { - asRunes := []rune(res) - if i >= len(res) { + asRunes := []rune(field.Name) + if i >= len(field.Name) { break } asRunes[i+1] = []rune(strings.ToUpper(string(asRunes[i+1])))[0] asRunes = append(asRunes[:i-1], asRunes[i+1:]...) - res = string(asRunes) - i = strings.Index(res, "_") + field.Name = string(asRunes) + i = strings.Index(field.Name, "_") } + return field.Name +} + +// Возвращает имя в БД +func fieldDBName(field *structs.Field) (res string) { + res = strings.Trim(field.Name, " ") + if res == "" { + log.Fatal("Shoudl be set name for field") + } + res = strings.ToLower(res) + return +} + +func fieldType(field *structs.Field) string { + if field == nil { + log.Fatal("field is empty") + } + field.Type = strings.ToLower(strings.Trim(field.Type, " ")) + if field.Type == "" { + field.Type = "text" + } + switch field.Type { + case "text": + return "*string" + case "string": + return "*string" + case "int": + return "*int" + case "bigint": + return "*int64" + default: + log.Fatalf("Unknow format %s", field.Type) + } + return "" +} + +func fieldTypeDB(field *structs.Field) string { + field.Type = strings.ToLower(strings.Trim(field.Type, " ")) + switch field.Type { + case "text": + return "TEXT" + case "string": + tmp := "VARCHAR" + if field.Length != nil && *field.Length == 0 { + tmp += fmt.Sprintf("(%d)", *field.Length) + } else { + tmp += "(255)" + } + //return "TEXT" + return tmp + case "int": + return "INT" + case "bigint": + return "BIGINT" + default: + log.Fatalf("Unknow format %s", field.Type) + } + return "" +} + +// Генерирование описания таблиц в БД +func fieldDescript(field *structs.Field) (str string) { + // TODO + str = fmt.Sprintf("`gorm:\"column:%s;type:%s", fieldDBName(field), fieldTypeDB(field)) + field.Description = strings.Trim(field.Description, " ") + if field.Description != "" { + str += fmt.Sprintf(";comment:%s", field.Description) + } + str += "\"`" + return } diff --git a/lib/tmpl/backend/db/model/base-int.tmpl b/lib/tmpl/backend/db/model/base-int.tmpl index a75ff74..b07d6d6 100644 --- a/lib/tmpl/backend/db/model/base-int.tmpl +++ b/lib/tmpl/backend/db/model/base-int.tmpl @@ -1,8 +1,15 @@ +// This file generated automatic. Do not change this! + package models +import ( + "time" +) + + type BaseInt struct { ID uint64 `gorm:"type:autoIncrement;primaryKey" json:"id,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"` - // DeletedAt gorm.DeletedAt `sql:"index" json:"deletedAt"` + DeletedAt gorm.DeletedAt `sql:"index" json:"deletedAt"` } diff --git a/lib/tmpl/backend/db/model/base.tmpl b/lib/tmpl/backend/db/model/base.tmpl index 584217b..0cbd673 100644 --- a/lib/tmpl/backend/db/model/base.tmpl +++ b/lib/tmpl/backend/db/model/base.tmpl @@ -1,3 +1,5 @@ +// This file generated automatic. Do not change this! + package models import ( @@ -11,7 +13,7 @@ type Base struct { ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"` - // DeletedAt gorm.DeletedAt `sql:"index" json:"deletedAt"` + DeletedAt gorm.DeletedAt `sql:"index" json:"deletedAt"` } // BeforeCreate will set a UUID rather than numeric ID. diff --git a/lib/tmpl/backend/db/model/model.tmpl b/lib/tmpl/backend/db/model/model.tmpl index 9fa88ed..b6022d2 100644 --- a/lib/tmpl/backend/db/model/model.tmpl +++ b/lib/tmpl/backend/db/model/model.tmpl @@ -1,3 +1,5 @@ +// This file generated automatic. Do not change this! + package model import ( @@ -13,6 +15,6 @@ type {{.Name}} struct { BaseInt {{ end }} {{ range $index, $field := .Fields }} - {{ fieldName $field.Name }} {{ $field.Type }} + {{ fieldName $field }} {{ fieldType $field }} {{ fieldDescript $field }} {{ end }} } diff --git a/lib/tmpl/backend/go.tmpl b/lib/tmpl/backend/go.tmpl new file mode 100644 index 0000000..6f53213 --- /dev/null +++ b/lib/tmpl/backend/go.tmpl @@ -0,0 +1,3 @@ +module {{ .Name }} + +go 1.20 \ No newline at end of file diff --git a/main.go b/main.go index 81f98ae..870ca70 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "flag" "log" "os" @@ -10,11 +11,16 @@ import ( "gopkg.in/yaml.v3" ) -func init() { - lib.PrepareParams() -} - func main() { + lib.PrepareParams() + if lib.AppConfig.IsHelp { + flag.Usage() + os.Exit(0) + } + if lib.AppConfig.Filename == "" { + flag.Usage() + os.Exit(0) + } lib.Project = &structs.Project{} if _, err := os.Stat(lib.AppConfig.Filename); os.IsNotExist(err) { log.Fatal(`Метафайл не найден`) diff --git a/structs/db.go b/structs/db.go index fff3711..89e63f5 100644 --- a/structs/db.go +++ b/structs/db.go @@ -15,14 +15,15 @@ type Table struct { Pk string `yaml:"pk,omitempty" json:"pk,omitempty"` // Тип первичного ключа. Если uuid, то генерируется V4, int и bigint - используется bigint с автоинкрементом, string - первичный ключ как текстовое поле. Если не указано, то поумолчанию используется UUID Description string `yaml:"description,omitempty" json:"description,omitempty"` // Описание таблицы Fields []Field `yaml:"fields,omitempty" json:"fields,omitempty"` // Поля таблицы + Recursive bool `yaml:"recursive,omitempty" json:"recursive,omitempty"` // Рекурсивная таблица FKs []FK `yaml:"fks,omitempty" json:"fks,omitempty"` // Внешние ключи + FkFields []Field `yaml:"-" json:"-"` // Поля с описанием внешних ключей } // Описание поля type Field struct { ID uuid.UUID `yaml:"id,omitempty" json:"id,omitempty"` // Идентификатор поля. Если пустой, то генерируется автоматически для дальнейше работы Name string `yaml:"name,omitempty" json:"name,omitempty"` // Название поля - Recursive bool `yaml:"recursive,omitempty" json:"recursive,omitempty"` // Рекурсивная таблица Type string `yaml:"type,omitempty" json:"type,omitempty"` // Тип поля. По умолчанию text Length *int `yaml:"length,omitempty" json:"len,omitempty"` // Размер поля для строковых типов и Decimal Accuracy *int `yaml:"accuracy,omitempty" json:"accuracy,omitempty"` // Точность поля для Decimal