262 lines
6 KiB
Go
262 lines
6 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 the header. This makes e.g. Github ignore diffs in generated files.
|
|
if _, err = fmt.Fprint(bfd, "// Code generated by go-bindata.\n"); err != nil {
|
|
return err
|
|
}
|
|
if _, err = fmt.Fprint(bfd, "// sources:\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, asset := range toc {
|
|
relative, _ := filepath.Rel(wd, asset.Path)
|
|
if _, err = fmt.Fprintf(bfd, "// %s\n", filepath.ToSlash(relative)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err = fmt.Fprint(bfd, "// DO NOT EDIT!\n\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write build tags, if applicable.
|
|
if len(c.Tags) > 0 {
|
|
if _, err = fmt.Fprintf(bfd, "// +build %s\n\n", c.Tags); 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 {
|
|
dirpath := dir
|
|
if len(prefix) > 0 {
|
|
dirpath, _ = filepath.Abs(dirpath)
|
|
prefix, _ = filepath.Abs(prefix)
|
|
prefix = filepath.ToSlash(prefix)
|
|
}
|
|
|
|
fi, err := os.Stat(dirpath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var list []os.FileInfo
|
|
|
|
if !fi.IsDir() {
|
|
dirpath = filepath.Dir(dirpath)
|
|
list = []os.FileInfo{fi}
|
|
} else {
|
|
visitedPaths[dirpath] = true
|
|
fd, err := os.Open(dirpath)
|
|
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(dirpath, 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 {
|
|
recursivePath := filepath.Join(dir, file.Name())
|
|
visitedPaths[asset.Path] = true
|
|
findFiles(recursivePath, 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(dirpath + "/" + 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):]
|
|
} else {
|
|
asset.Name = filepath.Join(dir, file.Name())
|
|
}
|
|
|
|
// 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 {
|
|
var inBytes, outBytes []byte
|
|
var toUpper bool
|
|
|
|
name = strings.ToLower(name)
|
|
inBytes = []byte(name)
|
|
|
|
for i := 0; i < len(inBytes); i++ {
|
|
if regFuncName.Match([]byte{inBytes[i]}) {
|
|
toUpper = true
|
|
} else if toUpper {
|
|
outBytes = append(outBytes, []byte(strings.ToUpper(string(inBytes[i])))...)
|
|
toUpper = false
|
|
} else {
|
|
outBytes = append(outBytes, inBytes[i])
|
|
}
|
|
}
|
|
|
|
name = string(outBytes)
|
|
|
|
// 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
|
|
}
|