From b60d15fe75ad8e26d224ae8f301e6c22b3c2afd3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Jul 2023 09:32:25 +0530 Subject: [PATCH] Make short duration formatter re-useable --- kittens/transfer/send.go | 50 ++---------------------------- kittens/transfer/send_test.go | 22 ------------- tools/utils/humanize/times.go | 48 ++++++++++++++++++++++++++++ tools/utils/humanize/times_test.go | 35 +++++++++++++++++++++ 4 files changed, 85 insertions(+), 70 deletions(-) create mode 100644 tools/utils/humanize/times_test.go diff --git a/kittens/transfer/send.go b/kittens/transfer/send.go index db8825bc0..4ee11cd74 100644 --- a/kittens/transfer/send.go +++ b/kittens/transfer/send.go @@ -425,52 +425,6 @@ type Progress struct { max_path_length int } -func optional_cut(x string, sep string) (string, string) { - a, b, found := strings.Cut(x, sep) - if found { - return a, b - } - return "00", a -} - -func zero_pad(x string) string { - if len(x) < 2 { - x = strings.Repeat("0", 2-len(x)) + x - } - return x -} - -// render the duration in exactly 8 chars -func render_duration(val time.Duration) (ans string) { - if val >= time.Second { - if val.Hours() > 24 { - days := int(val.Hours() / 24) - if days > 99 { - ans = `∞` - } else { - if days == 1 { - ans = ">1 day" - } else { - ans = fmt.Sprintf(">%d days", int(days)) - } - } - } else { - ans = val.String() - hr, rest := optional_cut(ans, `h`) - min, rest := optional_cut(rest, `m`) - secs, _, _ := strings.Cut(rest, ".") - secs = strings.Replace(secs, `s`, ``, 1) - ans = zero_pad(hr) + `:` + zero_pad(min) + `:` + zero_pad(secs) - } - } else { - ans = "<1 sec" - } - if w := wcswidth.Stringwidth(ans); w < 8 { - ans = strings.Repeat(" ", 8-w) + ans - } - return -} - func reduce_to_single_grapheme(text string) string { limit := utf8.RuneCountInString(text) if limit < 2 { @@ -520,7 +474,7 @@ func render_progress_in_width(path string, p Progress, width int, ctx *markup.Co if p.is_complete || p.bytes_so_far >= p.total_bytes { ratio = humanize.Size(uint64(p.total_bytes), humanize.SizeOptions{Separator: sep}) rate = humanize.Size(uint64(safe_divide(float64(p.total_bytes), p.secs_so_far)), humanize.SizeOptions{Separator: sep}) + `/s` - eta = ctx.Green(render_duration(time.Duration(float64(time.Second) * p.secs_so_far))) + eta = ctx.Green(humanize.ShortDuration(time.Duration(float64(time.Second) * p.secs_so_far))) } else { tb := humanize.Size(p.total_bytes) sval, _, _ := strings.Cut(tb, " ") @@ -529,7 +483,7 @@ func render_progress_in_width(path string, p Progress, width int, ctx *markup.Co rate = humanize.Size(p.bytes_per_sec, humanize.SizeOptions{Separator: sep}) + `/s` bytes_left := p.total_bytes - p.bytes_so_far eta_seconds := safe_divide(bytes_left, p.bytes_per_sec) - eta = render_duration(time.Duration(float64(time.Second) * eta_seconds)) + eta = humanize.ShortDuration(time.Duration(float64(time.Second) * eta_seconds)) } lft := p.spinner_char + ` ` max_space_for_path := width/2 - wcswidth.Stringwidth(lft) diff --git a/kittens/transfer/send_test.go b/kittens/transfer/send_test.go index f98d0b628..948833a50 100644 --- a/kittens/transfer/send_test.go +++ b/kittens/transfer/send_test.go @@ -101,25 +101,3 @@ func TestPathMappingSend(t *testing.T) { ae(f.file_type, FileType_link) }) } - -func TestRenderDuration(t *testing.T) { - q := func(i float64, e string) { - d := time.Duration(i * float64(time.Second)) - if diff := cmp.Diff(e, render_duration(d)); diff != "" { - t.Fatalf("Failed for %f (%s): %s", i, d, diff) - } - } - q(0.1, " <1 sec") - q(1, `00:00:01`) - q(1.1234567, `00:00:01`) - q(60.1234567, `00:01:00`) - q(61.1234567, `00:01:01`) - q(3600, `01:00:00`) - q(3601.1234567, `01:00:01`) - day := 24. * 3600. - q(day, "24:00:00") - q(day+1, " >1 day") - q(day*2, " >2 days") - q(day*23, ">23 days") - q(day*999, " ∞") -} diff --git a/tools/utils/humanize/times.go b/tools/utils/humanize/times.go index dd3fbf5ef..9d6513794 100644 --- a/tools/utils/humanize/times.go +++ b/tools/utils/humanize/times.go @@ -2,8 +2,10 @@ package humanize import ( "fmt" + "kitty/tools/wcswidth" "math" "sort" + "strings" "time" ) @@ -115,3 +117,49 @@ func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnit } return fmt.Sprintf(mag.Format, args...) } + +func optional_cut(x string, sep string) (string, string) { + a, b, found := strings.Cut(x, sep) + if found { + return a, b + } + return "00", a +} + +func zero_pad(x string) string { + if len(x) < 2 { + x = strings.Repeat("0", 2-len(x)) + x + } + return x +} + +// Render the duration in exactly 8 chars +func ShortDuration(val time.Duration) (ans string) { + if val >= time.Second { + if val > Day { + days := int(val.Hours() / 24) + if days > 99 { + ans = `∞` + } else { + if days == 1 { + ans = ">1 day" + } else { + ans = fmt.Sprintf(">%d days", int(days)) + } + } + } else { + ans = val.String() + hr, rest := optional_cut(ans, `h`) + min, rest := optional_cut(rest, `m`) + secs, _, _ := strings.Cut(rest, ".") + secs = strings.Replace(secs, `s`, ``, 1) + ans = zero_pad(hr) + `:` + zero_pad(min) + `:` + zero_pad(secs) + } + } else { + ans = "<1 sec" + } + if w := wcswidth.Stringwidth(ans); w < 8 { + ans = strings.Repeat(" ", 8-w) + ans + } + return +} diff --git a/tools/utils/humanize/times_test.go b/tools/utils/humanize/times_test.go new file mode 100644 index 000000000..ca0c0c540 --- /dev/null +++ b/tools/utils/humanize/times_test.go @@ -0,0 +1,35 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package humanize + +import ( + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +var _ = fmt.Print + +func TestShortDuration(t *testing.T) { + q := func(i float64, e string) { + d := time.Duration(i * float64(time.Second)) + if diff := cmp.Diff(e, ShortDuration(d)); diff != "" { + t.Fatalf("Failed for %f (%s): %s", i, d, diff) + } + } + q(0.1, " <1 sec") + q(1, `00:00:01`) + q(1.1234567, `00:00:01`) + q(60.1234567, `00:01:00`) + q(61.1234567, `00:01:01`) + q(3600, `01:00:00`) + q(3601.1234567, `01:00:01`) + day := 24. * 3600. + q(day, "24:00:00") + q(day+1, " >1 day") + q(day*2, " >2 days") + q(day*23, ">23 days") + q(day*999, " ∞") +}