feat: new parser & generator

This commit is contained in:
2025-03-09 13:12:12 +07:00
parent 70fc3345d8
commit a3283b9a57
25 changed files with 372 additions and 301 deletions

View File

@@ -2,35 +2,28 @@ package cmd
import ( import (
"log" "log"
"nto_cli/utils" "nto_cli/model"
"os" "os"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
func SelectionInput() ([]string, string) { func SelectionInput(models []model.Model) *[]model.Model {
if len(os.Args) == 1 { unimplementedModels := model.GetNotImplementedModels(models)
log.Fatalf("Please provide path to models.go") var result []model.Model
}
modelsPath := os.Args[1] if len(unimplementedModels) == 0 {
structNames := utils.GetNotImplementedStructs(modelsPath)
if len(structNames) == 0 {
log.Println("No unimplemented models -> nothing to do") log.Println("No unimplemented models -> nothing to do")
os.Exit(0) os.Exit(0)
} }
var result []string
app := tview.NewApplication() app := tview.NewApplication()
form := tview.NewForm() form := tview.NewForm()
var checkboxes []*tview.Checkbox var checkboxes []*tview.Checkbox
for _, name := range structNames { for _, m := range unimplementedModels {
cb := tview.NewCheckbox().SetLabel(name) cb := tview.NewCheckbox().SetLabel(m.Name)
checkboxes = append(checkboxes, cb) checkboxes = append(checkboxes, cb)
form.AddFormItem(cb) form.AddFormItem(cb)
} }
@@ -38,15 +31,15 @@ func SelectionInput() ([]string, string) {
form.AddButton("Generate", func() { form.AddButton("Generate", func() {
for i, cb := range checkboxes { for i, cb := range checkboxes {
if cb.IsChecked() { if cb.IsChecked() {
result = append(result, structNames[i]) result = append(result, unimplementedModels[i])
} }
} }
app.Stop() app.Stop()
}) })
if err := app.SetRoot(form, true).Run(); err != nil { if err := app.SetRoot(form, true).Run(); err != nil {
panic(err) log.Fatalf("Failed to initialize dialog: %s", err)
} }
return result, modelsPath return &result
} }

View File

@@ -1,7 +0,0 @@
package entities
type Field struct {
Name string
Type string
Metadata []Metadata
}

View File

@@ -1,33 +0,0 @@
package entities
import (
"strings"
)
type Metadata struct {
Name string
Values []string
}
func NewMetadata(tag string) *Metadata {
tag = strings.TrimSpace(tag)
tagName := ""
var values []string
if strings.Contains(tag, "=") {
tagName = tag[:strings.Index(tag, "=")]
if tag[strings.Index(tag, "=")+1] == '[' {
values = append(values, strings.Split(tag[strings.Index(tag, "=")+2:len(tag)-1], ";")...)
for i := range values {
values[i] = strings.TrimSpace(values[i])
}
} else {
values = append(values, strings.TrimSpace(tag[strings.Index(tag, "=")+1:]))
}
} else {
tagName = tag
}
return &Metadata{
Name: strings.TrimSpace(strings.ToLower(tagName)),
Values: values,
}
}

View File

@@ -2,18 +2,18 @@ package generation
import ( import (
"log" "log"
"nto_cli/entities" "nto_cli/model"
"nto_cli/utils" "nto_cli/utils"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
) )
func Generate(structName string, fields []entities.Field) { func Generate(model *model.Model) {
mkPath := filepath.Join(utils.FindFrontendPath(), strings.ToLower(structName)) mkPath := filepath.Join(utils.FindFrontendPath(), strings.ToLower(model.Name))
if err := os.Mkdir(mkPath, 0755); err != nil { if err := os.Mkdir(mkPath, 0755); err != nil {
log.Fatalf("Failed to mkdir for model: %s", err) log.Fatalf("Failed to mkdir for model: %s", err)
} }
GenerateService(structName, mkPath) GenerateService(model, mkPath)
GenerateScheme(structName, fields, mkPath) GenerateScheme(model, mkPath)
} }

View File

@@ -4,7 +4,7 @@ import (
_ "embed" _ "embed"
"fmt" "fmt"
"log" "log"
"nto_cli/entities" "nto_cli/model"
"nto_cli/utils" "nto_cli/utils"
"os" "os"
"path/filepath" "path/filepath"
@@ -28,20 +28,20 @@ type TemplateData struct {
StructName string StructName string
LowerName string LowerName string
GolangServicesPath string GolangServicesPath string
Fields []entities.Field Fields []model.Field
Dependencies []Dependency Dependencies []Dependency
} }
func GenerateScheme(structName string, fields []entities.Field, mkPath string) { func GenerateScheme(model *model.Model, mkPath string) {
data := TemplateData{ data := TemplateData{
StructName: structName, StructName: model.Name,
LowerName: strings.ToLower(structName), LowerName: strings.ToLower(model.Name),
GolangServicesPath: GolangServicesPath, GolangServicesPath: GolangServicesPath,
Fields: fields, Fields: model.Fields,
Dependencies: processDependencies(fields), Dependencies: processDependencies(model.Fields),
} }
schemeFilename := strings.ToUpper(structName[:1]) + strings.ToLower(structName[1:]) + "Scheme.vue" schemeFilename := strings.ToUpper(model.Name[:1]) + strings.ToLower(model.Name[1:]) + "Scheme.vue"
schemeFilePath := filepath.Join(mkPath, schemeFilename) schemeFilePath := filepath.Join(mkPath, schemeFilename)
schemeFile, err := os.Create(schemeFilePath) schemeFile, err := os.Create(schemeFilePath)
if err != nil { if err != nil {
@@ -63,25 +63,25 @@ func GenerateScheme(structName string, fields []entities.Field, mkPath string) {
if err != nil { if err != nil {
log.Fatalf("Failed to execute template: %s", err) log.Fatalf("Failed to execute template: %s", err)
} }
log.Printf("Scheme for `%s` model is written: %s", structName, schemeFilePath) log.Printf("Scheme for `%s` model is written: %s", model.Name, schemeFilePath)
_ = utils.FormatFilesWithPrettier([]string{schemeFilePath}) _ = utils.FormatFilesWithPrettier([]string{schemeFilePath})
} }
func processDependencies(fields []entities.Field) []Dependency { func processDependencies(fields []model.Field) []Dependency {
var dependencies []Dependency var dependencies []Dependency
for _, field := range fields { for _, field := range fields {
for _, meta := range field.Metadata { dependency := field.Metadata.RelatedModel
if meta.Name == "data" { if dependency == "" {
dependency := meta.Values[0] continue
}
dependencies = append(dependencies, Dependency{ dependencies = append(dependencies, Dependency{
ImportName: strings.ToUpper(dependency[:1]) + strings.ToLower(dependency[1:]) + "Service", ImportName: strings.ToUpper(dependency[:1]) + strings.ToLower(dependency[1:]) + "Service",
ServiceName: strings.ToLower(dependency) + "Service", ServiceName: strings.ToLower(dependency) + "Service",
LowerName: strings.ToLower(dependency), LowerName: strings.ToLower(dependency),
FieldName: field.Name, FieldName: field.Name,
}) })
}
}
} }
return dependencies return dependencies

View File

@@ -4,6 +4,7 @@ import (
_ "embed" _ "embed"
"fmt" "fmt"
"log" "log"
"nto_cli/model"
"nto_cli/utils" "nto_cli/utils"
"os" "os"
"path/filepath" "path/filepath"
@@ -20,8 +21,8 @@ type ServiceTemplateContext struct {
ServicesPath string ServicesPath string
} }
func GenerateService(structName, mkPath string) { func GenerateService(model *model.Model, mkPath string) {
servicePath := filepath.Join(mkPath, strings.ToLower(structName)+".service.ts") servicePath := filepath.Join(mkPath, strings.ToLower(model.Name)+".service.ts")
serviceFile, err := os.Create(servicePath) serviceFile, err := os.Create(servicePath)
if err != nil { if err != nil {
log.Fatalf("Failed to create service file: %s", err) log.Fatalf("Failed to create service file: %s", err)
@@ -35,8 +36,8 @@ func GenerateService(structName, mkPath string) {
}(serviceFile) }(serviceFile)
context := ServiceTemplateContext{ context := ServiceTemplateContext{
LowerModelName: strings.ToLower(structName), LowerModelName: strings.ToLower(model.Name),
ModelName: structName, ModelName: model.Name,
ServicesPath: GolangServicesPath, ServicesPath: GolangServicesPath,
} }
@@ -50,6 +51,6 @@ func GenerateService(structName, mkPath string) {
if err != nil { if err != nil {
log.Fatalf("Failed to execute template: %s", err) log.Fatalf("Failed to execute template: %s", err)
} }
log.Printf("Service for `%s` model is written: %s", structName, servicePath) log.Printf("Service for `%s` model is written: %s", model.Name, servicePath)
_ = utils.FormatFilesWithPrettier([]string{servicePath}) _ = utils.FormatFilesWithPrettier([]string{servicePath})
} }

View File

@@ -20,7 +20,7 @@ onMounted(async () => {
const scheme: Scheme<{{.StructName}}> = reactive({ const scheme: Scheme<{{.StructName}}> = reactive({
{{range .Fields}} {{range .Fields}}
{{.Name}}: {{.Generate}}, {{.Name}}: {{.GenerateFieldCode}},
{{end}} {{end}}
}) })

1
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/fatih/structtag v1.2.0 // indirect github.com/fatih/structtag v1.2.0 // indirect
github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.7.1 // indirect github.com/gdamore/tcell/v2 v2.7.1 // indirect
github.com/kuzgoga/fogg v0.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 // indirect github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 // indirect

2
go.sum
View File

@@ -4,6 +4,8 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc=
github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/kuzgoga/fogg v0.1.0 h1:W4EqVPo9WyLQnaNdDWF3ydt/D9e146Ffoh+o8fnXr0A=
github.com/kuzgoga/fogg v0.1.0/go.mod h1:x0cKa6kIaweLKtzMWXw0xZZnl2PrLDpMmi+yL3xEIEg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=

17
main.go
View File

@@ -4,21 +4,20 @@ import (
"log" "log"
"nto_cli/cmd" "nto_cli/cmd"
"nto_cli/generation" "nto_cli/generation"
"nto_cli/model"
"nto_cli/utils" "nto_cli/utils"
"os"
) )
func main() { func main() {
log.SetFlags(0) log.SetFlags(0)
structNames, path := cmd.SelectionInput() modelsPath := utils.GetModelsPath()
models, err := model.ParseModelsPackage(modelsPath)
for _, structName := range structNames {
file, err := os.Open(path)
if err != nil { if err != nil {
log.Fatalf("Failed to open file: %s", err) log.Fatalf("Failed to parse models: %s", err)
} }
structFields := utils.GetStructFields(file, structName) selectedModels := cmd.SelectionInput(models)
_ = file.Close()
generation.Generate(structName, structFields) for _, m := range *selectedModels {
generation.Generate(&m)
} }
} }

View File

@@ -1,4 +1,4 @@
package entities package model
import ( import (
"bytes" "bytes"
@@ -7,31 +7,29 @@ import (
) )
const fieldTemplate = `{ const fieldTemplate = `{
{{ range .Metadata }}{{ if eq .Name "hidden" }} hidden: true, {{ if .Metadata.Hidden }} hidden: true,
{{ else if eq .Name "label" }} russian: "{{ index .Values 0 }}", {{ end }}{{ if .Metadata.Label }} russian: "{{ .Metadata.Label }}",
{{ else if eq .Name "readonly" }} readonly: true, {{ end }}{{ if .Metadata.Readonly }} readonly: true,
{{ end }}{{ end }}{{ if .IsArray }} many: true, {{ end }}{{ if .IsArray }} many: true,
{{ end }}{{ .GeneratedType }} {{ end }}{{ .GeneratedType }}
}` }`
type FieldTemplateContext struct { type FieldTemplateContext struct {
Metadata []Metadata Metadata FieldMetadata
IsArray bool IsArray bool
GeneratedType string GeneratedType string
} }
func (f *Field) Generate() string { func (field *Field) GenerateFieldCode() string {
tmpl, err := template.New("field").Parse(fieldTemplate) tmpl, err := template.New("field").Parse(fieldTemplate)
if err != nil { if err != nil {
panic(fmt.Sprintf("Error parsing field template: %v", err)) panic(fmt.Sprintf("Error parsing field template: %v", err))
} }
isArray := len(f.Type) >= 2 && f.Type[0:2] == "[]"
data := FieldTemplateContext{ data := FieldTemplateContext{
Metadata: f.Metadata, Metadata: field.Metadata,
IsArray: isArray, IsArray: field.Metadata.IsSlice,
GeneratedType: f.GenerateType(), GeneratedType: field.GenerateType(),
} }
var result bytes.Buffer var result bytes.Buffer

View File

@@ -1,16 +1,14 @@
package entities package model
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"slices" "go/ast"
"strings" "strings"
"text/template" "text/template"
) )
var PrimitiveTypes = map[string]string{ var PrimitiveTypes = map[string]string{
"date": "date",
"number": "number",
"string": "string", "string": "string",
"boolean": "boolean", "boolean": "boolean",
"bool": "boolean", "bool": "boolean",
@@ -43,7 +41,7 @@ type TypeTemplateContext struct {
Field string Field string
} }
func (f *Field) GenerateType() string { func (field *Field) GenerateType() string {
keys := make([]string, 0, len(PrimitiveTypes)) keys := make([]string, 0, len(PrimitiveTypes))
for k := range PrimitiveTypes { for k := range PrimitiveTypes {
keys = append(keys, k) keys = append(keys, k)
@@ -51,19 +49,13 @@ func (f *Field) GenerateType() string {
var data TypeTemplateContext var data TypeTemplateContext
if slices.Contains(keys, strings.ToLower(f.Type)) { if field.Metadata.IsPrimitiveType {
data.IsPrimitive = true data.IsPrimitive = true
data.PrimitiveType = PrimitiveTypes[strings.ToLower(f.Type)] typeName := field.Type.(*ast.Ident).Name
data.PrimitiveType = PrimitiveTypes[typeName]
} else { } else {
data.IsPrimitive = false data.IsPrimitive = false
field := "[]" data.Field = "['" + strings.Join(field.Metadata.RelatedFields, "', '") + "']"
for _, meta := range f.Metadata {
if meta.Name == "field" && len(meta.Values) > 0 {
field = "['" + strings.Join(meta.Values, "', '") + "']"
break
}
}
data.Field = field
} }
tmpl, err := template.New("type").Parse(typeTemplate) tmpl, err := template.New("type").Parse(typeTemplate)

View 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
View 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
View 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
}
}
}
}

View File

@@ -1,15 +0,0 @@
package utils
import "strings"
func ContainsMany(str string, substrings ...string) bool {
var matches int
for _, substr := range substrings {
if strings.Contains(str, substr) {
matches++
} else {
return false
}
}
return matches == len(substrings)
}

14
utils/get_models_path.go Normal file
View File

@@ -0,0 +1,14 @@
package utils
import (
"log"
"os"
)
func GetModelsPath() string {
if len(os.Args) == 1 {
log.Fatalf("Please provide path to models.go")
}
return os.Args[1]
}

View File

@@ -1,11 +0,0 @@
package utils
func GetNotImplementedStructs(modelsFilePath string) []string {
var models []string
for _, model := range GetStructsList(modelsFilePath) {
if !IsEntityImplemented(model) {
models = append(models, model)
}
}
return models
}

View File

@@ -1,40 +0,0 @@
package utils
import (
"bufio"
"log"
"nto_cli/entities"
"os"
"strings"
)
func GetStructFields(file *os.File, structName string) []entities.Field {
bracketsCount := 1
var structFound bool
var structFields []entities.Field
scanner := bufio.NewScanner(file)
for i := 1; scanner.Scan() && bracketsCount > 0; i++ {
line := scanner.Text()
if ContainsMany(line, structName, "type", "struct") {
structFound = true
}
if structFound {
bracketsCount += strings.Count(line, "{")
bracketsCount -= strings.Count(line, "}")
line = strings.TrimSpace(line)
newField, err := SplitStructField(line)
if err != nil {
return structFields
}
if newField != nil {
structFields = append(structFields, *newField)
}
}
}
if err := scanner.Err(); err != nil {
log.Fatalf("Failed to read file with scanner: %s", err)
}
return structFields
}

View File

@@ -1,36 +0,0 @@
package utils
import (
"bufio"
"log"
"os"
"strings"
)
func GetStructsList(modelsFilePath string) []string {
file, err := os.Open(modelsFilePath)
defer file.Close()
if err != nil {
log.Fatalf("Failed to open a file: %s", err)
}
var structNames []string
s := bufio.NewScanner(file)
for s.Scan() {
line := s.Text()
if strings.Contains(line, "type ") && strings.Contains(line, " struct") {
start := strings.Index(line, "type ") + 5
end := strings.Index(line, " struct")
name := strings.TrimSpace(line[start:end])
if name != "" {
structNames = append(structNames, name)
}
}
}
if s.Err() != nil {
log.Fatalf("Unexpected scanner error: %s", err)
}
return structNames
}

View File

@@ -0,0 +1,63 @@
package utils
import "go/ast"
var primitives = map[string]bool{
"bool": true,
"string": true,
"int": true,
"int8": true,
"int16": true,
"int32": true,
"int64": true,
"uint": true,
"uint8": true,
"uint16": true,
"uint32": true,
"uint64": true,
"uintptr": true,
"byte": true,
"rune": true,
"float32": true,
"float64": true,
"complex64": true,
"complex128": true,
}
func IsPrimitiveType(expr ast.Expr) bool {
if ident, ok := expr.(*ast.Ident); ok {
return primitives[ident.Name]
}
if _, ok := expr.(*ast.ArrayType); ok {
// Arrays and slices are not primitives
return false
}
if _, ok := expr.(*ast.MapType); ok {
// Maps are not primitives
return false
}
if _, ok := expr.(*ast.ChanType); ok {
// Channels are not primitives
return false
}
if _, ok := expr.(*ast.StructType); ok {
// Structs are not primitives
return false
}
if _, ok := expr.(*ast.StarExpr); ok {
// Handle pointers
return IsPrimitiveType(expr.(*ast.StarExpr).X)
}
if _, ok := expr.(*ast.SelectorExpr); ok {
// Handle selector expressions (like pkg.Type)
return false
}
return false
}

11
utils/is_slice.go Normal file
View File

@@ -0,0 +1,11 @@
package utils
import "go/ast"
func IsSlice(expr ast.Expr) bool {
arrayType, ok := expr.(*ast.ArrayType)
if !ok {
return false
}
return arrayType.Len == nil
}

View File

@@ -0,0 +1,21 @@
package utils
import (
"go/ast"
)
func ResolveBaseType(expr ast.Expr) *string {
switch e := expr.(type) {
case *ast.Ident:
return &e.Name
case *ast.StarExpr:
return ResolveBaseType(e.X)
case *ast.ArrayType:
return ResolveBaseType(e.Elt)
case *ast.SelectorExpr:
return ResolveBaseType(e.X)
case *ast.ParenExpr:
return ResolveBaseType(e.X)
}
return nil
}

View File

@@ -1,14 +0,0 @@
package utils
import "strings"
func SplitBySingleSpace(input string) []string {
parts := strings.Split(strings.TrimSpace(input), " ")
var result []string
for _, p := range parts {
if p != "" {
result = append(result, p)
}
}
return result
}

View File

@@ -1,59 +0,0 @@
package utils
import (
"errors"
"log"
"nto_cli/entities"
"strings"
"github.com/fatih/structtag"
)
func SplitStructField(field string) (*entities.Field, error) {
if strings.Contains(field, "type") {
return nil, nil
}
if len(strings.TrimSpace(field)) < 2 {
return nil, errors.New("unexpected end of struct field")
}
startBacktick := strings.Index(field, "`")
endBacktick := -1
var metadata []entities.Metadata
if startBacktick > -1 {
endBacktick = strings.Index(field[startBacktick+1:], "`")
if endBacktick > -1 {
endBacktick += startBacktick + 1
structTag := field[startBacktick+1 : endBacktick]
tags, err := structtag.Parse(structTag)
if err != nil {
log.Fatalf("failed to parse struct tag: %s", err)
}
if uiTag, e := tags.Get("ui"); e == nil {
uiTags := append([]string{uiTag.Name}, uiTag.Options...)
for _, t := range uiTags {
analyzed := entities.NewMetadata(t)
if analyzed != nil {
metadata = append(metadata, *analyzed)
}
}
}
}
} else {
startBacktick = len(field)
}
field = strings.TrimSpace(field[:startBacktick])
data := SplitBySingleSpace(field)
name := data[0]
dataType := data[1]
return &entities.Field{
Metadata: metadata,
Type: dataType,
Name: name,
}, nil
}