mirror of
https://github.com/opbnq-q/nto-cli.git
synced 2025-12-06 17:00:33 +07:00
feat: new parser & generator
This commit is contained in:
41
model/gen_field.go
Normal file
41
model/gen_field.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const fieldTemplate = `{
|
||||
{{ if .Metadata.Hidden }} hidden: true,
|
||||
{{ end }}{{ if .Metadata.Label }} russian: "{{ .Metadata.Label }}",
|
||||
{{ end }}{{ if .Metadata.Readonly }} readonly: true,
|
||||
{{ end }}{{ if .IsArray }} many: true,
|
||||
{{ end }}{{ .GeneratedType }}
|
||||
}`
|
||||
|
||||
type FieldTemplateContext struct {
|
||||
Metadata FieldMetadata
|
||||
IsArray bool
|
||||
GeneratedType string
|
||||
}
|
||||
|
||||
func (field *Field) GenerateFieldCode() string {
|
||||
tmpl, err := template.New("field").Parse(fieldTemplate)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error parsing field template: %v", err))
|
||||
}
|
||||
|
||||
data := FieldTemplateContext{
|
||||
Metadata: field.Metadata,
|
||||
IsArray: field.Metadata.IsSlice,
|
||||
GeneratedType: field.GenerateType(),
|
||||
}
|
||||
|
||||
var result bytes.Buffer
|
||||
if err := tmpl.Execute(&result, data); err != nil {
|
||||
panic(fmt.Sprintf("Error executing field template: %v", err))
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
72
model/gen_field_type.go
Normal file
72
model/gen_field_type.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var PrimitiveTypes = map[string]string{
|
||||
"string": "string",
|
||||
"boolean": "boolean",
|
||||
"bool": "boolean",
|
||||
"int": "number",
|
||||
"uint": "number",
|
||||
"float32": "number",
|
||||
"float64": "number",
|
||||
"int32": "number",
|
||||
"int64": "number",
|
||||
"uint32": "number",
|
||||
"uint64": "number",
|
||||
"int8": "number",
|
||||
"int16": "number",
|
||||
"uint8": "number",
|
||||
"uint16": "number",
|
||||
"byte": "number",
|
||||
"rune": "number",
|
||||
}
|
||||
|
||||
const typeTemplate = ` type: {
|
||||
{{ if .IsPrimitive }} primitive: "{{ .PrimitiveType }}",{{ else }} nested: {
|
||||
values: [],
|
||||
field: {{ .Field }}
|
||||
}, {{ end }}
|
||||
},`
|
||||
|
||||
type TypeTemplateContext struct {
|
||||
IsPrimitive bool
|
||||
PrimitiveType string
|
||||
Field string
|
||||
}
|
||||
|
||||
func (field *Field) GenerateType() string {
|
||||
keys := make([]string, 0, len(PrimitiveTypes))
|
||||
for k := range PrimitiveTypes {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
var data TypeTemplateContext
|
||||
|
||||
if field.Metadata.IsPrimitiveType {
|
||||
data.IsPrimitive = true
|
||||
typeName := field.Type.(*ast.Ident).Name
|
||||
data.PrimitiveType = PrimitiveTypes[typeName]
|
||||
} else {
|
||||
data.IsPrimitive = false
|
||||
data.Field = "['" + strings.Join(field.Metadata.RelatedFields, "', '") + "']"
|
||||
}
|
||||
|
||||
tmpl, err := template.New("type").Parse(typeTemplate)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error parsing template: %v", err))
|
||||
}
|
||||
|
||||
var result bytes.Buffer
|
||||
if err := tmpl.Execute(&result, data); err != nil {
|
||||
panic(fmt.Sprintf("Error executing template: %v", err))
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
13
model/get_not_implemented_models.go
Normal file
13
model/get_not_implemented_models.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
import "nto_cli/utils"
|
||||
|
||||
func GetNotImplementedModels(models []Model) []Model {
|
||||
var unimplementedModels []Model
|
||||
for _, m := range models {
|
||||
if !utils.IsEntityImplemented(m.Name) {
|
||||
unimplementedModels = append(unimplementedModels, m)
|
||||
}
|
||||
}
|
||||
return unimplementedModels
|
||||
}
|
||||
27
model/model.go
Normal file
27
model/model.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import "go/ast"
|
||||
|
||||
type Model struct {
|
||||
Name string
|
||||
Fields []Field
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Name string
|
||||
Type ast.Expr
|
||||
Tag string
|
||||
Metadata FieldMetadata
|
||||
}
|
||||
|
||||
type FieldMetadata struct {
|
||||
Hidden bool
|
||||
Readonly bool
|
||||
Label string
|
||||
IsSlice bool
|
||||
IsPrimitiveType bool
|
||||
IsRelatedModel bool
|
||||
RelatedModel string
|
||||
Datatype string
|
||||
RelatedFields []string
|
||||
}
|
||||
151
model/parse_models.go
Normal file
151
model/parse_models.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log/slog"
|
||||
"nto_cli/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kuzgoga/fogg"
|
||||
)
|
||||
|
||||
func ParseModelsPackage(modelsPkgDir string) ([]Model, error) {
|
||||
var models []Model
|
||||
fileset := token.NewFileSet()
|
||||
|
||||
files, err := os.ReadDir(modelsPkgDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
filepathPath := filepath.Join(modelsPkgDir, file.Name())
|
||||
fileAst, err := parser.ParseFile(fileset, filepathPath, nil, parser.DeclarationErrors)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
models = append(models, parseFileStructs(fileset, fileAst.Decls)...)
|
||||
}
|
||||
|
||||
ParseRelatedModels(&models)
|
||||
|
||||
return models, nil
|
||||
}
|
||||
|
||||
func parseFileStructs(fileSet *token.FileSet, decls []ast.Decl) []Model {
|
||||
var models []Model
|
||||
for _, decl := range 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 && typeSpec.Name != nil {
|
||||
models = append(models, parseModelDecl(fileSet, typeSpec))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return models
|
||||
}
|
||||
|
||||
func parseModelDecl(fileset *token.FileSet, decl *ast.TypeSpec) Model {
|
||||
name := decl.Name.Name
|
||||
structType, _ := decl.Type.(*ast.StructType)
|
||||
var fields []Field
|
||||
|
||||
for _, fieldDecl := range structType.Fields.List {
|
||||
fieldPos := fileset.Position(fieldDecl.Pos()).String()
|
||||
if len(fieldDecl.Names) == 0 {
|
||||
slog.Error("%s Embedded structure isn't supported", fieldPos)
|
||||
continue
|
||||
}
|
||||
|
||||
var tag string
|
||||
if fieldDecl.Tag != nil {
|
||||
tag = fieldDecl.Tag.Value[1 : len(fieldDecl.Tag.Value)-1]
|
||||
}
|
||||
|
||||
storage, err := fogg.Parse(tag)
|
||||
if err != nil {
|
||||
slog.Error("%s Struct tag parsing error: %s", fieldPos, err)
|
||||
}
|
||||
|
||||
var metadata FieldMetadata
|
||||
|
||||
if storage.HasTag("ui") {
|
||||
uiTag := storage.GetTag("ui")
|
||||
metadata.Hidden = uiTag.HasOption("hidden")
|
||||
metadata.Label = uiTag.GetParamOr("label", "")
|
||||
metadata.IsSlice = utils.IsSlice(fieldDecl.Type)
|
||||
metadata.IsPrimitiveType = utils.IsPrimitiveType(fieldDecl.Type)
|
||||
metadata.Datatype = uiTag.GetParamOr("datatype", "")
|
||||
if uiTag.HasParam("field") {
|
||||
metadata.RelatedFields = strings.Split(uiTag.GetParam("field").Value, ".")
|
||||
}
|
||||
} else {
|
||||
slog.Warn("%s Field does not have a UI tag", fieldPos)
|
||||
}
|
||||
|
||||
field := Field{
|
||||
Name: fieldDecl.Names[0].Name,
|
||||
Type: fieldDecl.Type,
|
||||
Tag: tag,
|
||||
Metadata: metadata,
|
||||
}
|
||||
fields = append(fields, field)
|
||||
}
|
||||
return Model{
|
||||
Name: name,
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
func ParseRelatedModels(models *[]Model) {
|
||||
for i := range *models {
|
||||
model := &(*models)[i]
|
||||
for j := range model.Fields {
|
||||
field := &model.Fields[j]
|
||||
if field.Metadata.IsPrimitiveType {
|
||||
continue
|
||||
}
|
||||
|
||||
relatedModelName := utils.ResolveBaseType(field.Type)
|
||||
if relatedModelName == nil {
|
||||
slog.Error("Failed to resolve base type for field `%s` in model `%s`", field.Name, model.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, m := range *models {
|
||||
if m.Name == *relatedModelName {
|
||||
field.Metadata.RelatedModel = m.Name
|
||||
field.Metadata.IsRelatedModel = true
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
slog.Error("Cannot classify field type `%s` in model `%s`, type `%s`",
|
||||
field.Name, model.Name, *relatedModelName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user