github.com/bingoohuang/pkger@v0.0.0-20210127185155-a71b9df4c4c7/parser/source.go (about)

     1  package parser
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"path/filepath"
     8  	"strconv"
     9  	"sync"
    10  
    11  	"github.com/bingoohuang/pkger/here"
    12  )
    13  
    14  type Source struct {
    15  	Abs  string // full path on disk to file
    16  	Path here.Path
    17  	Here here.Info
    18  }
    19  
    20  type ParsedSource struct {
    21  	Source
    22  	FileSet *token.FileSet
    23  	Ast     *ast.File
    24  	decls   map[string]Decls
    25  	once    sync.Once
    26  	err     error
    27  }
    28  
    29  func (p *ParsedSource) Parse() error {
    30  	(&p.once).Do(func() {
    31  		p.err = p.parse()
    32  	})
    33  	return p.err
    34  }
    35  
    36  func (p *ParsedSource) valueIdent(node *ast.Ident) (s string) {
    37  	s = node.Name
    38  	if node.Obj.Kind != ast.Con {
    39  		return
    40  	}
    41  	// As per ast package a Con object is always a *ValueSpec,
    42  	// but double-checking to avoid panics
    43  	if x, ok := node.Obj.Decl.(*ast.ValueSpec); ok {
    44  		// The const var can be defined inline with other vars,
    45  		// as in `const a, b = "a", "b"`.
    46  		for i, v := range x.Names {
    47  			if v.Name == node.Name {
    48  				s = p.valueNode(x.Values[i])
    49  				break
    50  			}
    51  		}
    52  	}
    53  	return
    54  }
    55  
    56  func (p *ParsedSource) valueNode(node ast.Node) string {
    57  	var s string
    58  	switch x := node.(type) {
    59  	case *ast.BasicLit:
    60  		s = x.Value
    61  	case *ast.Ident:
    62  		s = p.valueIdent(x)
    63  	}
    64  	return s
    65  }
    66  
    67  func (p *ParsedSource) value(node ast.Node) (string, error) {
    68  	s := p.valueNode(node)
    69  	return strconv.Unquote(s)
    70  }
    71  
    72  func (p *ParsedSource) parse() error {
    73  	p.decls = map[string]Decls{}
    74  	var fn walker = func(node ast.Node) bool {
    75  		ce, ok := node.(*ast.CallExpr)
    76  		if !ok {
    77  			return true
    78  		}
    79  
    80  		sel, ok := ce.Fun.(*ast.SelectorExpr)
    81  		if !ok {
    82  			return true
    83  		}
    84  
    85  		pkg, ok := sel.X.(*ast.Ident)
    86  		if !ok {
    87  			return true
    88  		}
    89  
    90  		if pkg.Name != "pkger" {
    91  			return true
    92  		}
    93  
    94  		var fn func(f File, pos token.Position, value string) Decl
    95  
    96  		name := sel.Sel.Name
    97  		switch name {
    98  		case "MkdirAll":
    99  			fn = func(f File, pos token.Position, value string) Decl {
   100  				return MkdirAllDecl{
   101  					file:  &f,
   102  					pos:   pos,
   103  					value: value,
   104  				}
   105  			}
   106  		case "Create":
   107  			fn = func(f File, pos token.Position, value string) Decl {
   108  				return CreateDecl{
   109  					file:  &f,
   110  					pos:   pos,
   111  					value: value,
   112  				}
   113  			}
   114  		case "Include", "Read", "ReadStr", "MustRead", "MustReadStr":
   115  			fn = func(f File, pos token.Position, value string) Decl {
   116  				return IncludeDecl{
   117  					file:  &f,
   118  					pos:   pos,
   119  					value: value,
   120  					typ:   "pkger." + name,
   121  				}
   122  			}
   123  		case "Stat":
   124  			fn = func(f File, pos token.Position, value string) Decl {
   125  				return StatDecl{
   126  					file:  &f,
   127  					pos:   pos,
   128  					value: value,
   129  				}
   130  			}
   131  		case "Open":
   132  			fn = func(f File, pos token.Position, value string) Decl {
   133  				return OpenDecl{
   134  					file:  &f,
   135  					pos:   pos,
   136  					value: value,
   137  				}
   138  			}
   139  		case "Dir":
   140  			fn = func(f File, pos token.Position, value string) Decl {
   141  				return HTTPDecl{
   142  					file:  &f,
   143  					pos:   pos,
   144  					value: value,
   145  				}
   146  			}
   147  		case "Walk":
   148  			fn = func(f File, pos token.Position, value string) Decl {
   149  				return WalkDecl{
   150  					file:  &f,
   151  					pos:   pos,
   152  					value: value,
   153  				}
   154  			}
   155  		default:
   156  			return true
   157  		}
   158  
   159  		if len(ce.Args) < 1 {
   160  			p.err = fmt.Errorf("declarations require at least one argument")
   161  			return false
   162  		}
   163  
   164  		n := ce.Args[0]
   165  		val, err := p.value(n)
   166  		if err != nil {
   167  			p.err = fmt.Errorf("%s: %s", err, n)
   168  			return false
   169  		}
   170  
   171  		info, err := here.Dir(filepath.Dir(p.Abs))
   172  		if err != nil {
   173  			p.err = fmt.Errorf("%s: %s", err, p.Abs)
   174  			return false
   175  		}
   176  
   177  		pt, err := info.Parse(val)
   178  		if err != nil {
   179  			p.err = fmt.Errorf("%s: %s", err, p.Abs)
   180  			return false
   181  		}
   182  
   183  		if pt.Pkg != info.Module.Path {
   184  			info, err = here.Package(pt.Pkg)
   185  			if err != nil {
   186  				p.err = fmt.Errorf("%s: %s", err, p.Abs)
   187  				return false
   188  			}
   189  		}
   190  
   191  		f := File{
   192  			Abs:  filepath.Join(info.Module.Dir, pt.Name),
   193  			Here: info,
   194  			Path: pt,
   195  		}
   196  
   197  		p.decls[name] = append(p.decls[name], fn(f, p.FileSet.Position(n.Pos()), val))
   198  		return true
   199  	}
   200  	ast.Walk(fn, p.Ast)
   201  	return nil
   202  }
   203  
   204  func (p *ParsedSource) DeclsMap() (map[string]Decls, error) {
   205  	err := p.Parse()
   206  	return p.decls, err
   207  }
   208  
   209  // wrap a function to fulfill ast.Visitor interface
   210  type walker func(ast.Node) bool
   211  
   212  func (w walker) Visit(node ast.Node) ast.Visitor {
   213  	if w(node) {
   214  		return w
   215  	}
   216  	return nil
   217  }