From 70c4a5f5f06b2ceb230e551340dd9ba81d393507 Mon Sep 17 00:00:00 2001 From: Yu Huang Date: Sat, 22 May 2021 10:14:59 -0400 Subject: [PATCH] add parser --- parser/parser.go | 184 ++++++++++++++++++++++++++++++++++++++++++ parser/parser_test.go | 39 +++++++++ 2 files changed, 223 insertions(+) create mode 100644 parser/parser.go create mode 100644 parser/parser_test.go diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..28ca6ff --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,184 @@ +package beeParser + +import ( + "encoding/json" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" +) + +// 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 +} + +type StructField struct { + Name string + Type string + NestedType *StructNode + Comment string + Doc string + Tag string +} + +func (sf *StructField) IsBuiltInType() bool { + _, found := builtInTypeMap[sf.Type] + return found +} + +func (sf *StructField) Key() string { + return sf.Name +} + +func (sf *StructField) Value() interface{} { + val, found := builtInTypeMap[sf.Type] + if found { + return val + } + + return sf.NestedType.ToKV() +} + +type StructNode struct { + Name string + Fields []*StructField +} + +func (sn *StructNode) ToKV() map[string]interface{} { + value := map[string]interface{}{} + for _, field := range sn.Fields { + value[field.Key()] = field.Value() + } + return value +} + +type ConfigGenerator struct { + StructMap map[string]*StructNode // @todo key = {package}+{struct name} + RootStruct string //match with the key of StructMap +} + +func NewConfigGenerator(filePath string, src interface{}, rootStruct string) (*ConfigGenerator, error) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, filePath, src, parser.ParseComments) + if err != nil { + return nil, err + } + + structMap := map[string]*StructNode{} + + ast.Inspect(f, func(n ast.Node) bool { + // ast.Print(nil, n) + ts, ok := n.(*ast.TypeSpec) + if !ok || ts.Type == nil { + return true + } + + structName := ts.Name.Name + if structName != rootStruct { + return true + } + + s, ok := ts.Type.(*ast.StructType) + if !ok { + return true + } + + structMap[structName] = ParseStruct(structName, s) + + return false + }) + + if _, found := structMap[rootStruct]; !found { + return nil, errors.New("non-exist root struct") + } + + return &ConfigGenerator{ + StructMap: structMap, + RootStruct: rootStruct, + }, nil +} + +func (cg *ConfigGenerator) ToJSON() ([]byte, error) { + rootStruct := cg.StructMap[cg.RootStruct] + value := rootStruct.ToKV() + return json.MarshalIndent(value, "", " ") +} + +func ParseField(field *ast.Field) *StructField { + fieldName := field.Names[0].Name + fieldType := fmt.Sprint(field.Type) + + fieldTag := "" + if field.Tag != nil { + fieldTag = field.Tag.Value + } + fieldComment := "" + if field.Comment != nil { + fieldComment = field.Comment.Text() + } + fieldDoc := "" + if field.Doc != nil { + 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, + } + } + ts, ok := field.Type.(*ast.Ident).Obj.Decl.(*ast.TypeSpec) + if !ok || ts.Type == nil { + return nil + } + + s, ok := ts.Type.(*ast.StructType) + 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 + } + + return nil +} + +func ParseStruct(structName string, s *ast.StructType) *StructNode { + fields := []*StructField{} + for _, field := range s.Fields.List { + parsedField := ParseField(field) + if parsedField != nil { + fields = append(fields, parsedField) + } + } + + return &StructNode{ + Name: structName, + Fields: fields, + } +} diff --git a/parser/parser_test.go b/parser/parser_test.go new file mode 100644 index 0000000..af1161a --- /dev/null +++ b/parser/parser_test.go @@ -0,0 +1,39 @@ +package beeParser + +import ( + "fmt" + "log" +) + +func ExampleConfigGenerator() { + const src = ` +package p +import "http" + +type StructB struct { + Field1 string +} +type StructA struct { + Field1 string + Field2 StructB + Field3 []string + Field4 map[string]string + Field5 http.SameSite +} +` + cg, err := NewConfigGenerator("./sample.go", src, "StructA") + if err != nil { + log.Fatal(err) + } + + b, err := cg.ToJSON() + if err != nil { + log.Fatal(err) + } + + fmt.Println(string(b)) + + // Output: + // { + // } +}