119 lines
3.2 KiB
Go
119 lines
3.2 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Unlimited is the zero value used by both size and duration limits to mean
|
|
// "no limit". Callers must therefore always compare against 0 before enforcing.
|
|
const Unlimited = 0
|
|
|
|
var sizeUnits = []struct {
|
|
suffix string
|
|
mult int64
|
|
}{
|
|
{"KIB", 1 << 10}, {"MIB", 1 << 20}, {"GIB", 1 << 30}, {"TIB", 1 << 40},
|
|
{"KB", 1e3}, {"MB", 1e6}, {"GB", 1e9}, {"TB", 1e12},
|
|
{"K", 1 << 10}, {"M", 1 << 20}, {"G", 1 << 30}, {"T", 1 << 40},
|
|
{"B", 1},
|
|
}
|
|
|
|
// ParseSize accepts "2GiB", "500MB", "1048576", "unlimited" or "0". Binary
|
|
// units (KiB) and bare "K"/"M"/"G" are powers of 1024; "KB"/"MB"/"GB" are
|
|
// powers of 1000, per SI.
|
|
func ParseSize(s string) (int64, error) {
|
|
t := strings.ToUpper(strings.TrimSpace(s))
|
|
if t == "" || t == "0" || t == "UNLIMITED" || t == "NONE" {
|
|
return Unlimited, nil
|
|
}
|
|
mult := int64(1)
|
|
for _, u := range sizeUnits {
|
|
if strings.HasSuffix(t, u.suffix) {
|
|
t = strings.TrimSpace(strings.TrimSuffix(t, u.suffix))
|
|
mult = u.mult
|
|
break
|
|
}
|
|
}
|
|
n, err := strconv.ParseFloat(t, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid size %q", s)
|
|
}
|
|
if n < 0 {
|
|
return 0, fmt.Errorf("invalid size %q: negative", s)
|
|
}
|
|
v := int64(n * float64(mult))
|
|
if v < 0 {
|
|
return 0, fmt.Errorf("invalid size %q: overflow", s)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// FormatSize renders a *limit* for humans, where zero means "no limit". Use
|
|
// FormatBytes for an actual quantity, where zero means zero.
|
|
func FormatSize(n int64) string {
|
|
if n == Unlimited {
|
|
return "unlimited"
|
|
}
|
|
return FormatBytes(n)
|
|
}
|
|
|
|
// FormatBytes renders a byte count for humans.
|
|
func FormatBytes(n int64) string {
|
|
units := []string{"B", "KiB", "MiB", "GiB", "TiB"}
|
|
f, i := float64(n), 0
|
|
for f >= 1024 && i < len(units)-1 {
|
|
f /= 1024
|
|
i++
|
|
}
|
|
if i == 0 {
|
|
return fmt.Sprintf("%d B", n)
|
|
}
|
|
return strings.TrimSuffix(strconv.FormatFloat(f, 'f', 2, 64), ".00") + " " + units[i]
|
|
}
|
|
|
|
// ParseDuration extends time.ParseDuration with "d" (day) and "w" (week)
|
|
// suffixes, and maps "never"/"0" to Unlimited. Durations are the one place the
|
|
// stdlib's vocabulary is genuinely too small: "72h" is a poor way to say "3d".
|
|
func ParseDuration(s string) (time.Duration, error) {
|
|
t := strings.ToLower(strings.TrimSpace(s))
|
|
if t == "" || t == "0" || t == "never" || t == "unlimited" || t == "none" {
|
|
return Unlimited, nil
|
|
}
|
|
// Expand a trailing d/w into hours so time.ParseDuration can finish the job.
|
|
for suffix, hours := range map[string]float64{"d": 24, "w": 168} {
|
|
if num, ok := strings.CutSuffix(t, suffix); ok {
|
|
n, err := strconv.ParseFloat(num, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid duration %q", s)
|
|
}
|
|
t = strconv.FormatFloat(n*hours, 'f', -1, 64) + "h"
|
|
break
|
|
}
|
|
}
|
|
d, err := time.ParseDuration(t)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid duration %q", s)
|
|
}
|
|
if d < 0 {
|
|
return 0, fmt.Errorf("invalid duration %q: negative", s)
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// FormatDuration is the human-facing inverse of ParseDuration.
|
|
func FormatDuration(d time.Duration) string {
|
|
switch {
|
|
case d == Unlimited:
|
|
return "never"
|
|
case d%(24*time.Hour) == 0:
|
|
return fmt.Sprintf("%dd", d/(24*time.Hour))
|
|
case d%time.Hour == 0:
|
|
return fmt.Sprintf("%dh", d/time.Hour)
|
|
default:
|
|
return d.String()
|
|
}
|
|
}
|