refactoring percentage decorator
Vladimir Bauer
6 years ago
| 63 | 63 | |
| 64 | 64 | bb := make([][]byte, width) |
| 65 | 65 | |
| 66 | cwidth := int(internal.Percentage(stat.Total, stat.Current, int64(width))) | |
| 66 | cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width)) | |
| 67 | 67 | |
| 68 | 68 | for i := 0; i < cwidth; i++ { |
| 69 | 69 | bb[i] = s.format[rFill] |
| 74 | 74 | if s.refillAmount > stat.Current { |
| 75 | 75 | rwidth = cwidth |
| 76 | 76 | } else { |
| 77 | rwidth = int(internal.Percentage(stat.Total, int64(s.refillAmount), int64(width))) | |
| 77 | rwidth = int(internal.PercentageRound(stat.Total, int64(s.refillAmount), width)) | |
| 78 | 78 | } |
| 79 | 79 | for i := 0; i < rwidth; i++ { |
| 80 | 80 | bb[i] = s.format[rRefill] |
| 1 | 1 | |
| 2 | 2 | import ( |
| 3 | 3 | "fmt" |
| 4 | "io" | |
| 5 | "strconv" | |
| 6 | "strings" | |
| 4 | 7 | |
| 5 | 8 | "github.com/vbauerster/mpb/v4/internal" |
| 6 | 9 | ) |
| 10 | ||
| 11 | type PercentageType float64 | |
| 12 | ||
| 13 | func (s PercentageType) Format(st fmt.State, verb rune) { | |
| 14 | var prec int | |
| 15 | switch verb { | |
| 16 | case 'd': | |
| 17 | case 's': | |
| 18 | prec = -1 | |
| 19 | default: | |
| 20 | if p, ok := st.Precision(); ok { | |
| 21 | prec = p | |
| 22 | } else { | |
| 23 | prec = 6 | |
| 24 | } | |
| 25 | } | |
| 26 | ||
| 27 | res := strconv.FormatFloat(float64(s), 'f', prec, 64) | |
| 28 | ||
| 29 | if st.Flag(' ') { | |
| 30 | res += " " | |
| 31 | } | |
| 32 | res += "%" | |
| 33 | ||
| 34 | if w, ok := st.Width(); ok { | |
| 35 | if len(res) < w { | |
| 36 | pad := strings.Repeat(" ", w-len(res)) | |
| 37 | if st.Flag('-') { | |
| 38 | res += pad | |
| 39 | } else { | |
| 40 | res = pad + res | |
| 41 | } | |
| 42 | } | |
| 43 | } | |
| 44 | ||
| 45 | io.WriteString(st, res) | |
| 46 | } | |
| 7 | 47 | |
| 8 | 48 | // Percentage returns percentage decorator. |
| 9 | 49 | // |
| 10 | 50 | // `wcc` optional WC config |
| 11 | 51 | func Percentage(wcc ...WC) Decorator { |
| 52 | return PercentageFmt("% d", wcc...) | |
| 53 | } | |
| 54 | ||
| 55 | // PercentageFmt percentage decorator with custom fmt. | |
| 56 | // "%.1f" = "1.0%" or "% .1f" = "1.0 %" | |
| 57 | // "%d" = "1%" or "% d" = "1 %" | |
| 58 | func PercentageFmt(fmt string, wcc ...WC) Decorator { | |
| 12 | 59 | var wc WC |
| 13 | 60 | for _, widthConf := range wcc { |
| 14 | 61 | wc = widthConf |
| 15 | 62 | } |
| 16 | 63 | wc.Init() |
| 17 | 64 | d := &percentageDecorator{ |
| 18 | WC: wc, | |
| 65 | WC: wc, | |
| 66 | fmt: fmt, | |
| 19 | 67 | } |
| 20 | 68 | return d |
| 21 | 69 | } |
| 22 | 70 | |
| 23 | 71 | type percentageDecorator struct { |
| 24 | 72 | WC |
| 73 | fmt string | |
| 25 | 74 | completeMsg *string |
| 26 | 75 | } |
| 27 | 76 | |
| 29 | 78 | if st.Completed && d.completeMsg != nil { |
| 30 | 79 | return d.FormatMsg(*d.completeMsg) |
| 31 | 80 | } |
| 32 | str := fmt.Sprintf("%d %%", internal.Percentage(st.Total, st.Current, 100)) | |
| 33 | return d.FormatMsg(str) | |
| 81 | p := internal.Percentage(st.Total, st.Current, 100) | |
| 82 | return d.FormatMsg(fmt.Sprintf(d.fmt, PercentageType(p))) | |
| 34 | 83 | } |
| 35 | 84 | |
| 36 | 85 | func (d *percentageDecorator) OnCompleteMessage(msg string) { |
| 13 | 13 | type SpeedKiB float64 |
| 14 | 14 | |
| 15 | 15 | func (s SpeedKiB) Format(st fmt.State, verb rune) { |
| 16 | prec, ok := st.Precision() | |
| 17 | ||
| 18 | if verb == 'd' || !ok { | |
| 19 | prec = 0 | |
| 20 | } | |
| 21 | if verb == 'f' && !ok { | |
| 22 | prec = 6 | |
| 23 | } | |
| 24 | // retain old beahavior if s verb used | |
| 25 | if verb == 's' { | |
| 26 | prec = 1 | |
| 16 | var prec int | |
| 17 | switch verb { | |
| 18 | case 'd': | |
| 19 | case 's': | |
| 20 | prec = -1 | |
| 21 | default: | |
| 22 | if p, ok := st.Precision(); ok { | |
| 23 | prec = p | |
| 24 | } else { | |
| 25 | prec = 6 | |
| 26 | } | |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | 29 | var res, unit string |
| 53 | 53 | if w, ok := st.Width(); ok { |
| 54 | 54 | if len(res) < w { |
| 55 | 55 | pad := strings.Repeat(" ", w-len(res)) |
| 56 | if st.Flag(int('-')) { | |
| 56 | if st.Flag('-') { | |
| 57 | 57 | res += pad |
| 58 | 58 | } else { |
| 59 | 59 | res = pad + res |
| 67 | 67 | type SpeedKB float64 |
| 68 | 68 | |
| 69 | 69 | func (s SpeedKB) Format(st fmt.State, verb rune) { |
| 70 | prec, ok := st.Precision() | |
| 71 | ||
| 72 | if verb == 'd' || !ok { | |
| 73 | prec = 0 | |
| 74 | } | |
| 75 | if verb == 'f' && !ok { | |
| 76 | prec = 6 | |
| 77 | } | |
| 78 | // retain old beahavior if s verb used | |
| 79 | if verb == 's' { | |
| 80 | prec = 1 | |
| 70 | var prec int | |
| 71 | switch verb { | |
| 72 | case 'd': | |
| 73 | case 's': | |
| 74 | prec = -1 | |
| 75 | default: | |
| 76 | if p, ok := st.Precision(); ok { | |
| 77 | prec = p | |
| 78 | } else { | |
| 79 | prec = 6 | |
| 80 | } | |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | var res, unit string |
| 48 | 48 | "1024 %f": {1024, "%f", "1.000000KiB/s"}, |
| 49 | 49 | "1024 %d": {1024, "%d", "1KiB/s"}, |
| 50 | 50 | "1024 %.1f": {1024, "%.1f", "1.0KiB/s"}, |
| 51 | "1024 %s": {1024, "%s", "1.0KiB/s"}, | |
| 51 | "1024 %s": {1024, "%s", "1KiB/s"}, | |
| 52 | 52 | "3*MiB/s+140KiB/s %f": {3*MiB + 140*KiB, "%f", "3.136719MiB/s"}, |
| 53 | 53 | "3*MiB/s+140KiB/s %d": {3*MiB + 140*KiB, "%d", "3MiB/s"}, |
| 54 | 54 | "3*MiB/s+140KiB/s %.1f": {3*MiB + 140*KiB, "%.1f", "3.1MiB/s"}, |
| 55 | "3*MiB/s+140KiB/s %s": {3*MiB + 140*KiB, "%s", "3.1MiB/s"}, | |
| 55 | "3*MiB/s+140KiB/s %s": {3*MiB + 140*KiB, "%s", "3.13671875MiB/s"}, | |
| 56 | 56 | "2*GiB/s %f": {2 * GiB, "%f", "2.000000GiB/s"}, |
| 57 | 57 | "2*GiB/s %d": {2 * GiB, "%d", "2GiB/s"}, |
| 58 | 58 | "2*GiB/s %.1f": {2 * GiB, "%.1f", "2.0GiB/s"}, |
| 59 | "2*GiB/s %s": {2 * GiB, "%s", "2.0GiB/s"}, | |
| 59 | "2*GiB/s %s": {2 * GiB, "%s", "2GiB/s"}, | |
| 60 | 60 | "4*TiB/s %f": {4 * TiB, "%f", "4.000000TiB/s"}, |
| 61 | 61 | "4*TiB/s %d": {4 * TiB, "%d", "4TiB/s"}, |
| 62 | 62 | "4*TiB/s %.1f": {4 * TiB, "%.1f", "4.0TiB/s"}, |
| 63 | "4*TiB/s %s": {4 * TiB, "%s", "4.0TiB/s"}, | |
| 63 | "4*TiB/s %s": {4 * TiB, "%s", "4TiB/s"}, | |
| 64 | 64 | } |
| 65 | 65 | for name, tc := range cases { |
| 66 | 66 | t.Run(name, func(t *testing.T) { |
| 112 | 112 | |
| 113 | 113 | "1000 %f": {1000, "%f", "1.000000kB/s"}, |
| 114 | 114 | "1000 %d": {1000, "%d", "1kB/s"}, |
| 115 | "1000 %s": {1000, "%s", "1.0kB/s"}, | |
| 115 | "1000 %s": {1000, "%s", "1kB/s"}, | |
| 116 | 116 | "1024 %f": {1024, "%f", "1.024000kB/s"}, |
| 117 | 117 | "1024 %d": {1024, "%d", "1kB/s"}, |
| 118 | 118 | "1024 %.1f": {1024, "%.1f", "1.0kB/s"}, |
| 119 | "1024 %s": {1024, "%s", "1.0kB/s"}, | |
| 119 | "1024 %s": {1024, "%s", "1.024kB/s"}, | |
| 120 | 120 | "3*MB/s+140*KB/s %f": {3*MB + 140*KB, "%f", "3.140000MB/s"}, |
| 121 | 121 | "3*MB/s+140*KB/s %d": {3*MB + 140*KB, "%d", "3MB/s"}, |
| 122 | 122 | "3*MB/s+140*KB/s %.1f": {3*MB + 140*KB, "%.1f", "3.1MB/s"}, |
| 123 | "3*MB/s+140*KB/s %s": {3*MB + 140*KB, "%s", "3.1MB/s"}, | |
| 123 | "3*MB/s+140*KB/s %s": {3*MB + 140*KB, "%s", "3.14MB/s"}, | |
| 124 | 124 | "2*GB/s %f": {2 * GB, "%f", "2.000000GB/s"}, |
| 125 | 125 | "2*GB/s %d": {2 * GB, "%d", "2GB/s"}, |
| 126 | 126 | "2*GB/s %.1f": {2 * GB, "%.1f", "2.0GB/s"}, |
| 127 | "2*GB/s %s": {2 * GB, "%s", "2.0GB/s"}, | |
| 127 | "2*GB/s %s": {2 * GB, "%s", "2GB/s"}, | |
| 128 | 128 | "4*TB/s %f": {4 * TB, "%f", "4.000000TB/s"}, |
| 129 | 129 | "4*TB/s %d": {4 * TB, "%d", "4TB/s"}, |
| 130 | 130 | "4*TB/s %.1f": {4 * TB, "%.1f", "4.0TB/s"}, |
| 131 | "4*TB/s %s": {4 * TB, "%s", "4.0TB/s"}, | |
| 131 | "4*TB/s %s": {4 * TB, "%s", "4TB/s"}, | |
| 132 | 132 | } |
| 133 | 133 | for name, tc := range cases { |
| 134 | 134 | t.Run(name, func(t *testing.T) { |
| 2 | 2 | import "math" |
| 3 | 3 | |
| 4 | 4 | // Percentage is a helper function, to calculate percentage. |
| 5 | func Percentage(total, current, width int64) int64 { | |
| 5 | func Percentage(total, current int64, width int) float64 { | |
| 6 | 6 | if total <= 0 { |
| 7 | 7 | return 0 |
| 8 | 8 | } |
| 9 | p := float64(width*current) / float64(total) | |
| 10 | return int64(math.Round(p)) | |
| 9 | return float64(int64(width)*current) / float64(total) | |
| 11 | 10 | } |
| 11 | ||
| 12 | func PercentageRound(total, current int64, width int) float64 { | |
| 13 | return math.Round(Percentage(total, current, width)) | |
| 14 | } |
| 3 | 3 | |
| 4 | 4 | func TestPercentage(t *testing.T) { |
| 5 | 5 | // key is barWidth |
| 6 | testSuite := map[int64][]struct { | |
| 7 | name string | |
| 8 | total, current, expected int64 | |
| 6 | testSuite := map[int][]struct { | |
| 7 | name string | |
| 8 | total int64 | |
| 9 | current int64 | |
| 10 | expected int64 | |
| 9 | 11 | }{ |
| 10 | 12 | 100: { |
| 11 | 13 | {"t,c,e{-1,-1,0}", -1, -1, 0}, |
| 63 | 65 | |
| 64 | 66 | for width, cases := range testSuite { |
| 65 | 67 | for _, tc := range cases { |
| 66 | got := Percentage(tc.total, tc.current, width) | |
| 68 | got := int64(PercentageRound(tc.total, tc.current, width)) | |
| 67 | 69 | if got != tc.expected { |
| 68 | 70 | t.Errorf("width %d; %s: Expected: %d, got: %d\n", width, tc.name, tc.expected, got) |
| 69 | 71 | } |