refactoring bar filler
Vladimir Bauer
6 years ago
| 17 | 17 | rRefill |
| 18 | 18 | ) |
| 19 | 19 | |
| 20 | var defaultBarStyle = "[=>-]<+" | |
| 20 | // DefaultBarStyle is applied when bar constructed with *Progress.AddBar method. | |
| 21 | // | |
| 22 | // '1th rune' stands for left boundary rune | |
| 23 | // | |
| 24 | // '2th rune' stands for fill rune | |
| 25 | // | |
| 26 | // '3th rune' stands for tip rune | |
| 27 | // | |
| 28 | // '4th rune' stands for empty rune | |
| 29 | // | |
| 30 | // '5th rune' stands for right boundary rune | |
| 31 | // | |
| 32 | // '6th rune' stands for reverse tip rune | |
| 33 | // | |
| 34 | // '7th rune' stands for refill rune | |
| 35 | // | |
| 36 | const DefaultBarStyle string = "[=>-]<+" | |
| 21 | 37 | |
| 22 | 38 | type barFiller struct { |
| 23 | format [][]byte | |
| 24 | refillAmount int64 | |
| 25 | reverse bool | |
| 26 | noBrackets bool | |
| 39 | format [][]byte | |
| 40 | tip []byte | |
| 41 | refill int64 | |
| 42 | reverse bool | |
| 43 | flush func(w io.Writer, bb [][]byte) | |
| 27 | 44 | } |
| 28 | 45 | |
| 29 | // NewBarFiller bar Filler used with *Progress.AddBar | |
| 30 | func NewBarFiller() Filler { | |
| 31 | filler := &barFiller{ | |
| 32 | format: make([][]byte, utf8.RuneCountInString(defaultBarStyle)), | |
| 46 | // NewBarFiller constucts mpb.Filler, to be used with *Progress.Add method. | |
| 47 | func NewBarFiller(style string, reverse bool) Filler { | |
| 48 | if style == "" { | |
| 49 | style = DefaultBarStyle | |
| 33 | 50 | } |
| 34 | filler.setStyle(defaultBarStyle) | |
| 35 | return filler | |
| 51 | bf := &barFiller{ | |
| 52 | format: make([][]byte, utf8.RuneCountInString(style)), | |
| 53 | } | |
| 54 | bf.SetStyle(style) | |
| 55 | bf.SetReverse(reverse) | |
| 56 | return bf | |
| 36 | 57 | } |
| 37 | 58 | |
| 38 | func (s *barFiller) setStyle(style string) { | |
| 59 | func (s *barFiller) SetStyle(style string) { | |
| 39 | 60 | if !utf8.ValidString(style) { |
| 40 | 61 | return |
| 41 | 62 | } |
| 44 | 65 | src = append(src, []byte(string(r))) |
| 45 | 66 | } |
| 46 | 67 | copy(s.format, src) |
| 68 | if s.reverse { | |
| 69 | s.tip = s.format[rRevTip] | |
| 70 | } else { | |
| 71 | s.tip = s.format[rTip] | |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | func (s *barFiller) SetReverse(reverse bool) { | |
| 76 | if reverse { | |
| 77 | s.tip = s.format[rRevTip] | |
| 78 | s.flush = func(w io.Writer, bb [][]byte) { | |
| 79 | for i := len(bb) - 1; i >= 0; i-- { | |
| 80 | w.Write(bb[i]) | |
| 81 | } | |
| 82 | } | |
| 83 | } else { | |
| 84 | s.tip = s.format[rTip] | |
| 85 | s.flush = func(w io.Writer, bb [][]byte) { | |
| 86 | for i := 0; i < len(bb); i++ { | |
| 87 | w.Write(bb[i]) | |
| 88 | } | |
| 89 | } | |
| 90 | } | |
| 91 | s.reverse = reverse | |
| 47 | 92 | } |
| 48 | 93 | |
| 49 | 94 | func (s *barFiller) SetRefill(amount int64) { |
| 50 | s.refillAmount = amount | |
| 95 | s.refill = amount | |
| 51 | 96 | } |
| 52 | 97 | |
| 53 | 98 | func (s *barFiller) Fill(w io.Writer, width int, stat *decor.Statistics) { |
| 54 | ||
| 55 | if !s.noBrackets { | |
| 56 | // don't count rLeft and rRight as progress | |
| 57 | width -= 2 | |
| 58 | if width < 2 { | |
| 59 | return | |
| 60 | } | |
| 61 | w.Write(s.format[rLeft]) | |
| 62 | defer w.Write(s.format[rRight]) | |
| 99 | // don't count rLeft and rRight as progress | |
| 100 | width -= 2 | |
| 101 | if width < 2 { | |
| 102 | return | |
| 63 | 103 | } |
| 104 | w.Write(s.format[rLeft]) | |
| 105 | defer w.Write(s.format[rRight]) | |
| 64 | 106 | |
| 65 | 107 | bb := make([][]byte, width) |
| 66 | 108 | |
| 70 | 112 | bb[i] = s.format[rFill] |
| 71 | 113 | } |
| 72 | 114 | |
| 73 | if s.refillAmount > 0 { | |
| 115 | if s.refill > 0 { | |
| 74 | 116 | var rwidth int |
| 75 | if s.refillAmount > stat.Current { | |
| 117 | if s.refill > stat.Current { | |
| 76 | 118 | rwidth = cwidth |
| 77 | 119 | } else { |
| 78 | rwidth = int(internal.PercentageRound(stat.Total, int64(s.refillAmount), width)) | |
| 120 | rwidth = int(internal.PercentageRound(stat.Total, int64(s.refill), width)) | |
| 79 | 121 | } |
| 80 | 122 | for i := 0; i < rwidth; i++ { |
| 81 | 123 | bb[i] = s.format[rRefill] |
| 83 | 125 | } |
| 84 | 126 | |
| 85 | 127 | if cwidth > 0 && cwidth < width { |
| 86 | bb[cwidth-1] = s.format[rTip] | |
| 128 | bb[cwidth-1] = s.tip | |
| 87 | 129 | } |
| 88 | 130 | |
| 89 | 131 | for i := cwidth; i < width; i++ { |
| 90 | 132 | bb[i] = s.format[rEmpty] |
| 91 | 133 | } |
| 92 | 134 | |
| 93 | if s.reverse { | |
| 94 | if cwidth > 0 && cwidth < width { | |
| 95 | bb[cwidth-1] = s.format[rRevTip] | |
| 96 | } | |
| 97 | for i := len(bb) - 1; i >= 0; i-- { | |
| 98 | w.Write(bb[i]) | |
| 99 | } | |
| 100 | } else { | |
| 101 | for i := 0; i < len(bb); i++ { | |
| 102 | w.Write(bb[i]) | |
| 103 | } | |
| 104 | } | |
| 135 | s.flush(w, bb) | |
| 105 | 136 | } |
| 129 | 129 | } |
| 130 | 130 | } |
| 131 | 131 | |
| 132 | // BarStyle sets custom bar style, default one is "[=>-]<+". | |
| 133 | // | |
| 134 | // '[' left bracket rune | |
| 135 | // | |
| 136 | // '=' fill rune | |
| 137 | // | |
| 138 | // '>' tip rune | |
| 139 | // | |
| 140 | // '-' empty rune | |
| 141 | // | |
| 142 | // ']' right bracket rune | |
| 143 | // | |
| 144 | // '<' reverse tip rune, used when BarReverse option is set | |
| 145 | // | |
| 146 | // '+' refill rune, used when *Bar.SetRefill(int64) is called | |
| 147 | // | |
| 148 | // It's ok to provide first five runes only, for example BarStyle("╢▌▌░╟"). | |
| 149 | // To omit left and right bracket runes, either set style as " =>- " | |
| 150 | // or use BarNoBrackets option. | |
| 132 | // BarStyle overrides mpb.DefaultBarStyle, for example BarStyle("╢▌▌░╟"). | |
| 133 | // If you need to override `reverse tip` and `refill rune` set 6th and | |
| 134 | // 7th rune respectively, for example BarStyle("[=>-]<+"). | |
| 151 | 135 | func BarStyle(style string) BarOption { |
| 152 | chk := func(filler Filler) (interface{}, bool) { | |
| 153 | if style == "" { | |
| 154 | return nil, false | |
| 155 | } | |
| 156 | t, ok := filler.(*barFiller) | |
| 157 | return t, ok | |
| 158 | } | |
| 159 | cb := func(t interface{}) { | |
| 160 | t.(*barFiller).setStyle(style) | |
| 161 | } | |
| 162 | return MakeFillerTypeSpecificBarOption(chk, cb) | |
| 163 | } | |
| 164 | ||
| 165 | // BarNoBrackets omits left and right edge runes of the bar. Edges are | |
| 166 | // brackets in default bar style, hence the name of the option. | |
| 167 | func BarNoBrackets() BarOption { | |
| 168 | chk := func(filler Filler) (interface{}, bool) { | |
| 169 | t, ok := filler.(*barFiller) | |
| 170 | return t, ok | |
| 171 | } | |
| 172 | cb := func(t interface{}) { | |
| 173 | t.(*barFiller).noBrackets = true | |
| 174 | } | |
| 175 | return MakeFillerTypeSpecificBarOption(chk, cb) | |
| 136 | if style == "" { | |
| 137 | return nil | |
| 138 | } | |
| 139 | type styleSetter interface { | |
| 140 | SetStyle(string) | |
| 141 | } | |
| 142 | return func(s *bState) { | |
| 143 | if t, ok := s.filler.(styleSetter); ok { | |
| 144 | t.SetStyle(style) | |
| 145 | } | |
| 146 | } | |
| 176 | 147 | } |
| 177 | 148 | |
| 178 | 149 | // BarNoPop disables bar pop out of container. Effective when |
| 185 | 156 | |
| 186 | 157 | // BarReverse reverse mode, bar will progress from right to left. |
| 187 | 158 | func BarReverse() BarOption { |
| 188 | chk := func(filler Filler) (interface{}, bool) { | |
| 189 | t, ok := filler.(*barFiller) | |
| 190 | return t, ok | |
| 191 | } | |
| 192 | cb := func(t interface{}) { | |
| 193 | t.(*barFiller).reverse = true | |
| 194 | } | |
| 195 | return MakeFillerTypeSpecificBarOption(chk, cb) | |
| 159 | type revSetter interface { | |
| 160 | SetReverse(bool) | |
| 161 | } | |
| 162 | return func(s *bState) { | |
| 163 | if t, ok := s.filler.(revSetter); ok { | |
| 164 | t.SetReverse(true) | |
| 165 | } | |
| 166 | } | |
| 196 | 167 | } |
| 197 | 168 | |
| 198 | 169 | // SpinnerStyle sets custom spinner style. |
| 12 | 12 | total, current int64 |
| 13 | 13 | barWidth int |
| 14 | 14 | trimSpace bool |
| 15 | noBrackets bool | |
| 15 | reverse bool | |
| 16 | 16 | rup int64 |
| 17 | 17 | want string |
| 18 | 18 | }{ |
| 32 | 32 | trimSpace: true, |
| 33 | 33 | want: "", |
| 34 | 34 | }, |
| 35 | { | |
| 36 | name: "t,c,bw,noBrackets{60,20,80}", | |
| 37 | total: 60, | |
| 38 | current: 20, | |
| 39 | barWidth: 80, | |
| 40 | noBrackets: true, | |
| 41 | want: "", | |
| 42 | }, | |
| 43 | 35 | }, |
| 44 | 36 | 1: { |
| 45 | 37 | { |
| 57 | 49 | trimSpace: true, |
| 58 | 50 | want: "", |
| 59 | 51 | }, |
| 60 | { | |
| 61 | name: "t,c,bw,noBrackets{60,20,80}", | |
| 62 | total: 60, | |
| 63 | current: 20, | |
| 64 | barWidth: 80, | |
| 65 | noBrackets: true, | |
| 66 | want: "", | |
| 67 | }, | |
| 68 | 52 | }, |
| 69 | 53 | 2: { |
| 70 | 54 | { |
| 82 | 66 | trimSpace: true, |
| 83 | 67 | want: "", |
| 84 | 68 | }, |
| 85 | { | |
| 86 | name: "t,c,bw,noBrackets{60,20,80,true}", | |
| 87 | total: 60, | |
| 88 | current: 20, | |
| 89 | barWidth: 80, | |
| 90 | noBrackets: true, | |
| 91 | want: " ", | |
| 92 | }, | |
| 93 | 69 | }, |
| 94 | 70 | 3: { |
| 95 | 71 | { |
| 107 | 83 | trimSpace: true, |
| 108 | 84 | want: "", |
| 109 | 85 | }, |
| 110 | { | |
| 111 | name: "t,c,bw,trim{60,20,80,true}", | |
| 112 | total: 60, | |
| 113 | current: 20, | |
| 114 | barWidth: 80, | |
| 115 | noBrackets: true, | |
| 116 | want: " - ", | |
| 117 | }, | |
| 118 | 86 | }, |
| 119 | 87 | 4: { |
| 120 | 88 | { |
| 132 | 100 | trimSpace: true, |
| 133 | 101 | want: "[>-]", |
| 134 | 102 | }, |
| 135 | { | |
| 136 | name: "t,c,bw,noBrackets{60,20,80,true}", | |
| 137 | total: 60, | |
| 138 | current: 20, | |
| 139 | barWidth: 80, | |
| 140 | noBrackets: true, | |
| 141 | want: " >- ", | |
| 142 | }, | |
| 143 | 103 | }, |
| 144 | 104 | 5: { |
| 145 | 105 | { |
| 157 | 117 | trimSpace: true, |
| 158 | 118 | want: "[>--]", |
| 159 | 119 | }, |
| 160 | { | |
| 161 | name: "t,c,bw,noBrackets{60,20,80,true}", | |
| 162 | total: 60, | |
| 163 | current: 20, | |
| 164 | barWidth: 80, | |
| 165 | noBrackets: true, | |
| 166 | want: " >-- ", | |
| 167 | }, | |
| 168 | 120 | }, |
| 169 | 121 | 6: { |
| 170 | 122 | { |
| 182 | 134 | trimSpace: true, |
| 183 | 135 | want: "[>---]", |
| 184 | 136 | }, |
| 185 | { | |
| 186 | name: "t,c,bw,noBrackets{60,20,80,true}", | |
| 187 | total: 60, | |
| 188 | current: 20, | |
| 189 | barWidth: 80, | |
| 190 | noBrackets: true, | |
| 191 | want: " >--- ", | |
| 192 | }, | |
| 193 | 137 | }, |
| 194 | 138 | 7: { |
| 195 | 139 | { |
| 207 | 151 | trimSpace: true, |
| 208 | 152 | want: "[=>---]", |
| 209 | 153 | }, |
| 210 | { | |
| 211 | name: "t,c,bw,noBrackets{60,20,80,true}", | |
| 212 | total: 60, | |
| 213 | current: 20, | |
| 214 | barWidth: 80, | |
| 215 | noBrackets: true, | |
| 216 | want: " =>--- ", | |
| 217 | }, | |
| 218 | 154 | }, |
| 219 | 155 | 8: { |
| 220 | 156 | { |
| 232 | 168 | trimSpace: true, |
| 233 | 169 | want: "[=>----]", |
| 234 | 170 | }, |
| 235 | { | |
| 236 | name: "t,c,bw,noBrackets{60,20,80,true}", | |
| 237 | total: 60, | |
| 238 | current: 20, | |
| 239 | barWidth: 80, | |
| 240 | noBrackets: true, | |
| 241 | want: " =>---- ", | |
| 242 | }, | |
| 243 | 171 | }, |
| 244 | 172 | 80: { |
| 245 | 173 | { |
| 257 | 185 | trimSpace: true, |
| 258 | 186 | want: "[=========================>----------------------------------------------------]", |
| 259 | 187 | }, |
| 260 | { | |
| 261 | name: "t,c,bw,noBrackets{60,20,80,true}", | |
| 262 | total: 60, | |
| 263 | current: 20, | |
| 264 | barWidth: 80, | |
| 265 | noBrackets: true, | |
| 266 | want: " =========================>---------------------------------------------------- ", | |
| 267 | }, | |
| 268 | 188 | }, |
| 269 | 189 | 100: { |
| 270 | 190 | { |
| 311 | 231 | barWidth: 100, |
| 312 | 232 | trimSpace: true, |
| 313 | 233 | want: "[===============================>------------------------------------------------------------------]", |
| 234 | }, | |
| 235 | { | |
| 236 | name: "t,c,bw,trim{100,33,100,true}", | |
| 237 | total: 100, | |
| 238 | current: 33, | |
| 239 | barWidth: 100, | |
| 240 | trimSpace: true, | |
| 241 | reverse: true, | |
| 242 | want: "[------------------------------------------------------------------<===============================]", | |
| 314 | 243 | }, |
| 315 | 244 | { |
| 316 | 245 | name: "t,c,bw,rup{100,33,100,33}", |
| 328 | 257 | rup: 33, |
| 329 | 258 | trimSpace: true, |
| 330 | 259 | want: "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]", |
| 260 | }, | |
| 261 | { | |
| 262 | name: "t,c,bw,rup,trim{100,33,100,33,true}", | |
| 263 | total: 100, | |
| 264 | current: 33, | |
| 265 | barWidth: 100, | |
| 266 | rup: 33, | |
| 267 | trimSpace: true, | |
| 268 | reverse: true, | |
| 269 | want: "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]", | |
| 331 | 270 | }, |
| 332 | 271 | { |
| 333 | 272 | name: "t,c,bw,rup{100,40,100,32}", |
| 382 | 321 | var tmpBuf bytes.Buffer |
| 383 | 322 | for termWidth, cases := range testSuite { |
| 384 | 323 | for _, tc := range cases { |
| 385 | s := newTestState() | |
| 324 | s := newTestState(tc.reverse) | |
| 386 | 325 | s.width = tc.barWidth |
| 387 | 326 | s.total = tc.total |
| 388 | 327 | s.current = tc.current |
| 389 | 328 | s.trimSpace = tc.trimSpace |
| 390 | s.filler.(*barFiller).noBrackets = tc.noBrackets | |
| 391 | 329 | if tc.rup > 0 { |
| 392 | 330 | if f, ok := s.filler.(interface{ SetRefill(int64) }); ok { |
| 393 | 331 | f.SetRefill(tc.rup) |
| 410 | 348 | } |
| 411 | 349 | } |
| 412 | 350 | |
| 413 | func newTestState() *bState { | |
| 351 | func newTestState(reverse bool) *bState { | |
| 414 | 352 | s := &bState{ |
| 415 | filler: NewBarFiller(), | |
| 353 | filler: NewBarFiller(DefaultBarStyle, reverse), | |
| 416 | 354 | bufP: new(bytes.Buffer), |
| 417 | 355 | bufB: new(bytes.Buffer), |
| 418 | 356 | bufA: new(bytes.Buffer), |
| 0 | 0 | package mpb |
| 1 | 1 | |
| 2 | var ( | |
| 3 | SyncWidth = syncWidth | |
| 4 | DefaultBarStyle = defaultBarStyle | |
| 5 | ) | |
| 2 | // make syncWidth func public in test | |
| 3 | var SyncWidth = syncWidth |
| 98 | 98 | |
| 99 | 99 | // AddBar creates a new progress bar and adds to the container. |
| 100 | 100 | func (p *Progress) AddBar(total int64, options ...BarOption) *Bar { |
| 101 | return p.Add(total, NewBarFiller(), options...) | |
| 101 | return p.Add(total, NewBarFiller(DefaultBarStyle, false), options...) | |
| 102 | 102 | } |
| 103 | 103 | |
| 104 | 104 | // AddSpinner creates a new spinner bar and adds to the container. |
| 105 | 105 | func (p *Progress) AddSpinner(total int64, alignment SpinnerAlignment, options ...BarOption) *Bar { |
| 106 | return p.Add(total, NewSpinnerFiller(alignment), options...) | |
| 106 | return p.Add(total, NewSpinnerFiller(DefaultSpinnerStyle, alignment), options...) | |
| 107 | 107 | } |
| 108 | 108 | |
| 109 | 109 | // Add creates a bar which renders itself by provided filler. |
| 110 | 110 | // Set total to 0, if you plan to update it later. |
| 111 | 111 | func (p *Progress) Add(total int64, filler Filler, options ...BarOption) *Bar { |
| 112 | 112 | if filler == nil { |
| 113 | filler = NewBarFiller() | |
| 113 | filler = NewBarFiller(DefaultBarStyle, false) | |
| 114 | 114 | } |
| 115 | 115 | p.bwg.Add(1) |
| 116 | 116 | result := make(chan *Bar) |
| 17 | 17 | SpinnerOnRight |
| 18 | 18 | ) |
| 19 | 19 | |
| 20 | var defaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 20 | // DefaultSpinnerStyle is applied when bar constructed with *Progress.AddSpinner method. | |
| 21 | var DefaultSpinnerStyle = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} | |
| 21 | 22 | |
| 22 | 23 | type spinnerFiller struct { |
| 23 | 24 | frames []string |
| 25 | 26 | alignment SpinnerAlignment |
| 26 | 27 | } |
| 27 | 28 | |
| 28 | // NewSpinnerFiller spinner Filler used with *Progress.AddSpinner | |
| 29 | func NewSpinnerFiller(alignment SpinnerAlignment) Filler { | |
| 29 | // NewSpinnerFiller constucts mpb.Filler, to be used with *Progress.Add method. | |
| 30 | func NewSpinnerFiller(style []string, alignment SpinnerAlignment) Filler { | |
| 31 | if len(style) == 0 { | |
| 32 | style = DefaultSpinnerStyle | |
| 33 | } | |
| 30 | 34 | filler := &spinnerFiller{ |
| 31 | frames: defaultSpinnerStyle, | |
| 35 | frames: style, | |
| 32 | 36 | alignment: alignment, |
| 33 | 37 | } |
| 34 | 38 | return filler |