mirror of
https://github.com/opbnq-q/nto-cli.git
synced 2025-12-06 15:20:33 +07:00
feat: new parser & generator
This commit is contained in:
@@ -2,35 +2,28 @@ package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"nto_cli/utils"
|
||||
"nto_cli/model"
|
||||
"os"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
func SelectionInput() ([]string, string) {
|
||||
if len(os.Args) == 1 {
|
||||
log.Fatalf("Please provide path to models.go")
|
||||
}
|
||||
func SelectionInput(models []model.Model) *[]model.Model {
|
||||
unimplementedModels := model.GetNotImplementedModels(models)
|
||||
var result []model.Model
|
||||
|
||||
modelsPath := os.Args[1]
|
||||
|
||||
structNames := utils.GetNotImplementedStructs(modelsPath)
|
||||
|
||||
if len(structNames) == 0 {
|
||||
if len(unimplementedModels) == 0 {
|
||||
log.Println("No unimplemented models -> nothing to do")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var result []string
|
||||
|
||||
app := tview.NewApplication()
|
||||
|
||||
form := tview.NewForm()
|
||||
var checkboxes []*tview.Checkbox
|
||||
|
||||
for _, name := range structNames {
|
||||
cb := tview.NewCheckbox().SetLabel(name)
|
||||
for _, m := range unimplementedModels {
|
||||
cb := tview.NewCheckbox().SetLabel(m.Name)
|
||||
checkboxes = append(checkboxes, cb)
|
||||
form.AddFormItem(cb)
|
||||
}
|
||||
@@ -38,15 +31,15 @@ func SelectionInput() ([]string, string) {
|
||||
form.AddButton("Generate", func() {
|
||||
for i, cb := range checkboxes {
|
||||
if cb.IsChecked() {
|
||||
result = append(result, structNames[i])
|
||||
result = append(result, unimplementedModels[i])
|
||||
}
|
||||
}
|
||||
app.Stop()
|
||||
})
|
||||
|
||||
if err := app.SetRoot(form, true).Run(); err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("Failed to initialize dialog: %s", err)
|
||||
}
|
||||
|
||||
return result, modelsPath
|
||||
return &result
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package entities
|
||||
|
||||
type Field struct {
|
||||
Name string
|
||||
Type string
|
||||
Metadata []Metadata
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,18 @@ package generation
|
||||
|
||||
import (
|
||||
"log"
|
||||
"nto_cli/entities"
|
||||
"nto_cli/model"
|
||||
"nto_cli/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Generate(structName string, fields []entities.Field) {
|
||||
mkPath := filepath.Join(utils.FindFrontendPath(), strings.ToLower(structName))
|
||||
func Generate(model *model.Model) {
|
||||
mkPath := filepath.Join(utils.FindFrontendPath(), strings.ToLower(model.Name))
|
||||
if err := os.Mkdir(mkPath, 0755); err != nil {
|
||||
log.Fatalf("Failed to mkdir for model: %s", err)
|
||||
}
|
||||
GenerateService(structName, mkPath)
|
||||
GenerateScheme(structName, fields, mkPath)
|
||||
GenerateService(model, mkPath)
|
||||
GenerateScheme(model, mkPath)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"nto_cli/entities"
|
||||
"nto_cli/model"
|
||||
"nto_cli/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -28,20 +28,20 @@ type TemplateData struct {
|
||||
StructName string
|
||||
LowerName string
|
||||
GolangServicesPath string
|
||||
Fields []entities.Field
|
||||
Fields []model.Field
|
||||
Dependencies []Dependency
|
||||
}
|
||||
|
||||
func GenerateScheme(structName string, fields []entities.Field, mkPath string) {
|
||||
func GenerateScheme(model *model.Model, mkPath string) {
|
||||
data := TemplateData{
|
||||
StructName: structName,
|
||||
LowerName: strings.ToLower(structName),
|
||||
StructName: model.Name,
|
||||
LowerName: strings.ToLower(model.Name),
|
||||
GolangServicesPath: GolangServicesPath,
|
||||
Fields: fields,
|
||||
Dependencies: processDependencies(fields),
|
||||
Fields: model.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)
|
||||
schemeFile, err := os.Create(schemeFilePath)
|
||||
if err != nil {
|
||||
@@ -63,25 +63,25 @@ func GenerateScheme(structName string, fields []entities.Field, mkPath string) {
|
||||
if err != nil {
|
||||
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})
|
||||
}
|
||||
|
||||
func processDependencies(fields []entities.Field) []Dependency {
|
||||
func processDependencies(fields []model.Field) []Dependency {
|
||||
var dependencies []Dependency
|
||||
|
||||
for _, field := range fields {
|
||||
for _, meta := range field.Metadata {
|
||||
if meta.Name == "data" {
|
||||
dependency := meta.Values[0]
|
||||
dependencies = append(dependencies, Dependency{
|
||||
ImportName: strings.ToUpper(dependency[:1]) + strings.ToLower(dependency[1:]) + "Service",
|
||||
ServiceName: strings.ToLower(dependency) + "Service",
|
||||
LowerName: strings.ToLower(dependency),
|
||||
FieldName: field.Name,
|
||||
})
|
||||
}
|
||||
dependency := field.Metadata.RelatedModel
|
||||
if dependency == "" {
|
||||
continue
|
||||
}
|
||||
dependencies = append(dependencies, Dependency{
|
||||
ImportName: strings.ToUpper(dependency[:1]) + strings.ToLower(dependency[1:]) + "Service",
|
||||
ServiceName: strings.ToLower(dependency) + "Service",
|
||||
LowerName: strings.ToLower(dependency),
|
||||
FieldName: field.Name,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return dependencies
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"nto_cli/model"
|
||||
"nto_cli/utils"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -20,8 +21,8 @@ type ServiceTemplateContext struct {
|
||||
ServicesPath string
|
||||
}
|
||||
|
||||
func GenerateService(structName, mkPath string) {
|
||||
servicePath := filepath.Join(mkPath, strings.ToLower(structName)+".service.ts")
|
||||
func GenerateService(model *model.Model, mkPath string) {
|
||||
servicePath := filepath.Join(mkPath, strings.ToLower(model.Name)+".service.ts")
|
||||
serviceFile, err := os.Create(servicePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create service file: %s", err)
|
||||
@@ -35,8 +36,8 @@ func GenerateService(structName, mkPath string) {
|
||||
}(serviceFile)
|
||||
|
||||
context := ServiceTemplateContext{
|
||||
LowerModelName: strings.ToLower(structName),
|
||||
ModelName: structName,
|
||||
LowerModelName: strings.ToLower(model.Name),
|
||||
ModelName: model.Name,
|
||||
ServicesPath: GolangServicesPath,
|
||||
}
|
||||
|
||||
@@ -50,6 +51,6 @@ func GenerateService(structName, mkPath string) {
|
||||
if err != nil {
|
||||
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})
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ onMounted(async () => {
|
||||
|
||||
const scheme: Scheme<{{.StructName}}> = reactive({
|
||||
{{range .Fields}}
|
||||
{{.Name}}: {{.Generate}},
|
||||
{{.Name}}: {{.GenerateFieldCode}},
|
||||
{{end}}
|
||||
})
|
||||
|
||||
@@ -32,4 +32,4 @@ const getDefaults = () => getDefaultValues(scheme)
|
||||
<main class="w-screen h-screen">
|
||||
<Table :scheme :service :getDefaults></Table>
|
||||
</main>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
1
go.mod
1
go.mod
@@ -6,6 +6,7 @@ require (
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // 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/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -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/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc=
|
||||
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/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
|
||||
19
main.go
19
main.go
@@ -4,21 +4,20 @@ import (
|
||||
"log"
|
||||
"nto_cli/cmd"
|
||||
"nto_cli/generation"
|
||||
"nto_cli/model"
|
||||
"nto_cli/utils"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
structNames, path := cmd.SelectionInput()
|
||||
modelsPath := utils.GetModelsPath()
|
||||
models, err := model.ParseModelsPackage(modelsPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse models: %s", err)
|
||||
}
|
||||
selectedModels := cmd.SelectionInput(models)
|
||||
|
||||
for _, structName := range structNames {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open file: %s", err)
|
||||
}
|
||||
structFields := utils.GetStructFields(file, structName)
|
||||
_ = file.Close()
|
||||
generation.Generate(structName, structFields)
|
||||
for _, m := range *selectedModels {
|
||||
generation.Generate(&m)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package entities
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -7,31 +7,29 @@ import (
|
||||
)
|
||||
|
||||
const fieldTemplate = `{
|
||||
{{ range .Metadata }}{{ if eq .Name "hidden" }} hidden: true,
|
||||
{{ else if eq .Name "label" }} russian: "{{ index .Values 0 }}",
|
||||
{{ else if eq .Name "readonly" }} readonly: true,
|
||||
{{ end }}{{ end }}{{ if .IsArray }} many: true,
|
||||
{{ 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 []Metadata
|
||||
Metadata FieldMetadata
|
||||
IsArray bool
|
||||
GeneratedType string
|
||||
}
|
||||
|
||||
func (f *Field) Generate() 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))
|
||||
}
|
||||
|
||||
isArray := len(f.Type) >= 2 && f.Type[0:2] == "[]"
|
||||
|
||||
data := FieldTemplateContext{
|
||||
Metadata: f.Metadata,
|
||||
IsArray: isArray,
|
||||
GeneratedType: f.GenerateType(),
|
||||
Metadata: field.Metadata,
|
||||
IsArray: field.Metadata.IsSlice,
|
||||
GeneratedType: field.GenerateType(),
|
||||
}
|
||||
|
||||
var result bytes.Buffer
|
||||
@@ -1,16 +1,14 @@
|
||||
package entities
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"slices"
|
||||
"go/ast"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var PrimitiveTypes = map[string]string{
|
||||
"date": "date",
|
||||
"number": "number",
|
||||
"string": "string",
|
||||
"boolean": "boolean",
|
||||
"bool": "boolean",
|
||||
@@ -43,7 +41,7 @@ type TypeTemplateContext struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
func (f *Field) GenerateType() string {
|
||||
func (field *Field) GenerateType() string {
|
||||
keys := make([]string, 0, len(PrimitiveTypes))
|
||||
for k := range PrimitiveTypes {
|
||||
keys = append(keys, k)
|
||||
@@ -51,19 +49,13 @@ func (f *Field) GenerateType() string {
|
||||
|
||||
var data TypeTemplateContext
|
||||
|
||||
if slices.Contains(keys, strings.ToLower(f.Type)) {
|
||||
if field.Metadata.IsPrimitiveType {
|
||||
data.IsPrimitive = true
|
||||
data.PrimitiveType = PrimitiveTypes[strings.ToLower(f.Type)]
|
||||
typeName := field.Type.(*ast.Ident).Name
|
||||
data.PrimitiveType = PrimitiveTypes[typeName]
|
||||
} else {
|
||||
data.IsPrimitive = false
|
||||
field := "[]"
|
||||
for _, meta := range f.Metadata {
|
||||
if meta.Name == "field" && len(meta.Values) > 0 {
|
||||
field = "['" + strings.Join(meta.Values, "', '") + "']"
|
||||
break
|
||||
}
|
||||
}
|
||||
data.Field = field
|
||||
data.Field = "['" + strings.Join(field.Metadata.RelatedFields, "', '") + "']"
|
||||
}
|
||||
|
||||
tmpl, err := template.New("type").Parse(typeTemplate)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
14
utils/get_models_path.go
Normal 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]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
63
utils/is_primitive_type.go
Normal file
63
utils/is_primitive_type.go
Normal 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
11
utils/is_slice.go
Normal 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
|
||||
}
|
||||
21
utils/resolve_base_type.go
Normal file
21
utils/resolve_base_type.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user