generate.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright 2017 Frédéric Guillot. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. // +build ignore
  5. package main
  6. import (
  7. "crypto/sha256"
  8. "encoding/base64"
  9. "fmt"
  10. "io/ioutil"
  11. "os"
  12. "path"
  13. "path/filepath"
  14. "strings"
  15. "text/template"
  16. "github.com/tdewolff/minify/v2"
  17. "github.com/tdewolff/minify/v2/css"
  18. "github.com/tdewolff/minify/v2/js"
  19. )
  20. const tpl = `// Code generated by go generate; DO NOT EDIT.
  21. package {{ .Package }} // import "miniflux.app/{{ .ImportPath }}"
  22. var {{ .Map }} = map[string]string{
  23. {{ range $constant, $content := .Files }}` + "\t" + `"{{ $constant }}": ` + "`{{ $content }}`" + `,
  24. {{ end }}}
  25. var {{ .Map }}Checksums = map[string]string{
  26. {{ range $constant, $content := .Checksums }}` + "\t" + `"{{ $constant }}": "{{ $content }}",
  27. {{ end }}}
  28. `
  29. var bundleTpl = template.Must(template.New("").Parse(tpl))
  30. type Bundle struct {
  31. Package string
  32. Map string
  33. ImportPath string
  34. Files map[string]string
  35. Checksums map[string]string
  36. }
  37. func (b *Bundle) Write(filename string) {
  38. f, err := os.Create(filename)
  39. if err != nil {
  40. panic(err)
  41. }
  42. defer f.Close()
  43. bundleTpl.Execute(f, b)
  44. }
  45. func NewBundle(pkg, mapName, importPath string) *Bundle {
  46. return &Bundle{
  47. Package: pkg,
  48. Map: mapName,
  49. ImportPath: importPath,
  50. Files: make(map[string]string),
  51. Checksums: make(map[string]string),
  52. }
  53. }
  54. func readFile(filename string) []byte {
  55. data, err := ioutil.ReadFile(filename)
  56. if err != nil {
  57. panic(err)
  58. }
  59. return data
  60. }
  61. func checksum(data []byte) string {
  62. return fmt.Sprintf("%x", sha256.Sum256(data))
  63. }
  64. func basename(filename string) string {
  65. return path.Base(filename)
  66. }
  67. func stripExtension(filename string) string {
  68. filename = strings.TrimSuffix(filename, path.Ext(filename))
  69. return strings.Replace(filename, " ", "_", -1)
  70. }
  71. func glob(pattern string) []string {
  72. // There is no Glob function in path package, so we have to use filepath and replace in case of Windows
  73. files, _ := filepath.Glob(pattern)
  74. for i := range files {
  75. if strings.Contains(files[i], "\\") {
  76. files[i] = strings.Replace(files[i], "\\", "/", -1)
  77. }
  78. }
  79. return files
  80. }
  81. func concat(files []string) string {
  82. var b strings.Builder
  83. for _, file := range files {
  84. b.Write(readFile(file))
  85. }
  86. return b.String()
  87. }
  88. func generateJSBundle(bundleFile string, bundleFiles map[string][]string, prefixes, suffixes map[string]string) {
  89. bundle := NewBundle("static", "Javascripts", "ui/static")
  90. m := minify.New()
  91. m.AddFunc("text/javascript", js.Minify)
  92. for name, srcFiles := range bundleFiles {
  93. var b strings.Builder
  94. if prefix, found := prefixes[name]; found {
  95. b.WriteString(prefix)
  96. }
  97. b.WriteString(concat(srcFiles))
  98. if suffix, found := suffixes[name]; found {
  99. b.WriteString(suffix)
  100. }
  101. minifiedData, err := m.String("text/javascript", b.String())
  102. if err != nil {
  103. panic(err)
  104. }
  105. bundle.Files[name] = minifiedData
  106. bundle.Checksums[name] = checksum([]byte(minifiedData))
  107. }
  108. bundle.Write(bundleFile)
  109. }
  110. func generateCSSBundle(bundleFile string, themes map[string][]string) {
  111. bundle := NewBundle("static", "Stylesheets", "ui/static")
  112. m := minify.New()
  113. m.AddFunc("text/css", css.Minify)
  114. for theme, srcFiles := range themes {
  115. data := concat(srcFiles)
  116. minifiedData, err := m.String("text/css", data)
  117. if err != nil {
  118. panic(err)
  119. }
  120. bundle.Files[theme] = minifiedData
  121. bundle.Checksums[theme] = checksum([]byte(minifiedData))
  122. }
  123. bundle.Write(bundleFile)
  124. }
  125. func generateBinaryBundle(bundleFile string, srcFiles []string) {
  126. bundle := NewBundle("static", "Binaries", "ui/static")
  127. for _, srcFile := range srcFiles {
  128. data := readFile(srcFile)
  129. filename := basename(srcFile)
  130. encodedData := base64.StdEncoding.EncodeToString(data)
  131. bundle.Files[filename] = string(encodedData)
  132. bundle.Checksums[filename] = checksum(data)
  133. }
  134. bundle.Write(bundleFile)
  135. }
  136. func generateBundle(bundleFile, pkg, mapName string, srcFiles []string) {
  137. bundle := NewBundle(pkg, mapName, pkg)
  138. for _, srcFile := range srcFiles {
  139. data := readFile(srcFile)
  140. filename := stripExtension(basename(srcFile))
  141. bundle.Files[filename] = string(data)
  142. bundle.Checksums[filename] = checksum(data)
  143. }
  144. bundle.Write(bundleFile)
  145. }
  146. func main() {
  147. generateJSBundle("ui/static/js.go", map[string][]string{
  148. "app": []string{
  149. "ui/static/js/dom_helper.js",
  150. "ui/static/js/touch_handler.js",
  151. "ui/static/js/keyboard_handler.js",
  152. "ui/static/js/mouse_handler.js",
  153. "ui/static/js/form_handler.js",
  154. "ui/static/js/request_builder.js",
  155. "ui/static/js/unread_counter_handler.js",
  156. "ui/static/js/entry_handler.js",
  157. "ui/static/js/feed_handler.js",
  158. "ui/static/js/confirm_handler.js",
  159. "ui/static/js/menu_handler.js",
  160. "ui/static/js/modal_handler.js",
  161. "ui/static/js/nav_handler.js",
  162. "ui/static/js/link_state_handler.js",
  163. "ui/static/js/bootstrap.js",
  164. },
  165. "sw": []string{
  166. "ui/static/js/sw.js",
  167. },
  168. }, map[string]string{
  169. "app": "(function(){'use strict';",
  170. "sw": "'use strict';",
  171. }, map[string]string{
  172. "app": "})();",
  173. })
  174. generateCSSBundle("ui/static/css.go", map[string][]string{
  175. "default": []string{"ui/static/css/common.css"},
  176. "black": []string{"ui/static/css/common.css", "ui/static/css/black.css"},
  177. "sansserif": []string{"ui/static/css/common.css", "ui/static/css/sansserif.css"},
  178. })
  179. generateBinaryBundle("ui/static/bin.go", glob("ui/static/bin/*"))
  180. generateBundle("database/sql.go", "database", "SqlMap", glob("database/sql/*.sql"))
  181. generateBundle("template/views.go", "template", "templateViewsMap", glob("template/html/*.html"))
  182. generateBundle("template/common.go", "template", "templateCommonMap", glob("template/html/common/*.html"))
  183. generateBundle("locale/translations.go", "locale", "translations", glob("locale/translations/*.json"))
  184. }