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 static assert(__traits(isPOD, Size)); 114 115 /++ 116 Configuration of size format. 117 +/ 118 struct Config 119 { 120 /// The symbol of the size unit. 121 string symbol = "B"; 122 123 /// The name of the size unit (singular). 124 string unitName = "byte"; 125 126 /// The name of the size unit (plural). 127 string unitNamePlural = "bytes"; 128 129 /// The type of prefix used along with the symbol. 130 PrefixUse prefixUse = PrefixUse.binary; 131 132 /// The spacing between the value and the unit. 133 string spacing = " "; 134 135 /// Whether to use the name of the symbol if there is no prefix. 136 bool useNameIfNoPrefix = false; 137 138 private size_t maxUnitLength() 139 { 140 import std.algorithm : max; 141 return max( 142 useNameIfNoPrefix ? max(unitName.length, unitNamePlural.length) : 0, 143 1 + (prefixUse == PrefixUse.IEC ? 1 : 0) + symbol.length 144 ); 145 } 146 } 147 /// 148 unittest 149 { 150 enum Config config = { 151 symbol: "O", 152 unitName: "octet", 153 unitNamePlural: "octets", 154 prefixUse: PrefixUse.IEC, 155 useNameIfNoPrefix: true 156 }; 157 158 alias MySize = SizeBase!config; 159 160 assert("%4.1f".format(MySize(0)) == " 0 octet"); 161 assert("%4.1f".format(MySize(1)) == " 1 octet"); 162 assert("%4.1f".format(MySize(42)) == " 42 octets"); 163 assert("%4.1f".format(MySize(1024)) == " 1.0 KiO"); 164 assert("%4.1f".format(MySize(2_590_000)) == " 2.5 MiO"); 165 } 166 167 /++ 168 The use of prefix when formatting long size values. 169 +/ 170 enum PrefixUse 171 { 172 /++ 173 Sizes will be formatted using the traditional _binary prefixes, e.g. 1024 174 bytes = 1 kilobyte = 1 KB. 175 +/ 176 binary, 177 178 /++ 179 Sizes will be formatted using the _IEC recommandations for binary prefixes 180 equivalent of a multiple of 1024, e.g. 1024 bytes = kibibyte = 1 KiB. 181 +/ 182 IEC, 183 184 /++ 185 Sizes will be formatted using _decimal prefixes, e.g. 1024 bytes = 1.024 186 kilobyte = 1.024 kB. 187 +/ 188 decimal 189 }