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 
14 import pluginlib;
15 import sdlang;
16 
17 final class ConfigOption
18 {
19 	this(Variant value, Variant defaultValue)
20 	{
21 		this.value = value;
22 		this.defaultValue = defaultValue;
23 	}
24 
25 	T get(T)()
26 	{
27 		static if (isArray!T)
28 			return value.get!T();
29 		else
30 			return value.coerce!T();
31 	}
32 
33 	T set(T)(T newValue)
34 	{
35 		value = newValue;
36 		return newValue;
37 	}
38 
39 	Variant value;
40 	Variant defaultValue;
41 }
42 
43 final class ConfigManager : IResourceManager
44 {
45 private:
46 	ConfigOption[string] options;
47 	string filename;
48 
49 public:
50 
51 	override string id() @property { return "voxelman.managers.configmanager"; }
52 
53 	this(string filename)
54 	{
55 		this.filename = filename;
56 	}
57 
58 	override void loadResources()
59 	{
60 		load();
61 	}
62 
63 	// Runtime options are not saved. Use them to store global options that need no saving
64 	ConfigOption registerOption(T)(string optionName, T defaultValue)
65 	{
66 		if (auto opt = optionName in options)
67 			return *opt;
68 		auto option = new ConfigOption(Variant(defaultValue), Variant(defaultValue));
69 		options[optionName] = option;
70 		return option;
71 	}
72 
73 	ConfigOption opIndex(string optionName)
74 	{
75 		return options.get(optionName, null);
76 	}
77 
78 	void load()
79 	{
80 		if (!exists(filename))
81 			return;
82 
83 		Tag root;
84 
85 		try
86 		{
87 			string fileData = cast(string)read(filename);
88 			root = parseSource(fileData, filename);
89 		}
90 		catch(SDLangParseException e)
91 		{
92 			warning(e.msg);
93 			return;
94 		}
95 
96 		foreach(optionPair; options.byKeyValue)
97 		{
98 			if (optionPair.key !in root.tags) continue;
99 			auto tags = root.tags[optionPair.key];
100 
101 			if (tags.length == 1)
102 			{
103 				try
104 				{
105 					parseValue(optionPair.value, optionPair.key, tags[0].values);
106 					//infof("%s %s %s", optionPair.key, optionPair.value.value, optionPair.value.defaultValue);
107 				}
108 				catch(VariantException e)
109 				{
110 					warningf("Error parsing config option: %s - %s", optionPair.key, e.msg);
111 				}
112 			}
113 			else if (tags.length > 1)
114 				warningf("Multiple definitions of '%s'", optionPair.key);
115 			else
116 				warningf("Empty option '%s'", optionPair.key);
117 		}
118 	}
119 
120 	void save() {}
121 
122 private:
123 
124 	static void parseValue(ConfigOption option, string optionName, Value[] values)
125 	{
126 		if (values.length == 1)
127 		{
128 			Value value = values[0];
129 
130 			if (option.value.type == typeid(bool)) {
131 				option.value = Variant(value.coerce!bool);
132 			}
133 			else if (option.value.type == typeid(string)) {
134 				option.value = Variant(value.coerce!string);
135 			}
136 			else if (option.value.convertsTo!long) {
137 				option.value = Variant(value.coerce!long);
138 			}
139 			else if (option.value.convertsTo!real) {
140 				option.value = Variant(value.coerce!double);
141 			}
142 			else
143 			{
144 				warningf("Cannot parse '%s' from '%s'", optionName, value.to!string);
145 			}
146 		}
147 		else if (values.length > 1)
148 		{
149 			void parseArray(T)()
150 			{
151 				T[] items;
152 				foreach(v; values)
153 					items ~= v.coerce!T;
154 				option.value = Variant(items);
155 			}
156 
157 			info(option.value.convertsTo!(long[]));
158 
159 			if (option.value.type == typeid(long[])) {
160 				if (option.value.length != values.length)
161 					return;
162 
163 				parseArray!long;
164 			}
165 			else if (option.value.type == typeid(int[])) {
166 				if (option.value.length != values.length)
167 					return;
168 
169 				parseArray!int;
170 			}
171 			if (option.value.type == typeid(ulong[])) {
172 				if (option.value.length != values.length)
173 					return;
174 
175 				parseArray!ulong;
176 			}
177 			else if (option.value.type == typeid(uint[])) {
178 				if (option.value.length != values.length)
179 					return;
180 
181 				parseArray!uint;
182 			}
183 			else if (option.value.type == typeid(real[]) ||
184 				option.value.type == typeid(double[]) ||
185 				option.value.type == typeid(float[])) {
186 				if (option.value.length != values.length)
187 					return;
188 
189 				parseArray!double;
190 			}
191 			else
192 			{
193 				warningf("Cannot parse '%s' from '%s'", optionName, values.to!string);
194 			}
195 		}
196 	}
197 }