Commit 9678cd2
Changed files (4)
go.mod
@@ -4,9 +4,13 @@ go 1.24.0
require (
github.com/alecthomas/chroma/v2 v2.20.0
+ github.com/tdewolff/minify/v2 v2.24.7
github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
golang.org/x/net v0.47.0
)
-require github.com/dlclark/regexp2 v1.11.5 // indirect
+require (
+ github.com/dlclark/regexp2 v1.11.5 // indirect
+ github.com/tdewolff/parse/v2 v2.8.5 // indirect
+)
go.sum
@@ -17,6 +17,12 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tdewolff/minify/v2 v2.24.7 h1:aJNQ2s0WYZg58j5ZJQo0Mk0UXMPhvCXCMHbJEgWIDXQ=
+github.com/tdewolff/minify/v2 v2.24.7/go.mod h1:0Ukj0CRpo/sW/nd8uZ4ccXaV1rEVIWA3dj8U7+Shhfw=
+github.com/tdewolff/parse/v2 v2.8.5 h1:ZmBiA/8Do5Rpk7bDye0jbbDUpXXbCdc3iah4VeUvwYU=
+github.com/tdewolff/parse/v2 v2.8.5/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
+github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
+github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
main.go
@@ -19,6 +19,8 @@ var (
flagDefaultBranch string
flagTheme string
flagPreviewThemes bool
+ flagMinify bool
+ flagGzip bool
)
type Params struct {
@@ -63,6 +65,8 @@ func main() {
flag.StringVar(&flagDefaultBranch, "default-branch", "", "Default branch to use (autodetect master or main)")
flag.StringVar(&flagTheme, "theme", "github", "Style theme")
flag.BoolVar(&flagPreviewThemes, "preview-themes", false, "Preview available themes")
+ flag.BoolVar(&flagMinify, "minify", false, "Minify all generated HTML files")
+ flag.BoolVar(&flagGzip, "gzip", false, "Compress all generated HTML files")
flag.Usage = usage
flag.Parse()
@@ -228,6 +232,13 @@ func main() {
panic(err)
}
}
+
+ if flagMinify || flagGzip {
+ echo("> post-processing HTML...")
+ if err := postProcessHTML(params.OutputDir, flagMinify, flagGzip); err != nil {
+ panic(err)
+ }
+ }
}
func usage() {
post_process.go
@@ -0,0 +1,138 @@
+package main
+
+import (
+ "compress/gzip"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+
+ "github.com/tdewolff/minify/v2"
+ "github.com/tdewolff/minify/v2/css"
+ "github.com/tdewolff/minify/v2/html"
+ "github.com/tdewolff/minify/v2/svg"
+
+ "github.com/antonmedv/gitmal/pkg/progress_bar"
+)
+
+func postProcessHTML(root string, doMinify bool, doGzip bool) error {
+ // 1) Collect all HTML files first
+ var files []string
+ if err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return nil
+ }
+ if strings.HasSuffix(d.Name(), ".html") {
+ files = append(files, path)
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ if len(files) == 0 {
+ return nil
+ }
+
+ // 2) Setup progress bar
+ labels := []string{}
+ if doMinify {
+ labels = append(labels, "minify")
+ }
+ if doGzip {
+ labels = append(labels, "gzip")
+ }
+ pb := progress_bar.NewProgressBar(strings.Join(labels, " + "), len(files))
+ defer pb.Done()
+
+ // 3) Worker pool
+ workers := runtime.NumCPU()
+ if workers < 1 {
+ workers = 1
+ }
+ jobs := make(chan string, workers*2)
+ var wg sync.WaitGroup
+ var mu sync.Mutex
+ var firstErr error
+
+ workerFn := func() {
+ defer wg.Done()
+ var m *minify.M
+ if doMinify {
+ m = minify.New()
+ m.AddFunc("text/html", html.Minify)
+ m.AddFunc("text/css", css.Minify)
+ m.AddFunc("image/svg+xml", svg.Minify)
+ }
+ for path := range jobs {
+ data, err := os.ReadFile(path)
+ if err == nil && doMinify {
+ if md, e := m.Bytes("text/html", data); e == nil {
+ data = md
+ } else {
+ err = e
+ }
+ }
+ if err == nil {
+ if doGzip {
+ // write to file.html.gz
+ gzPath := path + ".gz"
+ if e := writeGzip(gzPath, data); e != nil {
+ err = e
+ } else if e := os.Remove(path); e != nil { // remove original .html
+ err = e
+ }
+ } else {
+ if e := os.WriteFile(path, data, 0o644); e != nil {
+ err = e
+ }
+ }
+ }
+
+ if err != nil {
+ mu.Lock()
+ if firstErr == nil {
+ firstErr = err
+ }
+ mu.Unlock()
+ }
+ pb.Inc()
+ }
+ }
+
+ wg.Add(workers)
+ for i := 0; i < workers; i++ {
+ go workerFn()
+ }
+ for _, f := range files {
+ jobs <- f
+ }
+ close(jobs)
+ wg.Wait()
+
+ return firstErr
+}
+
+func writeGzip(path string, data []byte) error {
+ f, err := os.Create(path)
+ if err != nil {
+ return err
+ }
+ defer func() { _ = f.Close() }()
+ gw := gzip.NewWriter(f)
+ gw.Name = filepath.Base(strings.TrimSuffix(path, ".gz"))
+ if _, err := io.Copy(gw, strings.NewReader(string(data))); err != nil {
+ _ = gw.Close()
+ return err
+ }
+ if err := gw.Close(); err != nil {
+ return err
+ }
+ return nil
+}