1 // yamkeys - a runtime configuration management utility. 2 // Copyright © 2013 Nathan M. Swan 3 // Available under the MIT Expat License, see LICENSE file. 4 // Written in the D Programming Language 5 6 /++ 7 + Simple configuration management based on YAML. 8 + 9 + Authors: Nathan M. Swan, nathanmswan@gmail.com 10 + License: MIT (Expat) License 11 + Copyright: Copyright © 2013 Nathan M. Swan 12 +/ 13 14 module yamkeys; 15 16 private: 17 import std.algorithm; 18 import std.array; 19 import std.file; 20 import std.getopt; 21 import std.string; 22 import std.traits; 23 import std.path; 24 25 import dyaml.all; 26 27 public: 28 /// The global configuration variable. 29 __gshared Configuration config; 30 31 shared static this() { 32 config = new Configuration(); 33 } 34 35 /// Creates code which declares a variable of type T which is loaded with 36 /// configurations by calling config.load, 37 /// as well as a function of the given name which, given config, returns the 38 /// variable. 39 string configure(T, string name = defaultName!T)() { 40 import std.array; 41 import std.string; 42 43 string code = 44 `private __gshared TYPE _vcyam_NAME; `~ 45 `shared static this() { config.load(_vcyam_NAME, "NAME"); } `~ 46 `public @property TYPE NAME(yamkeys.Configuration c) { `~ 47 `return _vcyam_NAME; }`; 48 49 code = replace(code, `TYPE`, T.stringof); 50 code = replace(code, `NAME`, name); 51 52 return code; 53 } 54 55 /// The default key name for a type: its lowercased local name. 56 template defaultName(T) { 57 enum defaultName = splitter(T.stringof.toLower(), '.').back; 58 } 59 60 /// Holds the state for the configuration files. 61 final class Configuration { 62 public: 63 /// Attempts to place the YAML data at the key label into obj. 64 /// This is usually already done by the configure template. 65 void load(T)(ref T obj, string label = defaultName!T) { 66 static if (__traits(compiles, new T()) && is(T == class)) { 67 if (obj is null) { 68 obj = new T(); 69 } 70 } 71 72 foreach(Node cfg; evaluate(label)) { 73 load!T(obj, cfg); 74 } 75 } 76 77 private: 78 bool configFolderExists; 79 Node yConfig; 80 Node yLocal; 81 82 Node emptyYamlMap; 83 84 string scName = null; Node* scInL=null, scInC=null; 85 string dcName = null; Node* dcInL=null, dcInC=null; 86 87 88 this() { 89 import core.runtime; 90 import std.stdio; 91 92 string[string] mp; 93 emptyYamlMap = Node(mp); 94 95 bool configFolderExists = "config".exists && "config".isDir; 96 yConfig = getFromYamlFile("config", configFolderExists, "default"); 97 yLocal = getFromYamlFile("local", configFolderExists); 98 99 string[] args = Runtime.args().dup; 100 getopt(args, std.getopt.config.passThrough, "config", &scName); 101 if (scName is null) { 102 if (auto cp = "config" in yLocal) { 103 scName = cp.as!string; 104 } 105 } 106 107 if (auto cp = "default" in yLocal) { 108 dcName = cp.as!string; 109 } else if (auto cp = "default" in yConfig) { 110 dcName = cp.as!string; 111 } else { 112 enum NoDefConf = "No default configuration is specified."; 113 version (Have_vibe_d) { 114 import vibe.core.log; 115 logWarn(NoDefConf); 116 } else { 117 import std.stdio; 118 stderr.writeln(NoDefConf); 119 } 120 } 121 122 if (yLocal == emptyYamlMap && scName !is null) { 123 yLocal["config"] = scName; 124 Dumper(configFolderExists ? "config/local.yml" : "local.yml") 125 .dump(yLocal); 126 } 127 128 if (scName !is null) { 129 scInL = scName in yLocal; 130 scInC = scName in yConfig; 131 } 132 dcInL = dcName in yLocal; 133 dcInC = dcName in yConfig; 134 } 135 136 Node getFromYamlFile(string name, bool configFolderExists, 137 string folderName=null) 138 { 139 auto namesToCheck = [name ~ ".yml", name ~ ".yaml"]; 140 if (configFolderExists) { 141 alias ds = dirSeparator; 142 if (folderName is null) folderName = name; 143 namesToCheck ~= ["config" ~ ds ~ folderName ~ ".yml", 144 "config" ~ ds ~ folderName ~ ".yaml"]; 145 } 146 147 foreach(fname; namesToCheck) { 148 if (fname.exists) { 149 return Loader(fname).load(); 150 } 151 } 152 return emptyYamlMap; 153 } 154 155 Node[] evaluate(string key) { 156 Node[] configs; 157 158 if (dcInC) 159 if (auto val = key in *dcInC) 160 configs ~= *val; 161 if (dcInL) 162 if (auto val = key in *dcInL) 163 configs ~= *val; 164 if (scInC) 165 if (auto val = key in *scInC) 166 configs ~= *val; 167 if (scInL) 168 if (auto val = key in *scInL) 169 configs ~= *val; 170 171 return configs; 172 } 173 174 void load(T)(ref T obj, Node node) { 175 static if (isBasicType!T && !is(T == enum) || isSomeString!T) { 176 obj = node.get!T; 177 } 178 else static if (isDynamicArray!T) { 179 obj = []; 180 obj.reserve(node.length); 181 foreach(ref Node e; node) { 182 typeof(obj[0]) obje; 183 load!(typeof(obje))(obje, e); 184 obj ~= obje; 185 } 186 } 187 else static if (isAssociativeArray!T) { 188 T map; 189 obj = map; 190 foreach(ref Node k, ref Node v; node) { 191 KeyType!T objk; 192 load!(KeyType!T)(objk, k); 193 194 ValueType!T objv; 195 load!(ValueType!T)(objv, v); 196 197 obj[objk] = objv; 198 } 199 } 200 else static if (isAggregateType!T) { 201 static if (__traits(compiles, new T()) && is(T == class)) { 202 if (obj is null) { 203 obj = new T(); 204 } 205 } 206 207 alias FT = FieldTypeTuple!T; 208 209 enum src = ({ 210 string res; 211 foreach(mb; [__traits(allMembers, T)]) { 212 res ~= `if (auto mbPtr = "`~mb~`" in node) {`; 213 string tpof = `typeof(obj.`~mb~`)`; 214 string loadStmt = 215 tpof~` t = `~tpof~`.init; `~ 216 `load!(`~tpof~`)`~ 217 `(t, node["`~mb~`"]);`~ 218 `obj.`~mb~` = t;`; 219 res ~= " 220 static if (__traits(compiles, obj."~mb~"=typeof(obj."~mb~").init)) { 221 mixin(`"~loadStmt~"`); } 222 }"; 223 } 224 return res; 225 })(); 226 227 mixin(src); 228 } 229 } 230 } 231 232 class YamkeysException : Exception { 233 this(string msg, string file=__FILE__, int line=__LINE__, Throwable t=null) { 234 super(msg, file, line, t); 235 } 236 }