mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-24 17:27:39 +02:00
Make short duration formatter re-useable
This commit is contained in:
@@ -425,52 +425,6 @@ type Progress struct {
|
|||||||
max_path_length int
|
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 {
|
func reduce_to_single_grapheme(text string) string {
|
||||||
limit := utf8.RuneCountInString(text)
|
limit := utf8.RuneCountInString(text)
|
||||||
if limit < 2 {
|
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 {
|
if p.is_complete || p.bytes_so_far >= p.total_bytes {
|
||||||
ratio = humanize.Size(uint64(p.total_bytes), humanize.SizeOptions{Separator: sep})
|
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`
|
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 {
|
} else {
|
||||||
tb := humanize.Size(p.total_bytes)
|
tb := humanize.Size(p.total_bytes)
|
||||||
sval, _, _ := strings.Cut(tb, " ")
|
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`
|
rate = humanize.Size(p.bytes_per_sec, humanize.SizeOptions{Separator: sep}) + `/s`
|
||||||
bytes_left := p.total_bytes - p.bytes_so_far
|
bytes_left := p.total_bytes - p.bytes_so_far
|
||||||
eta_seconds := safe_divide(bytes_left, p.bytes_per_sec)
|
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 + ` `
|
lft := p.spinner_char + ` `
|
||||||
max_space_for_path := width/2 - wcswidth.Stringwidth(lft)
|
max_space_for_path := width/2 - wcswidth.Stringwidth(lft)
|
||||||
|
|||||||
@@ -101,25 +101,3 @@ func TestPathMappingSend(t *testing.T) {
|
|||||||
ae(f.file_type, FileType_link)
|
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, " ∞")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package humanize
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"kitty/tools/wcswidth"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -115,3 +117,49 @@ func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnit
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf(mag.Format, args...)
|
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
|
||||||
|
}
|
||||||
|
|||||||
35
tools/utils/humanize/times_test.go
Normal file
35
tools/utils/humanize/times_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
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, " ∞")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user