Генерирование полной схемы БД

This commit is contained in:
Ymnuk 2023-11-07 16:35:47 +03:00
parent a70a7748fe
commit c5572183fe
14 changed files with 263 additions and 44 deletions

13
.vscode/launch.json vendored
View File

@ -51,6 +51,19 @@
"--outdir-frontend",
"../zmap/frontend"
]
},
{
"name": "zmap docs",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": [
"--metafile",
"/home/ymnuk/projects/zmap/zmap.yml",
"--outdir-doc",
"../zmap/docs"
]
}
]
}

6
go.mod
View File

@ -9,4 +9,8 @@ require (
gopkg.in/yaml.v3 v3.0.1
)
require github.com/alexflint/go-scalar v1.2.0 // indirect
require (
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/yuzutech/kroki-go v0.8.1 // indirect
)

4
go.sum
View File

@ -8,12 +8,16 @@ github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdB
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuzutech/kroki-go v0.8.1 h1:p6doXpgWUXgLrVWt6MA1/ODH/oMBsnNGC3f7wGUewbM=
github.com/yuzutech/kroki-go v0.8.1/go.mod h1:JyGBdT3Od/PHEDf95GirXwhf1PM31k+tevrXGVjqNpg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -19,9 +19,7 @@ var AppConfig struct {
OutdirDoc string `arg:"-d,--outdir-doc" help:"Директория для сохранения сгенерированной документации по проекта"`
IsMarkdown bool `arg:"--format-markdown" help:"Выходная документация в формате Markdown"`
IsHtml bool `arg:"--format-html" help:"Выходная документация в формате HTML"`
IsGraphviz bool `arg:"--graphviz" help:"Генерировать графики в формате GraphViz (должен быть установлен локально)."`
IsMermaid bool `arg:"--mermaid" help:"Генерировать графики в формате Mermaid"`
MermaidAddr string `arg:"--mermaid-addr" default:"http://localhost" help:"Адрес сервера Mermaid. Если не указан и указан выходной формат Markdown, то просто встраивается в файл без предварительной генерации."`
KrokiAddr string `arg:"--kroki-addr" default:"https://kroki.io" help:"Адрес сервера Kroki."`
IsSingleDoc bool `arg:"--single-doc" help:"Генерировать документацию в одном файле без разделения"`
}

58
lib/documentation.go Normal file
View File

@ -0,0 +1,58 @@
package lib
import (
"git.ymnuktech.ru/ymnuk/yt-gen-app/lib/templ"
"git.ymnuktech.ru/ymnuk/yt-gen-app/structs"
"github.com/yuzutech/kroki-go"
)
func Documentation() {
if !AppConfig.IsHtml {
AppConfig.IsMarkdown = true
}
// Генерация общей схемы БД
fullDocDBGen()
// Генерация каждой таблицы по отдельности с прилегающими ближайшими таблицами
/*for i := range Project.DB.Tables {
docDBGen(Project.DB.Tables[i])
}*/
// TODO
}
// Генерирования списка таблиц
/*func listTablesDocGen() string {
// TODO
}
// Генерирование полей таблицы
func tableDocGen(table structs.Table) string {
// TODO
}*/
// Генерация общей схемы БД
func fullDocDBGen() {
// TODO
if tmpl, err := templ.ReadTmplFile("tmpl/docs/db/full-schema.tmpl"); err != nil {
panic(err)
} else {
if buff, err := templ.ExecuteTmplFile(tmpl, Project.DB); err != nil {
panic(err)
} else {
//fmt.Println(string(buff))
templ.WriteFile(AppConfig.OutdirDoc+"/test.txt", buff)
client := NewKrokiClient()
if result, err := client.FromString(string(buff), "dbml", kroki.SVG); err != nil {
panic(err)
} else {
//fmt.Println(result)
templ.WriteFile(AppConfig.OutdirDoc+"/test.svg", []byte(result))
}
}
}
}
// Генерация отдельной таблицы с прилегающими ближайшими таблицами
func docDBGen(table structs.Table) {
// TODO
}

View File

@ -21,4 +21,12 @@ func Generate() {
}
Frontend()
}
if AppConfig.OutdirDoc != "" {
// Генерация документации
if err := os.MkdirAll(AppConfig.OutdirDoc, 0755); err != nil {
log.Fatal(err)
}
Documentation()
}
}

20
lib/kroki-client.go Normal file
View File

@ -0,0 +1,20 @@
package lib
import (
"time"
"github.com/yuzutech/kroki-go"
)
var krokiClient *kroki.Client
func NewKrokiClient() *kroki.Client {
if krokiClient == nil {
tmp := kroki.New(kroki.Configuration{
URL: AppConfig.KrokiAddr,
Timeout: time.Second * 60,
})
krokiClient = &tmp
}
return krokiClient
}

