cmd/htpacker: swap out progress bar library

Switch to a simpler progress bar library. I've been having trouble tracking
down a panic that seemed to affect the prior code; the new code is much
easier and uses far fewer async channels and things.
This commit is contained in:
Laurence Withers 2024-08-24 09:54:25 +01:00
parent 3974db129e
commit 19b2560e2d
6 changed files with 144 additions and 177 deletions

View File

@ -5,9 +5,8 @@ go 1.22
require (
github.com/andybalholm/brotli v1.1.0
github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2
github.com/logrusorgru/aurora/v4 v4.0.0
github.com/gosuri/uiprogress v0.0.1
github.com/spf13/cobra v1.8.1
github.com/vbauerster/mpb/v8 v8.7.3
golang.org/x/sys v0.22.0
gopkg.in/yaml.v2 v2.4.0
src.lwithers.me.uk/go/htpack v1.3.3
@ -15,13 +14,11 @@ require (
)
require (
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gosuri/uilive v0.0.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

View File

@ -1,16 +1,14 @@
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2 h1:VA6jElpcJ+wkwEBufbnVkSBCA2TEnxdRppjRT5Kvh0A=
github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2/go.mod h1:Yi95+RbwKz7uGndSuUhoq7LJKh8qH8DT9fnL4ewU30k=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY=
github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI=
github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw=
github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -20,24 +18,13 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/vbauerster/mpb/v8 v8.7.3 h1:n/mKPBav4FFWp5fH4U0lPpXfiOmCEgl5Yx/NM3tKJA0=
github.com/vbauerster/mpb/v8 v8.7.3/go.mod h1:9nFlNpDGVoTmQ4QvNjSLtwLmAFjwmq0XaAF26toHGNM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -56,6 +43,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -73,7 +61,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
src.lwithers.me.uk/go/htpack v1.3.3 h1:Xvl6qR9HfSblmCgPyu+ACQ9o3aLQSIy3l8CrMbzj/jc=
src.lwithers.me.uk/go/htpack v1.3.3/go.mod h1:qKgCBgZ6iiiuYOxZkYOPVpXLBzp6gXEd4A0ksxgR6Nk=

View File

@ -144,7 +144,7 @@ func PackSpec(c *cobra.Command, spec, out string) error {
}
func doPack(ftp packer.FilesToPack, out string) error {
prog := mpbProgress(ftp)
prog := newUiProgress(ftp)
err := packer.Pack2(ftp, out, prog)
prog.Complete()

View File

@ -1,148 +0,0 @@
package main
import (
"strconv"
"github.com/logrusorgru/aurora/v4"
"github.com/vbauerster/mpb/v8"
"github.com/vbauerster/mpb/v8/decor"
"src.lwithers.me.uk/go/htpack/cmd/htpacker/packer"
)
// mpbProgress returns a new progress object that keeps the user informed via
// the visual mpb library.
func mpbProgress(ftp packer.FilesToPack) *mpbProg {
mp := new(mpbProg)
mp.un.max = len(ftp)
for _, f := range ftp {
if !f.DisableCompression && !f.DisableGzip {
mp.gzip.max++
}
if !f.DisableCompression && !f.DisableBrotli {
mp.brotli.max++
}
}
barStyle := mpb.BarStyle().Filler("█").Refiller("█").Padding("░").Tip("█").Build()
mp.p = mpb.New()
mp.un.bar = mp.p.MustAdd(int64(mp.un.max),
barStyle,
mpb.PrependDecorators(barName("uncompressed")),
mpb.AppendDecorators(&mp.un))
if mp.gzip.max > 0 {
mp.gzip.bar = mp.p.MustAdd(int64(mp.gzip.max),
barStyle,
mpb.PrependDecorators(barName("gzip")),
mpb.AppendDecorators(&mp.gzip))
}
if mp.brotli.max > 0 {
mp.brotli.bar = mp.p.MustAdd(int64(mp.brotli.max),
barStyle,
mpb.PrependDecorators(barName("brotli")),
mpb.AppendDecorators(&mp.brotli))
}
return mp
}
func barName(n string) decor.Decorator {
return decor.Name(aurora.Magenta(n).String(), decor.WCSyncWidth)
}
// mpbProg is the mpb progress tracker. It has one bar for each type of
// compression, and its methods simply dispatch onto the type-specific
// bars.
type mpbProg struct {
un, gzip, brotli mpbProg1
p *mpb.Progress
}
func (mp *mpbProg) Count(_ int) {
}
func (mp *mpbProg) Begin(filename, compression string) {
switch compression {
case "uncompressed":
mp.un.Begin(filename)
case "gzip":
mp.gzip.Begin(filename)
case "brotli":
mp.brotli.Begin(filename)
default:
return
}
}
func (mp *mpbProg) End(filename, compression string) {
switch compression {
case "uncompressed":
mp.un.End(filename)
case "gzip":
mp.gzip.End(filename)
case "brotli":
mp.brotli.End(filename)
default:
return
}
}
func (mp *mpbProg) Complete() {
mp.un.Complete()
mp.gzip.Complete()
mp.brotli.Complete()
mp.p.Wait()
}
// mpbProg1 is a type-specific progress bar. In addition to holding state and
// methods for updating the bar, it also implements decor.Decor.
type mpbProg1 struct {
max int // number of items we expect
done int // number of items completed
cur []string // list of currently-packing filenames
bar *mpb.Bar
// embedding this type lets us implement decor.Decor
decor.WC
}
func (mp1 *mpbProg1) Decor(stat decor.Statistics) (string, int) {
if stat.Completed {
return "", 0
}
switch len(mp1.cur) {
case 0:
return aurora.Gray(8, "(idle)").String(), 6
case 1:
return aurora.Blue(mp1.cur[0]).String(), len(mp1.cur[0])
default:
s := mp1.cur[0]
d := strconv.Itoa(len(mp1.cur) - 1)
return aurora.Sprintf(aurora.Green("%s + %s more"), s, d), len(s) + len(d) + 8
}
}
func (mp1 *mpbProg1) Begin(filename string) {
mp1.cur = append(mp1.cur, filename)
}
func (mp1 *mpbProg1) End(filename string) {
for i, v := range mp1.cur {
if v == filename {
mp1.cur[i] = mp1.cur[len(mp1.cur)-1]
mp1.cur = mp1.cur[:len(mp1.cur)-1]
break
}
}
mp1.done++
if mp1.bar != nil {
mp1.bar.SetCurrent(int64(mp1.done))
}
}
func (mp1 *mpbProg1) Complete() {
if mp1.bar != nil {
mp1.bar.SetTotal(int64(mp1.max), true)
}
}

View File

@ -364,7 +364,7 @@ func (p *packer) packFile(path string, fileToPack FileToPack) {
defer p.progress.End(fileToPack.Filename, "gzip")
if err := p.Gzip(data, info); err != nil {
return fmt.Errorf("gzip compression of %s "+
"failed: %v", fileToPack.Filename, err)
"failed: %w", fileToPack.Filename, err)
}
return nil
})
@ -375,7 +375,7 @@ func (p *packer) packFile(path string, fileToPack FileToPack) {
defer p.progress.End(fileToPack.Filename, "brotli")
if err := p.Brotli(data, info); err != nil {
return fmt.Errorf("brotli compression of %s "+
"failed: %v", fileToPack.Filename, err)
"failed: %w", fileToPack.Filename, err)
}
return nil
})

