github.com/visualfc/goembed@v0.3.3/resolve.go (about)

     1  package goembed
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"fmt"
     7  	"go/printer"
     8  	"go/token"
     9  	"io/ioutil"
    10  	"path"
    11  	"path/filepath"
    12  	"sort"
    13  
    14  	"github.com/visualfc/goembed/resolve"
    15  )
    16  
    17  // File is embed data info
    18  type File struct {
    19  	Name string
    20  	Data []byte
    21  	Hash [16]byte // truncated SHA256 hash
    22  }
    23  
    24  // Resolve is load embed data interface
    25  type Resolve interface {
    26  	Load(dir string, fset *token.FileSet, em *Embed) ([]*File, error)
    27  	Files() []*File
    28  }
    29  
    30  type resolveFile struct {
    31  	data map[string]*File
    32  }
    33  
    34  // NewResolve create load embed data interface
    35  func NewResolve() Resolve {
    36  	return &resolveFile{make(map[string]*File)}
    37  }
    38  
    39  // BuildFS is build files to new files list with directory
    40  func BuildFS(files []*File) []*File {
    41  	have := make(map[string]bool)
    42  	var list []*File
    43  	for _, file := range files {
    44  		if !have[file.Name] {
    45  			have[file.Name] = true
    46  			list = append(list, file)
    47  		}
    48  		for dir := path.Dir(file.Name); dir != "." && !have[dir]; dir = path.Dir(dir) {
    49  			have[dir] = true
    50  			list = append(list, &File{Name: dir + "/"})
    51  		}
    52  	}
    53  	sort.Slice(list, func(i, j int) bool {
    54  		return embedFileLess(list[i].Name, list[j].Name)
    55  	})
    56  	return list
    57  }
    58  
    59  func (r *resolveFile) Files() (files []*File) {
    60  	for _, v := range r.data {
    61  		files = append(files, v)
    62  	}
    63  	sort.Slice(files, func(i, j int) bool {
    64  		return embedFileLess(files[i].Name, files[j].Name)
    65  	})
    66  	return
    67  }
    68  
    69  func (r *resolveFile) Load(dir string, fset *token.FileSet, em *Embed) ([]*File, error) {
    70  	list, err := resolve.ResolveEmbed(dir, em.Patterns)
    71  	if err != nil {
    72  		return nil, fmt.Errorf("%v: %w", em.Pos, err)
    73  	}
    74  	var files []*File
    75  	for _, v := range list {
    76  		fpath := filepath.Join(dir, v)
    77  		f, ok := r.data[fpath]
    78  		if !ok {
    79  			data, err := ioutil.ReadFile(fpath)
    80  			if err != nil {
    81  				return nil, fmt.Errorf("%v: embed %v: %w", em.Pos, em.Patterns, err)
    82  			}
    83  			f = &File{
    84  				Name: v,
    85  				Data: data,
    86  			}
    87  			if len(data) > 0 {
    88  				hash := sha256.Sum256(data)
    89  				copy(f.Hash[:], hash[:16])
    90  			}
    91  			r.data[fpath] = f
    92  		}
    93  		files = append(files, f)
    94  	}
    95  	if em.Kind != EmbedFiles && len(files) > 1 {
    96  		var buf bytes.Buffer
    97  		printer.Fprint(&buf, fset, em.Spec.Type)
    98  		return nil, fmt.Errorf("%v: invalid go:embed: multiple files for type %v", fset.Position(em.Spec.Names[0].NamePos), buf.String())
    99  	}
   100  	sort.Slice(files, func(i, j int) bool {
   101  		return embedFileLess(files[i].Name, files[j].Name)
   102  	})
   103  	return files, nil
   104  }
   105  
   106  func embedFileNameSplit(name string) (dir, elem string, isDir bool) {
   107  	if name[len(name)-1] == '/' {
   108  		isDir = true
   109  		name = name[:len(name)-1]
   110  	}
   111  	i := len(name) - 1
   112  	for i >= 0 && name[i] != '/' {
   113  		i--
   114  	}
   115  	if i < 0 {
   116  		return ".", name, isDir
   117  	}
   118  	return name[:i], name[i+1:], isDir
   119  }
   120  
   121  // embedFileLess implements the sort order for a list of embedded files.
   122  // See the comment inside ../../../../embed/embed.go's Files struct for rationale.
   123  func embedFileLess(x, y string) bool {
   124  	xdir, xelem, _ := embedFileNameSplit(x)
   125  	ydir, yelem, _ := embedFileNameSplit(y)
   126  	return xdir < ydir || xdir == ydir && xelem < yelem
   127  }