diff --git a/pkg/tools/gen/genkcl.go b/pkg/tools/gen/genkcl.go index 30fb3dcf..20b7f1a4 100644 --- a/pkg/tools/gen/genkcl.go +++ b/pkg/tools/gen/genkcl.go @@ -2,20 +2,15 @@ package gen import ( "errors" - "fmt" "io" "path/filepath" - "regexp" - "strconv" "strings" "kcl-lang.io/kcl-go/pkg/loader" - "kcl-lang.io/kcl-go/pkg/logger" ) type GenKclOptions struct { Mode Mode - ParseFromTag bool CastingOption castingOption UseIntegersForNumbers bool } @@ -104,180 +99,3 @@ func (k *kclGenerator) GenSchema(w io.Writer, filename string, src interface{}) return errors.New("unknown mode") } } - -func (k *kclGenerator) genSchemaFromGoStruct(w io.Writer, filename string, src interface{}) error { - fmt.Fprintln(w) - goStructs, err := ParseGoSourceCode(filename, src) - if err != nil { - return err - } - for _, goStruct := range goStructs { - fmt.Fprintf(w, "schema %s:\n", goStruct.Name) - if goStruct.StructComment != "" { - fmt.Fprintf(w, " \"\"\"%s\"\"\"\n", goStruct.StructComment) - } - for _, field := range goStruct.Fields { - kclFieldName, kclFieldType, err := k.GetTypeName(field) - if err != nil { - logger.GetLogger().Warningf("get struct tag key kcl info err: %s, will generate kcl schema from the struct field metadata data, field info %#v", err.Error(), field) - kclFieldName, kclFieldType = k.GetKclTypeFromStructField(field) - } - fmt.Fprintf(w, " %s: %s\n", kclFieldName, kclFieldType) - } - fmt.Fprintf(w, "\n") - } - return nil -} - -func (k *kclGenerator) GetTypeName(f *GoStructField) (string, string, error) { - if k.opts.ParseFromTag { - return k.parserGoStructFieldTag(f.FieldTag) - } - fieldName, fieldType := k.GetKclTypeFromStructField(f) - return fieldName, fieldType, nil -} - -func (k *kclGenerator) parserGoStructFieldTag(tag string) (string, string, error) { - tagMap := make(map[string]string, 0) - sp := strings.Split(tag, "`") - if len(sp) == 1 { - return "", "", errors.New("this field not found tag string like ``") - } - value, ok := k.Lookup(sp[1], "kcl") - if !ok { - return "", "", errors.New("not found tag key named kcl") - } - reg := "name=.*,type=.*" - match, err := regexp.Match(reg, []byte(value)) - if err != nil { - return "", "", err - } - if !match { - return "", "", errors.New("don't match the kcl tag info, the tag info style is name=NAME,type=TYPE") - } - tagInfo := strings.Split(value, ",") - for _, s := range tagInfo { - t := strings.Split(s, "=") - tagMap[t[0]] = t[1] - } - fieldType := tagMap["type"] - if strings.Contains(tagMap["type"], ")|") { - typeUnionList := strings.Split(tagMap["type"], "|") - var ss []string - for _, u := range typeUnionList { - _, _, litValue := k.isLitType(u) - ss = append(ss, litValue) - } - fieldType = strings.Join(ss, "|") - } - return tagMap["name"], fieldType, nil -} - -func (k *kclGenerator) GetKclTypeFromStructField(f *GoStructField) (string, string) { - return f.FieldName, k.isLitGoType(f.FieldType) -} - -func (k *kclGenerator) isLitType(fieldType string) (ok bool, basicTyp, litValue string) { - if !strings.HasSuffix(fieldType, ")") { - return - } - - i := strings.Index(fieldType, "(") + 1 - j := strings.LastIndex(fieldType, ")") - - switch { - case strings.HasPrefix(fieldType, "bool("): - return true, "bool", fieldType[i:j] - case strings.HasPrefix(fieldType, "int("): - return true, "int", fieldType[i:j] - case strings.HasPrefix(fieldType, "float("): - return true, "float", fieldType[i:j] - case strings.HasPrefix(fieldType, "str("): - return true, "str", strconv.Quote(fieldType[i:j]) - } - return -} - -func (k *kclGenerator) isLitGoType(fieldType string) string { - switch fieldType { - case "int", "int32", "int64": - return "int" - case "float", "float64": - return "float" - case "string": - return "str" - case "bool": - return "bool" - case "interface{}": - return "any" - default: - if strings.HasPrefix(fieldType, "*") { - i := strings.Index(fieldType, "*") + 1 - return k.isLitGoType(fieldType[i:]) - } - if strings.HasPrefix(fieldType, "map") { - i := strings.Index(fieldType, "[") + 1 - j := strings.Index(fieldType, "]") - return fmt.Sprintf("{%s:%s}", k.isLitGoType(fieldType[i:j]), k.isLitGoType(fieldType[j+1:])) - } - if strings.HasPrefix(fieldType, "[]") { - i := strings.Index(fieldType, "]") + 1 - return fmt.Sprintf("[%s]", k.isLitGoType(fieldType[i:])) - } - return fieldType - } -} - -func (k *kclGenerator) Lookup(tag, key string) (value string, ok bool) { - // When modifying this code, also update the validateStructTag code - // in cmd/vet/structtag.go. - - for tag != "" { - // Skip leading space. - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // Scan to colon. A space, a quote or a control character is a syntax error. - // Strictly speaking, control chars include the range [0x7f, 0x9f], not just - // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters - // as it is simpler to inspect the tag's bytes than the tag's runes. - i = 0 - for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { - i++ - } - if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // Scan quoted string to find value. - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if key == name { - value, err := strconv.Unquote(qvalue) - if err != nil { - break - } - return value, true - } - } - return "", false -} diff --git a/pkg/tools/gen/genkcl_gostruct.go b/pkg/tools/gen/genkcl_gostruct.go new file mode 100644 index 00000000..26dcb41a --- /dev/null +++ b/pkg/tools/gen/genkcl_gostruct.go @@ -0,0 +1,442 @@ +package gen + +import ( + "errors" + "fmt" + "go/ast" + "go/token" + "go/types" + "io" + "regexp" + "strconv" + "strings" + + "github.com/iancoleman/strcase" + "golang.org/x/tools/go/packages" +) + +type goStruct struct { + pkgPath string + pkgName string + name string + fields []field + doc string + fieldDocs map[string]string +} + +type field struct { + name string + ty types.Type + tag string +} + +type genKclTypeContext struct { + context + // Go package path. + pkgPath string + goStructs map[*types.TypeName]goStruct + oneFile bool +} + +func (k *kclGenerator) genSchemaFromGoStruct(w io.Writer, filename string, _ interface{}) error { + ctx := genKclTypeContext{ + pkgPath: filename, + context: context{ + resultMap: make(map[string]convertResult), + imports: make(map[string]struct{}), + paths: []string{}, + }, + oneFile: true, + } + results, err := ctx.convertSchemaFromGoPackage() + if err != nil { + return err + } + kclSch := kclFile{ + Schemas: []schema{}, + } + for _, result := range results { + if result.IsSchema { + kclSch.Schemas = append(kclSch.Schemas, result.schema) + } + } + // generate kcl schema code + return k.genKcl(w, kclSch) +} + +func (ctx *genKclTypeContext) typeName(defName string, fieldName string, ty types.Type) typeInterface { + switch ty := ty.(type) { + case *types.Basic: + switch ty.Kind() { + case types.Bool, types.UntypedBool: + return typePrimitive(typBool) + case types.Int, + types.Int8, + types.Int16, + types.Int32, + types.Int64, + types.Uint, + types.Uint8, + types.Uint16, + types.Uint32, + types.Uint64, + types.Uintptr, + types.UnsafePointer, + types.UntypedInt: + return typePrimitive(typInt) + case types.Float32, + types.Float64, + types.Complex64, + types.Complex128, + types.UntypedFloat, + types.UntypedComplex: + return typePrimitive(typFloat) + case types.String, types.UntypedString, types.UntypedRune: + return typePrimitive(typStr) + default: + return typePrimitive(typAny) + } + case *types.Pointer: + return ctx.typeName(defName, fieldName, ty.Elem()) + case *types.Named: + obj := ty.Obj() + switch { + case obj.Pkg().Path() == "time" && obj.Name() == "Time": + return typePrimitive(typStr) + case obj.Pkg().Path() == "time" && obj.Name() == "Duration": + return typePrimitive(typInt) + case obj.Pkg().Path() == "math/big" && obj.Name() == "Int": + return typePrimitive(typInt) + default: + if _, ok := ctx.goStructs[obj]; !ok { + return ctx.typeName(defName, fieldName, ty.Underlying()) + } else { + return typeCustom{ + Name: obj.Name(), + } + } + } + case *types.Array: + return typeArray{ + Items: ctx.typeName(defName, fieldName, ty.Elem()), + } + case *types.Slice: + return typeArray{ + Items: ctx.typeName(defName, fieldName, ty.Elem()), + } + case *types.Map: + return typeDict{ + Key: ctx.typeName(defName, fieldName, ty.Key()), + Value: ctx.typeName(defName, fieldName, ty.Elem()), + } + case *types.Struct: + schemaName := fmt.Sprintf("%s%s", defName, strcase.ToCamel(fieldName)) + if _, ok := ctx.resultMap[schemaName]; !ok { + result := convertResult{IsSchema: true} + ctx.resultMap[schemaName] = result + for i := 0; i < ty.NumFields(); i++ { + sf := ty.Field(i) + typeName := ctx.typeName(schemaName, sf.Name(), sf.Type()) + result.schema.Name = schemaName + result.schema.Properties = append(result.Properties, property{ + Name: formatName(sf.Name()), + Type: typeName, + }) + ctx.resultMap[schemaName] = result + } + } + return typeCustom{ + Name: schemaName, + } + case *types.Union: + var types []typeInterface + for i := 0; i < ty.Len(); i++ { + types = append(types, ctx.typeName(defName, fieldName, ty.Term(i).Type())) + } + return typeUnion{ + Items: types, + } + case *types.Interface: + if !ty.IsComparable() { + return typePrimitive(typAny) + } + var types []typeInterface + for i := 0; i < ty.NumEmbeddeds(); i++ { + types = append(types, ctx.typeName(defName, fieldName, ty.EmbeddedType(i))) + } + return typeUnion{ + Items: types, + } + default: + return typePrimitive(typAny) + } +} + +func (ctx *genKclTypeContext) convertSchemaFromGoPackage() ([]convertResult, error) { + structs, error := fetchStructs(ctx.pkgPath) + ctx.goStructs = structs + if error != nil { + return nil, error + } + var results []convertResult + for _, s := range structs { + name := s.name + if _, ok := ctx.resultMap[name]; !ok { + result := convertResult{IsSchema: true} + ctx.resultMap[name] = result + for _, field := range s.fields { + typeName := ctx.typeName(name, field.name, field.ty) + fieldName := formatName(field.name) + tagName, tagTy, err := parserGoStructFieldTag(field.tag) + if err == nil && tagName != "" && tagTy != nil { + fieldName = tagName + typeName = tagTy + } + result.schema.Name = name + result.schema.Description = s.doc + result.schema.Properties = append(result.Properties, property{ + Name: fieldName, + Type: typeName, + Description: s.fieldDocs[field.name], + }) + ctx.resultMap[name] = result + } + } + } + // Append anonymous structs + for _, key := range getSortedKeys(ctx.resultMap) { + if ctx.resultMap[key].IsSchema { + results = append(results, ctx.resultMap[key]) + } + } + return results, nil +} + +func fetchStructs(pkgPath string) (map[*types.TypeName]goStruct, error) { + cfg := &packages.Config{Mode: packages.NeedTypes | packages.NeedDeps | packages.NeedSyntax | packages.NeedTypesInfo} + pkgs, err := packages.Load(cfg, pkgPath) + if err != nil { + return nil, err + } + structs := make(map[*types.TypeName]goStruct) + for _, pkg := range pkgs { + astFiles := pkg.Syntax + scope := pkg.Types.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if obj, ok := obj.(*types.TypeName); ok { + named, _ := obj.Type().(*types.Named) + if structType, ok := named.Underlying().(*types.Struct); ok { + structDoc := getStructDoc(name, astFiles) + fields, fieldDocs := getStructFieldsAndDocs(structType, name, astFiles) + pkgPath := named.Obj().Pkg().Path() + pkgName := named.Obj().Pkg().Name() + structs[named.Obj()] = goStruct{ + pkgPath: pkgPath, + pkgName: pkgName, + name: name, + fields: fields, + doc: structDoc, + fieldDocs: fieldDocs, + } + } + } + } + } + return structs, nil +} + +func getStructDoc(structName string, astFiles []*ast.File) string { + for _, file := range astFiles { + for _, decl := range file.Decls { + if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE { + for _, spec := range genDecl.Specs { + if typeSpec, ok := spec.(*ast.TypeSpec); ok && typeSpec.Name.Name == structName { + if genDecl.Doc != nil { + return genDecl.Doc.Text() + } + } + } + } + } + } + return "" +} + +func getStructFieldsAndDocs(structType *types.Struct, structName string, astFiles []*ast.File) ([]field, map[string]string) { + fieldDocs := make(map[string]string) + var fields []field + for i := 0; i < structType.NumFields(); i++ { + f := structType.Field(i) + var tag string + for _, file := range astFiles { + for _, decl := range file.Decls { + if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE { + for _, spec := range genDecl.Specs { + if typeSpec, ok := spec.(*ast.TypeSpec); ok && typeSpec.Name.Name == structName { + if structType, ok := typeSpec.Type.(*ast.StructType); ok { + for _, field := range structType.Fields.List { + for _, fieldName := range field.Names { + if fieldName.Name == f.Name() { + if field.Doc != nil { + fieldDocs[fieldName.Name] = field.Doc.Text() + } + if field.Tag != nil { + tag = field.Tag.Value + } + } + } + } + } + } + } + } + } + } + if f.Embedded() { + embeddedFields, embeddedFieldDocs := getEmbeddedFieldsAndDocs(f.Type(), astFiles, structName) + fields = append(fields, embeddedFields...) + for k, v := range embeddedFieldDocs { + fieldDocs[k] = v + } + } else { + if f.Exported() { + fields = append(fields, field{ + name: f.Name(), + ty: f.Type(), + tag: tag, + }) + } + } + } + return fields, fieldDocs +} + +func getEmbeddedFieldsAndDocs(t types.Type, astFiles []*ast.File, structName string) ([]field, map[string]string) { + fieldDocs := make(map[string]string) + var fields []field + switch t := t.(type) { + case *types.Pointer: + fields, fieldDocs = getEmbeddedFieldsAndDocs(t.Elem(), astFiles, structName) + case *types.Named: + if structType, ok := t.Underlying().(*types.Struct); ok { + fields, fieldDocs = getStructFieldsAndDocs(structType, structName, astFiles) + } + case *types.Struct: + fields, fieldDocs = getStructFieldsAndDocs(t, structName, astFiles) + } + return fields, fieldDocs +} + +func parserGoStructFieldTag(tag string) (string, typeInterface, error) { + tagMap := make(map[string]string, 0) + sp := strings.Split(tag, "`") + if len(sp) == 1 { + return "", nil, errors.New("this field not found tag string like ``") + } + value, ok := lookupTag(sp[1], "kcl") + if !ok { + return "", nil, errors.New("not found tag key named kcl") + } + reg := "name=.*,type=.*" + match, err := regexp.Match(reg, []byte(value)) + if err != nil { + return "", nil, err + } + if !match { + return "", nil, errors.New("don't match the kcl tag info, the tag info style is name=NAME,type=TYPE") + } + tagInfo := strings.Split(value, ",") + for _, s := range tagInfo { + t := strings.Split(s, "=") + tagMap[t[0]] = t[1] + } + fieldType := tagMap["type"] + if strings.Contains(tagMap["type"], ")|") { + typeUnionList := strings.Split(tagMap["type"], "|") + var ss []string + for _, u := range typeUnionList { + _, _, litValue := isLitType(u) + ss = append(ss, litValue) + } + fieldType = strings.Join(ss, "|") + } + return tagMap["name"], typeCustom{ + Name: fieldType, + }, nil +} + +func isLitType(fieldType string) (ok bool, basicTyp, litValue string) { + if !strings.HasSuffix(fieldType, ")") { + return + } + + i := strings.Index(fieldType, "(") + 1 + j := strings.LastIndex(fieldType, ")") + + switch { + case strings.HasPrefix(fieldType, "bool("): + return true, "bool", fieldType[i:j] + case strings.HasPrefix(fieldType, "int("): + return true, "int", fieldType[i:j] + case strings.HasPrefix(fieldType, "float("): + return true, "float", fieldType[i:j] + case strings.HasPrefix(fieldType, "str("): + return true, "str", strconv.Quote(fieldType[i:j]) + } + return +} + +func lookupTag(tag, key string) (value string, ok bool) { + // When modifying this code, also update the validateStructTag code + // in cmd/vet/structtag.go. + + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if key == name { + value, err := strconv.Unquote(qvalue) + if err != nil { + break + } + return value, true + } + } + return "", false +} diff --git a/pkg/tools/gen/genkcl_jsonschema.go b/pkg/tools/gen/genkcl_jsonschema.go index 27e14956..2e38df42 100644 --- a/pkg/tools/gen/genkcl_jsonschema.go +++ b/pkg/tools/gen/genkcl_jsonschema.go @@ -24,15 +24,19 @@ const ( camelCase ) +type context struct { + imports map[string]struct{} + resultMap map[string]convertResult + paths []string + castingOption castingOption +} + type convertContext struct { + context rootSchema *jsonschema.Schema - imports map[string]struct{} - resultMap map[string]convertResult - paths []string // pathObjects is used to avoid infinite loop when converting recursive schema // TODO: support recursive schema - pathObjects []*jsonschema.Schema - castingOption castingOption + pathObjects []*jsonschema.Schema } type convertResult struct { @@ -66,10 +70,12 @@ func (k *kclGenerator) genSchemaFromJsonSchema(w io.Writer, filename string, src // convert json schema to kcl schema ctx := convertContext{ - rootSchema: js, - resultMap: make(map[string]convertResult), - imports: make(map[string]struct{}), - paths: []string{}, + rootSchema: js, + context: context{ + resultMap: make(map[string]convertResult), + imports: make(map[string]struct{}), + paths: []string{}, + }, pathObjects: []*jsonschema.Schema{}, } result := convertSchemaFromJsonSchema(&ctx, js, diff --git a/pkg/tools/gen/genkcl_test.go b/pkg/tools/gen/genkcl_test.go index 7f0ff4cc..b7bf63d9 100644 --- a/pkg/tools/gen/genkcl_test.go +++ b/pkg/tools/gen/genkcl_test.go @@ -14,97 +14,107 @@ import ( func TestGenKcl(t *testing.T) { var buf bytes.Buffer - opts := &GenKclOptions{ - ParseFromTag: false, - } + opts := &GenKclOptions{} err := GenKcl(&buf, "./testdata/genkcldata.go", nil, opts) // err := GenKcl(&buf, "demo", code, opts) if err != nil { log.Fatal(err) } kclCode := buf.String() - fmt.Println("###############") - fmt.Print(kclCode) - expectedKclCodeFromTag := ` -schema Person: - """Person Example""" - name: str - age: int - friends: [str] - movies: {str:Movie} - MapInterface: {str:{str:any}} - Ep: employee - Com: Company - StarInt: int - StarMap: {str:str} - Inter: any + expectedKclCodeFromField := `""" +This file was generated by the KCL auto-gen tool. DO NOT EDIT. +Editing this file might prove futile when you re-run the KCL auto-gen generate command. +""" + +schema Company: + r""" + Company + + Attributes + ---------- + name : str, optional + employees : [Employee], optional + persons : Person, optional + """ + + name?: str + employees?: [Employee] + persons?: Person + +schema Employee: + r""" + Employee + + Attributes + ---------- + name : str, optional + age : int, optional + friends : [str], optional + movies : {str:Movie}, optional + bankCard : int, optional + nationality : str, optional + """ + + name?: str + age?: int + friends?: [str] + movies?: {str:Movie} + bankCard?: int + nationality?: str schema Movie: - desc: str - size: units.NumberMultiplier + r""" + Movie + + Attributes + ---------- + Desc : str, optional + size : units.NumberMultiplier, optional + kind : "Superhero"|"War"|"Unknown", optional + unknown1 : int|str, optional + unknown2 : any, optional + """ + + Desc?: str + size?: units.NumberMultiplier kind?: "Superhero"|"War"|"Unknown" unknown1?: int|str unknown2?: any -schema employee: - name: str - age: int - friends: [str] - movies: {str:Movie} - bankCard: int - nationality: str - -schema Company: - name: str - employees: [employee] - persons: Person - -` - expectedKclCodeFromField := ` schema Person: - """Person Example""" - name: str - age: int - friends: [str] - movies: {str:Movie} - MapInterface: {str:{str:any}} - Ep: employee - Com: Company - StarInt: int - StarMap: {str:str} - Inter: any - -schema Movie: - desc: str - size: int - kind: str - unknown1: any - unknown2: any - -schema employee: - name: str - age: int - friends: [str] - movies: {str:Movie} - bankCard: int - nationality: str - -schema Company: - name: str - employees: [employee] - persons: Person + r""" + Person Example + + + Attributes + ---------- + name : str, optional + age : int, optional + friends : [str], optional + movies : {str:Movie}, optional + MapInterface : {str:{str:any}}, optional + Ep : Employee, optional + Com : Company, optional + StarInt : int, optional + StarMap : {str:str}, optional + Inter : any, optional + """ + + name?: str + age?: int + friends?: [str] + movies?: {str:Movie} + MapInterface?: {str:{str:any}} + Ep?: Employee + Com?: Company + StarInt?: int + StarMap?: {str:str} + Inter?: any ` - if opts.ParseFromTag { - if kclCode != expectedKclCodeFromTag { - panic(fmt.Sprintf("test failed, expected %s got %s", expectedKclCodeFromTag, kclCode)) - } - } else { - if kclCode != expectedKclCodeFromField { - panic(fmt.Sprintf("test failed, expected %s got %s", expectedKclCodeFromField, kclCode)) - } + if kclCode != expectedKclCodeFromField { + panic(fmt.Sprintf("test failed, expected %s got %s", expectedKclCodeFromField, kclCode)) } - } func TestGenKclFromJsonSchema(t *testing.T) { diff --git a/pkg/tools/gen/gotypes.go b/pkg/tools/gen/gotypes.go deleted file mode 100644 index 00a61716..00000000 --- a/pkg/tools/gen/gotypes.go +++ /dev/null @@ -1,107 +0,0 @@ -package gen - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "strings" -) - -type GoStruct struct { - Name string - Fields []*GoStructField - FieldNum int - StructComment string -} - -type GoStructField struct { - FieldName string - FieldType string - FieldTag string - FieldTagKind string - FieldComment string -} - -// ParseGoSourceCode parse go source code from .go file path or source code -func ParseGoSourceCode(filename string, src interface{}) ([]*GoStruct, error) { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) - if err != nil { - return nil, err - } - goStructList := make([]*GoStruct, 0) - for _, v := range f.Decls { - goStruct := &GoStruct{} - if stc, ok := v.(*ast.GenDecl); ok && stc.Tok == token.TYPE { - if stc.Doc != nil { - goStruct.StructComment = strings.TrimRight(stc.Doc.Text(), "\n") - } - for _, spec := range stc.Specs { - goStructFields := make([]*GoStructField, 0) - if tp, ok := spec.(*ast.TypeSpec); ok { - goStruct.Name = tp.Name.Name - if stp, ok := tp.Type.(*ast.StructType); ok { - if !stp.Struct.IsValid() { - continue - } - goStruct.FieldNum = stp.Fields.NumFields() - for _, field := range stp.Fields.List { - goStructField := &GoStructField{} - - // get field name - if len(field.Names) == 1 { - goStructField.FieldName = field.Names[0].Name - } else if len(field.Names) > 1 { - for _, name := range field.Names { - goStructField.FieldName = goStructField.FieldName + name.String() + "," - } - } - - // get tag - if field.Tag != nil { - goStructField.FieldTag = field.Tag.Value - goStructField.FieldTagKind = field.Tag.Kind.String() - } - - // get comment - if field.Comment != nil { - goStructField.FieldComment = strings.TrimRight(field.Comment.Text(), "") - } - - // get field type - goStructField.FieldType = getTypeName(field.Type) - goStructFields = append(goStructFields, goStructField) - } - } - goStruct.Fields = goStructFields - } - } - goStructList = append(goStructList, goStruct) - } - } - return goStructList, nil -} - -func getTypeName(f ast.Expr) string { - if ft, ok := f.(*ast.Ident); ok { - return ft.Name - } - if ft, ok := f.(*ast.ArrayType); ok { - item := getTypeName(ft.Elt) - return fmt.Sprintf("[]%s", item) - } - if ft, ok := f.(*ast.MapType); ok { - key := getTypeName(ft.Key) - value := getTypeName(ft.Value) - return fmt.Sprintf("map[%s]%s", key, value) - } - if ft, ok := f.(*ast.StarExpr); ok { - value := getTypeName(ft.X) - return fmt.Sprintf("*%s", value) - } - if _, ok := f.(*ast.InterfaceType); ok { - return "interface{}" - } - return "" -} diff --git a/pkg/tools/gen/gotypes_test.go b/pkg/tools/gen/gotypes_test.go deleted file mode 100644 index 891b285c..00000000 --- a/pkg/tools/gen/gotypes_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package gen - -import ( - "fmt" - "testing" -) - -func TestParseGoFiles(t *testing.T) { - goStructs, err := ParseGoSourceCode("./testdata/genkcldata.go", nil) - if err != nil { - t.Fatalf("parse go source code err: %s", err.Error()) - } - t.Logf("%#v", goStructs) - for _, v := range goStructs { - fmt.Printf("Struct Name: %s\n", v.Name) - fmt.Printf("struct Num: %d\n", v.FieldNum) - fmt.Printf("struct Comment: %s\n", v.StructComment) - for _, f := range v.Fields { - fmt.Printf("\tFieldName: %s\n", f.FieldName) - fmt.Printf("\tFieldType: %s\n", f.FieldType) - fmt.Printf("\tFieldTag: %s\n", f.FieldTag) - fmt.Printf("\tFieldTagKind: %s\n", f.FieldTagKind) - fmt.Printf("\tFieldComment: %s\n", f.FieldComment) - fmt.Printf("\t\n") - } - } -} diff --git a/pkg/tools/gen/testdata/genkcldata.go b/pkg/tools/gen/testdata/genkcldata.go index 25391715..c3fa3bb5 100644 --- a/pkg/tools/gen/testdata/genkcldata.go +++ b/pkg/tools/gen/testdata/genkcldata.go @@ -2,12 +2,12 @@ package testdata // Person Example type Person struct { - name string `kcl:"name=name,type=str"` // kcl-type: str - age int `kcl:"name=age,type=int"` // kcl-type: int - friends []string `kcl:"name=friends,type=[str]"` // kcl-type: [str] - movies map[string]*Movie `kcl:"name=movies,type={str:Movie}"` // kcl-type: {str:Movie} + Name string `kcl:"name=name,type=str"` // kcl-type: str + Age int `kcl:"name=age,type=int"` // kcl-type: int + Friends []string `kcl:"name=friends,type=[str]"` // kcl-type: [str] + Movies map[string]*Movie `kcl:"name=movies,type={str:Movie}"` // kcl-type: {str:Movie} MapInterface map[string]map[string]interface{} - Ep *employee + Ep *Employee Com Company StarInt *int StarMap map[string]string @@ -15,24 +15,24 @@ type Person struct { } type Movie struct { - desc string `kcl:"nam=desc,type=str"` // kcl-type: str - size int `kcl:"name=size,type=units.NumberMultiplier"` // kcl-type: units.NumberMultiplier - kind string `kcl:"name=kind?,type=str(Superhero)|str(War)|str(Unknown)"` // kcl-type: "Superhero"|"War"|"Unknown" - unknown1 interface{} `kcl:"name=unknown1?,type=int|str"` // kcl-type: int|str - unknown2 interface{} `kcl:"name=unknown2?,type=any"` // kcl-type: any + Desc string `kcl:"nam=desc,type=str"` // kcl-type: str + Size int `kcl:"name=size,type=units.NumberMultiplier"` // kcl-type: units.NumberMultiplier + Kind string `kcl:"name=kind,type=str(Superhero)|str(War)|str(Unknown)"` // kcl-type: "Superhero"|"War"|"Unknown" + Unknown1 interface{} `kcl:"name=unknown1,type=int|str"` // kcl-type: int|str + Unknown2 interface{} `kcl:"name=unknown2,type=any"` // kcl-type: any } -type employee struct { - name string `kcl:"name=name,type=str"` // kcl-type: str - age int `kcl:"name=age,type=int"` // kcl-type: int - friends []string `kcl:"name=friends,type=[str]"` // kcl-type: [str] - movies map[string]*Movie `kcl:"name=movies,type={str:Movie}"` // kcl-type: {str:Movie} - bankCard int `kcl:"name=bankCard,type=int"` // kcl-type: int - nationality string `kcl:"name=nationality,type=str"` // kcl-type: str +type Employee struct { + Name string `kcl:"name=name,type=str"` // kcl-type: str + Age int `kcl:"name=age,type=int"` // kcl-type: int + Friends []string `kcl:"name=friends,type=[str]"` // kcl-type: [str] + Movies map[string]*Movie `kcl:"name=movies,type={str:Movie}"` // kcl-type: {str:Movie} + BankCard int `kcl:"name=bankCard,type=int"` // kcl-type: int + Nationality string `kcl:"name=nationality,type=str"` // kcl-type: str } type Company struct { - name string `kcl:"name=name,type=str"` // kcl-type: str - employees []*employee `kcl:"name=employees,type=[employee]"` // kcl-type: [employee] - persons *Person `kcl:"name=persons,type=Person"` // kcl-type: Person + Name string `kcl:"name=name,type=str"` // kcl-type: str + Employees []*Employee `kcl:"name=employees,type=[Employee]"` // kcl-type: [Employee] + Persons *Person `kcl:"name=persons,type=Person"` // kcl-type: Person }