diff --git a/conv.go b/conv.go index f142f42..3d1456e 100644 --- a/conv.go +++ b/conv.go @@ -195,9 +195,9 @@ func asString[T stringi](val any) (T, error) { } return T(v.String()), nil case Size: - return T(v.String()), nil + return T(FormatSize(int64(v), 'f', -2, true)), nil case Rate: - return T(v.String()), nil + return T(FormatRate(v, 'f', -2, true)), nil case interface{ String() string }: return T(v.String()), nil case interface{ Bytes() []byte }: @@ -486,26 +486,22 @@ func asSize(val any) (Size, error) { return v.Size(), nil } if v, err := asInt[int64](val); err == nil { - return Size{v * B, DefaultPrec, DefaultIEC}, nil + return Size(v), nil } if v, err := asUint[uint64](val); err == nil { - return Size{int64(v * uint64(B)), DefaultPrec, DefaultIEC}, nil + return Size(v), nil } if v, err := asFloat[float64](val); err == nil { - return Size{int64(v * float64(B)), DefaultPrec, DefaultIEC}, nil + return Size(v), nil } s, err := asString[string](val) switch { case err != nil: - return Size{0, DefaultPrec, DefaultIEC}, err + return 0, err case s == "": - return Size{0, DefaultPrec, DefaultIEC}, nil - } - size, prec, iec, err := ParseSize(s) - if err != nil { - return Size{0, DefaultPrec, DefaultIEC}, err + return 0, nil } - return Size{size, prec, iec}, nil + return ParseSize(s) } // asRate converts the value to a [Rate]. @@ -517,26 +513,22 @@ func asRate(val any) (Rate, error) { return v.Rate(), nil } if v, err := asInt[int64](val); err == nil { - return Rate{v * B, DefaultPrec, DefaultIEC, DefaultUnit}, nil + return Rate{Size(v), DefaultRateUnit}, nil } if v, err := asUint[uint64](val); err == nil { - return Rate{int64(v * uint64(B)), DefaultPrec, DefaultIEC, DefaultUnit}, nil + return Rate{Size(v), DefaultRateUnit}, nil } if v, err := asFloat[float64](val); err == nil { - return Rate{int64(v * float64(B)), DefaultPrec, DefaultIEC, DefaultUnit}, nil + return Rate{Size(v), DefaultRateUnit}, nil } s, err := asString[string](val) switch { case err != nil: - return Rate{0, DefaultPrec, DefaultIEC, DefaultUnit}, err + return Rate{}, err case s == "": - return Rate{0, DefaultPrec, DefaultIEC, DefaultUnit}, nil - } - rate, prec, iec, unit, err := ParseRate(s) - if err != nil { - return Rate{0, DefaultPrec, DefaultIEC, DefaultUnit}, err + return Rate{}, nil } - return Rate{rate, prec, iec, unit}, nil + return ParseRate(s) } // asUnmarshal creates a new value as T, and unmarshals the value to it. diff --git a/ox.go b/ox.go index c69369c..8544588 100644 --- a/ox.go +++ b/ox.go @@ -219,12 +219,10 @@ var ( } return v } - // DefaultPrec is the default [Size]/[Rate] display precision. - DefaultPrec = -2 - // DefaultIEC is the default [Size]/[Rate] IEC display setting. - DefaultIEC = true - // DefaultUnit is the default [Rate] unit. - DefaultUnit = time.Second + // DefaultSizePrec is the default [Size] display precision. + DefaultSizePrec = -2 + // DefaultRateUnit is the default [Rate] unit. + DefaultRateUnit = time.Second ) // Run creates and builds the execution [Context] based on the passed @@ -709,174 +707,6 @@ func BuildVersion() string { return ver } -// ParseRate parses a byte rate string. -func ParseRate(s string) (int64, int, bool, time.Duration, error) { - unit := time.Second - if i := strings.LastIndexByte(s, '/'); i != -1 { - switch s[i+1:] { - // unitMap is the duration unit map. - case "ns": - unit = time.Nanosecond - case "us", "µs", "μs": // U+00B5 = micro symbol, U+03BC = Greek letter mu - unit = time.Microsecond - case "ms": - unit = time.Millisecond - case "s": - unit = time.Second - case "m": - unit = time.Minute - case "h": - unit = time.Hour - default: - return 0, 0, false, 0, fmt.Errorf("%w %q", ErrInvalidRate, s) - } - s = s[:i] - } - sz, prec, iec, err := ParseSize(s) - if err != nil { - return 0, 0, false, 0, err - } - return sz, prec, iec, unit, nil -} - -// FormatSize formats a byte rate. -func FormatRate(size int64, prec int, iec bool, unit time.Duration) string { - return FormatSize(size, prec, iec) + "/" + unitString(unit) -} - -// ParseSize parses a byte size string. -func ParseSize(s string) (int64, int, bool, error) { - m := sizeRE.FindStringSubmatch(s) - if m == nil { - return 0, 0, false, fmt.Errorf("%w %q", ErrInvalidSize, s) - } - f, err := strconv.ParseFloat(m[2], 64) - switch { - case err != nil: - return 0, 0, false, fmt.Errorf("%w: %w", ErrInvalidSize, err) - case m[1] == "-": - f = -f - } - sz, iec, err := sizeType(m[3]) - if err != nil { - return 0, 0, false, fmt.Errorf("%w: %w", ErrInvalidSize, err) - } - prec := DefaultPrec - if i := strings.LastIndexByte(m[2], '.'); i != -1 { - prec = len(m[2]) - i - 1 - } - return int64(f * float64(sz)), prec, iec, nil -} - -// FormatSize formats a byte size. -// -// Formatting rules follow [strconv.FormatFloat] rules, with additional option -// for a precision of -2, which will remove trailing zeroes and the decimal if -// the decimal places are all zeroes. -func FormatSize(size int64, prec int, iec bool) string { - n, t, suffix := KB, "kMGTPE", "B" - if iec { - n, t, suffix = KiB, "KMGTPE", "iB" - } - var neg string - if size < 0 { - neg, size = "-", -size - } - if size < n { - return neg + strconv.FormatInt(size, 10) + " B" - } - e, d := 0, n - for i := size / n; n <= i; i /= n { - d *= n - e++ - } - precabs := prec - if precabs < -1 { - precabs = -precabs - } - s := strconv.FormatFloat(float64(size)/float64(d), 'f', precabs, 64) - if prec < -1 { - s = strings.TrimRight(s, ".0") - } - return neg + s + " " + string(t[e]) + suffix -} - -// sizeType returns the size of s. -func sizeType(s string) (int64, bool, error) { - switch strings.ToLower(s) { - case "", "b": - return B, false, nil - case "kb": - return KB, false, nil - case "mb": - return MB, false, nil - case "gb": - return GB, false, nil - case "tb": - return TB, false, nil - case "pb": - return PB, false, nil - case "eb": - return EB, false, nil - case "kib": - return KiB, true, nil - case "mib": - return MiB, true, nil - case "gib": - return GiB, true, nil - case "tib": - return TiB, true, nil - case "pib": - return PiB, true, nil - case "eib": - return EiB, true, nil - } - return 0, false, fmt.Errorf("%w %q", ErrUnknownSize, s) -} - -// Byte sizes. -const ( - B int64 = 1 - KB int64 = 1_000 - MB int64 = 1_000_000 - GB int64 = 1_000_000_000 - TB int64 = 1_000_000_000_000 - PB int64 = 1_000_000_000_000_000 - EB int64 = 1_000_000_000_000_000_000 -) - -// IEC byte sizes. -const ( - KiB int64 = 1_024 - MiB int64 = 1_048_576 - GiB int64 = 1_073_741_824 - TiB int64 = 1_099_511_627_776 - PiB int64 = 1_125_899_906_842_624 - EiB int64 = 1_152_921_504_606_846_976 -) - -// unitString returns the string for a time unit (duration). -func unitString(unit time.Duration) string { - switch { - case unit == 0, unit > time.Hour: - return unitString(DefaultUnit) - case unit > time.Minute: - return "h" - case unit > time.Second: - return "m" - case unit > time.Millisecond: - return "s" - case unit > time.Microsecond: - return "ms" - case unit > time.Nanosecond: - return "µs" - } - return "ns" -} - -// sizeRE matches sizes. -var sizeRE = regexp.MustCompile(`(?i)^([-+])?([0-9]+(?:\.[0-9]*)?)(?: ?([a-z]+))?$`) - // prepend is a generic prepend. func prepend[S ~[]E, E any](v S, s ...E) S { return append(s, v...) diff --git a/ox_test.go b/ox_test.go index c0f8b7a..3f6bf6e 100644 --- a/ox_test.go +++ b/ox_test.go @@ -6,7 +6,6 @@ import ( "strconv" "strings" "testing" - "time" ) func TestSuggestions(t *testing.T) { @@ -112,87 +111,6 @@ func TestLdist(t *testing.T) { } } -func TestFormatSize(t *testing.T) { - tests := []struct { - size int64 - prec int - iec bool - exp string - }{ - {0, DefaultPrec, false, "0 B"}, - {0, DefaultPrec, true, "0 B"}, - {int64(0.754 * float64(MB)), DefaultPrec, false, "754 kB"}, - {int64(1.5 * float64(GB)), 2, false, "1.50 GB"}, - {int64(1.5 * float64(GB)), 1, false, "1.5 GB"}, - {int64(1.54 * float64(GB)), 2, false, "1.54 GB"}, - {int64(1.5 * float64(GiB)), 2, true, "1.50 GiB"}, - {int64(1.5 * float64(PiB)), 2, true, "1.50 PiB"}, - } - for i, test := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - s := FormatSize(test.size, test.prec, test.iec) - if s != test.exp { - t.Errorf("expected %q, got: %q", test.exp, s) - } - size, prec, iec, err := ParseSize(test.exp) - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - if size != test.size { - t.Errorf("expected size %d, got: %d", test.size, size) - } - if prec != test.prec { - t.Errorf("expected prec %d, got: %d", test.prec, prec) - } - if test.size != 0 && iec != test.iec { - t.Errorf("expected iec %t, got: %t", test.iec, iec) - } - }) - } -} - -func TestFormatRate(t *testing.T) { - tests := []struct { - rate int64 - prec int - iec bool - unit time.Duration - exp string - }{ - {0, DefaultPrec, false, time.Second, "0 B/s"}, - {0, DefaultPrec, true, time.Second, "0 B/s"}, - {int64(0.754 * float64(MB)), DefaultPrec, false, time.Hour, "754 kB/h"}, - {int64(1.5 * float64(GB)), 1, false, time.Microsecond, "1.5 GB/µs"}, - {int64(1.54 * float64(GB)), 2, false, time.Microsecond, "1.54 GB/µs"}, - {int64(1.5 * float64(GiB)), 2, true, time.Millisecond, "1.50 GiB/ms"}, - {int64(1.5 * float64(PiB)), 2, true, time.Second, "1.50 PiB/s"}, - } - for i, test := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - s := FormatRate(test.rate, test.prec, test.iec, test.unit) - if s != test.exp { - t.Errorf("expected %q, got: %q", test.exp, s) - } - rate, prec, iec, unit, err := ParseRate(test.exp) - if err != nil { - t.Fatalf("expected no error, got: %v", err) - } - if rate != test.rate { - t.Errorf("expected rate %d, got: %d", test.rate, rate) - } - if prec != test.prec { - t.Errorf("expected prec %d, got: %d", test.prec, prec) - } - if test.rate != 0 && iec != test.iec { - t.Errorf("expected iec %t, got: %t", test.iec, iec) - } - if unit != test.unit { - t.Errorf("expected unit %v, got: %v", test.unit, unit) - } - }) - } -} - func testContext(t *testing.T, code *int, args ...string) *Context { t.Helper() cmd := testCommand(t) diff --git a/parse_test.go b/parse_test.go index aebd38e..7971a77 100644 --- a/parse_test.go +++ b/parse_test.go @@ -198,7 +198,7 @@ func parseTests() []parseTest { "name: six", "path: [six]", "args: []", - "vars: [duration:15µs int:15 rate:186 MB/s size:15 MiB]", + "vars: [duration:15µs int:15 rate:177.38 MiB/s size:15 MiB]", }, }, } diff --git a/size.go b/size.go new file mode 100644 index 0000000..e9abcc7 --- /dev/null +++ b/size.go @@ -0,0 +1,297 @@ +package ox + +import ( + "bytes" + "fmt" + "regexp" + "strconv" + "strings" + "time" +) + +// ParseSize parses a byte size string. +func ParseSize(s string) (Size, error) { + m := sizeRE.FindStringSubmatch(s) + if m == nil { + return 0, fmt.Errorf("%w %q", ErrInvalidSize, s) + } + f, err := strconv.ParseFloat(m[2], 64) + switch { + case err != nil: + return 0, fmt.Errorf("%w: %w", ErrInvalidSize, err) + case m[1] == "-": + f = -f + } + sz, err := parseSize(m[3]) + if err != nil { + return 0, fmt.Errorf("%w: %w", ErrInvalidSize, err) + } + return Size(f * float64(sz)), nil +} + +// ParseRate parses a byte rate string. +func ParseRate(s string) (Rate, error) { + unit := time.Second + if i := strings.LastIndexByte(s, '/'); i != -1 { + switch strings.ToLower(s[i+1:]) { + // unitMap is the duration unit map. + case "ns": + unit = time.Nanosecond + case "us", "µs", "μs": // U+00B5 = micro symbol, U+03BC = Greek letter mu + unit = time.Microsecond + case "ms": + unit = time.Millisecond + case "s": + unit = time.Second + case "m": + unit = time.Minute + case "h": + unit = time.Hour + default: + return Rate{}, fmt.Errorf("%w %q", ErrInvalidRate, s) + } + s = s[:i] + } + sz, err := ParseSize(s) + if err != nil { + return Rate{}, err + } + return Rate{sz, unit}, nil +} + +// AppendSize appends the formatted size to b. A precision below -1 will format +// the value to the fixed precision then trims any trailing '.' / '0'. Formats +// the value based on the verb (see below). When space is true, a space will be +// appended between the formatted size and the suffix. +// +// Supported verbs: +// +// d/D - size in bytes (ex: 12345678) +// f/F - size in best fitting *B/*iB (ex: 999 B, 1.1 KiB) and always with a space +// z/Z - size in best fitting *B/*iB +// k/K - size in KB/KiB (ex: 0.9 kB, 2.3 KiB) +// m/M - size in MB/MiB (ex: 1.2345 MB) +// g/G - size in GB/GiB (ex: 1 GiB) +// t/T - size in TB/TiB (ex: 4.5 TiB) +// p/P - size in PB/PiB (ex: +// s/S - same as f/F +// v/V - same as f/F +func AppendSize(b []byte, size int64, verb rune, prec int, space bool) []byte { + neg, sz, unit := "", float64(size), "B" + switch verb { + case 'd', 'D': + return strconv.AppendInt(b, size, 10) + case 'k': + sz, unit = sz/KiB, "KiB" + case 'K': + sz, unit = sz/KB, "kB" + case 'm': + sz, unit = sz/MiB, "MiB" + case 'M': + sz, unit = sz/MB, "MB" + case 'g': + sz, unit = sz/GiB, "GiB" + case 'G': + sz, unit = sz/GB, "GB" + case 't': + sz, unit = sz/TiB, "TiB" + case 'T': + sz, unit = sz/TB, "TB" + case 'b': + sz, unit = sz/PiB, "PiB" + case 'P': + sz, unit = sz/PB, "PB" + case 'z': + neg, sz, unit = bestSize(size, true) + case 'Z': + neg, sz, unit = bestSize(size, false) + case 'f', 's', 'v': + neg, sz, unit = bestSize(size, true) + prec, space = DefaultSizePrec, true + case 'F', 'S', 'V': + neg, sz, unit = bestSize(size, false) + prec, space = DefaultSizePrec, true + default: + return append(b, "%"+string(verb)+"(error=unknown size verb)"...) + } + aprec := prec + if aprec < -1 { + aprec = -aprec + } + b = append(b, neg...) + b = strconv.AppendFloat(b, sz, 'f', aprec, 64) + // trim right {.,0} + if prec < -1 { + b = bytes.TrimRightFunc(b, func(r rune) bool { + return r == '0' + }) + b = bytes.TrimRightFunc(b, func(r rune) bool { + return r == '.' + }) + } + if space { + b = append(b, ' ') + } + return append(b, unit...) +} + +func bestSize(size int64, iec bool) (string, float64, string) { + n, units, suffix := int64(KB), "kMGTPE", "B" + if iec { + n, units, suffix = KiB, "KMGTPE", "iB" + } + var neg string + if size < 0 { + neg, size = "-", -size + } + if size < n { + return neg, float64(size), "B" + } + e, d := 0, n + for i := size / n; n <= i; i /= n { + d *= n + e++ + } + return neg, float64(size) / float64(d), string(units[e]) + suffix +} + +// FormatSize formats a byte size. +func FormatSize(size int64, verb rune, prec int, space bool) string { + return string(AppendSize(make([]byte, 0, 28), size, verb, prec, space)) +} + +// AppendRate appends the formatted rate to b. +func AppendRate(b []byte, rate Rate, verb rune, prec int, space bool) []byte { + return append(append(AppendSize(b, int64(rate.Size), verb, prec, space), '/'), unitString(rate.Unit)...) +} + +// FormatRate formats a byte rate. +func FormatRate(rate Rate, verb rune, prec int, space bool) string { + return string(AppendRate(make([]byte, 0, 31), rate, verb, prec, space)) +} + +// Size is a byte size. +type Size int64 + +// Format satisfies the [fmt.Formatter] interface. See [AppendSize] for +// recognized verbs. +func (size Size) Format(f fmt.State, verb rune) { + prec := DefaultSizePrec + if p, ok := f.Precision(); ok { + prec = p + } + _, _ = f.Write(AppendSize(make([]byte, 0, 28), int64(size), verb, prec, f.Flag(' '))) +} + +// MarshalText satisfies the [BinaryMarshalUnmarshaler] interface. +func (size Size) MarshalText() ([]byte, error) { + return AppendSize(make([]byte, 0, 28), int64(size), 'f', -2, true), nil +} + +// UnmarshalText satisfies the [BinaryMarshalUnmarshaler] interface. +func (sz *Size) UnmarshalText(b []byte) error { + i, err := ParseSize(string(b)) + if err != nil { + return err + } + *sz = Size(i) + return nil +} + +// Rate is a byte rate. +type Rate struct { + Size Size + Unit time.Duration +} + +// Int64 returns the bytes as an int64. +func (r Rate) Int64() int64 { + return int64(r.Size) +} + +// UnmarshalText satisfies the [BinaryMarshalUnmarshaler] interface. +func (r *Rate) UnmarshalText(b []byte) error { + /* + var err error + r.Rate, r.Prec, r.IEC, r.Unit, err = ParseRate(string(b)) + return err + */ + return nil +} + +// MarshalText satisfies the [BinaryMarshalUnmarshaler] interface. +func (r *Rate) MarshalText() ([]byte, error) { + return nil, nil +} + +// Byte sizes. +const ( + B = 1 + KB = 1_000 + MB = 1_000_000 + GB = 1_000_000_000 + TB = 1_000_000_000_000 + PB = 1_000_000_000_000_000 + EB = 1_000_000_000_000_000_000 + KiB = 1_024 + MiB = 1_048_576 + GiB = 1_073_741_824 + TiB = 1_099_511_627_776 + PiB = 1_125_899_906_842_624 + EiB = 1_152_921_504_606_846_976 +) + +// parseSize returns the byte size of s. +func parseSize(s string) (int64, error) { + switch strings.ToLower(s) { + case "", "b": + return B, nil + case "kb": + return KB, nil + case "mb": + return MB, nil + case "gb": + return GB, nil + case "tb": + return TB, nil + case "pb": + return PB, nil + case "eb": + return EB, nil + case "kib": + return KiB, nil + case "mib": + return MiB, nil + case "gib": + return GiB, nil + case "tib": + return TiB, nil + case "pib": + return PiB, nil + case "eib": + return EiB, nil + } + return 0, fmt.Errorf("%w %q", ErrUnknownSize, s) +} + +// unitString returns the string for a time unit (duration). +func unitString(unit time.Duration) string { + switch { + case unit == 0, unit > time.Hour: + return unitString(DefaultRateUnit) + case unit > time.Minute: + return "h" + case unit > time.Second: + return "m" + case unit > time.Millisecond: + return "s" + case unit > time.Microsecond: + return "ms" + case unit > time.Nanosecond: + return "µs" + } + return "ns" +} + +// sizeRE matches sizes. +var sizeRE = regexp.MustCompile(`(?i)^([-+])?([0-9]+(?:\.[0-9]*)?)(?: ?([a-z]+))?$`) diff --git a/size_test.go b/size_test.go new file mode 100644 index 0000000..62c569c --- /dev/null +++ b/size_test.go @@ -0,0 +1,64 @@ +package ox + +import ( + "fmt" + "strconv" + "testing" +) + +func TestFormatSize(t *testing.T) { + tests := []struct { + v Size + as string + exp string + }{ + {1000, "%d", "1000"}, + {1000, "%s", "1000 B"}, + {1000, "%S", "1 kB"}, + {-1000, "%s", "-1000 B"}, + {-1000, "%S", "-1 kB"}, + {1000, "%f", "1000 B"}, + {1024, "%d", "1024"}, + {1024, "%s", "1 KiB"}, + {1024, "%f", "1 KiB"}, + {1024, "%.6z", "1.000000KiB"}, + {1024, "%.0z", "1KiB"}, + {1024, "%.1z", "1.0KiB"}, + {1024, "%.2z", "1.00KiB"}, + {1024, "%.3z", "1.000KiB"}, + {12345678, "%d", "12345678"}, + {12345678, "%D", "12345678"}, + {12345678, "%s", "11.77 MiB"}, + {12345678, "%m", "11.77MiB"}, + {12345678, "%.3m", "11.774MiB"}, + {12345678, "% .3m", "11.774 MiB"}, + {12345678, "%d", "12345678"}, + {12345678, "%s", "11.77 MiB"}, + {12345678, "%f", "11.77 MiB"}, + {3*MiB + 100*KiB, "%d", "3248128"}, + {3*MiB + 100*KiB, "%s", "3.1 MiB"}, + {3*MiB + 100*KiB, "%F", "3.25 MB"}, + {2 * GiB, "%d", "2147483648"}, + {2 * GiB, "%s", "2 GiB"}, + {2 * GiB, "%f", "2 GiB"}, + {4 * TiB, "%d", "4398046511104"}, + {4 * TiB, "%s", "4 TiB"}, + {5*PiB + 200*TiB, "%d", "5849401859768320"}, + {5*PiB + 200*TiB, "% m", "5578424320 MiB"}, + {5*PiB + 200*TiB, "% g", "5447680 GiB"}, + {5*PiB + 200*TiB, "% t", "5320 TiB"}, + {5*PiB + 200*TiB, "% b", "5.2 PiB"}, + {5*PiB + 200*TiB, "%v", "5.2 PiB"}, + {5*PiB + 200*TiB, "%V", "5.85 PB"}, + {5*PiB + 200*TiB, "% x", "%x(error=unknown size verb)"}, + } + for i, test := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + s := fmt.Sprintf(test.as, test.v) + t.Logf("%d %q :: %q == %q", test.v, test.as, test.exp, s) + if s != test.exp { + t.Errorf("expected %q, got: %q", test.exp, s) + } + }) + } +} diff --git a/type.go b/type.go index 86712e6..c9ad61f 100644 --- a/type.go +++ b/type.go @@ -38,6 +38,8 @@ const ( Float32T Type = "float32" Complex128T Type = "complex128" Complex64T Type = "complex64" + SizeT Type = "size" + RateT Type = "rate" TimestampT Type = "timestamp" DateTimeT Type = "datetime" @@ -45,8 +47,6 @@ const ( TimeT Type = "time" DurationT Type = "duration" - SizeT Type = "size" - RateT Type = "rate" CountT Type = "count" PathT Type = "path" @@ -166,13 +166,13 @@ func init() { RegisterType(Float32T, NewVal[float32]()) RegisterType(Complex128T, NewVal[complex128]()) RegisterType(Complex64T, NewVal[complex64]()) + RegisterType(RateT, NewVal[Rate]()) + RegisterType(SizeT, NewVal[Size]()) RegisterType(TimestampT, NewTime(TimestampT, "")) RegisterType(DateTimeT, NewTime(DateTimeT, time.DateTime)) RegisterType(DateT, NewTime(DateT, time.DateOnly)) RegisterType(TimeT, NewTime(TimeT, time.TimeOnly)) RegisterType(DurationT, NewVal[time.Duration]()) - RegisterType(SizeT, NewSize()) - RegisterType(RateT, NewRate()) RegisterType(CountT, NewVal[uint64](CountT), NoArg(true, "")) RegisterType(PathT, NewVal[string](PathT)) // register text marshal types diff --git a/type_test.go b/type_test.go index 090180f..7ae4585 100644 --- a/type_test.go +++ b/type_test.go @@ -347,10 +347,10 @@ func typeTests(t *testing.T) []typeTest { SizeT, []test{ {"", "0 B"}, {"1", "1 B"}, - {"1 mib", Size{1 * MiB, DefaultPrec, DefaultIEC}}, - {"1.5GB", Size{int64(1.5 * float64(GB)), 1, false}}, - {"1.5GiB", Size{int64(1.5 * float64(GiB)), DefaultPrec, DefaultIEC}}, - {"15 MiB", Size{int64(15 * float64(MiB)), DefaultPrec, DefaultIEC}}, + {"1 mib", Size(1 * MiB)}, + {"1.5GB", Size(int64(1.5 * float64(GB)))}, + {"1.5GiB", Size(int64(1.5 * float64(GiB)))}, + {"15 MiB", Size(int64(15 * float64(MiB)))}, {"foo", ErrInvalidValue}, }, }, @@ -359,12 +359,12 @@ func typeTests(t *testing.T) []typeTest { {"", "0 B/s"}, {"1", "1 B/s"}, {"1 mib", "1 MiB/s"}, - {"1.5GB", "1.5 GB/s"}, - {"-15.4 MB", "-15.4 MB/s"}, + {"1.5GB", "1.4 GiB/s"}, + {"-15.4 MB", "-14.69 MiB/s"}, {"-15.4 mib", "-15.4 MiB/s"}, {"1/s", "1 B/s"}, {"1 mib/h", "1 MiB/h"}, - {"1.5GB/m", "1.5 GB/m"}, + {"1.5GB/m", "1.4 GiB/m"}, {"-15.4 MiB/s", "-15.4 MiB/s"}, {"1 gib/µs", "1 GiB/µs"}, {"foo", ErrInvalidValue}, diff --git a/value.go b/value.go index 4de3906..14c3871 100644 --- a/value.go +++ b/value.go @@ -657,95 +657,6 @@ func (val FormattedTime) IsValid() bool { return !val.v.IsZero() } -// Size is a byte size. -type Size struct { - Size int64 - Prec int - IEC bool -} - -// NewSize creates a byte size. -func NewSize() func() (Value, error) { - return func() (Value, error) { - return &anyVal[Size]{ - typ: SizeT, - v: Size{0, DefaultPrec, DefaultIEC}, - }, nil - } -} - -// Rate returns the size as a [Rate]. -func (val *Size) Rate() Rate { - return Rate{val.Size, val.Prec, val.IEC, DefaultUnit} -} - -// Int64 returns the kilobytes -func (val *Size) Int64() int64 { - return val.Size -} - -// String satisfies the [fmt.Stringer] interface. -func (val *Size) String() string { - return FormatSize(val.Size, val.Prec, val.IEC) -} - -// UnmarshalText satisfies the [BinaryMarshalUnmarshaler] interface. -func (val *Size) UnmarshalText(b []byte) error { - var err error - val.Size, val.Prec, val.IEC, err = ParseSize(string(b)) - return err -} - -// MarshalText satisfies the [BinaryMarshalUnmarshaler] interface. -func (val *Size) MarshalText() ([]byte, error) { - return []byte(FormatSize(val.Size, val.Prec, val.IEC)), nil -} - -// Rate is a byte rate. -type Rate struct { - Rate int64 - Prec int - IEC bool - Unit time.Duration -} - -// NewRate creates a byte rate. -func NewRate() func() (Value, error) { - return func() (Value, error) { - return &anyVal[Rate]{ - typ: RateT, - v: Rate{0, DefaultPrec, DefaultIEC, DefaultUnit}, - }, nil - } -} - -// Size returns the rate as a [Size]. -func (val *Rate) Size() Size { - return Size{val.Rate, val.Prec, val.IEC} -} - -// Int64 returns the kilobytes -func (val *Rate) Int64() int64 { - return val.Rate -} - -// String satisfies the [fmt.Stringer] interface. -func (val *Rate) String() string { - return FormatRate(val.Rate, val.Prec, val.IEC, val.Unit) -} - -// UnmarshalText satisfies the [BinaryMarshalUnmarshaler] interface. -func (val *Rate) UnmarshalText(b []byte) error { - var err error - val.Rate, val.Prec, val.IEC, val.Unit, err = ParseRate(string(b)) - return err -} - -// MarshalText satisfies the [BinaryMarshalUnmarshaler] interface. -func (val *Rate) MarshalText() ([]byte, error) { - return []byte(FormatRate(val.Rate, val.Prec, val.IEC, val.Unit)), nil -} - // sliceSet sets a value on a slice -- expects reflect.ValueOf(&). func sliceSet(value reflect.Value, s string) error { v := reflect.New(value.Elem().Type().Elem())