convert.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
  2. // license. Its contents can be found at:
  3. // http://creativecommons.org/publicdomain/zero/1.0/
  4. package bindata
  5. import (
  6. "bufio"
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "sort"
  12. "strings"
  13. "unicode"
  14. )
  15. // Translate reads assets from an input directory, converts them
  16. // to Go code and writes new files to the output specified
  17. // in the given configuration.
  18. func Translate(c *Config) error {
  19. var toc []Asset
  20. // Ensure our configuration has sane values.
  21. err := c.validate()
  22. if err != nil {
  23. return err
  24. }
  25. var knownFuncs = make(map[string]int)
  26. var visitedPaths = make(map[string]bool)
  27. // Locate all the assets.
  28. for _, input := range c.Input {
  29. err = findFiles(input.Path, c.Prefix, input.Recursive, &toc, c.Ignore, knownFuncs, visitedPaths)
  30. if err != nil {
  31. return err
  32. }
  33. }
  34. // Create output file.
  35. fd, err := os.Create(c.Output)
  36. if err != nil {
  37. return err
  38. }
  39. defer fd.Close()
  40. // Create a buffered writer for better performance.
  41. bfd := bufio.NewWriter(fd)
  42. defer bfd.Flush()
  43. // Write the header. This makes e.g. Github ignore diffs in generated files.
  44. if _, err = fmt.Fprint(bfd, "// Code generated by go-bindata.\n"); err != nil {
  45. return err
  46. }
  47. if _, err = fmt.Fprint(bfd, "// sources:\n"); err != nil {
  48. return err
  49. }
  50. wd, err := os.Getwd()
  51. if err != nil {
  52. return err
  53. }
  54. for _, asset := range toc {
  55. relative, _ := filepath.Rel(wd, asset.Path)
  56. if _, err = fmt.Fprintf(bfd, "// %s\n", filepath.ToSlash(relative)); err != nil {
  57. return err
  58. }
  59. }
  60. if _, err = fmt.Fprint(bfd, "// DO NOT EDIT!\n\n"); err != nil {
  61. return err
  62. }
  63. // Write build tags, if applicable.
  64. if len(c.Tags) > 0 {
  65. if _, err = fmt.Fprintf(bfd, "// +build %s\n\n", c.Tags); err != nil {
  66. return err
  67. }
  68. }
  69. // Write package declaration.
  70. _, err = fmt.Fprintf(bfd, "package %s\n\n", c.Package)
  71. if err != nil {
  72. return err
  73. }
  74. // Write assets.
  75. if c.Debug || c.Dev {
  76. err = writeDebug(bfd, c, toc)
  77. } else {
  78. err = writeRelease(bfd, c, toc)
  79. }
  80. if err != nil {
  81. return err
  82. }
  83. // Write table of contents
  84. if err := writeTOC(bfd, toc); err != nil {
  85. return err
  86. }
  87. // Write hierarchical tree of assets
  88. if err := writeTOCTree(bfd, toc); err != nil {
  89. return err
  90. }
  91. // Write restore procedure
  92. return writeRestore(bfd)
  93. }
  94. // Implement sort.Interface for []os.FileInfo based on Name()
  95. type ByName []os.FileInfo
  96. func (v ByName) Len() int { return len(v) }
  97. func (v ByName) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
  98. func (v ByName) Less(i, j int) bool { return v[i].Name() < v[j].Name() }
  99. // findFiles recursively finds all the file paths in the given directory tree.
  100. // They are added to the given map as keys. Values will be safe function names
  101. // for each file, which will be used when generating the output code.
  102. func findFiles(dir, prefix string, recursive bool, toc *[]Asset, ignore []*regexp.Regexp, knownFuncs map[string]int, visitedPaths map[string]bool) error {
  103. dirpath := dir
  104. if len(prefix) > 0 {
  105. dirpath, _ = filepath.Abs(dirpath)
  106. prefix, _ = filepath.Abs(prefix)
  107. prefix = filepath.ToSlash(prefix)
  108. }
  109. fi, err := os.Stat(dirpath)
  110. if err != nil {
  111. return err
  112. }
  113. var list []os.FileInfo
  114. if !fi.IsDir() {
  115. dirpath = filepath.Dir(dirpath)
  116. list = []os.FileInfo{fi}
  117. } else {
  118. visitedPaths[dirpath] = true
  119. fd, err := os.Open(dirpath)
  120. if err != nil {
  121. return err
  122. }
  123. defer fd.Close()
  124. list, err = fd.Readdir(0)
  125. if err != nil {
  126. return err
  127. }
  128. // Sort to make output stable between invocations
  129. sort.Sort(ByName(list))
  130. }
  131. for _, file := range list {
  132. var asset Asset
  133. asset.Path = filepath.Join(dirpath, file.Name())
  134. asset.Name = filepath.ToSlash(asset.Path)
  135. ignoring := false
  136. for _, re := range ignore {
  137. if re.MatchString(asset.Path) {
  138. ignoring = true
  139. break
  140. }
  141. }
  142. if ignoring {
  143. continue
  144. }
  145. if file.IsDir() {
  146. if recursive {
  147. recursivePath := filepath.Join(dir, file.Name())
  148. visitedPaths[asset.Path] = true
  149. findFiles(recursivePath, prefix, recursive, toc, ignore, knownFuncs, visitedPaths)
  150. }
  151. continue
  152. } else if file.Mode()&os.ModeSymlink == os.ModeSymlink {
  153. var linkPath string
  154. if linkPath, err = os.Readlink(asset.Path); err != nil {
  155. return err
  156. }
  157. if !filepath.IsAbs(linkPath) {
  158. if linkPath, err = filepath.Abs(dirpath + "/" + linkPath); err != nil {
  159. return err
  160. }
  161. }
  162. if _, ok := visitedPaths[linkPath]; !ok {
  163. visitedPaths[linkPath] = true
  164. findFiles(asset.Path, prefix, recursive, toc, ignore, knownFuncs, visitedPaths)
  165. }
  166. continue
  167. }
  168. if strings.HasPrefix(asset.Name, prefix) {
  169. asset.Name = asset.Name[len(prefix):]
  170. } else {
  171. asset.Name = filepath.Join(dir, file.Name())
  172. }
  173. // If we have a leading slash, get rid of it.
  174. if len(asset.Name) > 0 && asset.Name[0] == '/' {
  175. asset.Name = asset.Name[1:]
  176. }
  177. // This shouldn't happen.
  178. if len(asset.Name) == 0 {
  179. return fmt.Errorf("Invalid file: %v", asset.Path)
  180. }
  181. asset.Func = safeFunctionName(asset.Name, knownFuncs)
  182. asset.Path, _ = filepath.Abs(asset.Path)
  183. *toc = append(*toc, asset)
  184. }
  185. return nil
  186. }
  187. var regFuncName = regexp.MustCompile(`[^a-zA-Z0-9_]`)
  188. // safeFunctionName converts the given name into a name
  189. // which qualifies as a valid function identifier. It
  190. // also compares against a known list of functions to
  191. // prevent conflict based on name translation.
  192. func safeFunctionName(name string, knownFuncs map[string]int) string {
  193. var inBytes, outBytes []byte
  194. var toUpper bool
  195. name = strings.ToLower(name)
  196. inBytes = []byte(name)
  197. for i := 0; i < len(inBytes); i++ {
  198. if regFuncName.Match([]byte{inBytes[i]}) {
  199. toUpper = true
  200. } else if toUpper {
  201. outBytes = append(outBytes, []byte(strings.ToUpper(string(inBytes[i])))...)
  202. toUpper = false
  203. } else {
  204. outBytes = append(outBytes, inBytes[i])
  205. }
  206. }
  207. name = string(outBytes)
  208. // Identifier can't start with a digit.
  209. if unicode.IsDigit(rune(name[0])) {
  210. name = "_" + name
  211. }
  212. if num, ok := knownFuncs[name]; ok {
  213. knownFuncs[name] = num + 1
  214. name = fmt.Sprintf("%s%d", name, num)
  215. } else {
  216. knownFuncs[name] = 2
  217. }
  218. return name
  219. }