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 }