1 /** 2 Copyright: Copyright (c) 2015-2016 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.config.configmanager; 7 8 import std.experimental.logger; 9 import std.file : read, exists; 10 public import std.variant; 11 import std.traits : isArray; 12 import std.conv : to; 13 import core.runtime : Runtime; 14 15 import pluginlib; 16 import sdlang; 17 18 alias ConfigValue = Algebraic!(int, double, string, bool, int[], double[]); 19 20 final class ConfigOption 21 { 22 this(ConfigValue value, ConfigValue defaultValue) 23 { 24 this.value = value; 25 this.defaultValue = defaultValue; 26 } 27 28 T get(T)() 29 { 30 static if (isArray!T) 31 return value.get!T(); 32 else 33 return value.coerce!T(); 34 } 35 36 T set(T)(T newValue) 37 { 38 value = newValue; 39 return newValue; 40 } 41 42 ConfigValue value; 43 ConfigValue defaultValue; 44 } 45 46 final class ConfigManager : IResourceManager 47 { 48 private: 49 ConfigOption[string] options; 50 string filename; 51 52 public: 53 54 override string id() @property { return "voxelman.managers.configmanager"; } 55 56 this(string filename) 57 { 58 this.filename = filename; 59 } 60 61 override void loadResources() 62 { 63 load(); 64 } 65 66 // Runtime options are not saved. Use them to store global options that need no saving 67 ConfigOption registerOption(T)(string optionName, T defaultValue) 68 { 69 if (auto opt = optionName in options) 70 return *opt; 71 auto option = new ConfigOption(ConfigValue(defaultValue), ConfigValue(defaultValue)); 72 options[optionName] = option; 73 return option; 74 } 75 76 ConfigOption opIndex(string optionName) 77 { 78 return options.get(optionName, null); 79 } 80 81 void load() 82 { 83 bool readConfigFile = exists(filename); 84 Tag root; 85 string[] args = Runtime.args; 86 87 if (readConfigFile) 88 try 89 { 90 string fileData = cast(string)read(filename); 91 root = parseSource(fileData, filename); 92 } 93 catch(SDLangParseException e) 94 { 95 warning(e.msg); 96 return; 97 } 98 99 auto tempSep = std.getopt.arraySep; 100 std.getopt.arraySep = ","; 101 foreach(optionPair; options.byKeyValue) 102 { 103 if (readConfigFile) 104 { 105 parseValueFromConfig(optionPair.key, optionPair.value, root); 106 } 107 108 // override from console 109 parseValueFromCmd(optionPair.key, optionPair.value, args); 110 } 111 std.getopt.arraySep = tempSep; 112 } 113 114 void save() {} 115 116 private: 117 118 static void parseValueFromConfig(string optionName, ConfigOption option, Tag root) 119 { 120 if (optionName !in root.tags) return; 121 auto tags = root.tags[optionName]; 122 123 if (tags.length == 1) 124 { 125 try 126 { 127 parseValue(optionName, option, tags[0].values); 128 //infof("%s %s %s", optionName, option.value, option.defaultValue); 129 } 130 catch(VariantException e) 131 { 132 warningf("Error parsing config option: %s - %s", optionName, e.msg); 133 } 134 } 135 else if (tags.length > 1) 136 warningf("Multiple definitions of '%s'", optionName); 137 else 138 warningf("Empty option '%s'", optionName); 139 } 140 141 static void parseValue(string optionName, ConfigOption option, Value[] values) 142 { 143 if (values.length == 1) 144 { 145 Value value = values[0]; 146 147 if (option.value.type == typeid(bool)) { 148 option.value = ConfigValue(value.coerce!bool); 149 } 150 else if (option.value.type == typeid(string)) { 151 option.value = ConfigValue(value.coerce!string); 152 } 153 else if (option.value.type == typeid(int)) { 154 option.value = ConfigValue(value.coerce!int); 155 } 156 else if (option.value.type == typeid(double)) { 157 option.value = ConfigValue(value.coerce!double); 158 } 159 else 160 { 161 warningf("Cannot parse '%s' from '%s'", optionName, value.to!string); 162 } 163 } 164 else if (values.length > 1) 165 { 166 void parseArray(T)() 167 { 168 T[] items; 169 foreach(v; values) 170 items ~= v.coerce!T; 171 option.value = ConfigValue(items); 172 } 173 if (option.value.length != values.length) 174 return; 175 176 if (option.value.type == typeid(int[])) { 177 parseArray!int; 178 } else if (option.value.type == typeid(double[])) { 179 parseArray!double; 180 } else { 181 warningf("Cannot parse '%s' from '%s'", optionName, values.to!string); 182 } 183 //infof("conf %s %s", optionName, option.value); 184 } 185 } 186 187 static void parseValueFromCmd(string optionName, ConfigOption option, string[] args) 188 { 189 if (option.value.type == typeid(int)) { 190 parseSingle!int(optionName, option, args); 191 } 192 else if (option.value.type == typeid(double)) { 193 parseSingle!double(optionName, option, args); 194 } 195 else if (option.value.type == typeid(string)) { 196 parseSingle!string(optionName, option, args); 197 } 198 else if (option.value.type == typeid(bool)) { 199 parseSingle!bool(optionName, option, args); 200 } 201 else if (option.value.type == typeid(int[])) { 202 parseSingle!(int[])(optionName, option, args); 203 } 204 else if (option.value.type == typeid(double[])) { 205 parseSingle!(double[])(optionName, option, args); 206 } 207 } 208 } 209 210 import std.getopt; 211 void parseSingle(T)(string optionName, ConfigOption option, string[] args) 212 { 213 T val = option.get!T; 214 //infof("cmd1 %s %s", optionName, val); 215 216 static if(is(T == int[]) || is(T == double[])) 217 { 218 T newArray; 219 auto res = getopt(args, std.getopt.config.passThrough, optionName, &newArray); 220 if (newArray != newArray.init) 221 val = newArray; 222 } 223 else 224 { 225 getopt(args, std.getopt.config.passThrough, optionName, &val); 226 } 227 //infof("cmd2 %s %s", optionName, val); 228 229 option.value = ConfigValue(val); 230 }