1 /** 2 Copyright: Copyright (c) 2015-2018 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 voxelman.log; 9 import std.file : read, exists; 10 public import std.variant; 11 import std.traits : isArray; 12 import std.conv : to; 13 14 import pluginlib; 15 import sdlang; 16 17 alias ConfigValue = Algebraic!(int, double, string, bool, int[], double[]); 18 19 final class ConfigOption 20 { 21 this(ConfigValue value, ConfigValue defaultValue) 22 { 23 this.value = value; 24 this.defaultValue = defaultValue; 25 } 26 27 T get(T)() 28 { 29 static if (isArray!T) 30 return value.get!T(); 31 else 32 return value.coerce!T(); 33 } 34 35 T set(T)(T newValue) 36 { 37 value = newValue; 38 return newValue; 39 } 40 41 ConfigValue value; 42 ConfigValue defaultValue; 43 } 44 45 final class ConfigManager : IResourceManager 46 { 47 private: 48 ConfigOption[string] options; 49 string filename; 50 string[] args; 51 52 public: 53 54 override string id() @property { return "voxelman.managers.configmanager"; } 55 56 this(string filename, string[] args) 57 { 58 this.filename = filename; 59 this.args = args; 60 } 61 62 override void loadResources() 63 { 64 load(); 65 } 66 67 // Runtime options are not saved. Use them to store global options that need no saving 68 ConfigOption registerOption(T)(string optionName, T defaultValue) 69 { 70 if (auto opt = optionName in options) 71 return *opt; 72 auto option = new ConfigOption(ConfigValue(defaultValue), ConfigValue(defaultValue)); 73 options[optionName] = option; 74 return option; 75 } 76 77 ConfigOption opIndex(string optionName) 78 { 79 return options.get(optionName, null); 80 } 81 82 void load() 83 { 84 bool readConfigFile = exists(filename); 85 Tag root; 86 87 if (readConfigFile) 88 try 89 { 90 string fileData = cast(string)read(filename); 91 root = parseSource(fileData, filename); 92 } 93 catch(ParseException 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 }