Codebase list golang-github-vbauerster-mpb / 9799f9d
refactoring: BarStyleComposer Vladimir Bauer 5 years ago
1 changed file(s) with 176 addition(s) and 149 deletion(s). Raw diff Collapse all Expand all
22 import (
33 "bytes"
44 "io"
5 "unicode/utf8"
6
5
6 "github.com/acarl005/stripansi"
77 "github.com/mattn/go-runewidth"
8 "github.com/rivo/uniseg"
98 "github.com/vbauerster/mpb/v6/decor"
109 "github.com/vbauerster/mpb/v6/internal"
1110 )
1211
1312 const (
14 rLeft = iota
15 rFill
16 rTip
17 rSpace
18 rRight
19 rRevTip
20 rRefill
13 iLbound = iota
14 iRbound
15 iFiller
16 iRefiller
17 iPadding
18 components
2119 )
2220
23 // BarDefaultStyle is a style for rendering a progress bar.
24 // It consist of 7 ordered runes:
25 //
26 // '1st rune' stands for left boundary rune
27 //
28 // '2nd rune' stands for fill rune
29 //
30 // '3rd rune' stands for tip rune
31 //
32 // '4th rune' stands for space rune
33 //
34 // '5th rune' stands for right boundary rune
35 //
36 // '6th rune' stands for reverse tip rune
37 //
38 // '7th rune' stands for refill rune
39 //
40 const BarDefaultStyle string = "[=>-]<+"
41
42 type barFiller struct {
43 format [][]byte
44 rwidth []int
45 tip []byte
46 refill int64
47 reverse bool
48 flush func(io.Writer, *space, [][]byte)
49 }
50
51 type space struct {
52 space []byte
53 rwidth int
54 count int
55 }
56
57 // NewBarFiller returns a BarFiller implementation which renders a
58 // progress bar in regular direction. If style is empty string,
59 // BarDefaultStyle is applied. To be used with `*Progress.Add(...)
60 // *Bar` method.
61 func NewBarFiller(style string) BarFiller {
62 return newBarFiller(style, false)
63 }
64
65 // NewBarFillerRev returns a BarFiller implementation which renders a
66 // progress bar in reverse direction. If style is empty string,
67 // BarDefaultStyle is applied. To be used with `*Progress.Add(...)
68 // *Bar` method.
69 func NewBarFillerRev(style string) BarFiller {
70 return newBarFiller(style, true)
71 }
72
73 // NewBarFillerPick pick between regular and reverse BarFiller implementation
74 // based on rev param. To be used with `*Progress.Add(...) *Bar` method.
75 func NewBarFillerPick(style string, rev bool) BarFiller {
76 return newBarFiller(style, rev)
77 }
78
79 func newBarFiller(style string, rev bool) BarFiller {
80 bf := &barFiller{
81 format: make([][]byte, len(BarDefaultStyle)),
82 rwidth: make([]int, len(BarDefaultStyle)),
83 reverse: rev,
84 }
85 bf.parse(BarDefaultStyle)
86 if style != "" && style != BarDefaultStyle {
87 bf.parse(style)
21 // BarStyleComposer interface.
22 type BarStyleComposer interface {
23 BarFillerBuilder
24 Lbound(string) BarStyleComposer
25 Rbound(string) BarStyleComposer
26 Filler(string) BarStyleComposer
27 Refiller(string) BarStyleComposer
28 Padding(string) BarStyleComposer
29 Tip(...string) BarStyleComposer
30 Reverse() BarStyleComposer
31 }
32
33 type bFiller struct {
34 components [components]*component
35 tip struct {
36 count uint
37 frames []*component
38 }
39 flush func(dst io.Writer, src [][]byte, pad *component, padWidth int)
40 }
41
42 type component struct {
43 width int
44 bytes []byte
45 }
46
47 type barStyle struct {
48 lbound string
49 rbound string
50 filler string
51 refiller string
52 padding string
53 tip []string
54 rev bool
55 }
56
57 // BarStyle constructs default bar style which can be altered via
58 // BarStyleComposer interface.
59 func BarStyle() BarStyleComposer {
60 return &barStyle{
61 lbound: "[",
62 rbound: "]",
63 filler: "=",
64 refiller: "+",
65 padding: "-",
66 tip: []string{">"},
67 }
68 }
69
70 func (s *barStyle) Lbound(bound string) BarStyleComposer {
71 s.lbound = bound
72 return s
73 }
74
75 func (s *barStyle) Rbound(bound string) BarStyleComposer {
76 s.rbound = bound
77 return s
78 }
79
80 func (s *barStyle) Filler(filler string) BarStyleComposer {
81 s.filler = filler
82 return s
83 }
84
85 func (s *barStyle) Refiller(refiller string) BarStyleComposer {
86 s.refiller = refiller
87 return s
88 }
89
90 func (s *barStyle) Padding(padding string) BarStyleComposer {
91 s.padding = padding
92 return s
93 }
94
95 func (s *barStyle) Tip(tip ...string) BarStyleComposer {
96 if len(tip) != 0 {
97 s.tip = append(s.tip[:0], tip...)
98 }
99 return s
100 }
101
102 func (s *barStyle) Reverse() BarStyleComposer {
103 s.rev = true
104 return s
105 }
106
107 func (s *barStyle) Build() BarFiller {
108 bf := &bFiller{
109 flush: regFlush,
110 }
111 if s.rev {
112 bf.flush = revFlush
113 }
114 bf.components[iLbound] = &component{
115 width: runewidth.StringWidth(stripansi.Strip(s.lbound)),
116 bytes: []byte(s.lbound),
117 }
118 bf.components[iRbound] = &component{
119 width: runewidth.StringWidth(stripansi.Strip(s.rbound)),
120 bytes: []byte(s.rbound),
121 }
122 bf.components[iFiller] = &component{
123 width: runewidth.StringWidth(stripansi.Strip(s.filler)),
124 bytes: []byte(s.filler),
125 }
126 bf.components[iRefiller] = &component{
127 width: runewidth.StringWidth(stripansi.Strip(s.refiller)),
128 bytes: []byte(s.refiller),
129 }
130 bf.components[iPadding] = &component{
131 width: runewidth.StringWidth(stripansi.Strip(s.padding)),
132 bytes: []byte(s.padding),
133 }
134 bf.tip.frames = make([]*component, len(s.tip))
135 for i, t := range s.tip {
136 bf.tip.frames[i] = &component{
137 width: runewidth.StringWidth(stripansi.Strip(t)),
138 bytes: []byte(t),
139 }
88140 }
89141 return bf
90142 }
91143
92 func (s *barFiller) parse(style string) {
93 if !utf8.ValidString(style) {
94 panic("invalid bar style")
95 }
96 srcFormat := make([][]byte, len(BarDefaultStyle))
97 srcRwidth := make([]int, len(BarDefaultStyle))
98 i := 0
99 for gr := uniseg.NewGraphemes(style); i < len(BarDefaultStyle) && gr.Next(); i++ {
100 srcFormat[i] = gr.Bytes()
101 srcRwidth[i] = runewidth.StringWidth(gr.Str())
102 }
103 copy(s.format, srcFormat[:i])
104 copy(s.rwidth, srcRwidth[:i])
105 if s.reverse {
106 s.tip = s.format[rRevTip]
107 s.flush = reverseFlush
108 } else {
109 s.tip = s.format[rTip]
110 s.flush = regularFlush
111 }
112 }
113
114 func (s *barFiller) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
115 width := internal.CheckRequestedWidth(reqWidth, stat.AvailableWidth)
116 brackets := s.rwidth[rLeft] + s.rwidth[rRight]
144 func (s *bFiller) Fill(w io.Writer, width int, stat decor.Statistics) {
145 width = internal.CheckRequestedWidth(width, stat.AvailableWidth)
146 brackets := s.components[iLbound].width + s.components[iRbound].width
117147 if width < brackets {
118148 return
119149 }
120150 // don't count brackets as progress
121151 width -= brackets
122152
123 w.Write(s.format[rLeft])
124 defer w.Write(s.format[rRight])
125
126 cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width))
127 space := &space{
128 space: s.format[rSpace],
129 rwidth: s.rwidth[rSpace],
130 count: width - cwidth,
131 }
132
153 w.Write(s.components[iLbound].bytes)
154 defer w.Write(s.components[iRbound].bytes)
155
156 curWidth := int(internal.PercentageRound(stat.Total, stat.Current, width))
157 padWidth := width - curWidth
133158 index, refill := 0, 0
134 bb := make([][]byte, cwidth)
135
136 if cwidth > 0 && cwidth != width {
137 bb[index] = s.tip
138 cwidth -= s.rwidth[rTip]
159 bb := make([][]byte, curWidth)
160
161 if curWidth > 0 && curWidth != width {
162 tipFrame := s.tip.frames[s.tip.count%uint(len(s.tip.frames))]
163 bb[index] = tipFrame.bytes
164 curWidth -= tipFrame.width
165 s.tip.count++
139166 index++
140167 }
141168
142169 if stat.Refill > 0 {
143170 refill = int(internal.PercentageRound(stat.Total, int64(stat.Refill), width))
144 if refill > cwidth {
145 refill = cwidth
171 if refill > curWidth {
172 refill = curWidth
146173 }
147 cwidth -= refill
148 }
149
150 for cwidth > 0 {
151 bb[index] = s.format[rFill]
152 cwidth -= s.rwidth[rFill]
174 curWidth -= refill
175 }
176
177 for curWidth > 0 {
178 bb[index] = s.components[iFiller].bytes
179 curWidth -= s.components[iFiller].width
153180 index++
154181 }
155182
156183 for refill > 0 {
157 bb[index] = s.format[rRefill]
158 refill -= s.rwidth[rRefill]
184 bb[index] = s.components[iRefiller].bytes
185 refill -= s.components[iRefiller].width
159186 index++
160187 }
161188
162 if cwidth+refill < 0 || space.rwidth > 1 {
189 if curWidth+refill < 0 || s.components[iPadding].width > 1 {
163190 buf := new(bytes.Buffer)
164 s.flush(buf, space, bb[:index])
191 s.flush(buf, bb[:index], s.components[iPadding], padWidth)
165192 io.WriteString(w, runewidth.Truncate(buf.String(), width, "…"))
166193 return
167194 }
168195
169 s.flush(w, space, bb)
170 }
171
172 func regularFlush(w io.Writer, space *space, bb [][]byte) {
173 for i := len(bb) - 1; i >= 0; i-- {
174 w.Write(bb[i])
175 }
176 for space.count > 0 {
177 w.Write(space.space)
178 space.count -= space.rwidth
179 }
180 }
181
182 func reverseFlush(w io.Writer, space *space, bb [][]byte) {
183 for space.count > 0 {
184 w.Write(space.space)
185 space.count -= space.rwidth
186 }
187 for i := 0; i < len(bb); i++ {
188 w.Write(bb[i])
189 }
190 }
196 s.flush(w, bb, s.components[iPadding], padWidth)
197 }
198
199 func regFlush(dst io.Writer, src [][]byte, pad *component, padWidth int) {
200 for i := len(src) - 1; i >= 0; i-- {
201 dst.Write(src[i])
202 }
203 for padWidth > 0 {
204 dst.Write(pad.bytes)
205 padWidth -= pad.width
206 }
207 }
208
209 func revFlush(dst io.Writer, src [][]byte, pad *component, padWidth int) {
210 for padWidth > 0 {
211 dst.Write(pad.bytes)
212 padWidth -= pad.width
213 }
214 for i := 0; i < len(src); i++ {
215 dst.Write(src[i])
216 }
217 }