diff --git a/go.mod b/go.mod index e4418a5..f117de2 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,10 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/flosch/pongo2 v0.0.0-20200529170236-5abacdfa4915 github.com/fsnotify/fsnotify v1.4.9 + github.com/ghodss/yaml v1.0.0 github.com/go-delve/delve v1.5.0 github.com/go-sql-driver/mysql v1.5.0 + github.com/go-yaml/yaml v2.1.0+incompatible github.com/gorilla/websocket v1.4.2 github.com/lib/pq v1.7.0 github.com/pelletier/go-toml v1.8.1 diff --git a/go.sum b/go.sum index ce91369..4b88faa 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,7 @@ github.com/flosch/pongo2 v0.0.0-20200529170236-5abacdfa4915/go.mod h1:fB4mx6dzqF github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= @@ -94,6 +95,8 @@ github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRf github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= diff --git a/parser/annotator.go b/parser/annotator.go index 022c3d6..f2fcc9d 100644 --- a/parser/annotator.go +++ b/parser/annotator.go @@ -1,6 +1,8 @@ package beeParser import ( + "fmt" + "strconv" "strings" ) @@ -9,6 +11,8 @@ type Annotator interface { } type Annotation struct { + Key, Description string + Default interface{} } func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\r' } @@ -30,16 +34,27 @@ func handleTailWhitespace(s string) string { } //handle value to remove head and tail space. -func handleWhitespaceValues(values []string) []string { - res := make([]string, 0) +func handleWhitespaceValues(values []string) []interface{} { + res := make([]interface{}, 0) for _, v := range values { v = handleHeadWhitespace(v) v = handleTailWhitespace(v) - res = append(res, v) + res = append(res, transferType(v)) } return res } +//try to transfer string to original type +func transferType(str string) interface{} { + if res, err := strconv.Atoi(str); err == nil { + return res + } + if res, err := strconv.ParseBool(str); err == nil { + return res + } + return str +} + //parse annotation to generate array with key and values //start with "@" as a key-value pair,key and values are separated by a space,wrap to distinguish values. func (a *Annotation) Annotate(annotation string) map[string]interface{} { @@ -51,7 +66,26 @@ func (a *Annotation) Annotate(annotation string) map[string]interface{} { kvs := strings.Split(line, " ") key := kvs[0] values := strings.Split(strings.TrimSpace(line[len(kvs[0]):]), "\n") - results[key] = handleWhitespaceValues(values) + if len(values) == 1 { + results[key] = handleWhitespaceValues(values)[0] + } else { + results[key] = handleWhitespaceValues(values) + } } return results } + +func NewAnnotation(annotation string) *Annotation { + a := &Annotation{} + kvs := a.Annotate(annotation) + if v, ok := kvs["Key"]; ok { + a.Key = fmt.Sprintf("%v", v) + } + if v, ok := kvs["Description"]; ok { + a.Description = fmt.Sprintf("%v", v) + } + if v, ok := kvs["Default"]; ok { + a.Default = v + } + return a +} diff --git a/parser/annotator_test.go b/parser/annotator_test.go index d3d9ea5..17862bd 100644 --- a/parser/annotator_test.go +++ b/parser/annotator_test.go @@ -32,14 +32,14 @@ func TestMain(m *testing.M) { func TestAnnotate(t *testing.T) { expect1 := map[string]interface{}{ - "Name": []string{"Field1"}, - "Type": []string{"string"}, - "Path": []string{"https://github.com/beego/bee", "https://github.com/beego"}, + "Name": "Field1", + "Type": "string", + "Path": []interface{}{"https://github.com/beego/bee", "https://github.com/beego"}, } expect2 := map[string]interface{}{ - "Number": []string{"2"}, - "Projects": []string{"https://github.com/beego/bee", "", "https://github.com/beego"}, + "Number": 2, + "Projects": []interface{}{"https://github.com/beego/bee", "", "https://github.com/beego"}, } actual := BeeAnnotator.Annotate(Annotation1) @@ -55,13 +55,17 @@ func TestHandleWhitespaceValues(t *testing.T) { "", " bee ", " bee beego ", + " 1 ", + " false ", } - expect := []string{ + expect := []interface{}{ "beego", "", "bee", "bee beego", + 1, + false, } actual := handleWhitespaceValues(src) diff --git a/parser/formatter.go b/parser/formatter.go index f50926d..fcfb621 100644 --- a/parser/formatter.go +++ b/parser/formatter.go @@ -1,20 +1,97 @@ package beeParser -import "encoding/json" +import ( + "encoding/json" + "encoding/xml" -type AnnotationFormatter struct { - Annotation Annotator + "gopkg.in/yaml.v2" +) + +type JsonFormatter struct { } -func (f *AnnotationFormatter) Format(field *StructField) string { - if field.Comment == "" && field.Doc == "" { - return "" +func (f *JsonFormatter) FieldFormatFunc(field *StructField) ([]byte, error) { + annotation := NewAnnotation(field.Doc + field.Comment) + res := map[string]interface{}{} + if field.NestedType != nil { + res[annotation.Key] = field.NestedType + } else { + res[annotation.Key] = annotation.Default } - kvs := f.Annotation.Annotate(field.Doc + field.Comment) - res, _ := json.Marshal(kvs) - return string(res) + return json.Marshal(res) } -func NewAnnotationFormatter() *AnnotationFormatter { - return &AnnotationFormatter{Annotation: &Annotation{}} +func (f *JsonFormatter) StructFormatFunc(node *StructNode) ([]byte, error) { + return json.Marshal(node.Fields) +} + +func (f *JsonFormatter) Marshal(node *StructNode) ([]byte, error) { + return json.MarshalIndent(node, "", " ") +} + +type YamlFormatter struct { +} + +func (f *YamlFormatter) FieldFormatFunc(field *StructField) ([]byte, error) { + annotation := NewAnnotation(field.Doc + field.Comment) + res := map[string]interface{}{} + if field.NestedType != nil { + res[annotation.Key] = field.NestedType + } else { + res[annotation.Key] = annotation.Default + } + return yaml.Marshal(res) +} + +func (f *YamlFormatter) StructFormatFunc(node *StructNode) ([]byte, error) { + return yaml.Marshal(node.Fields) +} + +func (f *YamlFormatter) Marshal(node *StructNode) ([]byte, error) { + return yaml.Marshal(node) +} + +type XmlFormatter struct { +} + +func (f *XmlFormatter) FieldFormatFunc(field *StructField) ([]byte, error) { + annotation := NewAnnotation(field.Doc + field.Comment) + if field.NestedType != nil { + type xmlStruct struct { + XMLName xml.Name + Default interface{} `xml:",innerxml"` + Description string `xml:",comment"` + } + b, _ := field.NestedType.FormatFunc(field.NestedType) + return xml.Marshal(&xmlStruct{ + XMLName: xml.Name{Local: annotation.Key}, + Description: annotation.Description, + Default: b, + }) + } else { + type xmlStruct struct { + XMLName xml.Name + Default interface{} `xml:",chardata"` + Description string `xml:",comment"` + } + return xml.Marshal(&xmlStruct{ + XMLName: xml.Name{Local: annotation.Key}, + Description: annotation.Description, + Default: annotation.Default, + }) + } +} + +func (f *XmlFormatter) StructFormatFunc(node *StructNode) ([]byte, error) { + res := make([]byte, 0) + for _, f := range node.Fields { + b, _ := f.FormatFunc(f) + res = append(res, b...) + res = append(res, '\n') + } + return res, nil +} + +func (f *XmlFormatter) Marshal(node *StructNode) ([]byte, error) { + return node.FormatFunc(node) } diff --git a/parser/formatter_test.go b/parser/formatter_test.go index 11b6312..6bdea9b 100644 --- a/parser/formatter_test.go +++ b/parser/formatter_test.go @@ -1,33 +1,126 @@ package beeParser import ( - "testing" - - "github.com/stretchr/testify/assert" + "fmt" + "log" ) -func TestFormat(t *testing.T) { - except := `{ - "Name": [ - "Field1" - ], - "Path":[ - "https://github.com/beego/bee", - "https://github.com/beego" - ], - "test":[ - "test comment" - ] - }` +const src = ` +package p - field := &StructField{ - Comment: "@test test comment", - Doc: `@Name Field1 - @Path https://github.com/beego/bee - https://github.com/beego`, +type StructA struct { + // @Key Field1 + // @Default test + // @Description ddddddd + Field1 string + // @Key Field2 + Field2 struct{ + // @Key a + // @Default https://github.com/beego/bee + // https://github.com/beego + a string + // @Key b + // @Default https://github.com/beego/bee https://github.com/beego + b string + } + // @Key Field3 + // @Default 1 + Field3 int + // @Key Field4 + // @Default false + Field4 bool +} +` + +func ExampleJsonFormatter() { + sp, err := NewStructParser("src.go", src, "StructA", &JsonFormatter{}) + if err != nil { + log.Fatal(err) } - actual := NewAnnotationFormatter().Format(field) + b, err := sp.Marshal() + if err != nil { + log.Fatal(err) + } - assert.JSONEq(t, except, actual) + fmt.Println(string(b)) + + // Output: + //[ + // { + // "Field1": "test" + // }, + // { + // "Field2": [ + // { + // "a": [ + // "https://github.com/beego/bee", + // "https://github.com/beego" + // ] + // }, + // { + // "b": "https://github.com/beego/bee https://github.com/beego" + // } + // ] + // }, + // { + // "Field3": 1 + // }, + // { + // "Field4": false + // } + //] +} + +func ExampleYamlFormatter() { + sp, err := NewStructParser("src.go", src, "StructA", &YamlFormatter{}) + if err != nil { + log.Fatal(err) + } + + b, err := sp.Marshal() + if err != nil { + log.Fatal(err) + } + + fmt.Println(string(b)) + + // Output: + //| + // - | + // Field1: test + // - | + // Field2: | + // - | + // a: + // - https://github.com/beego/bee + // - https://github.com/beego + // - | + // b: https://github.com/beego/bee https://github.com/beego + // - | + // Field3: 1 + // - | + // Field4: false +} + +func ExampleXmlFormatter() { + sp, err := NewStructParser("src.go", src, "StructA", &XmlFormatter{}) + if err != nil { + log.Fatal(err) + } + + b, err := sp.Marshal() + if err != nil { + log.Fatal(err) + } + + fmt.Println(string(b)) + + // Output: + //test + // + //https://github.com/beego/bee https://github.com/beego + // + //1 + //false } diff --git a/parser/parser.go b/parser/parser.go index 8559e6b..055ea83 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -34,6 +34,13 @@ func (sf *StructField) MarshalText() ([]byte, error) { return sf.FormatFunc(sf) } +func (sf *StructField) MarshalJSON() ([]byte, error) { + if sf.FormatFunc == nil { + return nil, errors.New("format func is missing") + } + return sf.FormatFunc(sf) +} + // StructNode defines struct node type StructNode struct { Name string @@ -49,6 +56,14 @@ func (sn *StructNode) MarshalText() ([]byte, error) { return sn.FormatFunc(sn) } +func (sn *StructNode) MarshalJSON() ([]byte, error) { + if sn.FormatFunc == nil { + return nil, errors.New("format func is missing") + } + + return sn.FormatFunc(sn) +} + // StructParser parses structs in given file or string type StructParser struct { MainStruct *StructNode diff --git a/parser/sample_formatter.go b/parser/sample_formatter.go deleted file mode 100644 index a1288e7..0000000 --- a/parser/sample_formatter.go +++ /dev/null @@ -1,41 +0,0 @@ -package beeParser - -import "encoding/json" - -type AnnotationJSONFormatter struct { - Annotation Annotator -} - -func (f *AnnotationJSONFormatter) Format(field *StructField) string { - if field.Comment == "" && field.Doc == "" { - return "" - } - kvs := f.Annotation.Annotate(field.Doc + field.Comment) - res, _ := json.Marshal(kvs) - return string(res) -} - -func NewAnnotationJSONFormatter() *AnnotationJSONFormatter { - return &AnnotationJSONFormatter{Annotation: &Annotation{}} -} - -type AnnotationYAMLFormatter struct { - Annotation Annotator -} - -func (f *AnnotationYAMLFormatter) Format(field *StructField) string { - if field.Comment == "" && field.Doc == "" { - return "" - } - kvs := f.Annotation.Annotate(field.Doc + field.Comment) - res, _ := json.Marshal(kvs) - return string(res) -} - -func NewAnnotationYAMLFormatter() *AnnotationYAMLFormatter { - return &AnnotationYAMLFormatter{Annotation: &Annotation{}} -} - -type AnnotationTextFromatter struct { - Annotation Annotator -}