131
cmd/htpacker/progress.go Normal file
View File

@ -0,0 +1,131 @@
package main
import (
"bytes"
"slices"
"sync"
"github.com/gosuri/uiprogress"
"src.lwithers.me.uk/go/htpack/cmd/htpacker/packer"
)
type uiProgress struct {
p *uiprogress.Progress
uncompressed, gzip, brotli *uiProgressBar
}
func newUiProgress(ftp packer.FilesToPack) *uiProgress {
up := &uiProgress{
p: uiprogress.New(),
}
up.uncompressed = newUiProgressBar(up.p, len(ftp), "uncompressed")
var nGzip, nBrotli int
for _, f := range ftp {
if !f.DisableCompression && !f.DisableGzip {
nGzip++
}
if !f.DisableCompression && !f.DisableBrotli {
nBrotli++
}
}
if nGzip > 0 {
up.gzip = newUiProgressBar(up.p, nGzip, "gzip")
}
if nBrotli > 0 {
up.brotli = newUiProgressBar(up.p, nGzip, "brotli")
}
up.p.Start()
return up
}
func (up *uiProgress) Count(_ int) {
}
func (up *uiProgress) Begin(filename, compression string) {
up.bar(compression).begin(filename)
}
func (up *uiProgress) End(filename, compression string) {
up.bar(compression).end(filename)
}
func (up *uiProgress) bar(compression string) *uiProgressBar {
switch compression {
case "uncompressed":
return up.uncompressed
case "gzip":
return up.gzip
case "brotli":
return up.brotli
}
return nil
}
func (up *uiProgress) Complete() {
up.p.Stop()
}
type uiProgressBar struct {
bar *uiprogress.Bar
lock sync.Mutex
inflight []string
}
func newUiProgressBar(p *uiprogress.Progress, total int, compression string) *uiProgressBar {
bar := &uiProgressBar{
bar: p.AddBar(total).AppendCompleted(),
}
var buf bytes.Buffer
bar.bar.PrependFunc(func(*uiprogress.Bar) string {
bar.lock.Lock()
defer bar.lock.Unlock()
buf.Reset()
buf.WriteString(compression)
if len(bar.inflight) > 0 {
buf.WriteString(" (")
for i, f := range bar.inflight {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(f)
}
buf.WriteRune(')')
}
if buf.Len() < 40 {
buf.WriteString(" ")
buf.Truncate(40)
} else if buf.Len() > 40 {
buf.Truncate(39)
buf.WriteString("…")
}
return buf.String()
})
return bar
}
func (bar *uiProgressBar) begin(filename string) {
if bar == nil {
return
}
bar.lock.Lock()
defer bar.lock.Unlock()
bar.inflight = append(bar.inflight, filename)
}
func (bar *uiProgressBar) end(filename string) {
if bar == nil {
return
}
bar.lock.Lock()
defer bar.lock.Unlock()
bar.bar.Incr()
if idx := slices.Index(bar.inflight, filename); idx != -1 {
bar.inflight = slices.Delete(bar.inflight, idx, idx+1)
}
}