github.com/please-build/go-rules/tools/please_go@v0.0.0-20240319165128-ea27d6f5caba/embed/embed.go (about)

     1  // Package embed implements parsing of embed directives in Go files.
     2  package embed
     3  
     4  import (
     5  	"encoding/json"
     6  	"fmt"
     7  	"go/build"
     8  	"io"
     9  	"io/fs"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  )
    14  
    15  // Cfg is the structure of a Go embedcfg file.
    16  type Cfg struct {
    17  	Patterns map[string][]string
    18  	Files    map[string]string
    19  }
    20  
    21  // WriteEmbedConfig writes the embed config to w
    22  func WriteEmbedConfig(files []string, w io.Writer) error {
    23  	cfg, err := Parse(files)
    24  	if err != nil {
    25  		return err
    26  	}
    27  	b, err := json.Marshal(cfg)
    28  	if err != nil {
    29  		return err
    30  	}
    31  	_, err = w.Write(b)
    32  	return err
    33  }
    34  
    35  // Parse parses the given files and returns the embed information in them.
    36  func Parse(gofiles []string) (*Cfg, error) {
    37  	cfg := &Cfg{
    38  		Patterns: map[string][]string{},
    39  		Files:    map[string]string{},
    40  	}
    41  	for _, dir := range dirs(gofiles) {
    42  		pkg, err := build.ImportDir(dir, build.ImportComment)
    43  		if err != nil {
    44  			return nil, err
    45  		}
    46  		// We munge all patterns together at this point, if a file is in our input sources we want to know about it regardless.
    47  		if err := cfg.AddPackage(pkg); err != nil {
    48  			return nil, err
    49  		}
    50  	}
    51  	return cfg, nil
    52  }
    53  
    54  // AddPackage parses a go package and adds any embed patterns to the configuration
    55  func (cfg *Cfg) AddPackage(pkg *build.Package) error {
    56  	for _, pattern := range append(append(pkg.EmbedPatterns, pkg.TestEmbedPatterns...), pkg.XTestEmbedPatterns...) {
    57  		paths, err := relglob(pkg.Dir, pattern)
    58  		if err != nil {
    59  			return err
    60  		}
    61  		cfg.Patterns[pattern] = paths
    62  		for _, p := range paths {
    63  			cfg.Files[p] = path.Join(pkg.Dir, p)
    64  		}
    65  	}
    66  	return nil
    67  }
    68  
    69  func dirs(files []string) []string {
    70  	dirs := []string{}
    71  	seen := map[string]bool{}
    72  	for _, f := range files {
    73  		if dir := path.Dir(f); !seen[dir] {
    74  			dirs = append(dirs, dir)
    75  			seen[dir] = true
    76  		}
    77  	}
    78  	return dirs
    79  }
    80  
    81  func relglob(dir, pattern string) ([]string, error) {
    82  	// Go allows prefixing the pattern with all: which picks up files prefixed with . or _ (by default these should be ignored)
    83  	includeHidden := false
    84  	if strings.HasPrefix(pattern, "all:") {
    85  		pattern = strings.TrimPrefix(pattern, "all:")
    86  		includeHidden = true
    87  	}
    88  
    89  	paths, err := filepath.Glob(path.Join(dir, pattern))
    90  	if err == nil && len(paths) == 0 {
    91  		return nil, fmt.Errorf("pattern %s: no matching paths found", pattern)
    92  	}
    93  	ret := make([]string, 0, len(paths))
    94  	for _, p := range paths {
    95  		if err := filepath.WalkDir(p, func(path string, d fs.DirEntry, err error) error {
    96  			if err != nil {
    97  				return err
    98  			} else if !d.IsDir() {
    99  				if hidden := strings.HasPrefix(d.Name(), ".") || strings.HasPrefix(d.Name(), "_"); !hidden || includeHidden {
   100  					ret = append(ret, strings.TrimLeft(strings.TrimPrefix(path, dir), string(filepath.Separator)))
   101  				}
   102  			}
   103  			return nil
   104  		}); err != nil {
   105  			return nil, err
   106  		}
   107  	}
   108  	return ret, err
   109  }