// Copyright 2011-2015 visualfc . All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package astview import ( "fmt" "go/ast" "go/parser" "go/token" "io" "io/ioutil" "os" "strings" "github.com/visualfc/gotools/command" "golang.org/x/tools/go/types" ) var Command = &command.Command{ Run: runAstView, UsageLine: "astview [-stdin] files...", Short: "print go files astview", Long: `print go files astview`, } var astViewStdin bool func init() { Command.Flag.BoolVar(&astViewStdin, "stdin", false, "input from stdin") } func runAstView(cmd *command.Command, args []string) error { if len(args) == 0 { cmd.Usage() return os.ErrInvalid } if astViewStdin { view, err := NewFilePackageSource(args[0], os.Stdin, true) if err != nil { fmt.Fprintf(os.Stderr, "astview: %s", err) command.SetExitStatus(3) command.Exit() } view.PrintTree(os.Stdout) } else { err := PrintFilesTree(args, os.Stdout, true) if err != nil { fmt.Fprintf(os.Stderr, "astview:%s", err) command.SetExitStatus(3) command.Exit() } } return nil } const ( tag_package = "p" tag_imports_folder = "+m" tag_import = "mm" tag_type = "t" tag_struct = "s" tag_interface = "i" tag_value = "v" tag_const = "c" tag_func = "f" tag_value_folder = "+v" tag_const_folder = "+c" tag_func_folder = "+f" tag_factor_folder = "+tf" tag_type_method = "tm" tag_type_factor = "tf" tag_type_value = "tv" ) type PackageView struct { fset *token.FileSet pdoc *PackageDoc pkg *ast.Package expr bool } var AllFiles []string func (p *PackageView) posFileIndex(pos token.Position) int { var index = -1 for i := 0; i < len(AllFiles); i++ { if AllFiles[i] == pos.Filename { index = i break } } if index == -1 { AllFiles = append(AllFiles, pos.Filename) index = len(AllFiles) - 1 } return index } func (p *PackageView) posText(pos token.Position) (s string) { index := p.posFileIndex(pos) return fmt.Sprintf("%d:%d:%d", index, pos.Line, pos.Column) } func NewFilePackage(filename string) (*PackageView, error) { p := new(PackageView) p.fset = token.NewFileSet() file, err := parser.ParseFile(p.fset, filename, nil, parser.AllErrors) if file == nil { return nil, err } m := make(map[string]*ast.File) m[filename] = file pkg, err := ast.NewPackage(p.fset, m, nil, nil) if err != nil { return nil, err } p.pkg = pkg p.pdoc = NewPackageDoc(pkg, pkg.Name, true) return p, nil } func NewPackageView(pkg *ast.Package, fset *token.FileSet, expr bool) (*PackageView, error) { p := new(PackageView) p.fset = fset p.pkg = pkg p.pdoc = NewPackageDoc(pkg, pkg.Name, true) p.expr = expr return p, nil } func ParseFiles(fset *token.FileSet, filenames []string, mode parser.Mode) (pkgs map[string]*ast.Package, pkgsfiles []string, first error) { pkgs = make(map[string]*ast.Package) for _, filename := range filenames { if src, err := parser.ParseFile(fset, filename, nil, mode); src != nil { name := src.Name.Name pkg, found := pkgs[name] if !found { pkg = &ast.Package{ Name: name, Files: make(map[string]*ast.File), } pkgs[name] = pkg } pkg.Files[filename] = src pkgsfiles = append(pkgsfiles, filename) } else { first = err return } } return } func PrintFilesTree(filenames []string, w io.Writer, expr bool) error { fset := token.NewFileSet() pkgs, pkgsfiles, err := ParseFiles(fset, filenames, parser.AllErrors) if err != nil { return err } AllFiles = pkgsfiles for i := 0; i < len(AllFiles); i++ { fmt.Fprintf(w, "@%s\n", AllFiles[i]) } for _, pkg := range pkgs { view, err := NewPackageView(pkg, fset, expr) if err != nil { return err } view.PrintTree(w) } return nil } func NewFilePackageSource(filename string, f *os.File, expr bool) (*PackageView, error) { src, err := ioutil.ReadAll(f) if err != nil { return nil, err } p := new(PackageView) p.fset = token.NewFileSet() p.expr = expr file, err := parser.ParseFile(p.fset, filename, src, 0) if err != nil { return nil, err } m := make(map[string]*ast.File) m[filename] = file pkg, err := ast.NewPackage(p.fset, m, nil, nil) if err != nil { return nil, err } p.pdoc = NewPackageDoc(pkg, pkg.Name, true) return p, nil } func (p *PackageView) printFuncsHelper(w io.Writer, funcs []*FuncDoc, level int, tag string, tag_folder string) { for _, f := range funcs { pos := p.fset.Position(f.Decl.Pos()) if p.expr { fmt.Fprintf(w, "%d,%s,%s,%s@%s\n", level, tag, f.Name, p.posText(pos), types.ExprString(f.Decl.Type)) } else { fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag, f.Name, p.posText(pos)) } } } func (p *PackageView) PrintVars(w io.Writer, vars []*ValueDoc, level int, tag string, tag_folder string) { if len(tag_folder) > 0 && len(vars) > 0 { if tag_folder == tag_value_folder { fmt.Fprintf(w, "%d,%s,Variables\n", level, tag_folder) } else if tag_folder == tag_const_folder { fmt.Fprintf(w, "%d,%s,Constants\n", level, tag_folder) } level++ } for _, v := range vars { if v.Decl == nil { continue } for _, s := range v.Decl.Specs { if m, ok := s.(*ast.ValueSpec); ok { pos := p.fset.Position(m.Pos()) for i := 0; i < len(m.Names); i++ { if p.expr && m.Type != nil { fmt.Fprintf(w, "%d,%s,%s,%s@%s\n", level, tag, m.Names[i], p.posText(pos), types.ExprString(m.Type)) } else { fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag, m.Names[i], p.posText(pos)) } } } } } } func (p *PackageView) PrintTypes(w io.Writer, types []*TypeDoc, level int) { for _, d := range types { if d.Decl == nil { continue } typespec := d.Decl.Specs[0].(*ast.TypeSpec) var tag = tag_type if _, ok := typespec.Type.(*ast.InterfaceType); ok { tag = tag_interface } else if _, ok := typespec.Type.(*ast.StructType); ok { tag = tag_struct } pos := p.fset.Position(d.Decl.Pos()) fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag, d.Type.Name, p.posText(pos)) p.printFuncsHelper(w, d.Funcs, level+1, tag_type_factor, "") p.printFuncsHelper(w, d.Methods, level+1, tag_type_method, "") p.PrintTypeFields(w, d.Decl, level+1) //p.PrintVars(w, d.Consts, level+1, tag_const, "") //p.PrintVars(w, d.Vars, level+1, tag_value, "") } } func (p *PackageView) PrintTypeFields(w io.Writer, decl *ast.GenDecl, level int) { spec, ok := decl.Specs[0].(*ast.TypeSpec) if ok == false { return } switch d := spec.Type.(type) { case *ast.StructType: for _, list := range d.Fields.List { if list.Names == nil { continue } for _, m := range list.Names { pos := p.fset.Position(m.Pos()) if list.Type != nil { fmt.Fprintf(w, "%d,%s,%s,%s@%s\n", level, tag_type_value, m.Name, p.posText(pos), types.ExprString(list.Type)) } else { fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag_type_value, m.Name, p.posText(pos)) } } } case *ast.InterfaceType: for _, list := range d.Methods.List { if list.Names == nil { continue } for _, m := range list.Names { pos := p.fset.Position(m.Pos()) fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag_type_method, m.Name, p.posText(pos)) } } } } func (p *PackageView) PrintHeader(w io.Writer, level int) { fmt.Fprintf(w, "%d,%s,%s\n", level, tag_package, p.pdoc.PackageName) } func (p *PackageView) PrintImports(w io.Writer, level int, tag, tag_folder string) { if tag_folder != "" && len(p.pdoc.Imports) > 0 { fmt.Fprintf(w, "%d,%s,%s\n", level, tag_folder, "Imports") level++ } for _, name := range p.pdoc.Imports { vname := "\"" + name + "\"" var ps []string for _, file := range p.pkg.Files { for _, v := range file.Imports { if v.Path.Value == vname { pos := p.fset.Position(v.Pos()) ps = append(ps, p.posText(pos)) } } } fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag, name, strings.Join(ps, ";")) } } func (p *PackageView) PrintFuncs(w io.Writer, level int, tag_folder string) { hasFolder := false if len(p.pdoc.Funcs) > 0 || len(p.pdoc.Factorys) > 0 { hasFolder = true } if !hasFolder { return } if len(tag_folder) > 0 { fmt.Fprintf(w, "%d,%s,Functions\n", level, tag_folder) level++ } p.printFuncsHelper(w, p.pdoc.Factorys, level, tag_type_factor, tag_func_folder) p.printFuncsHelper(w, p.pdoc.Funcs, level, tag_func, tag_func_folder) } func (p *PackageView) PrintPackage(w io.Writer, level int) { p.PrintHeader(w, level) level++ p.PrintImports(w, level, tag_import, tag_imports_folder) p.PrintVars(w, p.pdoc.Vars, level, tag_value, tag_value_folder) p.PrintVars(w, p.pdoc.Consts, level, tag_const, tag_const_folder) p.PrintFuncs(w, level, tag_func_folder) p.PrintTypes(w, p.pdoc.Types, level) } // level,tag,pos@info func (p *PackageView) PrintTree(w io.Writer) { p.PrintPackage(w, 0) }