mirror of
https://github.com/beego/bee.git
synced 2024-11-21 18:40:54 +00:00
Merge pull request #782 from y4h2/parse-config
Parse AST to generate config document
This commit is contained in:
commit
b168005893
185
parser/parser.go
Normal file
185
parser/parser.go
Normal file
@ -0,0 +1,185 @@
|
||||
package beeParser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// StructField defines struct field
|
||||
type StructField struct {
|
||||
Name string
|
||||
Type types.Type
|
||||
NestedType *StructNode
|
||||
Comment string
|
||||
Doc string
|
||||
Tag string
|
||||
}
|
||||
|
||||
// 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{} {
|
||||
if sf.NestedType != nil {
|
||||
return sf.NestedType.ToKV()
|
||||
}
|
||||
|
||||
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 {
|
||||
value[field.Key()] = field.Value()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// StructParser parses structs in given file or string
|
||||
type StructParser struct {
|
||||
MainStruct *StructNode
|
||||
Info types.Info
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
cg.MainStruct = cg.ParseStruct(structName, s)
|
||||
return false
|
||||
})
|
||||
|
||||
if cg.MainStruct == nil {
|
||||
return nil, errors.New("non-exist root struct")
|
||||
}
|
||||
|
||||
return cg, nil
|
||||
}
|
||||
|
||||
func (cg *StructParser) ToJSON() ([]byte, error) {
|
||||
value := cg.MainStruct.ToKV()
|
||||
return json.MarshalIndent(value, "", " ")
|
||||
}
|
||||
|
||||
// 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 := cg.Info.TypeOf(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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
s, ok := ts.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
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,
|
||||
NestedType: nestedStruct,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 := cg.ParseField(field)
|
||||
if parsedField != nil {
|
||||
fields = append(fields, parsedField)
|
||||
}
|
||||
}
|
||||
|
||||
return &StructNode{
|
||||
Name: structName,
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
60
parser/parser_test.go
Normal file
60
parser/parser_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
package beeParser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func ExampleStructParser() {
|
||||
const src = `
|
||||
package p
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type StructB struct {
|
||||
Field1 string
|
||||
}
|
||||
type StructA struct {
|
||||
Field1 string
|
||||
Field2 struct{
|
||||
a string
|
||||
b string
|
||||
}
|
||||
Field3 []string
|
||||
Field4 map[string]string
|
||||
Field5 http.SameSite
|
||||
Field6 func(int)
|
||||
Field7 StructB
|
||||
}
|
||||
|
||||
`
|
||||
cg, err := NewStructParser("src.go", src, "StructA")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
b, err := cg.ToJSON()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
|
||||
// Output:
|
||||
// {
|
||||
// "Field1": "",
|
||||
// "Field2": {
|
||||
// "a": "",
|
||||
// "b": ""
|
||||
// },
|
||||
// "Field3": "",
|
||||
// "Field4": "",
|
||||
// "Field5": "",
|
||||
// "Field6": "",
|
||||
// "Field7": {
|
||||
// "Field1": ""
|
||||
// }
|
||||
// }
|
||||
}
|
Loading…
Reference in New Issue
Block a user