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 }