126 lines
2.4 KiB
Go
126 lines
2.4 KiB
Go
package textutil
|
||
|
||
import (
|
||
"fmt"
|
||
"strconv"
|
||
"time"
|
||
)
|
||
|
||
// SI selects decimal units (kB, MB, …). The default is IEC (KiB, MiB, …).
|
||
// It is set once at start-up, before any goroutines format sizes.
|
||
var SI bool
|
||
|
||
var (
|
||
iecUnits = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||
siUnits = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||
)
|
||
|
||
func scaled(n int64) (float64, string) {
|
||
base, units := 1024.0, iecUnits
|
||
if SI {
|
||
base, units = 1000.0, siUnits
|
||
}
|
||
neg := n < 0
|
||
v := float64(n)
|
||
if neg {
|
||
v = -v
|
||
}
|
||
i := 0
|
||
for v >= base && i < len(units)-1 {
|
||
v /= base
|
||
i++
|
||
}
|
||
if neg {
|
||
v = -v
|
||
}
|
||
return v, units[i]
|
||
}
|
||
|
||
func num(v float64, unit string) string {
|
||
a := v
|
||
if a < 0 {
|
||
a = -a
|
||
}
|
||
switch {
|
||
case unit == "B":
|
||
return strconv.FormatFloat(v, 'f', 0, 64)
|
||
case a < 10:
|
||
return strconv.FormatFloat(v, 'f', 2, 64)
|
||
case a < 100:
|
||
return strconv.FormatFloat(v, 'f', 1, 64)
|
||
default:
|
||
return strconv.FormatFloat(v, 'f', 0, 64)
|
||
}
|
||
}
|
||
|
||
// Size formats a byte count, e.g. "86.4 GiB".
|
||
func Size(n int64) string {
|
||
v, u := scaled(n)
|
||
return num(v, u) + " " + u
|
||
}
|
||
|
||
// SizeCompact formats a byte count in at most ~7 cells, e.g. "86GiB".
|
||
func SizeCompact(n int64) string {
|
||
v, u := scaled(n)
|
||
a := v
|
||
if a < 0 {
|
||
a = -a
|
||
}
|
||
if u != "B" && a < 10 {
|
||
return strconv.FormatFloat(v, 'f', 1, 64) + u
|
||
}
|
||
return strconv.FormatFloat(v, 'f', 0, 64) + u
|
||
}
|
||
|
||
// SignedSize formats a delta with an explicit sign.
|
||
func SignedSize(n int64) string {
|
||
if n > 0 {
|
||
return "+" + Size(n)
|
||
}
|
||
if n < 0 {
|
||
return "-" + Size(-n)
|
||
}
|
||
return "±0 B"
|
||
}
|
||
|
||
// Count formats an integer with thousands separators.
|
||
func Count(n int64) string {
|
||
s := strconv.FormatInt(n, 10)
|
||
neg := false
|
||
if n < 0 {
|
||
neg, s = true, s[1:]
|
||
}
|
||
out := make([]byte, 0, len(s)+len(s)/3)
|
||
for i := range len(s) {
|
||
if i > 0 && (len(s)-i)%3 == 0 {
|
||
out = append(out, ',')
|
||
}
|
||
out = append(out, s[i])
|
||
}
|
||
if neg {
|
||
return "-" + string(out)
|
||
}
|
||
return string(out)
|
||
}
|
||
|
||
// Duration formats an elapsed time as mm:ss or h:mm:ss.
|
||
func Duration(d time.Duration) string {
|
||
s := int64(d.Seconds())
|
||
if s >= 3600 {
|
||
return fmt.Sprintf("%d:%02d:%02d", s/3600, s/60%60, s%60)
|
||
}
|
||
return fmt.Sprintf("%02d:%02d", s/60, s%60)
|
||
}
|
||
|
||
// Percent formats part/whole as a percentage.
|
||
func Percent(part, whole int64) string {
|
||
if whole <= 0 {
|
||
return "–"
|
||
}
|
||
p := float64(part) * 100 / float64(whole)
|
||
if p < 10 {
|
||
return strconv.FormatFloat(p, 'f', 1, 64) + "%"
|
||
}
|
||
return strconv.FormatFloat(p, 'f', 0, 64) + "%"
|
||
}
|