1 module d_properties.properties; 2 3 import object : Error; 4 import d_properties.reader; 5 import d_properties.writer; 6 7 /** 8 * The properties is a struct containing key-value pairs of strings, which can 9 * be easily and efficiently written to and read from a file. 10 */ 11 public struct Properties { 12 /** 13 * The internal values of this properties struct. 14 */ 15 public string[string] values; 16 17 /** 18 * Constructs a Properties from the given values. 19 * Params: 20 * valueMap = The associative array of properties. 21 */ 22 public this(string[string] valueMap) { 23 foreach (key, value; valueMap) { 24 this.opIndexAssign(value, key); 25 } 26 } 27 28 /** 29 * Constructs a Properties by reading from each of the given files, in the 30 * order that they're provided. Note that properties in an earlier file 31 * will be overwritten by properties of the same key in later files. 32 * Params: 33 * filenames = The list of filenames. 34 */ 35 public this(string[] filenames ...) { 36 foreach (filename; filenames) { 37 Properties p = readFromFile(filename); 38 this.addAll(p); 39 } 40 } 41 42 /** 43 * Checks if the given property exists within this set. 44 * Params: 45 * key = The property name. 46 * Returns: True if the property exists. 47 */ 48 public bool has(string key) { 49 return cast(bool) (key in values); 50 } 51 52 /** 53 * Gets the value of a property, or returns the specified default value if 54 * the given property doesn't exist. 55 * Params: 56 * key = The property name. 57 * defaultValue = The default value to use, if no property exists. 58 * Returns: The value of the property, or the default value if the property 59 * doesn't exist. 60 */ 61 public string get(string key, string defaultValue=null) const { 62 if (key !in values) return defaultValue; 63 return values[key]; 64 } 65 66 /** 67 * Gets a property's value as a certain type. If the property does not 68 * exist, a `MissingPropertyException` is thrown. If the conversion could 69 * not be performed, a `std.conv.ConvException` is thrown. 70 * Params: 71 * key = The property name. 72 * Returns: The value of the property. 73 */ 74 public T get(T)(string key) const { 75 import std.conv : to; 76 if (key !in values) throw new MissingPropertyException(key); 77 return to!(T)(values[key]); 78 } 79 80 /** 81 * Gets a property's value as a certain type. If the property does not 82 * exist, a default value is returned. If the conversion could not be 83 * performed, a `std.conv.ConvException` is thrown. 84 * Params: 85 * key = The property name. 86 * defaultValue = The default value to use, if no property exists. 87 * Returns: The value of the property, or the default value if the property 88 * doesn't exist. 89 */ 90 public T get(T)(string key, T defaultValue) const { 91 import std.conv : to; 92 if (key !in values) return defaultValue; 93 return to!(T)(values[key]); 94 } 95 96 /** 97 * Adds all properties from the given Properties to this one, overwriting 98 * any properties with the same name. 99 * Params: 100 * properties = The properties to add to this one. 101 */ 102 public void addAll(Properties[] properties ...) { 103 foreach (p; properties) { 104 foreach (key, value; p.values) { 105 this.values[key] = value; 106 } 107 } 108 } 109 110 /** 111 * Adds all properties from the given files to this one. 112 * Params: 113 * filenames = The names of files to read properties from. 114 */ 115 public void addAll(string[] filenames ...) { 116 auto p = Properties(filenames); 117 this.addAll(p); 118 } 119 120 /** 121 * Gets a new set of properties containing only those whose names match 122 * the given prefix. 123 * Params: 124 * prefix = The prefix to get properties for. 125 * Returns: A new properties containing any properties that match the given 126 * prefix. 127 */ 128 public Properties getAll(string prefix) { 129 import std.algorithm : startsWith; 130 Properties p; 131 foreach (name, value; this.values) { 132 if (name.startsWith(prefix) && name.length > prefix.length) { 133 size_t idx = prefix.length; 134 if (name[idx] == '.' && name.length > prefix.length + 1) idx++; 135 p[name[idx .. $]] = value; 136 } 137 } 138 return p; 139 } 140 141 /** 142 * Gets a set of properties whose names match the given prefix, and uses 143 * them to populate a struct of the given type. 144 * Params: 145 * prefix = The prefix to get properties for. 146 * Returns: An instance of the given struct type. 147 */ 148 public T getAll(T)(string prefix) { 149 static if (!__traits(isPOD, T)) { 150 assert(0, "Only Plain Old Data structs may be used to get all."); 151 } 152 import std.traits; 153 import std.conv : to; 154 auto props = getAll(prefix); 155 T t; 156 foreach (member; __traits(allMembers, T)) { 157 if (props.has(member)) { 158 alias membertype = typeof(mixin("T()."~member)); 159 __traits(getMember, t, member) = to!(membertype)(props[member]); 160 } 161 } 162 return t; 163 } 164 165 /** 166 * Gets the value of a property, or throws a missing property exception. 167 * Params: 168 * key = The property name. 169 * Returns: The value of the property. 170 */ 171 string opIndex(string key) const { 172 if (key !in values) throw new MissingPropertyException(key); 173 return values[key]; 174 } 175 176 /** 177 * Assigns the given value to a property. 178 * Params: 179 * value = The value of the property. 180 * key = The property name. 181 */ 182 void opIndexAssign(string value, string key) { 183 values[key] = value; 184 } 185 186 /** 187 * Determines if this properties object is equal to the other. 188 * Params: 189 * other = The other properties to check for equality with. 190 * Returns: True if these properties are the same as those given. 191 */ 192 bool opEquals(const Properties other) const { 193 return this.values == other.values; 194 } 195 196 /** 197 * Implementation of the binary "in" operator to determine if a property is 198 * defined for this properties object. 199 * Params: 200 * key = The name of a property. 201 * Returns: True if the property exists in this properties object. 202 */ 203 bool opBinaryRight(string op)(string key) const if (op == "in") { 204 return (key in this.values) != null; 205 } 206 } 207 208 /** 209 * Exception that's thrown when attempting to index an unknown key. 210 */ 211 class MissingPropertyException : Error { 212 this(string missingKey) { 213 super("Missing value for key \"" ~ missingKey ~ "\"."); 214 } 215 }