asset.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // +build ignore
  2. package main
  3. import (
  4. "bufio"
  5. "encoding/base64"
  6. "flag"
  7. "fmt"
  8. "go/build"
  9. "hash/fnv"
  10. "io"
  11. "io/ioutil"
  12. "log"
  13. "net/http"
  14. "os"
  15. "path/filepath"
  16. "strconv"
  17. "strings"
  18. "text/template"
  19. "time"
  20. )
  21. var asset_dev = asset(asset.init(asset{Name: "asset_dev.go", Content: "" +
  22. "// +build dev\n\npackage main\n\nimport (\n\t\"go/build\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\ntype asset struct {\n\tName string\n\tContent string\n\t// don't bother precomputing ETag if we're reloading from disk\n}\n\nfunc (a asset) init() asset {\n\treturn a\n}\n\nfunc (a asset) importPath() string {\n\t// filled at code gen time\n\treturn \"{{.ImportPath}}\"\n}\n\nfunc (a asset) Open() (*os.File, error) {\n\tpath := a.importPath()\n\tpkg, err := build.Import(path, \".\", build.FindOnly)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tp := filepath.Join(pkg.Dir, a.Name)\n\treturn os.Open(p)\n}\n\nfunc (a asset) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tbody, err := a.Open()\n\tif err != nil {\n\t\t// show the os.Open message, with paths and all, but this only\n\t\t// happens in dev mode.\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer body.Close()\n\thttp.ServeContent(w, req, a.Name, time.Time{}, body)\n}\n" +
  23. ""}))
  24. type asset struct {
  25. Name string
  26. Content string
  27. etag string
  28. }
  29. func (a asset) init() asset {
  30. h := fnv.New64a()
  31. _, _ = io.WriteString(h, a.Content)
  32. a.etag = `"` + base64.StdEncoding.EncodeToString(h.Sum(nil)) + `"`
  33. return a
  34. }
  35. func (a asset) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  36. if a.etag != "" && w.Header().Get("ETag") == "" {
  37. w.Header().Set("ETag", a.etag)
  38. }
  39. body := strings.NewReader(a.Content)
  40. http.ServeContent(w, req, a.Name, time.Time{}, body)
  41. }
  42. var asset_nodev = asset(asset.init(asset{Name: "asset_nodev.go", Content: "" +
  43. "// +build !dev\n\npackage main\n\nimport (\n\t\"encoding/base64\"\n\t\"hash/fnv\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype asset struct {\n\tName string\n\tContent string\n\tetag string\n}\n\nfunc (a asset) init() asset {\n\t// This is a method to minize the namespace pollution. Use\n\t// chaining to make it callable in variable declarations.\n\th := fnv.New64a()\n\t_, _ = io.WriteString(h, a.Content)\n\ta.etag = `\"` + base64.StdEncoding.EncodeToString(h.Sum(nil)) + `\"`\n\treturn a\n}\n\nfunc (a asset) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tif a.etag != \"\" && w.Header().Get(\"ETag\") == \"\" {\n\t\tw.Header().Set(\"ETag\", a.etag)\n\t}\n\tbody := strings.NewReader(a.Content)\n\thttp.ServeContent(w, req, a.Name, time.Time{}, body)\n}\n" +
  44. ""}))
  45. var (
  46. flagVar = flag.String("var", "", "variable name to use, \"_\" to ignore (default: file basename without extension)")
  47. flagWrap = flag.String("wrap", "", "wrapper function or type (default: filename extension)")
  48. flagLib = flag.Bool("lib", true, "generate asset_*.gen.go files defining the asset type")
  49. )
  50. var prog = filepath.Base(os.Args[0])
  51. func usage() {
  52. fmt.Fprintf(os.Stderr, "Usage:\n")
  53. fmt.Fprintf(os.Stderr, " %s [OPTS] FILE..\n", prog)
  54. fmt.Fprintf(os.Stderr, "\n")
  55. fmt.Fprintf(os.Stderr, "Creates files FILE.gen.go and asset_*.gen.go\n")
  56. fmt.Fprintf(os.Stderr, "\n")
  57. fmt.Fprintf(os.Stderr, "Options:\n")
  58. flag.PrintDefaults()
  59. }
  60. func main() {
  61. log.SetFlags(0)
  62. log.SetPrefix(prog + ": ")
  63. flag.Usage = usage
  64. flag.Parse()
  65. if flag.NArg() == 0 {
  66. flag.Usage()
  67. os.Exit(2)
  68. }
  69. if flag.NArg() > 1 && *flagVar != "" && *flagVar != "_" {
  70. log.Fatal("cannot combine -var with multiple files")
  71. }
  72. packages := map[string]*build.Package{}
  73. for _, filename := range flag.Args() {
  74. dir, base := filepath.Split(filename)
  75. if dir == "" {
  76. dir = "."
  77. }
  78. pkg, err := getPkg(packages, dir)
  79. if err != nil {
  80. log.Fatal(err)
  81. }
  82. variable := *flagVar
  83. if variable == "" {
  84. variable = strings.SplitN(base, ".", 2)[0]
  85. }
  86. wrap := *flagWrap
  87. if wrap == "" {
  88. wrap = filepath.Ext(base)
  89. if wrap == "" {
  90. log.Fatalf("files without extension need -wrap: %s", filename)
  91. }
  92. wrap = wrap[1:]
  93. }
  94. if err := process(filename, pkg.Name, variable, wrap); err != nil {
  95. log.Fatal(err)
  96. }
  97. }
  98. }
  99. // autogen writes a warning that the file has been generated automatically.
  100. func autogen(w io.Writer) error {
  101. // broken into parts here so grep won't find it
  102. const warning = "// AUTOMATICALLY " + "GENERATED FILE. DO NOT EDIT.\n\n"
  103. _, err := io.WriteString(w, warning)
  104. return err
  105. }
  106. func process(filename, pkg, variable, wrap string) error {
  107. src, err := os.Open(filename)
  108. if err != nil {
  109. return err
  110. }
  111. defer src.Close()
  112. tmp, err := ioutil.TempFile(filepath.Dir(filename), ".tmp.asset-")
  113. if err != nil {
  114. return err
  115. }
  116. defer func() {
  117. if tmp != nil {
  118. _ = os.Remove(tmp.Name())
  119. }
  120. }()
  121. defer tmp.Close()
  122. in := bufio.NewReader(src)
  123. out := bufio.NewWriter(tmp)
  124. if err := autogen(out); err != nil {
  125. return err
  126. }
  127. if _, err := fmt.Fprintf(out, "package %s\n\n", pkg); err != nil {
  128. return err
  129. }
  130. if err := embed(variable, wrap, filepath.Base(filename), in, out); err != nil {
  131. return err
  132. }
  133. if err := out.Flush(); err != nil {
  134. return err
  135. }
  136. if err := tmp.Close(); err != nil {
  137. return err
  138. }
  139. gen := filename + ".gen.go"
  140. if err := os.Rename(tmp.Name(), gen); err != nil {
  141. return err
  142. }
  143. tmp = nil
  144. return nil
  145. }
  146. func embed(variable, wrap, filename string, in io.Reader, out io.Writer) error {
  147. _, err := fmt.Fprintf(out, "var %s = %s(asset.init(asset{Name: %q, Content: \"\" +\n",
  148. variable, wrap, filename)
  149. if err != nil {
  150. return err
  151. }
  152. buf := make([]byte, 1*1024*1024)
  153. eof := false
  154. for !eof {
  155. n, err := in.Read(buf)
  156. switch err {
  157. case io.EOF:
  158. eof = true
  159. case nil:
  160. default:
  161. return err
  162. }
  163. if n == 0 {
  164. continue
  165. }
  166. s := string(buf[:n])
  167. s = strconv.QuoteToASCII(s)
  168. s = "\t" + s + " +\n"
  169. if _, err := io.WriteString(out, s); err != nil {
  170. return err
  171. }
  172. }
  173. if _, err := fmt.Fprintf(out, "\t\"\"}))\n"); err != nil {
  174. return err
  175. }
  176. return nil
  177. }
  178. func getPkg(packages map[string]*build.Package, dir string) (*build.Package, error) {
  179. if pkg, found := packages[dir]; found {
  180. return pkg, nil
  181. }
  182. pkg, err := loadPkg(dir)
  183. if err != nil {
  184. return nil, err
  185. }
  186. if *flagLib {
  187. if err := auxiliary(pkg.Dir, pkg.ImportPath, pkg.Name); err != nil {
  188. return nil, err
  189. }
  190. }
  191. packages[dir] = pkg
  192. return pkg, nil
  193. }
  194. func loadPkg(dir string) (*build.Package, error) {
  195. if !filepath.IsAbs(dir) {
  196. if abs, err := filepath.Abs(dir); err == nil {
  197. dir = abs
  198. }
  199. }
  200. pkg, err := build.ImportDir(dir, 0)
  201. if err != nil {
  202. return nil, err
  203. }
  204. return pkg, nil
  205. }
  206. func auxiliary(dir, imp, pkg string) error {
  207. for filename, tmpl := range map[string]string{
  208. "asset_dev": asset_dev.Content,
  209. "asset_nodev": asset_nodev.Content,
  210. } {
  211. tmpl = strings.Replace(tmpl, "\npackage main\n", "\npackage "+pkg+"\n", 1)
  212. t, err := template.New("").Parse(tmpl)
  213. if err != nil {
  214. return err
  215. }
  216. tmp, err := ioutil.TempFile(dir, ".tmp.asset-")
  217. if err != nil {
  218. return err
  219. }
  220. defer func() {
  221. if tmp != nil {
  222. _ = os.Remove(tmp.Name())
  223. }
  224. }()
  225. defer tmp.Close()
  226. type data struct {
  227. ImportPath string
  228. }
  229. d := data{
  230. ImportPath: imp,
  231. }
  232. if err := autogen(tmp); err != nil {
  233. return err
  234. }
  235. if err := t.Execute(tmp, d); err != nil {
  236. return err
  237. }
  238. if err := tmp.Close(); err != nil {
  239. return err
  240. }
  241. gen := filepath.Join(dir, filename+".gen.go")
  242. if err := os.Rename(tmp.Name(), gen); err != nil {
  243. return err
  244. }
  245. tmp = nil
  246. }
  247. return nil
  248. }