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