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}