main.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. package gen
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "git.clearsky.net.au/cody/gex.git/gen/domquery"
  9. "git.clearsky.net.au/cody/gex.git/gen/img"
  10. "git.clearsky.net.au/cody/gex.git/gen/layout"
  11. "git.clearsky.net.au/cody/gex.git/gen/partial"
  12. "git.clearsky.net.au/cody/gex.git/utils"
  13. )
  14. var appRuleList cssRuleList
  15. //var appJsStr string
  16. func GeneratePages(dir string) error {
  17. if err := processTemplate(dir); err != nil {
  18. utils.Err(err)
  19. return err
  20. }
  21. // Process Partials
  22. if err := processPartials(dir); err != nil {
  23. utils.Err(err)
  24. return err
  25. }
  26. // Build the layout & blocks
  27. if err := processLayout(dir); err != nil {
  28. utils.Err(err)
  29. return err
  30. }
  31. // Process IMG on the generated files
  32. if err := ProcessImg(dir); err != nil {
  33. utils.Err(err)
  34. return err
  35. }
  36. // Clean block tags
  37. if err := cleanBlocks(dir); err != nil {
  38. utils.Err(err)
  39. return err
  40. }
  41. appRuleList = mergeMediaRules(appRuleList)
  42. appRuleList = orderRules(appRuleList)
  43. cssStr := rulesToCSSstr(appRuleList)
  44. os.WriteFile("app/pub/css/app.gen.css", []byte(cssStr), 0655)
  45. return nil
  46. }
  47. func processTemplate(dir string) error {
  48. processFile := func(fp string) error {
  49. if strings.Contains(fp, ".gen.tpl") {
  50. return nil
  51. }
  52. if !strings.Contains(fp, ".tpl") {
  53. return nil
  54. }
  55. // get the html string
  56. data, err := os.ReadFile(fp)
  57. if err != nil {
  58. utils.Err(err)
  59. return err
  60. }
  61. htmlStr := string(data)
  62. // add the scope class to attributes
  63. className := genRandName(8)
  64. htmlStr = AddClassAttr(htmlStr, className)
  65. // get & strip the css rules
  66. cssStr := ExtractCSS(&htmlStr)
  67. ruleList := toRuleList(cssStr)
  68. // scope the rules with css class
  69. ruleList = scopeSelectors(ruleList, className)
  70. appRuleList = append(appRuleList, ruleList...)
  71. // Process JS on the generated file
  72. htmlStr = ProcessJs(htmlStr, className)
  73. // write the .gen.html file as
  74. newPath := strings.Replace(fp, ".tpl", ".gen.tpl", 1)
  75. return os.WriteFile(newPath, []byte(htmlStr), 0655)
  76. }
  77. walk := func(path string, info os.FileInfo, err error) error {
  78. if err != nil {
  79. utils.Err(err)
  80. return err
  81. }
  82. if info.IsDir() {
  83. return nil
  84. }
  85. return processFile(path)
  86. }
  87. return filepath.Walk(dir, walk)
  88. }
  89. func processPartials(dir string) error {
  90. processFile := func(fp string) error {
  91. if !strings.Contains(fp, ".gen.tpl") {
  92. return nil
  93. }
  94. data, err := os.ReadFile(fp)
  95. if err != nil {
  96. utils.Err(err)
  97. return err
  98. }
  99. htmlStr := string(data)
  100. htmlStr, err = partial.ProcessHTML(htmlStr, fp)
  101. if err != nil {
  102. utils.Err(err)
  103. return err
  104. }
  105. return os.WriteFile(fp, []byte(htmlStr), 0655)
  106. }
  107. walk := func(path string, info os.FileInfo, err error) error {
  108. if err != nil {
  109. utils.Err(err)
  110. return err
  111. }
  112. if info.IsDir() {
  113. return nil
  114. }
  115. return processFile(path)
  116. }
  117. err := filepath.Walk(dir, walk)
  118. if err != nil {
  119. utils.Err(err)
  120. return err
  121. }
  122. return nil
  123. }
  124. func processLayout(dir string) error {
  125. processFile := func(fp string) error {
  126. if !strings.Contains(fp, ".gen.tpl") {
  127. return nil
  128. }
  129. data, err := os.ReadFile(fp)
  130. if err != nil {
  131. utils.Err(err)
  132. return err
  133. }
  134. htmlStr := string(data)
  135. htmlStr, err = layout.ProcessHTML(htmlStr, fp)
  136. if err != nil {
  137. utils.Err(err)
  138. return err
  139. }
  140. os.WriteFile(fp, []byte(htmlStr), 0655)
  141. return nil
  142. }
  143. walk := func(path string, info os.FileInfo, err error) error {
  144. if err != nil {
  145. utils.Err(err)
  146. return err
  147. }
  148. if info.IsDir() {
  149. return nil
  150. }
  151. return processFile(path)
  152. }
  153. filepath.Walk(dir, walk)
  154. return nil
  155. }
  156. func ProcessJs(htmlStr string, className string) string {
  157. dom := domquery.LoadHTML(htmlStr)
  158. scripts := dom.QuerySelectorAll("script")
  159. i := 0
  160. jsTag := ""
  161. for _, script := range scripts {
  162. scriptName := className + fmt.Sprintf("%d", i)
  163. if script.GetAttribute("inline") != "" {
  164. continue
  165. }
  166. if script.GetAttribute("src") != "" {
  167. continue
  168. }
  169. jsStr := script.InnerHTML()
  170. funcStr := `if (typeof ` + scriptName + ` === 'undefined') {`
  171. funcStr += `window.` + scriptName + ` = () => {`
  172. funcStr += jsStr
  173. funcStr += `
  174. if(typeof unload == "undefined")
  175. return
  176. const watchInt = setInterval(() => {
  177. let watch = document.querySelector(".` + className + `");
  178. if (!watch) {
  179. unload();
  180. clearInterval(watchInt);
  181. }
  182. },100);
  183. }
  184. }
  185. ` + scriptName + `()
  186. `
  187. srcPath := "app/pub/gen/" + scriptName + ".gen.js"
  188. os.WriteFile(srcPath, []byte(funcStr), 0655)
  189. jsTag += `<script defer src="/` + srcPath + `"></script>`
  190. if script.GetAttribute("type") == "module" {
  191. jsTag = `<script type="module" defer src="/` + srcPath + `"></script>`
  192. }
  193. script.Remove()
  194. i++
  195. }
  196. insDiv := dom.QuerySelector("." + className)
  197. insDiv.SetInnerHTML(jsTag + insDiv.InnerHTML())
  198. htmlStr = dom.InnerHTML()
  199. return htmlStr
  200. }
  201. func ProcessImg(dir string) error {
  202. processFile := func(fp string) error {
  203. htmlDir := filepath.Dir(fp)
  204. if !strings.Contains(fp, ".gen.tpl") {
  205. return nil
  206. }
  207. // get the html string
  208. data, err := os.ReadFile(fp)
  209. if err != nil {
  210. utils.Err(err)
  211. return err
  212. }
  213. htmlStr := string(data)
  214. document := domquery.LoadHTML(htmlStr)
  215. imgTags := document.QuerySelectorAll("img")
  216. for _, tag := range imgTags {
  217. if !(tag.HasAttribute("webp") && tag.HasAttribute("src")) {
  218. continue
  219. }
  220. img.Process(htmlDir, tag)
  221. }
  222. htmlStr = document.InnerHTML()
  223. os.WriteFile(fp, []byte(htmlStr), 0655)
  224. return nil
  225. }
  226. walk := func(path string, info os.FileInfo, err error) error {
  227. if err != nil {
  228. utils.Err(err)
  229. return err
  230. }
  231. if info.IsDir() {
  232. return nil
  233. }
  234. return processFile(path)
  235. }
  236. return filepath.Walk(dir, walk)
  237. }
  238. func cleanBlocks(dir string) error {
  239. processFile := func(fp string) error {
  240. if !strings.Contains(fp, ".gen.tpl") {
  241. return nil
  242. }
  243. // get the html string
  244. data, err := os.ReadFile(fp)
  245. if err != nil {
  246. utils.Err(err)
  247. return err
  248. }
  249. htmlStr := string(data)
  250. dom := domquery.LoadHTML(htmlStr)
  251. var clean func()
  252. clean = func() {
  253. block := dom.QuerySelector("block")
  254. if block.TagName() != "block" {
  255. return
  256. }
  257. block.SetOuterHTML(block.InnerHTML())
  258. clean()
  259. return
  260. }
  261. clean()
  262. htmlStr = dom.InnerHTML()
  263. os.WriteFile(fp, []byte(htmlStr), 0655)
  264. return nil
  265. }
  266. walk := func(dir string, info os.FileInfo, err error) error {
  267. if err != nil {
  268. utils.Err(err)
  269. return err
  270. }
  271. if info.IsDir() {
  272. return nil
  273. }
  274. return processFile(dir)
  275. }
  276. return filepath.Walk(dir, walk)
  277. }
  278. // generate a random string to use as a class name
  279. func genRandName(length int) string {
  280. const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  281. b := make([]byte, length)
  282. for i := range b {
  283. b[i] = charset[rand.Intn(len(charset))]
  284. }
  285. return string(b)
  286. }