github.com/visualfc/goembed@v0.3.3/parser/parser.go (about) 1 //go:build go1.16 2 // +build go1.16 3 4 package parser 5 6 import ( 7 "fmt" 8 "go/ast" 9 "go/token" 10 "sort" 11 "strconv" 12 "strings" 13 ) 14 15 // EmbedPatterns is go:embed patterns and pos 16 type EmbedPatterns struct { 17 Patterns []string // patterns from ast.File 18 PatternPos map[string][]token.Position // line information for Patterns 19 } 20 21 // ParseEmbed parser go:embed patterns from files 22 func ParseEmbed(fset *token.FileSet, files []*ast.File) (*EmbedPatterns, error) { 23 var embeds []fileEmbed 24 for _, file := range files { 25 ems, err := parseFile(fset, file) 26 if err != nil { 27 return nil, err 28 } 29 if len(ems) > 0 { 30 embeds = append(embeds, ems...) 31 } 32 } 33 if len(embeds) == 0 { 34 return nil, nil 35 } 36 embedMap := make(map[string][]token.Position) 37 for _, emb := range embeds { 38 embedMap[emb.pattern] = append(embedMap[emb.pattern], emb.pos) 39 } 40 return &EmbedPatterns{embedPatterns(embedMap), embedMap}, nil 41 } 42 43 func parseFile(fset *token.FileSet, file *ast.File) ([]fileEmbed, error) { 44 hasEmbed, err := haveEmbedImport(file) 45 if err != nil { 46 return nil, err 47 } 48 var embeds []fileEmbed 49 for _, group := range file.Comments { 50 for _, comment := range group.List { 51 if strings.HasPrefix(comment.Text, "//go:embed ") { 52 if !hasEmbed { 53 return nil, fmt.Errorf(`%v: go:embed only allowed in Go files that import "embed"`, fset.Position(comment.Slash+2)) 54 } 55 embs, err := parseGoEmbed(comment.Text[11:], fset.Position(comment.Slash+11)) 56 if err == nil { 57 embeds = append(embeds, embs...) 58 } 59 } 60 } 61 } 62 if len(embeds) == 0 { 63 return nil, nil 64 } 65 return embeds, nil 66 } 67 68 func embedPatterns(m map[string][]token.Position) []string { 69 all := make([]string, 0, len(m)) 70 for path := range m { 71 all = append(all, path) 72 } 73 sort.Strings(all) 74 return all 75 } 76 77 func haveEmbedImport(file *ast.File) (bool, error) { 78 name, err := FindEmbedImportName(file) 79 return name != "", err 80 } 81 82 // FindEmbedImportName is find embed package import name 83 func FindEmbedImportName(file *ast.File) (string, error) { 84 for _, decl := range file.Decls { 85 d, ok := decl.(*ast.GenDecl) 86 if !ok { 87 continue 88 } 89 for _, dspec := range d.Specs { 90 spec, ok := dspec.(*ast.ImportSpec) 91 if !ok { 92 continue 93 } 94 quoted := spec.Path.Value 95 path, err := strconv.Unquote(quoted) 96 if err != nil { 97 return "", fmt.Errorf("parser returned invalid quoted string: <%s>", quoted) 98 } 99 if path == "embed" { 100 if spec.Name != nil { 101 return spec.Name.Name, nil 102 } 103 return "embed", nil 104 } 105 } 106 } 107 return "", nil 108 } 109 110 type fileEmbed struct { 111 pattern string 112 pos token.Position 113 }