1 module config.setting; 2 3 import config.config : Config, InconsistentConfigState; 4 import config.util : unsafeCast; 5 6 import std.traits : isIntegral, isFloatingPoint, isSomeString; 7 import std.typecons : Nullable; 8 9 /// The type of a Setting 10 enum Type { 11 None, Int, Float, String, Bool, Group, Array, List 12 } 13 14 /// Integer format used when writing a file 15 enum IntegerFormat { 16 Dec, Hex 17 } 18 19 /// template that checks if T is a possible scalar candidate 20 enum isScalarCandidate(T) = is(T == bool) || isIntegral!T || isFloatingPoint!T || isSomeString!T; 21 22 23 /// returns true if a setting with Type type can hold a value of type T. 24 bool isScalarCompatible(T)(in Type type) pure { 25 switch(type) { 26 case Type.Bool: 27 return is(T == bool); 28 case Type.Int: 29 return isIntegral!T; 30 case Type.Float: 31 return isFloatingPoint!T; 32 case Type.String: 33 return isSomeString!T; 34 default: 35 return false; 36 } 37 } 38 39 template scalarType(T) 40 if (isScalarCandidate!T) 41 { 42 static if (is(T == bool)) { 43 enum scalarType = Type.Bool; 44 } 45 else static if (isIntegral!T) { 46 enum scalarType = Type.Int; 47 } 48 else static if (isFloatingPoint!T) { 49 enum scalarType = Type.Float; 50 } 51 else static if (isSomeString!T) { 52 enum scalarType = Type.String; 53 } 54 } 55 56 57 @property bool isScalar(in Type type) 58 { 59 return cast(int)type > Type.None && cast(int)type < Type.Group; 60 } 61 @property bool isAggregate(in Type type) 62 { 63 return cast(int)type >= Type.Group; 64 } 65 @property bool isNumber(in Type type) 66 { 67 return type == Type.Int || type == Type.Float; 68 } 69 70 template TypedSetting(Type type) 71 { 72 static if (type == Type.Bool || type == Type.Int || type == Type.Float || type == Type.String) 73 { 74 alias TypedSetting = ScalarSetting; 75 } 76 else static if (type == Type.Array) 77 { 78 alias TypedSetting = ArraySetting; 79 } 80 else static if (type == Type.List) 81 { 82 alias TypedSetting = ListSetting; 83 } 84 else static if (type == Type.Group) 85 { 86 alias TypedSetting = GroupSetting; 87 } 88 else static assert(false); 89 } 90 91 /// Setting type. Has a name and value which can be of any of Type 92 abstract class Setting { 93 94 @property string name() const { return _name; } 95 @property Type type() const { return _type; } 96 97 @property inout(Config) config() inout { return _config; } 98 @property inout(AggregateSetting) parent() inout { return _parent; } 99 100 inout(Setting) child(in size_t idx) inout { return null; } 101 inout(Setting) child(in string name) inout { return null; } 102 @property inout(Setting)[] children() inout { return []; } 103 104 bool remove(in size_t idx) { return false; } 105 bool remove(in string path) { return false; } 106 107 inout(Setting) lookUp(in string path) inout { return null; } 108 Nullable!T lookUpValue(T)(in string path) const { 109 if (auto s = lookUp(path)) { 110 if (auto ss = s.asScalar) { 111 return Nullable!T(ss.value!T); 112 } 113 } 114 return Nullable!T.init; 115 } 116 117 @property size_t index() const { 118 import std.algorithm : countUntil; 119 if (!parent) return -1; 120 return countUntil(parent._children, this); 121 } 122 123 @property bool isGroup() const { 124 return _type == Type.Group; 125 } 126 @property bool isArray() const { 127 return _type == Type.Array; 128 } 129 @property bool isList() const { 130 return _type == Type.List; 131 } 132 @property bool isScalar() const { 133 return _type.isScalar; 134 } 135 @property bool isAggregate() const { 136 return _type.isAggregate; 137 } 138 @property bool isNumber() const { 139 return _type.isNumber; 140 } 141 @property bool isRoot() const { 142 return _parent is null; 143 } 144 145 @property auto asScalar() inout { 146 return cast(ScalarSetting)this; 147 } 148 @property auto asArray() inout { 149 return cast(ArraySetting)this; 150 } 151 @property auto asList() inout { 152 return cast(ListSetting)this; 153 } 154 @property auto asGroup() inout { 155 return cast(GroupSetting)this; 156 } 157 @property auto as(Type t)() inout { 158 return cast(TypedSetting!t)this; 159 } 160 161 @property IntegerFormat integerFormat() const { 162 if (!_integerFormat.isNull) return _integerFormat; 163 if (parent) return parent.integerFormat; 164 return config.defaultIntegerFormat; 165 } 166 @property void integerFormat(Nullable!IntegerFormat format) { 167 _integerFormat = format; 168 } 169 170 package { 171 this(Config config, AggregateSetting parent, string name, Type type) { 172 _config = config; 173 _parent = parent; 174 _name = name; 175 _type = type; 176 } 177 } 178 179 private { 180 string _name; 181 Type _type; 182 Nullable!IntegerFormat _integerFormat; 183 184 Config _config; 185 AggregateSetting _parent; 186 string _file; 187 int _lineNumber; 188 } 189 } 190 191 /// Setting that holds a scalar value that can be one of Bool, Float, Integer or String 192 class ScalarSetting : Setting { 193 194 @property T value(T)() const 195 if (isScalarCandidate!T) 196 { 197 import std.exception : enforce; 198 import std.conv : to; 199 200 static if (isIntegral!T) 201 { 202 auto val = _value.get!long; 203 static if (!is(T == long) && T.sizeof<=long.sizeof) 204 { 205 enforce(val >= T.min && val <= T.max, 206 "cannot cast "~val.to!string~" to "~T.stringof); 207 } 208 return cast(T)val; 209 } 210 else static if (isFloatingPoint!T) 211 { 212 auto val = _value.get!double; 213 static if (!is(T == double) && T.sizeof<double.sizeof) // should be float unless a half type pops up 214 { 215 import std.math : abs; 216 enforce(abs(val) >= T.min_normal && abs(val) <= T.max, 217 "cannot cast "~val.to!string~" to "~T.stringof); 218 } 219 return cast(T)val; 220 } 221 else static if (isSomeString!T) 222 { 223 import std.conv : to; 224 return (_value.get!string).to!T; 225 } 226 else static if (is(T == bool)) { 227 return _value.get!T; 228 } 229 else static assert (false); 230 } 231 232 @property void value(T)(T val) 233 if (isScalarCandidate!T) 234 { 235 _type = scalarType!T; 236 static if (isIntegral!T) 237 { 238 static if (!is(T == long) && T.sizeof>=long.sizeof) 239 { 240 enforce(val >= long.min && val <= long.max, 241 "cannot cast "~val.to!string~" to long"); 242 } 243 _value = cast(long)val; 244 } 245 else static if (isFloatingPoint!T) 246 { 247 static if (T.sizeof>double.sizeof) 248 { 249 import std.math : abs; 250 enforce(abs(val) >= double.min_normal && abs(val) <= double.max, 251 "cannot cast "~val.to!string~" to double"); 252 } 253 _value = cast(double)val; 254 } 255 else static if (isSomeString!T) 256 { 257 import std.conv : to; 258 _value = val.to!string; 259 } 260 else static if (is(T == bool)) 261 { 262 _value = val; 263 } 264 else static assert(false); 265 } 266 267 268 package { 269 this(Config config, AggregateSetting parent, string name, Type type) { 270 assert(type.isScalar); 271 super(config, parent, name, type); 272 } 273 } 274 private { 275 import std.variant : Algebraic; 276 277 alias Value = Algebraic!(bool, long, double, string); 278 Value _value; 279 } 280 } 281 282 283 class AggregateSetting : Setting { 284 285 286 override inout(Setting) child(in size_t idx) inout 287 { 288 if (idx >= _children.length) return null; 289 return _children[idx]; 290 } 291 292 override inout(Setting) child(in string name) inout 293 { 294 import std.exception : enforce; 295 enforce(type == Type.Group, "only GroupSetting can have named children"); 296 foreach (c; _children) { 297 if (c.name == name) return c; 298 } 299 return null; 300 } 301 302 override @property inout(Setting)[] children() inout 303 { 304 return _children; 305 } 306 307 override bool remove(in size_t idx) { 308 import std.algorithm : remove; 309 310 size_t origLen = _children.length; 311 _children = remove(_children, idx); 312 return origLen != _children.length; 313 } 314 315 override bool remove(in string path) { 316 import config.config : pathTok; 317 import config.util : findSplitAmong; 318 import std.range : empty; 319 320 if (path.empty) return false; 321 322 immutable split = path.findSplitAmong(pathTok); 323 324 auto s = getChild(split[0]); 325 if (!s) return false; 326 327 if (!split[2].empty) { 328 return s.remove(split[2]); 329 } 330 else { 331 import std.algorithm : remove; 332 auto origLen = _children.length; 333 _children = remove!(c => c is s)(_children); 334 return _children.length != origLen; 335 } 336 } 337 338 override inout(Setting) lookUp(in string name) inout { 339 import config.config : pathTok; 340 import config.util : findSplitAmong; 341 import std.range : empty; 342 343 if (name.empty) return this; 344 345 immutable split = name.findSplitAmong(pathTok); 346 auto s = getChild(split[0]); 347 if (!split[2].empty && s) return s.lookUp(split[2]); 348 else return s; 349 } 350 351 package { 352 this(Config config, AggregateSetting parent, string name, Type type) { 353 assert(type.isAggregate); 354 super(config, parent, name, type); 355 } 356 357 void addChild(Setting child) { 358 assert(child.config is config && child.parent is this); 359 debug { 360 if (type == Type.Group) { 361 import std.algorithm : map, canFind; 362 assert(!_children.map!(c => c.name).canFind(child.name)); 363 } 364 } 365 _children ~= child; 366 } 367 368 void setChildren(Setting[] children) { 369 import std.algorithm : map, all; 370 import std.exception : enforce; 371 assert(children.map!(c => c.config).all!(cf => cf == config)); 372 assert(children.map!(c => c.parent).all!(p => p == this)); 373 374 if (type == Type.Group) { 375 bool[string] seen; 376 foreach (c; children) { 377 enforce(!(c.name in seen), 378 new InconsistentConfigState("more than one child named \""~c.name~ 379 "\" in GroupSetting \""~name~"\"")); 380 seen[c.name] = true; 381 } 382 } 383 384 _children = children; 385 } 386 } 387 388 private { 389 390 Setting addChild(string name, Type type) { 391 auto s = config.makeSetting(this, name, type); 392 if (s) _children ~= s; 393 return s; 394 } 395 396 inout(Setting) getChild(string name) inout { 397 import std.algorithm : find; 398 import std.exception : enforce; 399 import std.range : empty; 400 import std.conv : to; 401 402 auto square = find(name, '['); 403 auto childName = name[0 .. $-square.length]; 404 auto child = child(childName); 405 406 if (square.empty) return child; 407 408 assert(square[0] == '['); 409 enforce(square[$-1] == ']'); 410 411 immutable ind = to!size_t(name[1 .. $-1]); 412 return child.child(ind); 413 } 414 415 Setting[] _children; 416 } 417 } 418 419 420 class ArraySetting : AggregateSetting { 421 422 Setting add(Type type) { 423 import std.exception : enforce; 424 enforce(type.isScalar); 425 enforce(_children.length == 0 || _children[0].type == type); 426 427 return addChild("", type); 428 } 429 430 auto add(Type type)() { 431 import std.exception : enforce; 432 static assert(type.isScalar); 433 enforce(_children.length == 0 || _children[0].type == type); 434 435 return unsafeCast!(TypedSetting!type)(addChild("", type)); 436 } 437 438 ScalarSetting addScalar(T)(in T value) 439 if (isScalarCandidate!T) 440 { 441 import std.exception : enforce; 442 immutable type = scalarType!T; 443 enforce(_children.length == 0 || _children[0].type == type); 444 445 auto ss = unsafeCast!(ScalarSetting)(addChild("", type)); 446 ss.value = value; 447 return ss; 448 } 449 450 package { 451 this(Config config, AggregateSetting parent, string name) { 452 super(config, parent, name, Type.Array); 453 } 454 } 455 } 456 457 458 class ListSetting : AggregateSetting { 459 460 Setting add(Type type) { 461 return addChild("", type); 462 } 463 464 auto add(Type type)() 465 { 466 return unsafeCast!(TypedSetting!type)(addChild("", type)); 467 } 468 469 ScalarSetting addScalar(T)(in T value) 470 if (isScalarCandidate!T) 471 { 472 auto ss = unsafeCast!(ScalarSetting)(addChild("", scalarType!T)); 473 ss.value = value; 474 return ss; 475 } 476 477 package { 478 this(Config config, AggregateSetting parent, string name) { 479 super(config, parent, name, Type.List); 480 } 481 } 482 } 483 484 485 class GroupSetting : AggregateSetting { 486 487 Setting add(in string name, in Type type) { 488 import std.exception : enforce; 489 enforce(validateName(name)); 490 enforce(!child(name)); 491 return addChild(name, type); 492 } 493 494 auto add(Type type)(in string name) 495 { 496 import std.exception : enforce; 497 enforce(validateName(name)); 498 enforce(!child(name)); 499 return unsafeCast!(TypedSetting!type)(addChild(name, type)); 500 } 501 502 ScalarSetting addScalar(T)(in string name, in T value) 503 if (isScalarCandidate!T) 504 { 505 import std.exception : enforce; 506 enforce(validateName(name)); 507 enforce(!child(name)); 508 509 auto ss = unsafeCast!(ScalarSetting)(addChild(name, scalarType!T)); 510 ss.value = value; 511 return ss; 512 } 513 514 package { 515 this(Config config, AggregateSetting parent, string name) { 516 super(config, parent, name, Type.Group); 517 } 518 } 519 } 520 521 522 private: 523 524 bool validateName(in string name) pure { 525 import std.utf : byDchar; 526 import std.uni : isAlpha; 527 import std.ascii : isDigit; 528 import std.algorithm : canFind; 529 530 if (name.length == 0) return false; 531 532 auto dec = name.byDchar; 533 534 if (!dec.front.isAlpha && dec.front != '*') return false; 535 dec.popFront(); 536 537 foreach(dchar c; dec) { 538 if (!c.isAlpha && !c.isDigit && !"*_-"d.canFind(c)) { 539 return false; 540 } 541 } 542 return true; 543 }