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  }