1 module bytesize; 2 3 import std.meta : AliasSeq; 4 5 import std.math : pow; 6 import std.format : FormatSpec, formatValue; 7 8 /// 9 struct ByteSize 10 { 11 /// Converts this byte size to a string, use %f for base 1024 or %F for base 1000 12 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 13 { 14 int base = 1024; 15 if (fmt.precision == FormatSpec!char.UNSPECIFIED) 16 fmt.precision = 2; 17 18 if (fmt.spec == 's') 19 fmt.spec = 'f'; 20 else if (fmt.spec == 'S') 21 { 22 base = 1000; 23 fmt.spec = 'f'; 24 } 25 26 static immutable string PrefixList = "_KMGTPE"; 27 28 long tmp = bytes; 29 int order; 30 while (tmp > (base - 1)) 31 { 32 tmp /= base; 33 order++; 34 } 35 // as a long can only hold up to 8EiB we don't need to index check here 36 double val = bytes / pow(cast(double) base, order); 37 if (order > 0) 38 formatValue(sink, val, fmt); 39 else 40 { 41 auto ifmt = fmt; 42 ifmt.spec = 'd'; 43 ifmt.precision = 1; 44 formatValue(sink, bytes, ifmt); 45 } 46 sink(" "); 47 if (order > 0) 48 { 49 sink([PrefixList[order]]); 50 if (base == 1024) 51 sink("i"); 52 } 53 sink("B"); 54 } 55 56 string toString() const 57 { 58 const(char)[] s; 59 toString((d) { s ~= d; }, FormatSpec!char("%s")); 60 return cast(string) s; 61 } 62 63 @safe pure: 64 /++ 65 A $(D ByteSize) of $(D 0). It's shorter than doing something like 66 $(D size!"bytes"(0)) and more explicit than $(D ByteSize.init). 67 +/ 68 static @property nothrow @nogc ByteSize zero() 69 { 70 return ByteSize(0); 71 } 72 73 /++ 74 Largest $(D ByteSize) possible. 75 +/ 76 static @property nothrow @nogc ByteSize max() 77 { 78 return ByteSize(long.max); 79 } 80 81 /++ 82 Most negative $(D ByteSize) possible. 83 +/ 84 static @property nothrow @nogc ByteSize min() 85 { 86 return ByteSize(long.min); 87 } 88 89 /// 90 long bytes; 91 92 int opCmp(ByteSize rhs) const nothrow @nogc 93 { 94 if (bytes < rhs.bytes) 95 return -1; 96 if (bytes > rhs.bytes) 97 return 1; 98 return 0; 99 } 100 101 bool opEquals(ByteSize b) const nothrow @nogc 102 { 103 return bytes == b.bytes; 104 } 105 106 hash_t toHash() const nothrow @nogc 107 { 108 return cast(hash_t) bytes; 109 } 110 111 /// 112 bool isNegative() const nothrow @nogc 113 { 114 return bytes < 0; 115 } 116 117 /// 118 T total(string unit, T = long)() const nothrow @nogc 119 { 120 return cast(T)(bytes / cast(T) bytesInUnit!unit); 121 } 122 123 /// 124 auto opBinary(string op)(ByteSize rhs) const 125 { 126 return ByteSize(mixin("bytes " ~ op ~ " rhs.bytes")); 127 } 128 129 /// 130 auto opUnary(string op)() const 131 { 132 return ByteSize(mixin(op ~ "bytes")); 133 } 134 } 135 136 static assert(__traits(isPOD, ByteSize)); 137 138 /// 139 unittest 140 { 141 import std.format; 142 143 assert(1.bytes.toString == "1 B"); 144 assert(1.KiB.toString == "1.00 KiB"); 145 assert(1.MiB.toString == "1.00 MiB"); 146 assert(1.GiB.toString == "1.00 GiB"); 147 assert(1.TiB.toString == "1.00 TiB"); 148 assert(1.PiB.toString == "1.00 PiB"); 149 assert(1.EiB.toString == "1.00 EiB"); 150 151 assert(1.bytes.format!"%S" == "1 B"); 152 assert(1.KB.format!"%S" == "1.00 KB"); 153 assert(1.MB.format!"%S" == "1.00 MB"); 154 assert(1.GB.format!"%S" == "1.00 GB"); 155 assert(1.TB.format!"%S" == "1.00 TB"); 156 assert(1.PB.format!"%S" == "1.00 PB"); 157 assert(1.EB.format!"%S" == "1.00 EB"); 158 159 assert("%g".format(1024.bytes) == "1 KiB"); 160 assert("%.2f".format(2_590_000.bytes) == "2.47 MiB"); 161 } 162 163 /// Returns the number of bytes per one unit 164 enum bytesInUnit(string unit) = { 165 switch (unit) 166 { 167 case "bytes": 168 return 1; 169 case "KB": 170 return 1000L; 171 case "KiB": 172 return 1024L; 173 case "MB": 174 return 1000L * 1000L; 175 case "MiB": 176 return 1024L * 1024L; 177 case "GB": 178 return 1000L * 1000L * 1000L; 179 case "GiB": 180 return 1024L * 1024L * 1024L; 181 case "TB": 182 return 1000L * 1000L * 1000L * 1000L; 183 case "TiB": 184 return 1024L * 1024L * 1024L * 1024L; 185 case "PB": 186 return 1000L * 1000L * 1000L * 1000L * 1000L; 187 case "PiB": 188 return 1024L * 1024L * 1024L * 1024L * 1024L; 189 case "EB": 190 return 1000L * 1000L * 1000L * 1000L * 1000L * 1000L; 191 case "EiB": 192 return 1024L * 1024L * 1024L * 1024L * 1024L * 1024L; 193 default: 194 assert(false, 195 "Can only use bytes, KB, KiB, MB, MiB, GB, GiB, TB, TiB, PB, PiB, EB, EiB as units"); 196 } 197 }(); 198 199 //dfmt off 200 // only doing this so auto completion helps you... 201 202 /// Convenience method to convert to a ByteSize using `bytesInUnit` 203 ByteSize bytes(long l) { return ByteSize(l); } 204 /// ditto 205 ByteSize KB(long l) { return ByteSize(l * bytesInUnit!"KB"); } 206 /// ditto 207 ByteSize KiB(long l) { return ByteSize(l * bytesInUnit!"KiB"); } 208 /// ditto 209 ByteSize MB(long l) { return ByteSize(l * bytesInUnit!"MB"); } 210 /// ditto 211 ByteSize MiB(long l) { return ByteSize(l * bytesInUnit!"MiB"); } 212 /// ditto 213 ByteSize GB(long l) { return ByteSize(l * bytesInUnit!"GB"); } 214 /// ditto 215 ByteSize GiB(long l) { return ByteSize(l * bytesInUnit!"GiB"); } 216 /// ditto 217 ByteSize TB(long l) { return ByteSize(l * bytesInUnit!"TB"); } 218 /// ditto 219 ByteSize TiB(long l) { return ByteSize(l * bytesInUnit!"TiB"); } 220 /// ditto 221 ByteSize PB(long l) { return ByteSize(l * bytesInUnit!"PB"); } 222 /// ditto 223 ByteSize PiB(long l) { return ByteSize(l * bytesInUnit!"PiB"); } 224 /// ditto 225 ByteSize EB(long l) { return ByteSize(l * bytesInUnit!"EB"); } 226 /// ditto 227 ByteSize EiB(long l) { return ByteSize(l * bytesInUnit!"EiB"); } 228 229 /// ditto 230 ByteSize size(string unit)(long l) { return ByteSize(l * bytesInUnit!unit); } 231 //dfmt on