View File

@ -21,14 +21,20 @@ import (
var Content embed.FS
var funcMap = template.FuncMap{
//"includeTemplPart": IncludeTemplPart,
"fieldName": FieldName,
"fieldChildName": FieldChildName,
"fieldNamePrepare": FieldNamePrepare,
"fieldNameLowerPrepare": FieldNameLowerPrepare,
"fieldJsonNameStr": FieldJsonNameStr,
"fieldType": FieldType,
"fieldTypeStr": FieldTypeStr,
"fieldStringToType": FieldStringToType,
//"fieldDbTableType": FieldDbTableType,
"fieldTypeDB": FieldTypeDB,
"fieldTypeDBStr": FieldTypeDBStr,
"fieldTypeParentTable": FieldTypeParentTable,
"hasFieldType": HasFieldType,
"fieldDescript": FieldDescript,
@ -71,6 +77,22 @@ var funcMap = template.FuncMap{
"fieldInTableByName": FieldInTableByName,
}
func IncludeTemplPart(templName string, data interface{}) string {
var err error
var tmpl *template.Template
if tmpl, err = ReadTmplFile(templName); err != nil {
panic(err)
}
var out []byte
if out, err = ExecuteTmplFile(tmpl, data); err != nil {
panic(err)
}
/*arr:=strings.Split(string(out))
var newArr []string
for*/
return string(out)
}
func IsMethod(arr map[string][]string, method string) bool {
if arr == nil {
return false
@ -153,15 +175,12 @@ func FieldTypeParentTable(field *structs.Field) string {
return FieldType(&structs.Field{Type: field.TypeParentTable})
}
func FieldType(field *structs.Field) string {
if field == nil {
log.Fatal("field is empty")
func FieldTypeStr(value string) string {
value = strings.ToLower(strings.Trim(value, " "))
if value == "" {
return value
}
field.Type = strings.ToLower(strings.Trim(field.Type, " "))
if field.Type == "" {
field.Type = "text"
}
switch field.Type {
switch value {
case "text":
return "*string"
case "string":
@ -186,11 +205,24 @@ func FieldType(field *structs.Field) string {
return "[]byte"
default:
fkTmp := &structs.Field{
Name: field.Type,
Name: value,
}
return FieldName(fkTmp)
//log.Fatalf("Unknow format %s", field.Type)
}
}
func FieldType(field *structs.Field) string {
if field == nil {
log.Fatal("field is empty")
}
field.Type = strings.ToLower(strings.Trim(field.Type, " "))
value := FieldTypeStr(field.Type)
if value == "" {
field.Type = "text"
value = "text"
}
return value
//return ""
}
@ -212,15 +244,14 @@ func HasFieldType(fields []structs.Field, typeName string) (has bool) {
return false
}
func FieldTypeDB(field *structs.Field) string {
field.Type = strings.ToLower(strings.Trim(field.Type, " "))
switch field.Type {
func FieldTypeDBStr(value string, length *int) string {
switch value {
case "text":
return "TEXT"
case "string":
tmp := "VARCHAR"
if field.Length != nil && *field.Length == 0 {
tmp += fmt.Sprintf("(%d)", *field.Length)
if length != nil && *length == 0 {
tmp += fmt.Sprintf("(%d)", *length)
} else {
tmp += "(255)"
}
@ -243,11 +274,16 @@ func FieldTypeDB(field *structs.Field) string {
case "bool":
return "int"
default:
log.Fatalf("Unknow format %s", field.Type)
log.Fatalf("Unknow format %s", value)
}
return ""
}
func FieldTypeDB(field *structs.Field) string {
field.Type = strings.ToLower(strings.Trim(field.Type, " "))
return FieldTypeDBStr(field.Type, field.Length)
}
// Генерирование описания таблиц в БД
func FieldDescript(field *structs.Field, fieldId bool) (str string) {
str = "`"
@ -317,7 +353,6 @@ func ConfigParamTag(field *structs.ParamConfig) string {
tag += fmt.Sprintf(" help:\"%s\"", field.Help)
}
tag += "`"
// TODO
return tag
}
@ -333,6 +368,46 @@ func WriteTmplFile(filename string, outname string) error {
return nil
}
// Чтение шаблона и его подготовка
func ReadTmplFile(filename string) (tmpl *template.Template, err error) {
var (
buff []byte
)
if buff, err = Content.ReadFile(filename); err != nil {
return
}
var (
tmp string
//tmpl *template.Template
)
if tmp, err = RandomHex(4); err != nil {
return
}
if _, ok := funcMap["includeTemplPart"]; !ok {
funcMap["includeTemplPart"] = IncludeTemplPart
}
tmpl, err = template.New(tmp).Funcs(funcMap).Parse(string(buff))
return
}
// Запись файла на диск
func WriteFile(outname string, data []byte) (err error) {
err = os.WriteFile(outname, data, 0755)
return
}
// Выполнить шаблон
func ExecuteTmplFile(tmpl *template.Template, data interface{}) (out []byte, err error) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
if err = tmpl.Execute(w, data); err != nil {
return
}
w.Flush()
out = b.Bytes()
return
}
// Использование шаблона
func PrepareTmplFile(filename string, data interface{}, outname string) (err error) {
dir := filepath.Dir(outname)
@ -341,36 +416,25 @@ func PrepareTmplFile(filename string, data interface{}, outname string) (err err
}
fmt.Printf("Generate: %s\n", outname)
var (
buff []byte
)
if buff, err = Content.ReadFile(filename); err != nil {
return err
}
var (
tmp string
tmpl *template.Template
)
if tmp, err = RandomHex(4); err != nil {
return err
}
if tmpl, err = template.New(tmp).Funcs(funcMap).Parse(string(buff)); err != nil {
/*var b bytes.Buffer
w := bufio.NewWriter(&b)*/
var tmpl *template.Template
if tmpl, err = ReadTmplFile(filename); err != nil {
return
}
/*if err = tmpl.Execute(w, data); err != nil {
return err
}
var b bytes.Buffer
w := bufio.NewWriter(&b)
if err = tmpl.Execute(w, data); err != nil {
return err
w.Flush()*/
var out []byte
if out, err = ExecuteTmplFile(tmpl, data); err != nil {
return
}
w.Flush()
if err = os.WriteFile(outname, b.Bytes(), 0755); err != nil {
return err
}
return
return WriteFile(outname, out)
}
// Подготовка файла, если его нет на диске
func PrepareTmplIsNotExists(filename string, data interface{}, outname string) (err error) {
if err = MkdirIsNotExists(filepath.Dir(outname)); err != nil {

View File

@ -0,0 +1,10 @@
{{ $varNameField := fieldNamePrepare .Name }}
Table {{ fieldNameLowerPrepare $varNameField }} {
id {{ fieldTypeDBStr .Pk nil }} [primary key]
{{ range $index, $field := .Fields }}
{{ fieldNameLowerPrepare $field.Name }} {{ fieldTypeDB $field }}
{{ end }}
{{ range $index, $field := .FkFields }}
{{ fieldNameLowerPrepare $field.Name }}_id {{ fieldNameLowerPrepare $field.TypeParentTable }}
{{ end }}
}

View File

@ -0,0 +1,17 @@
{{ includeTemplPart "tmpl/docs/db/user.tmpl" nil }}
{{ includeTemplPart "tmpl/docs/db/role.tmpl" nil }}
{{ includeTemplPart "tmpl/docs/db/user-role.tmpl" nil }}
{{ range $index, $table := .Tables }}
{{ includeTemplPart "tmpl/docs/db/entity.tmpl" $table }}
{{ end }}
{{ range $indexi, $table := .Tables }}
{{ $varNameTable := fieldNamePrepare $table.Name }}
{{range $indexj, $field := $table.FkFields }}
{{ $fieldTypeParentTable := "" }}
{{ if eq $field.Name "Parent" }}{{ $fieldTypeParentTable = fieldNamePrepare $.Name }}{{ else }}{{ $fieldTypeParentTable = fieldType $field }}{{ end }}
Ref: {{ fieldNameLowerPrepare $varNameTable }}.{{ fieldNameLowerPrepare $field.Name }}_id > {{ fieldNameLowerPrepare $fieldTypeParentTable }}.id
{{ end }}
{{ end }}
{{ includeTemplPart "tmpl/docs/db/user-role-relations.tmpl" nil }}

View File

@ -0,0 +1,5 @@
Table role {
id UUID [primary key]
name VARCHAR(20)
description VARCHAR(20)
}

View File

@ -0,0 +1,2 @@
Ref: user_role.user_id > user.id
Ref: user_role.role_id > role.id

View File

@ -0,0 +1,5 @@
Table user_role {
id UUID
user_id UUID
role_id UUID
}

View File

@ -0,0 +1,11 @@
Table user {
id UUID [primary key]
login VARCHAR
surname VARCHAR(100)
name VARCHAR(100)
farname VARCHAR(100)
email VARCHAR(200)
password VARCGAR
ldap BOOLEAN
attempt INT
}