diff --git a/decor/counters.go b/decor/counters.go new file mode 100644 index 0000000..f7c239e --- /dev/null +++ b/decor/counters.go @@ -0,0 +1,140 @@ +package decor + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + _ = iota + KiB = 1 << (iota * 10) + MiB + GiB + TiB +) + +const ( + KB = 1000 + MB = KB * 1000 + GB = MB * 1000 + TB = GB * 1000 +) + +const ( + _ = iota + // Unit_KiB Kibibyte = 1024 b + Unit_KiB + // Unit_kB Kilobyte = 1000 b + Unit_kB +) + +type Unit uint + +type CounterKiB int64 + +func (c CounterKiB) Format(f fmt.State, r rune) { + prec, ok := f.Precision() + + if r == 'd' || !ok { + prec = 0 + } + if r == 'f' && !ok { + prec = 6 + } + // retain old beahavior if s verb used + if r == 's' { + prec = 1 + } + + var res, unit string + switch { + case c >= TiB: + unit = "TiB" + res = strconv.FormatFloat(float64(c)/TiB, 'f', prec, 64) + case c >= GiB: + unit = "GiB" + res = strconv.FormatFloat(float64(c)/GiB, 'f', prec, 64) + case c >= MiB: + unit = "MiB" + res = strconv.FormatFloat(float64(c)/MiB, 'f', prec, 64) + case c >= KiB: + unit = "KiB" + res = strconv.FormatFloat(float64(c)/KiB, 'f', prec, 64) + default: + unit = "b" + res = strconv.FormatInt(int64(c), 10) + } + + if f.Flag(int(' ')) { + res += " " + } + res += unit + + if w, ok := f.Width(); ok { + if len(res) < w { + pad := strings.Repeat(" ", w-len(res)) + if f.Flag(int('-')) { + res += pad + } else { + res = pad + res + } + } + } + + f.Write([]byte(res)) +} + +type CounterKB int64 + +func (c CounterKB) Format(f fmt.State, r rune) { + prec, ok := f.Precision() + + if r == 'd' || !ok { + prec = 0 + } + if r == 'f' && !ok { + prec = 6 + } + // retain old beahavior if s verb used + if r == 's' { + prec = 1 + } + + var res, unit string + switch { + case c >= TB: + unit = "TB" + res = strconv.FormatFloat(float64(c)/TB, 'f', prec, 64) + case c >= GB: + unit = "GB" + res = strconv.FormatFloat(float64(c)/GB, 'f', prec, 64) + case c >= MB: + unit = "MB" + res = strconv.FormatFloat(float64(c)/MB, 'f', prec, 64) + case c >= KB: + unit = "kB" + res = strconv.FormatFloat(float64(c)/KB, 'f', prec, 64) + default: + unit = "b" + res = strconv.FormatInt(int64(c), 10) + } + + if f.Flag(int(' ')) { + res += " " + } + res += unit + + if w, ok := f.Width(); ok { + if len(res) < w { + pad := strings.Repeat(" ", w-len(res)) + if f.Flag(int('-')) { + res += pad + } else { + res = pad + res + } + } + } + + f.Write([]byte(res)) +} diff --git a/decor/counters_test.go b/decor/counters_test.go new file mode 100644 index 0000000..2b00b3d --- /dev/null +++ b/decor/counters_test.go @@ -0,0 +1,136 @@ +package decor + +import ( + "fmt" + "testing" +) + +func TestCounterKiB(t *testing.T) { + cases := map[string]struct { + value int64 + verb, expected string + }{ + "verb %f": {12345678, "%f", "11.773756MiB"}, + "verb %.0f": {12345678, "%.0f", "12MiB"}, + "verb %.1f": {12345678, "%.1f", "11.8MiB"}, + "verb %.2f": {12345678, "%.2f", "11.77MiB"}, + "verb %.3f": {12345678, "%.3f", "11.774MiB"}, + + "verb % f": {12345678, "% f", "11.773756 MiB"}, + "verb % .0f": {12345678, "% .0f", "12 MiB"}, + "verb % .1f": {12345678, "% .1f", "11.8 MiB"}, + "verb % .2f": {12345678, "% .2f", "11.77 MiB"}, + "verb % .3f": {12345678, "% .3f", "11.774 MiB"}, + + "verb %8.f": {12345678, "%8.f", " 12MiB"}, + "verb %8.0f": {12345678, "%8.0f", " 12MiB"}, + "verb %8.1f": {12345678, "%8.1f", " 11.8MiB"}, + "verb %8.2f": {12345678, "%8.2f", "11.77MiB"}, + "verb %8.3f": {12345678, "%8.3f", "11.774MiB"}, + + "verb % 8.f": {12345678, "% 8.f", " 12 MiB"}, + "verb % 8.0f": {12345678, "% 8.0f", " 12 MiB"}, + "verb % 8.1f": {12345678, "% 8.1f", "11.8 MiB"}, + + "verb %-8.f": {12345678, "%-8.f", "12MiB "}, + "verb %-8.0f": {12345678, "%-8.0f", "12MiB "}, + "verb %-8.1f": {12345678, "%-8.1f", "11.8MiB "}, + "verb %-8.2f": {12345678, "%8.2f", "11.77MiB"}, + "verb %-8.3f": {12345678, "%8.3f", "11.774MiB"}, + + "verb % -8.f": {12345678, "% -8.f", "12 MiB "}, + "verb % -8.0f": {12345678, "% -8.0f", "12 MiB "}, + "verb % -8.1f": {12345678, "% -8.1f", "11.8 MiB"}, + + "1000b %f": {1000, "%f", "1000b"}, + "1000b %d": {1000, "%d", "1000b"}, + "1000b %s": {1000, "%d", "1000b"}, + "1024 %f": {1024, "%f", "1.000000KiB"}, + "1024 %d": {1024, "%d", "1KiB"}, + "1024 %.1f": {1024, "%.1f", "1.0KiB"}, + "1024 %s": {1024, "%s", "1.0KiB"}, + "3*MiB+140KiB %f": {3*MiB + 140*KiB, "%f", "3.136719MiB"}, + "3*MiB+140KiB %d": {3*MiB + 140*KiB, "%d", "3MiB"}, + "3*MiB+140KiB %.1f": {3*MiB + 140*KiB, "%.1f", "3.1MiB"}, + "3*MiB+140KiB %s": {3*MiB + 140*KiB, "%s", "3.1MiB"}, + "2*GiB %f": {2 * GiB, "%f", "2.000000GiB"}, + "2*GiB %d": {2 * GiB, "%d", "2GiB"}, + "2*GiB %.1f": {2 * GiB, "%.1f", "2.0GiB"}, + "2*GiB %s": {2 * GiB, "%s", "2.0GiB"}, + "4*TiB %f": {4 * TiB, "%f", "4.000000TiB"}, + "4*TiB %d": {4 * TiB, "%d", "4TiB"}, + "4*TiB %.1f": {4 * TiB, "%.1f", "4.0TiB"}, + "4*TiB %s": {4 * TiB, "%s", "4.0TiB"}, + } + for k, tc := range cases { + got := fmt.Sprintf(tc.verb, CounterKiB(tc.value)) + if got != tc.expected { + t.Errorf("%s: Expected: %q, got: %q\n", k, tc.expected, got) + } + } +} + +func TestCounterKB(t *testing.T) { + cases := map[string]struct { + value int64 + verb, expected string + }{ + "verb %f": {12345678, "%f", "12.345678MB"}, + "verb %.0f": {12345678, "%.0f", "12MB"}, + "verb %.1f": {12345678, "%.1f", "12.3MB"}, + "verb %.2f": {12345678, "%.2f", "12.35MB"}, + "verb %.3f": {12345678, "%.3f", "12.346MB"}, + + "verb % f": {12345678, "% f", "12.345678 MB"}, + "verb % .0f": {12345678, "% .0f", "12 MB"}, + "verb % .1f": {12345678, "% .1f", "12.3 MB"}, + "verb % .2f": {12345678, "% .2f", "12.35 MB"}, + "verb % .3f": {12345678, "% .3f", "12.346 MB"}, + + "verb %8.f": {12345678, "%8.f", " 12MB"}, + "verb %8.0f": {12345678, "%8.0f", " 12MB"}, + "verb %8.1f": {12345678, "%8.1f", " 12.3MB"}, + "verb %8.2f": {12345678, "%8.2f", " 12.35MB"}, + "verb %8.3f": {12345678, "%8.3f", "12.346MB"}, + + "verb % 8.f": {12345678, "% 8.f", " 12 MB"}, + "verb % 8.0f": {12345678, "% 8.0f", " 12 MB"}, + "verb % 8.1f": {12345678, "% 8.1f", " 12.3 MB"}, + + "verb %-8.f": {12345678, "%-8.f", "12MB "}, + "verb %-8.0f": {12345678, "%-8.0f", "12MB "}, + "verb %-8.1f": {12345678, "%-8.1f", "12.3MB "}, + "verb %-8.2f": {12345678, "%8.2f", " 12.35MB"}, + "verb %-8.3f": {12345678, "%8.3f", "12.346MB"}, + + "verb % -8.f": {12345678, "% -8.f", "12 MB "}, + "verb % -8.0f": {12345678, "% -8.0f", "12 MB "}, + "verb % -8.1f": {12345678, "% -8.1f", "12.3 MB "}, + + "1000b %f": {1000, "%f", "1.000000kB"}, + "1000b %d": {1000, "%d", "1kB"}, + "1000b %s": {1000, "%d", "1kB"}, + "1024 %f": {1024, "%f", "1.024000kB"}, + "1024 %d": {1024, "%d", "1kB"}, + "1024 %.1f": {1024, "%.1f", "1.0kB"}, + "1024 %s": {1024, "%s", "1.0kB"}, + "3*MB+140*KB %f": {3*MB + 140*KB, "%f", "3.140000MB"}, + "3*MB+140*KB %d": {3*MB + 140*KB, "%d", "3MB"}, + "3*MB+140*KB %.1f": {3*MB + 140*KB, "%.1f", "3.1MB"}, + "3*MB+140*KB %s": {3*MB + 140*KB, "%s", "3.1MB"}, + "2*GB %f": {2 * GB, "%f", "2.000000GB"}, + "2*GB %d": {2 * GB, "%d", "2GB"}, + "2*GB %.1f": {2 * GB, "%.1f", "2.0GB"}, + "2*GB %s": {2 * GB, "%s", "2.0GB"}, + "4*TB %f": {4 * TB, "%f", "4.000000TB"}, + "4*TB %d": {4 * TB, "%d", "4TB"}, + "4*TB %.1f": {4 * TB, "%.1f", "4.0TB"}, + "4*TB %s": {4 * TB, "%s", "4.0TB"}, + } + for k, tc := range cases { + got := fmt.Sprintf(tc.verb, CounterKB(tc.value)) + if got != tc.expected { + t.Errorf("%s: Expected: %q, got: %q\n", k, tc.expected, got) + } + } +} diff --git a/decor/decorators.go b/decor/decorators.go index 5531ee3..4099d3d 100644 --- a/decor/decorators.go +++ b/decor/decorators.go @@ -86,20 +86,26 @@ } // Counters provides basic counters decorator. -// Accepts pairFormat string, something like "%s / %s" to be used in -// fmt.Sprintf(pairFormat, current, total) and one of (Unit_KiB/Unit_kB) -// constant. If there're more than one bar, and you'd like to synchronize column -// width, conf param should have DwidthSync bit set. -func Counters(pairFormat string, unit Units, minWidth int, conf byte) DecoratorFunc { - format := "%%" - if (conf & DidentRight) != 0 { - format += "-" - } - format += "%ds" - return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { - current := Format(s.Current).To(unit) - total := Format(s.Total).To(unit) - str := fmt.Sprintf(pairFormat, current, total) +// pairFormat must contain two printf compatible verbs, like "%f" or "%d". +// First verb substituted with Current, second one with Total. For example (assuming decor.Unit_KiB used): +// "%.1f / %.1f" = "1.0MiB / 12.0MiB" or "% .1f / % .1f" = "1.0 MiB / 12.0 MiB" +// unit is one of decor.Unit_KiB/decor.Unit_kB or just zero if you need raw unitless numbers. +func Counters(pairFormat string, unit Unit, minWidth int, conf byte) DecoratorFunc { + format := "%%" + if (conf & DidentRight) != 0 { + format += "-" + } + format += "%ds" + return func(s *Statistics, myWidth chan<- int, maxWidth <-chan int) string { + var str string + switch unit { + case Unit_KiB: + str = fmt.Sprintf(pairFormat, CounterKiB(s.Current), CounterKiB(s.Total)) + case Unit_kB: + str = fmt.Sprintf(pairFormat, CounterKB(s.Current), CounterKB(s.Total)) + default: + str = fmt.Sprintf(pairFormat, s.Current, s.Total) + } if (conf & DwidthSync) != 0 { myWidth <- utf8.RuneCountInString(str) max := <-maxWidth diff --git a/decor/format.go b/decor/format.go deleted file mode 100644 index fabb1a5..0000000 --- a/decor/format.go +++ /dev/null @@ -1,91 +0,0 @@ -package decor - -import "fmt" - -const ( - _ = iota - KiB = 1 << (iota * 10) - MiB - GiB - TiB -) - -const ( - KB = 1000 - MB = KB * 1000 - GB = MB * 1000 - TB = GB * 1000 -) - -const ( - _ = iota - // Unit_KiB Kibibyte = 1024 b - Unit_KiB - // Unit_kB Kilobyte = 1000 b - Unit_kB -) - -type Units uint - -func Format(i int64) *formatter { - return &formatter{n: i} -} - -type formatter struct { - n int64 - unit Units - width int -} - -func (f *formatter) To(unit Units) *formatter { - f.unit = unit - return f -} - -func (f *formatter) Width(width int) *formatter { - f.width = width - return f -} - -func (f *formatter) String() string { - switch f.unit { - case Unit_KiB: - return formatKiB(f.n) - case Unit_kB: - return formatKB(f.n) - default: - return fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) - } -} - -func formatKiB(i int64) (result string) { - switch { - case i >= TiB: - result = fmt.Sprintf("%.1fTiB", float64(i)/TiB) - case i >= GiB: - result = fmt.Sprintf("%.1fGiB", float64(i)/GiB) - case i >= MiB: - result = fmt.Sprintf("%.1fMiB", float64(i)/MiB) - case i >= KiB: - result = fmt.Sprintf("%.1fKiB", float64(i)/KiB) - default: - result = fmt.Sprintf("%db", i) - } - return -} - -func formatKB(i int64) (result string) { - switch { - case i >= TB: - result = fmt.Sprintf("%.1fTB", float64(i)/TB) - case i >= GB: - result = fmt.Sprintf("%.1fGB", float64(i)/GB) - case i >= MB: - result = fmt.Sprintf("%.1fMB", float64(i)/MB) - case i >= KB: - result = fmt.Sprintf("%.1fkB", float64(i)/KB) - default: - result = fmt.Sprintf("%db", i) - } - return -} diff --git a/decor/format_test.go b/decor/format_test.go deleted file mode 100644 index f78ba02..0000000 --- a/decor/format_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package decor_test - -import ( - "testing" - - "github.com/vbauerster/mpb/decor" -) - -func TestFormatNoUnits(t *testing.T) { - actual := decor.Format(1234567).String() - expected := "1234567" - if actual != expected { - t.Errorf("Expected %q but found %q", expected, actual) - } -} - -func TestFormatWidth(t *testing.T) { - actual := decor.Format(1234567).Width(10).String() - expected := " 1234567" - if actual != expected { - t.Errorf("Expected %q but found %q", expected, actual) - } -} - -func TestFormatToBytes(t *testing.T) { - inputs := []struct { - v int64 - e string - }{ - {v: 1000, e: "1000b"}, - {v: 1024, e: "1.0KiB"}, - {v: 3*decor.MiB + 140*decor.KiB, e: "3.1MiB"}, - {v: 2 * decor.GiB, e: "2.0GiB"}, - {v: 4 * decor.TiB, e: "4.0TiB"}, - } - - for _, input := range inputs { - actual := decor.Format(input.v).To(decor.Unit_KiB).String() - if actual != input.e { - t.Errorf("Expected %q but found %q", input.e, actual) - } - } -} diff --git a/examples/io/multiple/main.go b/examples/io/multiple/main.go index 6645969..228c592 100644 --- a/examples/io/multiple/main.go +++ b/examples/io/multiple/main.go @@ -62,7 +62,7 @@ bar := p.AddBar(size, mpb.PrependDecorators( decor.StaticName(name, 0, 0), - decor.Counters("%3s / %3s", decor.Unit_KiB, 18, 0), + decor.Counters("%6.1f / %6.1f", decor.Unit_KiB, 18, 0), ), mpb.AppendDecorators(decor.ETA(5, decor.DwidthSync)), )