465fc29db9
This is an implementation where findFiles will follow symlinks and keep track of paths already followed. No paths will be evaluated more than once, therefore preventing recursive link evaluation. Commit includes test cases and test data
230 lines
5.2 KiB
Go
230 lines
5.2 KiB
Go
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
|
// license. Its contents can be found at:
|
|
// http://creativecommons.org/publicdomain/zero/1.0/
|
|
|
|
package bindata
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// Translate reads assets from an input directory, converts them
|
|
// to Go code and writes new files to the output specified
|
|
// in the given configuration.
|
|
func Translate(c *Config) error {
|
|
var toc []Asset
|
|
|
|
// Ensure our configuration has sane values.
|
|
err := c.validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var knownFuncs = make(map[string]int)
|
|
var visitedPaths = make(map[string]bool)
|
|
// Locate all the assets.
|
|
for _, input := range c.Input {
|
|
err = findFiles(input.Path, c.Prefix, input.Recursive, &toc, c.Ignore, knownFuncs, visitedPaths)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Create output file.
|
|
fd, err := os.Create(c.Output)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer fd.Close()
|
|
|
|
// Create a buffered writer for better performance.
|
|
bfd := bufio.NewWriter(fd)
|
|
defer bfd.Flush()
|
|
|
|
// Write build tags, if applicable.
|
|
if len(c.Tags) > 0 {
|
|
_, err = fmt.Fprintf(bfd, "// +build %s\n\n", c.Tags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write package declaration.
|
|
_, err = fmt.Fprintf(bfd, "package %s\n\n", c.Package)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write assets.
|
|
if c.Debug || c.Dev {
|
|
err = writeDebug(bfd, c, toc)
|
|
} else {
|
|
err = writeRelease(bfd, c, toc)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write table of contents
|
|
if err := writeTOC(bfd, toc); err != nil {
|
|
return err
|
|
}
|
|
// Write hierarchical tree of assets
|
|
if err := writeTOCTree(bfd, toc); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write restore procedure
|
|
return writeRestore(bfd)
|
|
}
|
|
|
|
// Implement sort.Interface for []os.FileInfo based on Name()
|
|
type ByName []os.FileInfo
|
|
|
|
func (v ByName) Len() int { return len(v) }
|
|
func (v ByName) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
|
func (v ByName) Less(i, j int) bool { return v[i].Name() < v[j].Name() }
|
|
|
|
// findFiles recursively finds all the file paths in the given directory tree.
|
|
// They are added to the given map as keys. Values will be safe function names
|
|
// for each file, which will be used when generating the output code.
|
|
func findFiles(dir, prefix string, recursive bool, toc *[]Asset, ignore []*regexp.Regexp, knownFuncs map[string]int, visitedPaths map[string]bool) error {
|
|
if len(prefix) > 0 {
|
|
dir, _ = filepath.Abs(dir)
|
|
prefix, _ = filepath.Abs(prefix)
|
|
prefix = filepath.ToSlash(prefix)
|
|
}
|
|
|
|
fi, err := os.Stat(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var list []os.FileInfo
|
|
|
|
if !fi.IsDir() {
|
|
dir = ""
|
|
list = []os.FileInfo{fi}
|
|
} else {
|
|
visitedPaths[dir] = true
|
|
fd, err := os.Open(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer fd.Close()
|
|
|
|
list, err = fd.Readdir(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Sort to make output stable between invocations
|
|
sort.Sort(ByName(list))
|
|
}
|
|
|
|
for _, file := range list {
|
|
var asset Asset
|
|
asset.Path = filepath.Join(dir, file.Name())
|
|
asset.Name = filepath.ToSlash(asset.Path)
|
|
|
|
ignoring := false
|
|
for _, re := range ignore {
|
|
if re.MatchString(asset.Path) {
|
|
ignoring = true
|
|
break
|
|
}
|
|
}
|
|
if ignoring {
|
|
continue
|
|
}
|
|
|
|
if file.IsDir() {
|
|
if recursive {
|
|
visitedPaths[asset.Path] = true
|
|
findFiles(asset.Path, prefix, recursive, toc, ignore, knownFuncs, visitedPaths)
|
|
}
|
|
continue
|
|
} else if file.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
var linkPath string
|
|
if linkPath, err = os.Readlink(asset.Path); err != nil {
|
|
return err
|
|
}
|
|
if !filepath.IsAbs(linkPath) {
|
|
if linkPath, err = filepath.Abs(dir + "/" + linkPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, ok := visitedPaths[linkPath]; !ok {
|
|
visitedPaths[linkPath] = true
|
|
findFiles(asset.Path, prefix, recursive, toc, ignore, knownFuncs, visitedPaths)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(asset.Name, prefix) {
|
|
asset.Name = asset.Name[len(prefix):]
|
|
}
|
|
|
|
// If we have a leading slash, get rid of it.
|
|
if len(asset.Name) > 0 && asset.Name[0] == '/' {
|
|
asset.Name = asset.Name[1:]
|
|
}
|
|
|
|
// This shouldn't happen.
|
|
if len(asset.Name) == 0 {
|
|
return fmt.Errorf("Invalid file: %v", asset.Path)
|
|
}
|
|
|
|
asset.Func = safeFunctionName(asset.Name, knownFuncs)
|
|
asset.Path, _ = filepath.Abs(asset.Path)
|
|
*toc = append(*toc, asset)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var regFuncName = regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
|
|
|
// safeFunctionName converts the given name into a name
|
|
// which qualifies as a valid function identifier. It
|
|
// also compares against a known list of functions to
|
|
// prevent conflict based on name translation.
|
|
func safeFunctionName(name string, knownFuncs map[string]int) string {
|
|
name = strings.ToLower(name)
|
|
name = regFuncName.ReplaceAllString(name, "_")
|
|
|
|
// Get rid of "__" instances for niceness.
|
|
for strings.Index(name, "__") > -1 {
|
|
name = strings.Replace(name, "__", "_", -1)
|
|
}
|
|
|
|
// Leading underscores are silly (unless they prefix a digit (see below)).
|
|
for len(name) > 1 && name[0] == '_' {
|
|
name = name[1:]
|
|
}
|
|
|
|
// Identifier can't start with a digit.
|
|
if unicode.IsDigit(rune(name[0])) {
|
|
name = "_" + name
|
|
}
|
|
|
|
if num, ok := knownFuncs[name]; ok {
|
|
knownFuncs[name] = num + 1
|
|
name = fmt.Sprintf("%s%d", name, num)
|
|
} else {
|
|
knownFuncs[name] = 2
|
|
}
|
|
|
|
return name
|
|
}
|