mirror of https://github.com/markbates/pkger.git
217 lines
4.0 KiB
Go
217 lines
4.0 KiB
Go
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"path/filepath"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/markbates/pkger/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) valueIdent(node *ast.Ident) (s string) {
|
|
s = node.Name
|
|
if node.Obj.Kind != ast.Con {
|
|
return
|
|
}
|
|
// As per ast package a Con object is always a *ValueSpec,
|
|
// but double-checking to avoid panics
|
|
if x, ok := node.Obj.Decl.(*ast.ValueSpec); ok {
|
|
// The const var can be defined inline with other vars,
|
|
// as in `const a, b = "a", "b"`.
|
|
for i, v := range x.Names {
|
|
if v.Name == node.Name {
|
|
s = p.valueNode(x.Values[i])
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *ParsedSource) valueNode(node ast.Node) string {
|
|
var s string
|
|
switch x := node.(type) {
|
|
case *ast.BasicLit:
|
|
s = x.Value
|
|
case *ast.Ident:
|
|
s = p.valueIdent(x)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (p *ParsedSource) value(node ast.Node) (string, error) {
|
|
s := p.valueNode(node)
|
|
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("%s: %s", err, n)
|
|
return false
|
|
}
|
|
|
|
info, err := here.Dir(filepath.Dir(p.Abs))
|
|
if err != nil {
|
|
p.err = fmt.Errorf("%s: %s", err, p.Abs)
|
|
return false
|
|
}
|
|
|
|
pt, err := info.Parse(val)
|
|
if err != nil {
|
|
p.err = fmt.Errorf("%s: %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("%s: %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
|
|
}
|