/
size.go
233 lines (196 loc) · 5.58 KB
/
size.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
package size
import (
"fmt"
"github.com/spf13/cast"
"github.com/thoas/go-funk"
"regexp"
"strings"
)
// See: http://en.wikipedia.org/wiki/Binary_prefix
const (
ByteBase = 1
DecimalBase = 1000
BinaryBase = 1024
KB = DecimalBase
MB = DecimalBase * KB
GB = DecimalBase * MB
TB = DecimalBase * GB
PB = DecimalBase * TB
KiB = BinaryBase
MiB = BinaryBase * KiB
GiB = BinaryBase * MiB
TiB = BinaryBase * GiB
PiB = BinaryBase * TiB
Byte Suffix = "B"
KiloByte Suffix = "kB"
MegaByte = "MB"
GigaByte = "GB"
TeraByte = "TB"
PetaByte = "PB"
KibiByte Suffix = "KiB"
MebiByte = "MiB"
GibiByte = "GiB"
TebiByte = "TiB"
PebiByte = "PiB"
FormatDefault = "%.4g%s"
)
type Suffix string
// Units provides a specification of the relationship of the suffix to the size.
type Units map[string]uint64
// Suffixes represents the specification of the ratio of size to suffix.
type Suffixes []*struct {
Unit uint64
Suffix Suffix
}
var (
decimalUnits = Units{
strings.ToLower(string(Byte)): ByteBase,
strings.ToLower(string(KiloByte)): KB,
strings.ToLower(string(MegaByte)): MB,
strings.ToLower(string(GigaByte)): GB,
strings.ToLower(string(TeraByte)): TB,
strings.ToLower(string(PetaByte)): PB,
}
binaryUnits = Units{
strings.ToLower(string(Byte)): ByteBase,
strings.ToLower(string(KibiByte)): KiB,
strings.ToLower(string(MebiByte)): MiB,
strings.ToLower(string(GibiByte)): GiB,
strings.ToLower(string(TebiByte)): TiB,
strings.ToLower(string(PebiByte)): PiB,
}
decimalSuffixes = Suffixes{
{
Unit: ByteBase,
Suffix: Byte,
},
{
Unit: KB,
Suffix: KiloByte,
},
{
Unit: MB,
Suffix: MegaByte,
},
{
Unit: GB,
Suffix: GigaByte,
},
{
Unit: TB,
Suffix: TeraByte,
},
{
Unit: PB,
Suffix: PetaByte,
},
}
binarySuffixes = Suffixes{
{
Unit: ByteBase,
Suffix: Byte,
},
{
Unit: KiB,
Suffix: KibiByte,
},
{
Unit: MiB,
Suffix: MebiByte,
},
{
Unit: GiB,
Suffix: GibiByte,
},
{
Unit: TiB,
Suffix: TebiByte,
},
{
Unit: PiB,
Suffix: PebiByte,
},
}
DecimalSizeRegexp = regexp.MustCompile(`(?m)^(\d+[\d\.]+?) ?([kKmMgGtTpPbB][bB]?)$`)
BinarySizeRegexp = regexp.MustCompile(`(?m)^(\d+[\d\.]+?) ?([kKmMgGtTpPbB][iI][bB]?)$`)
splitRegexp = regexp.MustCompile(`(?m)(\d+[\d\.]+?) ?([A-Za-z]+)$`)
)
// ParseSize defines the IEC/SI prefix and returns int64 as an integer or returns an error if it fails,
// units are case-insensitive, and the 'b' suffix is optional.
func ParseSize(size string) (uint64, error) {
if DecimalSizeRegexp.MatchString(size) {
return FromHumanSize(size)
}
if BinarySizeRegexp.MatchString(size) {
return FromBinarySize(size)
}
return 0, fmt.Errorf("size: format size '%s' unknown", size)
}
// FromHumanSize returns an integer from a human-readable specification of a
// size using SI standard (eg. "512kB", "20MB") or returns an error if it fails,
// units are case-insensitive, and the 'b' suffix is optional.
func FromHumanSize(size string) (uint64, error) {
size = strings.TrimSpace(size)
if !strings.HasSuffix(size, "b") && !strings.HasSuffix(size, "B") {
size += string(Byte)
}
return FromSize(size, decimalUnits)
}
// FromBinarySize parses a human-readable string representing an amount of RAM
// in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
// returns the number of bytes or returns an error if it fails,
// units are case-insensitive, and the 'b' suffix is optional.
func FromBinarySize(size string) (uint64, error) {
size = strings.TrimSpace(size)
if !strings.HasSuffix(size, "b") && !strings.HasSuffix(size, "B") {
size += string(Byte)
}
return FromSize(size, binaryUnits)
}
// FromSize parses the human-readable size string into the amount it represents,
// according to the given specification or returns an error if it fails.
func FromSize(size string, units Units) (uint64, error) {
matches := splitRegexp.FindAllStringSubmatch(size, -1)
if len(matches) == 0 {
return 0, fmt.Errorf("size: invalid format size '%s'", size)
}
if len(matches[0]) != 3 {
return 0, fmt.Errorf("size: invalid format size '%s'", size)
}
unit, exist := units[strings.ToLower(strings.TrimSpace(matches[0][2]))]
if !exist {
return 0, fmt.Errorf("size: unit '%s' unknown, available units [%s]",
matches[0][2],
strings.Join(cast.ToStringSlice(funk.Keys(units)), ", "),
)
}
unitSize, err := cast.ToFloat64E(matches[0][1])
if err != nil {
return 0, fmt.Errorf("size: cast invalid: %w", err)
}
return uint64(unitSize * float64(unit)), nil
}
// FormatHuman returns a human-readable approximation of a size
// capped at 4 valid numbers (eg. "25MB", "22GB").
func FormatHuman(unit uint64) string {
return FormatSize(FormatDefault, unit, decimalSuffixes)
}
// FormatBinary returns a human-readable size in bytes, kibibytes,
// mebibytes, gibibytes, or tebibytes (eg. "512kiB", "4PiB").
func FormatBinary(unit uint64) string {
return FormatSize(FormatDefault, unit, binarySuffixes)
}
// FormatSize returns a human-readable approximation of the size
// using the given format and the given Suffixes specification
func FormatSize(format string, unit uint64, suffixes Suffixes) string {
size, suffix := calculateSizeAndSuffix(float64(unit), suffixes)
return fmt.Sprintf(format, size, suffix)
}
func calculateSizeAndSuffix(size float64, suffixes Suffixes) (float64, Suffix) {
for i := len(suffixes) - 1; i >= 0; i-- {
if size >= float64(suffixes[i].Unit) {
return size / float64(suffixes[i].Unit), suffixes[i].Suffix
}
}
return size / float64(suffixes[0].Unit), suffixes[0].Suffix
}