1 /++ 2 + Print human readable units (e.g. time in days, hours, minutes or distance in km, m, cm, mm). 3 +/ 4 module unit; 5 6 /++ 7 + A unit allows to easily print mixed resolution values. 8 + Typical examples include time (with hours, minutes, ...) 9 + and distances (with km, m, cm, mm). 10 + The unitclass simplifies definition of such things as well 11 + as transforming a high resolution value, to a supposedly 12 + more human readable representation. 13 + e.g. 3_610_123 would convert to 1h 0m 10s 123ms. 14 +/ 15 public struct Unit 16 { 17 import std.algorithm.iteration; 18 import std.range; 19 20 /++ 21 + A scale is one resolution of a unit. 22 +/ 23 public struct Scale 24 { 25 /// the name of the scale (e.g. h for hour) 26 string name; 27 28 /++ factor to the next higher resolution (e.g. 60 from minutes to seconds) +/ 29 long factor; 30 31 /++ normal renderwidth for the application (e.g. 2 for minutes (00-59)) +/ 32 int digits; 33 } 34 35 /++ factory for Scale 36 + Params: 37 + name = unitname 38 + factor = factor to the next bigger unit 39 + digits = padding digits 40 +/ 41 static auto scale(string name, long factor, int digits = 1) 42 { 43 return Scale(name, factor, digits); 44 } 45 46 /++ 47 + One part of the transformed Unit. 48 + a part of a unit is e.g. the minute resolution of a duration. 49 +/ 50 public struct Part 51 { 52 /// name of the part 53 string name; 54 /// value of the part 55 long value; 56 /// number of digits 57 int digits; 58 /// convenient tostring function. e.g. 10min 59 auto toString() 60 { 61 import std.conv; 62 63 return value.to!(string) ~ name; 64 } 65 } 66 67 /// name of the unit 68 private string name; 69 /// resolutions of the unit 70 private Scale[] scales; 71 72 public this(string name, Scale[] scales) 73 { 74 import std.exception; 75 76 this.name = name; 77 this.scales = cumulativeFold!((result, x) => scale(x.name, 78 result.factor * x.factor, x.digits))(scales).array.retro.array; 79 enforce(__ctfe); 80 } 81 82 /++ 83 + transforms the unit to its parts 84 +/ 85 public auto transform(long v) immutable 86 { 87 import std.array; 88 89 auto res = appender!(Part[]); 90 auto tmp = v; 91 foreach (Scale scale; scales) 92 { 93 auto h = tmp / scale.factor; 94 tmp = v % scale.factor; 95 res.put(Part(scale.name, h, scale.digits)); 96 } 97 return res.data; 98 } 99 100 private static auto parseNumberAndUnit(string s) 101 { 102 import std.ascii; 103 104 string value; 105 while (!s.empty) 106 { 107 auto n = s.front; 108 if (n == ' ') 109 { 110 s.popFront; 111 continue; 112 } 113 if (isDigit(n)) 114 { 115 value ~= n; 116 s.popFront; 117 } 118 else 119 { 120 break; 121 } 122 } 123 string name; 124 while (!s.empty) 125 { 126 auto n = s.front; 127 if (n == ' ') 128 { 129 s.popFront; 130 continue; 131 } 132 if (isAlpha(n)) 133 { 134 name ~= n; 135 s.popFront; 136 } 137 else 138 { 139 break; 140 } 141 } 142 143 auto rest = s; 144 import std.typecons; 145 146 if ((name.length > 0) && (value.length > 0)) 147 { 148 return tuple!("found", "name", "value", "rest")(true, name, value, rest); 149 } 150 else 151 { 152 return tuple!("found", "name", "value", "rest")(false, "", "", ""); 153 } 154 } 155 156 long parse(string s) immutable 157 { 158 import std.ascii; 159 160 long res = 0; 161 auto next = parseNumberAndUnit(s); 162 while (next.found) 163 { 164 import std.algorithm; 165 166 auto scale = scales.find!(i => i.name == next.name); 167 if (scale.empty) 168 { 169 throw new Exception("unknown unit " ~ next.name); 170 } 171 import std.conv; 172 173 res += std.conv.to!(long)(next.value) * scale.front.factor; 174 next = parseNumberAndUnit(next.rest); 175 } 176 return res; 177 } 178 } 179 180 @("parse") unittest 181 { 182 import unit_threaded; 183 184 TIME.parse("1s 2ms").shouldEqual(1002); 185 TIME.parse("1s2ms").shouldEqual(1002); 186 TIME.parse("1blub2ms").shouldThrow; 187 } 188 189 @("creatingScales") unittest 190 { 191 import unit_threaded; 192 193 auto s = Unit.scale("ttt", 1, 2); 194 s.digits.shouldEqual(2); 195 196 s = Unit.scale("ttt2", 1); 197 s.digits.shouldEqual(1); 198 } 199 200 /++ 201 + get only relevant parts of an part array. 202 + relevant means all details starting from the first 203 + non 0 part. 204 +/ 205 auto onlyRelevant(Unit.Part[] parts) 206 { 207 import std.array; 208 209 auto res = appender!(Unit.Part[]); 210 bool needed = false; 211 foreach (part; parts) 212 { 213 if (needed || (part.value > 0)) 214 { 215 needed = true; 216 } 217 if (needed) 218 { 219 res.put(part); 220 } 221 } 222 return res.data; 223 } 224 225 /++ 226 + get the first nr of parts (or less if not enough parts are available). 227 +/ 228 auto mostSignificant(Unit.Part[] parts, long nr) 229 { 230 import std.algorithm.comparison; 231 232 auto max = min(parts.length, nr); 233 return parts[0 .. max]; 234 } 235 236 /++ 237 + example for a time unit definition 238 +/ 239 @("basicUsage") unittest 240 { 241 import unit_threaded; 242 243 auto res = TIME.transform(1 + 2 * 1000 + 3 * 1000 * 60 + 4 * 1000 * 60 * 60 244 + 5 * 1000 * 60 * 60 * 24); 245 res.length.shouldEqual(5); 246 res[0].name.shouldEqual("d"); 247 res[0].value.shouldEqual(5); 248 res[1].name.shouldEqual("h"); 249 res[1].value.shouldEqual(4); 250 res[2].name.shouldEqual("m"); 251 res[2].value.shouldEqual(3); 252 res[3].name.shouldEqual("s"); 253 res[3].value.shouldEqual(2); 254 res[4].name.shouldEqual("ms"); 255 res[4].value.shouldEqual(1); 256 257 res = TIME.transform(2001).onlyRelevant; 258 res.length.shouldEqual(2); 259 res[0].name.shouldEqual("s"); 260 res[0].value.shouldEqual(2); 261 res[1].name.shouldEqual("ms"); 262 res[1].value.shouldEqual(1); 263 264 res = TIME.transform(2001).onlyRelevant.mostSignificant(1); 265 res.length.shouldEqual(1); 266 res[0].name.shouldEqual("s"); 267 res[0].value.shouldEqual(2); 268 } 269 270 // dfmt off 271 static immutable TIME = 272 Unit("time", 273 [Unit.Scale("ms", 1), 274 Unit.Scale("s", 1000), 275 Unit.Scale("m", 60), 276 Unit.Scale("h", 60), 277 Unit.Scale("d", 24)]); 278 // dfmt on