github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runbits/runtime/progress/decor.go (about) 1 package progress 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/ActiveState/cli/internal/constants" 8 "github.com/ActiveState/cli/internal/errs" 9 "github.com/ActiveState/cli/internal/locale" 10 "github.com/ActiveState/cli/internal/logging" 11 "github.com/ActiveState/cli/internal/output" 12 "github.com/ActiveState/cli/internal/termutils" 13 "github.com/go-openapi/strfmt" 14 "github.com/vbauerster/mpb/v7" 15 "github.com/vbauerster/mpb/v7/decor" 16 ) 17 18 const progressBarWidth = 40 19 20 var refreshRate = constants.TerminalAnimationInterval 21 22 type bar struct { 23 *mpb.Bar 24 started time.Time 25 total int64 26 } 27 28 // Completed reports whether the bar has reached 100%. We have our own assertion prior to the mpb one as for whatever 29 // reason mpb reports completed even when it isn't, and I've not been able to diagnose why. 30 func (b *bar) Completed() bool { 31 if b.Bar.Current() < b.total { 32 return false 33 } 34 35 return b.Bar.Completed() 36 } 37 38 // trimName ensures that the name in a progress bar is not too wide for a terminal to display 39 func (p *ProgressDigester) trimName(name string) string { 40 if len(name) > p.maxNameWidth { 41 return name[0:p.maxNameWidth] 42 } 43 return name 44 } 45 46 // addTotalBar adds a bar counting a number of sub-events adding up to total 47 func (p *ProgressDigester) addTotalBar(name string, total int64, options ...mpb.BarOption) *bar { 48 logging.Debug("Adding total bar: %s", name) 49 return p.addBar(name, total, false, append(options, mpb.BarFillerClearOnComplete())...) 50 } 51 52 // addArtifactBar adds a bar counting the progress in a specific artifact setup step 53 func (p *ProgressDigester) addArtifactBar(id strfmt.UUID, step step, total int64, countsBytes bool) error { 54 name := locale.T("artifact_unknown_name") 55 if aname, ok := p.artifacts[id]; ok { 56 name = aname 57 } 58 logging.Debug("Adding %s artifact bar: %s", step.verb, name) 59 60 aStep := artifactStep{id, step} 61 if _, ok := p.artifactBars[aStep.ID()]; ok { 62 return errs.New("Artifact bar already exists") 63 } 64 p.artifactBars[aStep.ID()] = p.addBar(fmt.Sprintf(" - %s %s", step.verb, name), total, countsBytes, mpb.BarRemoveOnComplete(), mpb.BarPriority(step.priority+len(p.artifactBars))) 65 return nil 66 } 67 68 // updateArtifactBar sets the current progress of an artifact bar 69 func (p *ProgressDigester) updateArtifactBar(id strfmt.UUID, step step, inc int) error { 70 aStep := artifactStep{id, step} 71 if _, ok := p.artifactBars[aStep.ID()]; !ok { 72 return errs.New("%s Artifact bar doesn't exists", step.verb) 73 } 74 p.artifactBars[aStep.ID()].IncrBy(inc) 75 76 name := locale.T("artifact_unknown_name") 77 if aname, ok := p.artifacts[id]; ok { 78 name = aname 79 } 80 if p.artifactBars[aStep.ID()].Current() >= p.artifactBars[aStep.ID()].total { 81 logging.Debug("%s Artifact bar reached total: %s", step.verb, name) 82 } 83 84 return nil 85 } 86 87 // dropArtifactBar removes an artifact bar from the progress display 88 func (p *ProgressDigester) dropArtifactBar(id strfmt.UUID, step step) error { 89 name := locale.T("artifact_unknown_name") 90 if aname, ok := p.artifacts[id]; ok { 91 name = aname 92 } 93 logging.Debug("Dropping %s artifact bar: %s", step.verb, name) 94 95 aStep := artifactStep{id, step} 96 if _, ok := p.artifactBars[aStep.ID()]; !ok { 97 return errs.New("Artifact bar doesn't exists") 98 } 99 p.artifactBars[aStep.ID()].Abort(true) 100 return nil 101 } 102 103 func (p *ProgressDigester) addBar(name string, total int64, countsBytes bool, options ...mpb.BarOption) *bar { 104 name = p.trimName(name) 105 prependDecorators := []decor.Decorator{ 106 decor.Name(name, decor.WC{W: p.maxNameWidth, C: decor.DidentRight}), 107 decor.OnComplete( 108 decor.Spinner(output.SpinnerFrames, decor.WCSyncSpace), "", 109 ), 110 } 111 if countsBytes { 112 prependDecorators = append(prependDecorators, decor.CountersKiloByte("%.1f/%.1f", decor.WC{W: 17})) 113 } else { 114 prependDecorators = append(prependDecorators, decor.CountersNoUnit("%d/%d", decor.WC{W: 17})) 115 } 116 options = append(options, 117 mpb.BarFillerClearOnComplete(), 118 mpb.PrependDecorators(prependDecorators...), 119 mpb.AppendDecorators( 120 decor.OnComplete(decor.Percentage(decor.WC{W: 5}), ""), 121 ), 122 ) 123 124 return &bar{p.mainProgress.AddBar(total, options...), time.Now(), total} 125 } 126 127 // MaxNameWidth returns the maximum width to be used for a name in a progress bar 128 func MaxNameWidth() int { 129 tw := termutils.GetWidth() 130 131 // calculate the maximum width for a name displayed to the left of the progress bar 132 maxWidth := tw - progressBarWidth - 24 // 40 is the size for the progressbar, 24 is taken by counters (up to 999.9) and percentage display 133 if maxWidth < 0 { 134 maxWidth = 4 135 } 136 // limit to 30 characters such that text is not too far away from progress bars 137 if maxWidth > 30 { 138 return 30 139 } 140 return maxWidth 141 }