Initial commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user