From f01eedf30be24cf0f71f87777e036b4dcdac313d Mon Sep 17 00:00:00 2001 From: Ibby Hadeed Date: Tue, 19 Nov 2019 13:49:24 -0500 Subject: [PATCH] option to scan subdirectories + fixes --- cmd/gots/main.go | 19 +++++++--- parser/config.go | 4 +- parser/parse.go | 99 +++++++++++++++++++++++++++++++++++++++++++----- parser/parser.go | 3 ++ parser/tag.go | 42 +++++++++++++------- 5 files changed, 137 insertions(+), 30 deletions(-) diff --git a/cmd/gots/main.go b/cmd/gots/main.go index cc1e842..2b4191d 100644 --- a/cmd/gots/main.go +++ b/cmd/gots/main.go @@ -28,6 +28,7 @@ func main() { p := parser.New(&parser.Config{ BaseDir: ctx.String("dir"), OutFileName: o, + Subdirs: ctx.Bool("sub-dir"), }) p.Run() @@ -43,13 +44,21 @@ func main() { }, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "dir, d", - Usage: "Base directory to lookup exportable definitions", - Value: wd, + Name: "dir", + Aliases: []string{"d"}, + Usage: "Base directory to lookup exportable definitions", + Value: wd, }, &cli.StringFlag{ - Name: "outfile, o", - Usage: "Output file. If not specified, stdout will be used.", + Name: "outfile", + Aliases: []string{"o"}, + Usage: "Output file. If not specified, stdout will be used.", + }, + &cli.BoolFlag{ + Name: "sub-dirs", + Aliases: []string{"s"}, + Usage: "Scan sub-directories as well", + Value: false, }, }, }, diff --git a/parser/config.go b/parser/config.go index f520f9b..8753f8a 100644 --- a/parser/config.go +++ b/parser/config.go @@ -1,6 +1,6 @@ package parser type Config struct { - BaseDir string - OutFileName string + BaseDir, OutFileName string + Subdirs bool } diff --git a/parser/parse.go b/parser/parse.go index d90a1a9..eb0e581 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -4,7 +4,9 @@ import ( "go/ast" "go/parser" "go/token" + "io/ioutil" "log" + "path/filepath" ) func (p *Parser) parse() { @@ -13,14 +15,46 @@ func (p *Parser) parse() { var pkgs map[string]*ast.Package var err error - if pkgs, err = parser.ParseDir(fset, p.BaseDir, nil, parser.ParseComments); err != nil { - log.Panicf("unable to parse base directory: %s\n", err.Error()) + pkgIndex := make(map[string]string) + + var scanDir func(path string) + + scanDir = func(path string) { + contents, err := ioutil.ReadDir(path) + + if err != nil { + log.Panicf("unable to read directory %s: %s\n", path, err.Error()) + } + + for _, it := range contents { + if it.IsDir() { + scanDir(filepath.Join(path, it.Name())) + } + } + + if pkgs, err = parser.ParseDir(fset, path, nil, parser.PackageClauseOnly); err != nil { + log.Panicf("unable to scan directory %s: %s\n", path, err.Error()) + } else { + for k := range pkgs { + pkgIndex[k] = path + } + } } - for k := range pkgs { - for fk := range pkgs[k].Files { - p.wg.Add(1) - go p.parseFile(pkgs[k].Files[fk]) + scanDir(p.BaseDir) + + p.pkgIndex = pkgIndex + + for _, v := range pkgIndex { + if pkgs, err = parser.ParseDir(fset, v, nil, parser.ParseComments); err != nil { + log.Panicf("unable to parse base directory: %s\n", err.Error()) + } + + for pk := range pkgs { + for fk := range pkgs[pk].Files { + p.wg.Add(1) + go p.parseFile(pkgs[pk].Files[fk]) + } } } } @@ -83,9 +117,32 @@ func (p *Parser) parseFile(file *ast.File) { func (p *Parser) parseConst(spec *ast.ValueSpec) { c := &Constant{ - Name: spec.Names[0].Name, - Type: parseType(spec.Type), - Value: spec.Values[0].(*ast.BasicLit).Value, + Name: spec.Names[0].Name, + } + + if spec.Type != nil { + c.Type = parseType(spec.Type) + c.Value = spec.Values[0].(*ast.BasicLit).Value + } else { + switch spec.Values[0].(type) { + case *ast.CallExpr: + if val, ok := spec.Values[0].(*ast.CallExpr).Args[0].(*ast.BasicLit); ok { + c.Type = parseTypeFromKind(val.Kind) + c.Value = val.Value + } else { + panic("Unhandled case") + } + case *ast.BasicLit: + v := spec.Values[0].(*ast.BasicLit) + c.Type = parseTypeFromKind(v.Kind) + c.Value = v.Value + default: + panic("Unhandled case") + } + } + + if c.Value == "" { + panic("Unhandled case") } p.cMtx.Lock() @@ -150,6 +207,28 @@ func (p *Parser) parseTypeSpec(spec *ast.TypeSpec) { p.parseStruct(spec) case *ast.Ident: t := parseType(spec.Type) + p.tMtx.Lock() + defer p.tMtx.Unlock() + p.types = append(p.types, &TypeDef{ + Name: spec.Name.Name, + Type: t, + }) + return + case *ast.InterfaceType: + return + case *ast.SelectorExpr: + st := spec.Type.(*ast.SelectorExpr) + t := "any" + if xv, ok := st.X.(*ast.Ident); ! ok { + panic("unhandled case") + } else { + p.pMtx.Lock() + defer p.pMtx.Unlock() + if _, ok := p.pkgIndex[xv.Name]; ok { + t = st.Sel.Name + } + } + p.tMtx.Lock() defer p.tMtx.Unlock() p.types = append(p.types, &TypeDef{ @@ -158,6 +237,6 @@ func (p *Parser) parseTypeSpec(spec *ast.TypeSpec) { }) return default: - panic("??") + panic(spec.Type) } } diff --git a/parser/parser.go b/parser/parser.go index f0730d8..ddc9200 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -27,6 +27,9 @@ type Parser struct { cMtx sync.RWMutex constants []*Constant + pMtx sync.Mutex + pkgIndex map[string]string + tsw *typescript.Writer } diff --git a/parser/tag.go b/parser/tag.go index 651bb73..046512d 100644 --- a/parser/tag.go +++ b/parser/tag.go @@ -3,6 +3,7 @@ package parser import ( "errors" "go/ast" + "go/token" "regexp" "strings" ) @@ -13,8 +14,8 @@ type tag struct { } var jsonTagRgx = regexp.MustCompile(`(?i)json:"([a-z0-9_-]+),?(omitempty)?"`) -var gotsTagRgx = regexp.MustCompile(`(?i)gots:"([a-z0-9_,:]+)"`) -var gotsInnerTagRgx = regexp.MustCompile(`(?i)(name|type|optional):?([a-z0-9_]+)?`) +var gotsTagRgx = regexp.MustCompile(`(?i)gots:"([a-z0-9_,:\[\]]+)"`) +var gotsInnerTagRgx = regexp.MustCompile(`(?i)(name|type|optional):?([a-z0-9_\[\]]+)?`) var errJsonTagNotPresent = errors.New("json tag not present") var errJsonIgnored = errors.New("field is ignored") @@ -58,19 +59,34 @@ func parseTags(val string) (*tag, error) { return t, nil } +func parseTypeFromKind(t token.Token) string { + switch t { + case token.INT, token.FLOAT: + return "number" + case token.STRING: + return "string" + default: + panic("???") + } +} + +func parseTypeFromName(n string) string { + switch n { + case "string": + return "string" + case "uint8", "uint16", "uint32", "uint64", "uint", "int8", "int16", "int32", "int64", "int", "float32", "float64": + return "number" + case "bool": + return "boolean" + default: + return n + } +} + func parseType(t ast.Expr) string { switch t.(type) { case *ast.Ident: - switch t.(*ast.Ident).Name { - case "string": - return "string" - case "uint8", "uint16", "uint32", "uint64", "uint", "int8", "int16", "int32", "int64", "int", "float32", "float64": - return "number" - case "bool": - return "boolean" - default: - return t.(*ast.Ident).Name - } + return parseTypeFromName(t.(*ast.Ident).Name) case *ast.StarExpr: return parseType(t.(*ast.StarExpr).X) case *ast.MapType: @@ -78,7 +94,7 @@ func parseType(t ast.Expr) string { k, v := parseType(tt.Key), parseType(tt.Value) return strings.Join([]string{"{[key:", k, "]:", v, "}"}, "") case *ast.ArrayType: - return strings.Join([]string{"[]", parseType(t.(*ast.ArrayType).Elt)}, "") + return strings.Join([]string{parseType(t.(*ast.ArrayType).Elt), "[]"}, "") case *ast.SelectorExpr, *ast.InterfaceType: return "any" default: