package parser import ( "fmt" "go/ast" "go/token" "path/filepath" "strconv" "sync" "github.com/gobuffalo/here" ) type Source struct { Abs string // full path on disk to file Path here.Path Here here.Info } type ParsedSource struct { Source FileSet *token.FileSet Ast *ast.File decls map[string]Decls once sync.Once err error } func (p *ParsedSource) Parse() error { (&p.once).Do(func() { p.err = p.parse() }) return p.err } func (p *ParsedSource) value(node ast.Node) (string, error) { var s string switch x := node.(type) { case *ast.BasicLit: s = x.Value case *ast.Ident: s = x.Name } return strconv.Unquote(s) } func (p *ParsedSource) parse() error { p.decls = map[string]Decls{} var fn walker = func(node ast.Node) bool { ce, ok := node.(*ast.CallExpr) if !ok { return true } sel, ok := ce.Fun.(*ast.SelectorExpr) if !ok { return true } pkg, ok := sel.X.(*ast.Ident) if !ok { return true } if pkg.Name != "pkger" { return true } var fn func(f File, pos token.Position, value string) Decl name := sel.Sel.Name switch name { case "MkdirAll": fn = func(f File, pos token.Position, value string) Decl { return MkdirAllDecl{ file: &f, pos: pos, value: value, } } case "Create": fn = func(f File, pos token.Position, value string) Decl { return CreateDecl{ file: &f, pos: pos, value: value, } } case "Include": fn = func(f File, pos token.Position, value string) Decl { return IncludeDecl{ file: &f, pos: pos, value: value, } } case "Stat": fn = func(f File, pos token.Position, value string) Decl { return StatDecl{ file: &f, pos: pos, value: value, } } case "Open": fn = func(f File, pos token.Position, value string) Decl { return OpenDecl{ file: &f, pos: pos, value: value, } } case "Dir": fn = func(f File, pos token.Position, value string) Decl { return HTTPDecl{ file: &f, pos: pos, value: value, } } case "Walk": fn = func(f File, pos token.Position, value string) Decl { return WalkDecl{ file: &f, pos: pos, value: value, } } default: return true } if len(ce.Args) < 1 { p.err = fmt.Errorf("declarations require at least one argument") return false } n := ce.Args[0] val, err := p.value(n) if err != nil { p.err = fmt.Errorf("%w: %s", err, n) return false } info, err := here.Dir(filepath.Dir(p.Abs)) if err != nil { p.err = fmt.Errorf("%w: %s", err, p.Abs) return false } pt, err := info.Parse(val) if err != nil { p.err = fmt.Errorf("%w: %s", err, p.Abs) return false } if pt.Pkg != info.Module.Path { info, err = here.Package(pt.Pkg) if err != nil { p.err = fmt.Errorf("%w: %s", err, p.Abs) return false } } f := File{ Abs: filepath.Join(info.Module.Dir, pt.Name), Here: info, Path: pt, } p.decls[name] = append(p.decls[name], fn(f, p.FileSet.Position(n.Pos()), val)) return true } ast.Walk(fn, p.Ast) return nil } func (p *ParsedSource) DeclsMap() (map[string]Decls, error) { err := p.Parse() return p.decls, err } // wrap a function to fulfill ast.Visitor interface type walker func(ast.Node) bool func (w walker) Visit(node ast.Node) ast.Visitor { if w(node) { return w } return nil }