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 }