1 // Written in the D programming language 2 /++ 3 This small library allows to easily format file sizes in a human-readable 4 format. 5 6 Copyright: Copyright 2014-2015, Nicolas Sicard 7 Authors: Nicolas Sicard 8 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 Source: $(LINK https://github.com/biozic/sizefmt) 10 +/ 11 module sizefmt; 12 13 import std.format; 14 import std.traits; 15 version (unittest) import std.string; 16 // debug import std.stdio; 17 18 /++ 19 Wraps a size value of type $(D ulong) which is formatted appropriately 20 when output as text. 21 +/ 22 struct SizeBase(Config config) 23 { 24 /// The size that should be formatted. 25 ulong value; 26 27 /++ 28 Formats the size according to the format fmt, automatically choosing the 29 prefix and performing the unit conversion. 30 31 The size is formatted as a floating point value, so fmt has to be a 32 floating-point-value format specification (s, f, F, e, E, g, G, a or A). 33 But when the unit is just bytes, it is formatted as an integer. 34 +/ 35 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 36 { 37 import std.algorithm : min, max; 38 import std.math : pow; 39 40 // Override defaults for 's' spec. 41 if (fmt.spec == 's') 42 { 43 fmt.spec = 'f'; 44 fmt.precision = 2; 45 } 46 47 // List of prefixes (the first _ is for no prefix, 48 // the second _ if for kilo, which is special cased. 49 static immutable string PrefixList = "__MGTPEZY"; 50 51 static if (config.prefixUse == PrefixUse.decimal) 52 double base = 1000; 53 else 54 double base = 1024; 55 56 int order = 0; 57 double tmp = value; 58 while (tmp > (base - 1)) 59 { 60 ++order; 61 tmp /= base; 62 } 63 order = min(order, PrefixList.length); 64 65 // Output the numeric value 66 if (order > 0) 67 sink.formatValue(value / pow(base, order), fmt); 68 else 69 { 70 auto ifmt = fmt; 71 ifmt.spec = 'd'; 72 ifmt.precision = 1; 73 sink.formatValue(value, ifmt); 74 } 75 76 sink(config.spacing); 77 78 if (order > 0) 79 { 80 if (order == 1) 81 sink(config.prefixUse == PrefixUse.decimal ? "k" : "K"); 82 else 83 sink(PrefixList[order .. order + 1]); 84 85 static if (config.prefixUse == PrefixUse.IEC) 86 sink("i"); 87 } 88 89 static if (config.useNameIfNoPrefix) 90 { 91 if (order == 0) 92 sink(value <= 1 ? config.unitName : config.unitNamePlural); 93 else 94 sink(config.symbol); 95 } 96 else 97 sink(config.symbol); 98 } 99 } 100 101 /// Size struct with default options. 102 alias Size = SizeBase!(Config.init); 103 /// 104 unittest 105 { 106 assert("%s".format(Size(0)) == "0 B"); 107 assert("%s".format(Size(1)) == "1 B"); 108 assert("%s".format(Size(42)) == "42 B"); 109 assert("%g".format(Size(1024)) == "1 KB"); 110 assert("%.2f".format(Size(2_590_000)) == "2.47 MB"); 111 } 112 113 /// Size struct using IEC prefix 114 alias IECSize = SizeBase!iecConfig; 115 /// 116 unittest 117 { 118 assert("%s".format(IECSize(0)) == "0 B"); 119 assert("%s".format(IECSize(1)) == "1 B"); 120 assert("%s".format(IECSize(42)) == "42 B"); 121 assert("%g".format(IECSize(1024)) == "1 KiB"); 122 assert("%.2f".format(IECSize(2_590_000)) == "2.47 MiB"); 123 } 124 125 static assert(__traits(isPOD, Size)); 126 127 private enum Config iecConfig = { prefixUse: PrefixUse.IEC }; 128 129 /++ 130 Configuration of size format. 131 +/ 132 struct Config 133 { 134 /// The symbol of the size unit. 135 string symbol = "B"; 136 137 /// The name of the size unit (singular). 138 string unitName = "byte"; 139 140 /// The name of the size unit (plural). 141 string unitNamePlural = "bytes"; 142 143 /// The type of prefix used along with the symbol. 144 PrefixUse prefixUse = PrefixUse.binary; 145 146 /// The spacing between the value and the unit. 147 string spacing = " "; 148 149 /// Whether to use the name of the symbol if there is no prefix. 150 bool useNameIfNoPrefix = false; 151 152 private size_t maxUnitLength() 153 { 154 import std.algorithm : max; 155 return max( 156 useNameIfNoPrefix ? max(unitName.length, unitNamePlural.length) : 0, 157 1 + (prefixUse == PrefixUse.IEC ? 1 : 0) + symbol.length 158 ); 159 } 160 } 161 /// 162 unittest 163 { 164 enum Config config = { 165 symbol: "O", 166 unitName: "octet", 167 unitNamePlural: "octets", 168 prefixUse: PrefixUse.IEC, 169 useNameIfNoPrefix: true 170 }; 171 172 alias MySize = SizeBase!config; 173 174 assert("%4.1f".format(MySize(0)) == " 0 octet"); 175 assert("%4.1f".format(MySize(1)) == " 1 octet"); 176 assert("%4.1f".format(MySize(42)) == " 42 octets"); 177 assert("%4.1f".format(MySize(1024)) == " 1.0 KiO"); 178 assert("%4.1f".format(MySize(2_590_000)) == " 2.5 MiO"); 179 } 180 181 /++ 182 The use of prefix when formatting long size values. 183 +/ 184 enum PrefixUse 185 { 186 /++ 187 Sizes will be formatted using the traditional _binary prefixes, e.g. 1024 188 bytes = 1 kilobyte = 1 KB. 189 +/ 190 binary, 191 192 /++ 193 Sizes will be formatted using the _IEC recommandations for binary prefixes 194 equivalent of a multiple of 1024, e.g. 1024 bytes = kibibyte = 1 KiB. 195 +/ 196 IEC, 197 198 /++ 199 Sizes will be formatted using _decimal prefixes, e.g. 1024 bytes = 1.024 200 kilobyte = 1.024 kB. 201 +/ 202 decimal 203 }