feat/multi-repo
1package main
2
3import (
4 "bytes"
5 "compress/gzip"
6 "io"
7 "io/fs"
8 "os"
9 "path/filepath"
10 "runtime"
11 "strings"
12 "sync"
13
14 "github.com/tdewolff/minify/v2"
15 "github.com/tdewolff/minify/v2/css"
16 "github.com/tdewolff/minify/v2/html"
17 "github.com/tdewolff/minify/v2/svg"
18
19 "github.com/antonmedv/gitmal/pkg/progress_bar"
20)
21
22func postProcessHTML(root string, doMinify bool, doGzip bool) error {
23 // 1) Collect all HTML and CSS files
24 var files []string
25 if err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
26 if err != nil {
27 return err
28 }
29 if d.IsDir() {
30 return nil
31 }
32 if strings.HasSuffix(d.Name(), ".html") {
33 files = append(files, path)
34 } else if doMinify && strings.HasSuffix(d.Name(), ".css") {
35 files = append(files, path)
36 }
37 return nil
38 }); err != nil {
39 return err
40 }
41
42 if len(files) == 0 {
43 return nil
44 }
45
46 // 2) Setup progress bar
47 labels := []string{}
48 if doMinify {
49 labels = append(labels, "minify")
50 }
51 if doGzip {
52 labels = append(labels, "gzip")
53 }
54 pb := progress_bar.NewProgressBar(strings.Join(labels, " + "), len(files))
55 defer pb.Done()
56
57 // 3) Worker pool
58 workers := runtime.NumCPU()
59 if workers < 1 {
60 workers = 1
61 }
62 jobs := make(chan string, workers*2)
63 var wg sync.WaitGroup
64 var mu sync.Mutex
65 var firstErr error
66
67 workerFn := func() {
68 defer wg.Done()
69 var m *minify.M
70 if doMinify {
71 m = minify.New()
72 m.AddFunc("text/html", html.Minify)
73 m.AddFunc("text/css", css.Minify)
74 m.AddFunc("image/svg+xml", svg.Minify)
75 }
76 for path := range jobs {
77 data, err := os.ReadFile(path)
78 if err == nil && doMinify {
79 mime := "text/html"
80 if strings.HasSuffix(path, ".css") {
81 mime = "text/css"
82 }
83 if md, e := m.Bytes(mime, data); e == nil {
84 data = md
85 } else {
86 err = e
87 }
88 }
89 if err == nil {
90 if strings.HasSuffix(path, ".css") {
91 // CSS files: only minify, no gzip
92 if e := os.WriteFile(path, data, 0o644); e != nil {
93 err = e
94 }
95 } else if doGzip {
96 // write to file.html.gz
97 gzPath := path + ".gz"
98 if e := writeGzip(gzPath, data); e != nil {
99 err = e
100 } else if e := os.Remove(path); e != nil { // remove original .html
101 err = e
102 }
103 } else {
104 if e := os.WriteFile(path, data, 0o644); e != nil {
105 err = e
106 }
107 }
108 }
109
110 if err != nil {
111 mu.Lock()
112 if firstErr == nil {
113 firstErr = err
114 }
115 mu.Unlock()
116 }
117 pb.Inc()
118 }
119 }
120
121 wg.Add(workers)
122 for i := 0; i < workers; i++ {
123 go workerFn()
124 }
125 for _, f := range files {
126 jobs <- f
127 }
128 close(jobs)
129 wg.Wait()
130
131 return firstErr
132}
133
134func writeGzip(path string, data []byte) error {
135 f, err := os.Create(path)
136 if err != nil {
137 return err
138 }
139 defer func() { _ = f.Close() }()
140 gw := gzip.NewWriter(f)
141 gw.Name = filepath.Base(strings.TrimSuffix(path, ".gz"))
142 if _, err := io.Copy(gw, bytes.NewReader(data)); err != nil {
143 _ = gw.Close()
144 return err
145 }
146 if err := gw.Close(); err != nil {
147 return err
148 }
149 return nil
150}