supports inline struct and optimized code

This commit is contained in:
Yu Huang 2021-05-22 22:34:39 -04:00
parent c3bb85f7b1
commit aa69764e9f
2 changed files with 89 additions and 80 deletions

View File

@ -3,64 +3,45 @@ package beeParser
import (
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
)
// type to default value
var builtInTypeMap = map[string]interface{}{
"string": "",
"int": 0,
"int64": 0,
"int32": 0,
"uint": 0,
"uint32": 0,
"uint64": 0,
"bool": false,
// @todo add more type
}
// StructField defines struct field
type StructField struct {
Name string
Type ast.Expr
Type types.Type
NestedType *StructNode
Comment string
Doc string
Tag string
}
func (sf *StructField) IsBuiltInType() bool {
_, found := builtInTypeMap[fmt.Sprint(sf.Type)]
return found
}
// Key returns the key of the field
func (sf *StructField) Key() string {
return sf.Name
}
// Value returns the value of the field
// if the field contains nested struct, it will return a nested result
func (sf *StructField) Value() interface{} {
switch sf.Type.(type) {
case *ast.Ident:
val, found := builtInTypeMap[fmt.Sprint(sf.Type)]
if found {
return val
}
if sf.NestedType != nil {
return sf.NestedType.ToKV()
case *ast.ArrayType:
case *ast.MapType:
case *ast.SelectorExpr: // third party
}
return ""
}
// StructNode defines struct node
type StructNode struct {
Name string
Fields []*StructField
}
// ToKV transfers struct to key value pair
func (sn *StructNode) ToKV() map[string]interface{} {
value := map[string]interface{}{}
for _, field := range sn.Fields {
@ -69,19 +50,39 @@ func (sn *StructNode) ToKV() map[string]interface{} {
return value
}
type ConfigGenerator struct {
StructMap map[string]*StructNode // @todo key = {package}+{struct name}
RootStruct string //match with the key of StructMap
// StructParser parses structs in given file or string
type StructParser struct {
MainStruct *StructNode
Info types.Info
}
func NewConfigGenerator(filePath string, src interface{}, rootStruct string) (*ConfigGenerator, error) {
// NewStructParser is the constructor of StructParser
// filePath and src follow the same rule with go/parser.ParseFile
// If src != nil, ParseFile parses the source from src and the filename is only used when recording position information. The type of the argument for the src parameter must be string, []byte, or io.Reader. If src == nil, ParseFile parses the file specified by filename.
// rootStruct is the root struct we want to use
func NewStructParser(filePath string, src interface{}, rootStruct string) (*StructParser, error) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filePath, src, parser.ParseComments)
if err != nil {
return nil, err
}
structMap := map[string]*StructNode{}
info := types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
}
conf := types.Config{
Importer: importer.ForCompiler(fset, "source", nil),
}
_, err = conf.Check("src", fset, []*ast.File{f}, &info)
if err != nil {
return nil, err
}
cg := &StructParser{
Info: info,
}
ast.Inspect(f, func(n ast.Node) bool {
// ast.Print(nil, n)
@ -100,30 +101,27 @@ func NewConfigGenerator(filePath string, src interface{}, rootStruct string) (*C
return true
}
structMap[structName] = ParseStruct(structName, s)
cg.MainStruct = cg.ParseStruct(structName, s)
return false
})
if _, found := structMap[rootStruct]; !found {
if cg.MainStruct == nil {
return nil, errors.New("non-exist root struct")
}
return &ConfigGenerator{
StructMap: structMap,
RootStruct: rootStruct,
}, nil
return cg, nil
}
func (cg *ConfigGenerator) ToJSON() ([]byte, error) {
rootStruct := cg.StructMap[cg.RootStruct]
value := rootStruct.ToKV()
func (cg *StructParser) ToJSON() ([]byte, error) {
value := cg.MainStruct.ToKV()
return json.MarshalIndent(value, "", " ")
}
func ParseField(field *ast.Field) *StructField {
// ParseField parses struct field in nested way
func (cg *StructParser) ParseField(field *ast.Field) *StructField {
// ast.Print(nil, field)
fieldName := field.Names[0].Name
fieldType := field.Type
fieldType := cg.Info.TypeOf(field.Type)
fieldTag := ""
if field.Tag != nil {
@ -138,18 +136,12 @@ func ParseField(field *ast.Field) *StructField {
fieldDoc = field.Doc.Text()
}
switch field.Type.(type) {
case *ast.Ident: // built-in or nested
isNested := (field.Type.(*ast.Ident).Obj != nil)
if !isNested {
return &StructField{
Name: fieldName,
Type: fieldType,
Tag: fieldTag,
Comment: fieldComment,
Doc: fieldDoc,
}
}
var nestedStruct *StructNode
if s, isInlineStruct := field.Type.(*ast.StructType); isInlineStruct {
nestedStruct = cg.ParseStruct("", s)
}
if _, isNamedStructorBasic := field.Type.(*ast.Ident); isNamedStructorBasic && field.Type.(*ast.Ident).Obj != nil {
ts, ok := field.Type.(*ast.Ident).Obj.Decl.(*ast.TypeSpec)
if !ok || ts.Type == nil {
return nil
@ -159,32 +151,28 @@ func ParseField(field *ast.Field) *StructField {
if !ok {
return nil
}
return &StructField{
Name: fieldName,
Type: fieldType,
Tag: fieldTag,
Comment: fieldComment,
Doc: fieldDoc,
NestedType: ParseStruct(ts.Name.Name, s),
}
case *ast.ArrayType:
case *ast.MapType:
case *ast.SelectorExpr: // third party
nestedStruct = cg.ParseStruct(ts.Name.Name, s)
}
// fieldType.(*types.Basic) // basic type
// *ast.ArrayType:
// *ast.MapType:
// *ast.SelectorExpr: // third party
return &StructField{
Name: fieldName,
Type: fieldType,
Tag: fieldTag,
Comment: fieldComment,
Doc: fieldDoc,
Name: fieldName,
Type: fieldType,
Tag: fieldTag,
Comment: fieldComment,
Doc: fieldDoc,
NestedType: nestedStruct,
}
}
func ParseStruct(structName string, s *ast.StructType) *StructNode {
// ParseStruct parses struct in nested way
func (cg *StructParser) ParseStruct(structName string, s *ast.StructType) *StructNode {
fields := []*StructField{}
for _, field := range s.Fields.List {
parsedField := ParseField(field)
parsedField := cg.ParseField(field)
if parsedField != nil {
fields = append(fields, parsedField)
}

View File

@ -5,23 +5,32 @@ import (
"log"
)
func ExampleConfigGenerator() {
func ExampleStructParser() {
const src = `
package p
import "http"
import (
"net/http"
)
type StructB struct {
Field1 string
}
type StructA struct {
Field1 string
Field2 StructB
Field2 struct{
a string
b string
}
Field3 []string
Field4 map[string]string
Field5 http.SameSite
Field6 func(int)
Field7 StructB
}
`
cg, err := NewConfigGenerator("./sample.go", src, "StructA")
cg, err := NewStructParser("src.go", src, "StructA")
if err != nil {
log.Fatal(err)
}
@ -35,5 +44,17 @@ type StructA struct {
// Output:
// {
// "Field1": "",
// "Field2": {
// "a": "",
// "b": ""
// },
// "Field3": "",
// "Field4": "",
// "Field5": "",
// "Field6": "",
// "Field7": {
// "Field1": ""
// }
// }
}