├── .gitignore ├── setup.sh ├── go.mod ├── test.sh ├── misc └── fixture │ └── a │ └── model.go ├── doc.go ├── README.md ├── utils_test.go ├── .circleci └── config.yml ├── LICENSE ├── example_test.go ├── parsing_test.go ├── utils.go └── parsing.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eux 2 | 3 | go mod download 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/favclip/genbase 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eux 2 | 3 | packages=$(go list ./...) 4 | 5 | go test $packages $@ 6 | -------------------------------------------------------------------------------- /misc/fixture/a/model.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | // A is struct 4 | // +test 5 | type A struct { 6 | } 7 | 8 | type ( 9 | // +test 10 | B struct{} 11 | // C is struct 12 | // +test: opts 13 | C struct{} 14 | ) 15 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | genbase is a base library for code generator library construction. 3 | 4 | Do you want to check usage in real world? 5 | see https://github.com/favclip/jwg , https://github.com/favclip/qbg , https://github.com/favclip/smg . 6 | */ 7 | package genbase 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # genbase 2 | 3 | genbase is a base library for code generator library construction. 4 | 5 | Go ♥ code generation. 6 | 7 | ## Description 8 | 9 | *genbase* likes [typewriter](https://github.com/clipperhouse/typewriter). 10 | 11 | What is different between typewriter? 12 | genbase do code parsing and return wrapped ast only. 13 | 14 | code generator must design structure for the output. 15 | 16 | see [jwg](https://github.com/favclip/jwg) source code. 17 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package genbase 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPathJoinAll(t *testing.T) { 8 | ps := pathJoinAll("misc/fixture", "a", "b") 9 | 10 | if len(ps) != 2 { 11 | t.Fatalf("unexpected: %v", len(ps)) 12 | } 13 | 14 | if ps[0] != "misc/fixture/a" || ps[1] != "misc/fixture/b" { 15 | t.Fatal("unexpected", ps) 16 | } 17 | } 18 | 19 | func TestGetKeys(t *testing.T) { 20 | 21 | result := GetKeys("a:\"foo\" b:\"bar\"") 22 | if len(result) != 2 { 23 | t.Log("keys length is not 2, actual", len(result)) 24 | t.Fail() 25 | } 26 | if result[0] != "a" { 27 | t.Log("result[0] is not \"a\", actual", result[0]) 28 | t.Fail() 29 | } 30 | if result[1] != "b" { 31 | t.Log("result[1] is not \"b\", actual", result[1]) 32 | t.Fail() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: /go/src/github.com/favclip/genbase 5 | docker: 6 | - image: google/cloud-sdk:306.0.0 7 | environment: 8 | GOPATH: /go 9 | GOLANG_VERSION: 1.11.13 10 | GO111MODULE: "on" 11 | steps: 12 | - run: 13 | name: PATH update 14 | command: | 15 | echo "export PATH=\$PATH:/go/bin:/usr/local/go/bin" >> $BASH_ENV 16 | cat $BASH_ENV 17 | - run: 18 | name: install go binary 19 | command: | 20 | echo $PATH 21 | /usr/bin/curl -o go.tar.gz https://storage.googleapis.com/golang/go${GOLANG_VERSION}.linux-amd64.tar.gz && \ 22 | tar -zxf go.tar.gz && \ 23 | mv go /usr/local && \ 24 | rm go.tar.gz 25 | 26 | - checkout 27 | 28 | - run: ./setup.sh 29 | - run: ./test.sh 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 tv-asahi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package genbase_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/favclip/genbase" 7 | ) 8 | 9 | func Example() { 10 | p := &genbase.Parser{SkipSemanticsCheck: false} 11 | packageInfo, err := p.ParseStringSource("main.go", ` 12 | package sample 13 | 14 | // Sample is sample! 15 | // +sample 16 | type Sample struct { 17 | A string 18 | B, C string 19 | } 20 | `) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | g := genbase.NewGenerator(packageInfo) 26 | g.AddImport("strings", "sg") 27 | g.AddImport(`"fmt"`, "") 28 | g.PrintHeader("sample", &[]string{}) 29 | 30 | typeInfos := packageInfo.CollectTaggedTypeInfos("+sample") 31 | for _, typeInfo := range typeInfos { 32 | g.Printf("func (obj *%s) String() string {\n", typeInfo.Name()) 33 | st, err := typeInfo.StructType() 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | g.Printf("var ss []string\n") 39 | 40 | for _, fieldInfo := range st.FieldInfos() { 41 | for _, fieldName := range fieldInfo.Names { 42 | g.Printf("ss = append(ss, fmt.Sprintf(\"%s:%s\"))\n", fieldName.Name, "%v") 43 | } 44 | } 45 | 46 | g.Printf("return sg.Join(ss, \",\")\n") 47 | g.Printf("}\n") 48 | } 49 | 50 | generatedCode, err := g.Format() 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | fmt.Println(string(generatedCode)) 56 | // Output: 57 | // // Code generated by sample ; DO NOT EDIT 58 | // 59 | // package sample 60 | // 61 | // import ( 62 | // "fmt" 63 | // sg "strings" 64 | // ) 65 | // 66 | // func (obj *Sample) String() string { 67 | // var ss []string 68 | // ss = append(ss, fmt.Sprintf("A:%v")) 69 | // ss = append(ss, fmt.Sprintf("B:%v")) 70 | // ss = append(ss, fmt.Sprintf("C:%v")) 71 | // return sg.Join(ss, ",") 72 | // } 73 | } 74 | -------------------------------------------------------------------------------- /parsing_test.go: -------------------------------------------------------------------------------- 1 | package genbase 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParserParsePackageDir(t *testing.T) { 8 | p := &Parser{} 9 | pInfo, err := p.ParsePackageDir("./misc/fixture/a") 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | if len(pInfo.Files) != 1 { 15 | t.Fatalf("unexpected: %d", len(pInfo.Files)) 16 | } 17 | } 18 | 19 | func TestParserParsePackageFiles(t *testing.T) { 20 | p := &Parser{} 21 | pInfo, err := p.ParsePackageFiles([]string{"./misc/fixture/a/model.go"}) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | if len(pInfo.Files) != 1 { 27 | t.Fatalf("unexpected: %d", len(pInfo.Files)) 28 | } 29 | } 30 | 31 | func TestPackageInfoTypeInfos(t *testing.T) { 32 | p := &Parser{} 33 | pInfo, err := p.ParsePackageDir("./misc/fixture/a") 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | tis := pInfo.TypeInfos() 39 | if len(tis) != 3 { 40 | for _, ti := range tis { 41 | t.Log(ti.Name()) 42 | } 43 | t.Fatalf("unexpected: %d", len(tis)) 44 | } 45 | } 46 | 47 | func TestPackageInfoCollectTaggedTypeInfos(t *testing.T) { 48 | p := &Parser{} 49 | pInfo, err := p.ParsePackageDir("./misc/fixture/a") 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | tis := pInfo.CollectTaggedTypeInfos("+test") 55 | if len(tis) != 3 { 56 | for _, ti := range tis { 57 | t.Log(ti.Name()) 58 | } 59 | t.Fatalf("unexpected: %d", len(tis)) 60 | } 61 | for _, ti := range tis { 62 | if ti.AnnotatedComment.Text != "// +test" && ti.AnnotatedComment.Text != "// +test: opts" { 63 | t.Fatalf("unexpected: %s", ti.AnnotatedComment.Text) 64 | } 65 | } 66 | } 67 | 68 | func TestPackageInfoCollectTypeInfos(t *testing.T) { 69 | p := &Parser{} 70 | pInfo, err := p.ParsePackageDir("./misc/fixture/a") 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | tis := pInfo.CollectTypeInfos([]string{"C"}) 76 | if len(tis) != 1 { 77 | for _, ti := range tis { 78 | t.Log(ti.Name()) 79 | } 80 | t.Fatalf("unexpected: %d", len(tis)) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package genbase 2 | 3 | import ( 4 | "errors" 5 | "go/ast" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | func pathJoinAll(directory string, names ...string) []string { 11 | if directory == "." { 12 | return names 13 | } 14 | ret := make([]string, len(names)) 15 | for i, name := range names { 16 | ret[i] = filepath.Join(directory, name) 17 | } 18 | return ret 19 | } 20 | 21 | func findAnnotation(doc *ast.CommentGroup, directive string) *ast.Comment { 22 | if doc == nil { 23 | return nil 24 | } 25 | 26 | for _, c := range doc.List { 27 | l := c.Text 28 | t := strings.TrimLeft(l, "/ ") 29 | if !strings.HasPrefix(t, directive) { 30 | continue 31 | } 32 | 33 | t = strings.TrimPrefix(t, directive) 34 | 35 | if len(t) > 0 && (t[0] != ' ' && t[0] != ':') { 36 | continue 37 | } 38 | 39 | return c 40 | } 41 | 42 | return nil 43 | } 44 | 45 | // IsReferenceToOtherPackage returns expr contains reference to other packages. 46 | // this function used with Generator#AddImport method. 47 | func IsReferenceToOtherPackage(expr ast.Expr) (bool, string) { 48 | switch t := expr.(type) { 49 | case *ast.Ident: 50 | return false, "" 51 | case *ast.StarExpr: 52 | return IsReferenceToOtherPackage(t.X) 53 | case *ast.SelectorExpr: 54 | name, err := ExprToTypeName(t.X) 55 | if err != nil { 56 | return false, "" 57 | } 58 | return true, name 59 | case *ast.ArrayType: 60 | return IsReferenceToOtherPackage(t.Elt) 61 | default: 62 | return false, "" 63 | } 64 | } 65 | 66 | // ExprToTypeName convert ast.Expr to type name. 67 | func ExprToTypeName(expr ast.Expr) (string, error) { 68 | if ident, ok := expr.(*ast.Ident); ok { 69 | return ident.Name, nil 70 | } 71 | if star, ok := expr.(*ast.StarExpr); ok { 72 | x, err := ExprToTypeName(star.X) 73 | if err != nil { 74 | return "", nil 75 | } 76 | return "*" + x, nil 77 | } 78 | if selector, ok := expr.(*ast.SelectorExpr); ok { 79 | x, err := ExprToTypeName(selector.X) 80 | if err != nil { 81 | return "", nil 82 | } 83 | sel, err := ExprToTypeName(selector.Sel) 84 | if err != nil { 85 | return "", nil 86 | } 87 | return x + "." + sel, nil 88 | } 89 | if array, ok := expr.(*ast.ArrayType); ok { 90 | x, err := ExprToTypeName(array.Elt) 91 | if err != nil { 92 | return "", nil 93 | } 94 | return "[]" + x, nil 95 | } 96 | return "", errors.New("can't detect type name") 97 | } 98 | 99 | // ExprToBaseTypeName convert ast.Expr to type name without "*" and "[]". 100 | func ExprToBaseTypeName(expr ast.Expr) (string, error) { 101 | if ident, ok := expr.(*ast.Ident); ok { 102 | return ident.Name, nil 103 | } 104 | if star, ok := expr.(*ast.StarExpr); ok { 105 | x, err := ExprToBaseTypeName(star.X) 106 | if err != nil { 107 | return "", nil 108 | } 109 | return x, nil 110 | } 111 | if selector, ok := expr.(*ast.SelectorExpr); ok { 112 | x, err := ExprToBaseTypeName(selector.X) 113 | if err != nil { 114 | return "", nil 115 | } 116 | sel, err := ExprToBaseTypeName(selector.Sel) 117 | if err != nil { 118 | return "", nil 119 | } 120 | return x + "." + sel, nil 121 | } 122 | if array, ok := expr.(*ast.ArrayType); ok { 123 | x, err := ExprToBaseTypeName(array.Elt) 124 | if err != nil { 125 | return "", nil 126 | } 127 | return x, nil 128 | } 129 | return "", errors.New("can't detect type name") 130 | } 131 | 132 | // GetKeys extracts tag value. 133 | // likes reflect.StructTag.Get(string) 134 | func GetKeys(tag string) []string { 135 | result := []string{} 136 | 137 | // from reflect.StructTag.Get(string) 138 | 139 | for tag != "" { 140 | // skip leading space 141 | i := 0 142 | for i < len(tag) && tag[i] == ' ' { 143 | i++ 144 | } 145 | tag = tag[i:] 146 | if tag == "" { 147 | break 148 | } 149 | 150 | // scan to colon. 151 | // a space or a quote is a syntax error 152 | i = 0 153 | for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { 154 | i++ 155 | } 156 | if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { 157 | break 158 | } 159 | name := string(tag[:i]) 160 | result = append(result, name) 161 | tag = tag[i+1:] 162 | 163 | // scan quoted string to find value 164 | i = 1 165 | for i < len(tag) && tag[i] != '"' { 166 | if tag[i] == '\\' { 167 | i++ 168 | } 169 | i++ 170 | } 171 | if i >= len(tag) { 172 | break 173 | } 174 | tag = tag[i+1:] 175 | } 176 | return result 177 | } 178 | -------------------------------------------------------------------------------- /parsing.go: -------------------------------------------------------------------------------- 1 | package genbase 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/ast" 7 | "go/build" 8 | "go/importer" 9 | "go/parser" 10 | "go/token" 11 | "go/types" 12 | "strings" 13 | ) 14 | 15 | var ( 16 | // ErrNotStructType shows argument is not ast.StructType. 17 | ErrNotStructType = errors.New("type is not ast.StructType") 18 | ) 19 | 20 | // Parser is center of parsing strategy. 21 | type Parser struct { 22 | SkipSemanticsCheck bool 23 | } 24 | 25 | // PackageInfo is specified package informations. 26 | type PackageInfo struct { 27 | Dir string 28 | Files FileInfos 29 | Types *types.Package 30 | } 31 | 32 | // FileInfo is ast.File synonym. 33 | type FileInfo ast.File 34 | 35 | // FileInfos is []*FileInfo synonym. 36 | type FileInfos []*FileInfo 37 | 38 | // TypeInfo is type information gathering. 39 | // try http://goast.yuroyoro.net/ with http://play.golang.org/p/ruqMMsbDaw 40 | type TypeInfo struct { 41 | FileInfo *FileInfo 42 | GenDecl *ast.GenDecl 43 | TypeSpec *ast.TypeSpec 44 | AnnotatedComment *ast.Comment 45 | } 46 | 47 | // TypeInfos is []*TypeInfo synonym. 48 | type TypeInfos []*TypeInfo 49 | 50 | // StructTypeInfo is ast.StructType synonym. 51 | type StructTypeInfo ast.StructType 52 | 53 | // FieldInfo is ast.Field synonym. 54 | type FieldInfo ast.Field 55 | 56 | // FieldInfos is []*FieldInfo synonym. 57 | type FieldInfos []*FieldInfo 58 | 59 | // ParsePackageDir parses specified directory. 60 | func (p *Parser) ParsePackageDir(directory string) (*PackageInfo, error) { 61 | pkg, err := build.Default.ImportDir(directory, 0) 62 | if err != nil { 63 | return nil, fmt.Errorf("cannot process directory %s: %s", directory, err) 64 | } 65 | var names []string 66 | names = append(names, pkg.GoFiles...) 67 | names = append(names, pkg.CgoFiles...) 68 | names = append(names, pkg.SFiles...) 69 | names = pathJoinAll(directory, names...) 70 | return p.parsePackage(directory, names, nil) 71 | } 72 | 73 | // ParsePackageFiles parses specified files. 74 | func (p *Parser) ParsePackageFiles(fileNames []string) (*PackageInfo, error) { 75 | return p.parsePackage(".", fileNames, nil) 76 | } 77 | 78 | func (p *Parser) ParseStringSource(fileName string, code string) (*PackageInfo, error) { 79 | return p.parsePackage(".", []string{fileName}, []string{code}) 80 | } 81 | 82 | func (p *Parser) parsePackage(directory string, fileNames []string, codes []string) (*PackageInfo, error) { 83 | var files FileInfos 84 | pkg := &PackageInfo{} 85 | fs := token.NewFileSet() 86 | for idx, fileName := range fileNames { 87 | if !strings.HasSuffix(fileName, ".go") { 88 | continue 89 | } 90 | var code interface{} 91 | if idx < len(codes) { 92 | code = codes[idx] 93 | } 94 | parsedFile, err := parser.ParseFile(fs, fileName, code, parser.ParseComments) 95 | if err != nil { 96 | return nil, fmt.Errorf("parsing package: %s: %s", fileName, err) 97 | } 98 | files = append(files, (*FileInfo)(parsedFile)) 99 | } 100 | if len(files) == 0 { 101 | return nil, fmt.Errorf("%s: no buildable Go files", directory) 102 | } 103 | pkg.Files = files 104 | pkg.Dir = directory 105 | 106 | // resolve types 107 | config := types.Config{ 108 | FakeImportC: true, 109 | Importer: importer.Default(), 110 | IgnoreFuncBodies: true, 111 | DisableUnusedImportCheck: true, 112 | } 113 | info := &types.Info{ 114 | Defs: make(map[*ast.Ident]types.Object), 115 | } 116 | typesPkg, err := config.Check(pkg.Dir, fs, files.AstFiles(), info) 117 | if p.SkipSemanticsCheck && err != nil { 118 | return pkg, nil 119 | } else if err != nil { 120 | return nil, err 121 | } 122 | pkg.Types = typesPkg 123 | 124 | return pkg, nil 125 | } 126 | 127 | // TypeInfos is gathering TypeInfos, it included in package. 128 | func (pkg *PackageInfo) TypeInfos() TypeInfos { 129 | var types TypeInfos 130 | for _, file := range pkg.Files { 131 | if file == nil { 132 | continue 133 | } 134 | ast.Inspect(file.AstFile(), func(node ast.Node) bool { 135 | decl, ok := node.(*ast.GenDecl) 136 | if !ok || decl.Tok != token.TYPE { 137 | return true 138 | } 139 | found := false 140 | for _, spec := range decl.Specs { 141 | ts, ok := spec.(*ast.TypeSpec) 142 | if !ok { 143 | continue 144 | } 145 | types = append(types, &TypeInfo{ 146 | FileInfo: file, 147 | GenDecl: decl, 148 | TypeSpec: ts, 149 | }) 150 | found = true 151 | } 152 | return !found 153 | }) 154 | } 155 | return types 156 | } 157 | 158 | // CollectTaggedTypeInfos collects tagged TypeInfos. 159 | func (pkg *PackageInfo) CollectTaggedTypeInfos(tag string) TypeInfos { 160 | ret := TypeInfos{} 161 | 162 | types := pkg.TypeInfos() 163 | 164 | for _, t := range types { 165 | if c := findAnnotation(t.Doc(), tag); c != nil { 166 | t.AnnotatedComment = c 167 | ret = append(ret, t) 168 | } 169 | } 170 | 171 | return ret 172 | } 173 | 174 | // CollectTypeInfos collects specified TypeInfos. 175 | func (pkg *PackageInfo) CollectTypeInfos(typeNames []string) TypeInfos { 176 | ret := TypeInfos{} 177 | 178 | types := pkg.TypeInfos() 179 | 180 | outer: 181 | for _, t := range types { 182 | for _, name := range typeNames { 183 | if t.Name() == name { 184 | ret = append(ret, t) 185 | continue outer 186 | } 187 | } 188 | } 189 | 190 | return ret 191 | } 192 | 193 | // Name returns package name. 194 | func (pkg *PackageInfo) Name() string { 195 | return pkg.Files[0].Name.Name 196 | } 197 | 198 | // AstFile returns *ast.File. 199 | func (file *FileInfo) AstFile() *ast.File { 200 | return (*ast.File)(file) 201 | } 202 | 203 | // AstFiles returns []*ast.File. 204 | func (files FileInfos) AstFiles() []*ast.File { 205 | astFiles := make([]*ast.File, len(files)) 206 | for i, file := range files { 207 | astFiles[i] = file.AstFile() 208 | } 209 | return astFiles 210 | } 211 | 212 | // FindImportSpecByIdent finds *ast.ImportSpec by package ident. 213 | func (file *FileInfo) FindImportSpecByIdent(packageIdent string) *ast.ImportSpec { 214 | for _, imp := range file.Imports { 215 | if imp.Name != nil && imp.Name.Name == packageIdent { 216 | // import foo "foobar" 217 | return imp 218 | } else if strings.HasSuffix(imp.Path.Value, fmt.Sprintf(`/%s"`, packageIdent)) { 219 | // import "favclip/foo" 220 | return imp 221 | } else if imp.Path.Value == fmt.Sprintf(`"%s"`, packageIdent) { 222 | // import "foo" 223 | return imp 224 | } 225 | } 226 | return nil 227 | } 228 | 229 | // StructType returns *StructTypeInfo. 230 | func (t *TypeInfo) StructType() (*StructTypeInfo, error) { 231 | structType, ok := interface{}(t.TypeSpec.Type).(*ast.StructType) 232 | if !ok { 233 | return nil, ErrNotStructType 234 | } 235 | 236 | return (*StructTypeInfo)(structType), nil 237 | } 238 | 239 | // Name return type name. 240 | func (t *TypeInfo) Name() string { 241 | return t.TypeSpec.Name.Name 242 | } 243 | 244 | // Doc returns *ast.CommentGroup of TypeInfo. 245 | func (t *TypeInfo) Doc() *ast.CommentGroup { 246 | if t.TypeSpec.Doc != nil { 247 | return t.TypeSpec.Doc 248 | } 249 | if t.GenDecl.Doc != nil { 250 | return t.GenDecl.Doc 251 | } 252 | return nil 253 | } 254 | 255 | // AstStructType returns *ast.StructType. 256 | func (st *StructTypeInfo) AstStructType() *ast.StructType { 257 | return (*ast.StructType)(st) 258 | } 259 | 260 | // FieldInfos returns FieldInfos of struct. 261 | func (st *StructTypeInfo) FieldInfos() FieldInfos { 262 | var fields FieldInfos 263 | for _, field := range st.AstStructType().Fields.List { 264 | fields = append(fields, (*FieldInfo)(field)) 265 | } 266 | 267 | return fields 268 | } 269 | 270 | // TypeName returns type name of field. 271 | func (f *FieldInfo) TypeName() string { 272 | typeName, err := ExprToTypeName(f.Type) 273 | if err != nil { 274 | return fmt.Sprintf("!!%s!!", err.Error()) 275 | } 276 | return typeName 277 | } 278 | 279 | // IsPtr returns true if FieldInfo is pointer, otherwise returns false. 280 | func (f *FieldInfo) IsPtr() bool { 281 | _, ok := f.Type.(*ast.StarExpr) 282 | return ok 283 | } 284 | 285 | // IsArray returns true if FieldInfo is array, otherwise returns false. 286 | func (f *FieldInfo) IsArray() bool { 287 | _, ok := f.Type.(*ast.ArrayType) 288 | return ok 289 | } 290 | 291 | // IsPtrArray returns true if FieldInfo is pointer array, otherwise returns false. 292 | func (f *FieldInfo) IsPtrArray() bool { 293 | star, ok := f.Type.(*ast.StarExpr) 294 | if !ok { 295 | return false 296 | } 297 | _, ok = star.X.(*ast.ArrayType) 298 | return ok 299 | } 300 | 301 | // IsArrayPtr returns true if FieldInfo is pointer of array, otherwise returns false. 302 | func (f *FieldInfo) IsArrayPtr() bool { 303 | array, ok := f.Type.(*ast.ArrayType) 304 | if !ok { 305 | return false 306 | } 307 | _, ok = array.Elt.(*ast.StarExpr) 308 | return ok 309 | } 310 | 311 | // IsPtrArrayPtr returns true if FieldInfo is pointer of pointer array, otherwise returns false. 312 | func (f *FieldInfo) IsPtrArrayPtr() bool { 313 | star, ok := f.Type.(*ast.StarExpr) 314 | if !ok { 315 | return false 316 | } 317 | array, ok := star.X.(*ast.ArrayType) 318 | if !ok { 319 | return false 320 | } 321 | _, ok = array.Elt.(*ast.StarExpr) 322 | return ok 323 | } 324 | 325 | // IsInt64 returns true if FieldInfo is int64, otherwise returns false. 326 | func (f *FieldInfo) IsInt64() bool { 327 | typeName, err := ExprToBaseTypeName(f.Type) 328 | if err != nil { 329 | return false 330 | } 331 | return typeName == "int64" 332 | } 333 | 334 | // IsInt returns true if FieldInfo is int, otherwise returns false. 335 | func (f *FieldInfo) IsInt() bool { 336 | typeName, err := ExprToBaseTypeName(f.Type) 337 | if err != nil { 338 | return false 339 | } 340 | return typeName == "int" 341 | } 342 | 343 | // IsString returns true if FieldInfo is string, otherwise returns false. 344 | func (f *FieldInfo) IsString() bool { 345 | typeName, err := ExprToBaseTypeName(f.Type) 346 | if err != nil { 347 | return false 348 | } 349 | return typeName == "string" 350 | } 351 | 352 | // IsFloat32 returns true if FieldInfo is float32, otherwise returns false. 353 | func (f *FieldInfo) IsFloat32() bool { 354 | typeName, err := ExprToBaseTypeName(f.Type) 355 | if err != nil { 356 | return false 357 | } 358 | return typeName == "float32" 359 | } 360 | 361 | // IsFloat64 returns true if FieldInfo is float64, otherwise returns false. 362 | func (f *FieldInfo) IsFloat64() bool { 363 | typeName, err := ExprToBaseTypeName(f.Type) 364 | if err != nil { 365 | return false 366 | } 367 | return typeName == "float64" 368 | } 369 | 370 | // IsNumber returns true if FieldInfo is int or int64 or float32 or float64, otherwise returns false. 371 | func (f *FieldInfo) IsNumber() bool { 372 | return f.IsInt() || f.IsInt64() || f.IsFloat32() || f.IsFloat64() 373 | } 374 | 375 | // IsBool returns true if FieldInfo is bool, otherwise returns false. 376 | func (f *FieldInfo) IsBool() bool { 377 | typeName, err := ExprToBaseTypeName(f.Type) 378 | if err != nil { 379 | return false 380 | } 381 | return typeName == "bool" 382 | } 383 | 384 | // IsTime returns true if FieldInfo is time.Time, otherwise returns false. 385 | func (f *FieldInfo) IsTime() bool { 386 | typeName, err := ExprToBaseTypeName(f.Type) 387 | if err != nil { 388 | return false 389 | } 390 | return typeName == "time.Time" 391 | } 392 | --------------------------------------------------------------------------------