1 module variantconfig;
2 
3 import std.string : lineSplitter, split, strip, indexOf;
4 import std.stdio : File, writeln;
5 import std.file : exists, readText;
6 import std.algorithm : sort, find;
7 import std.traits : isNumeric;
8 import std.path : isValidFilename;
9 
10 public import std.variant;
11 
12 struct VariantConfig
13 {
14 private:
15 	void load(immutable string fileName) @safe
16 	{
17 		string text;
18 
19 		if(fileName.indexOf("=") == -1)
20 		{
21 			if(exists(fileName))
22 			{
23 				text = readText(fileName);
24 			}
25 		}
26 		else
27 		{
28 			text = fileName; // In this case it's a string not a filename.
29 		}
30 
31 		processText(text);
32 	}
33 
34 	void save() @trusted
35 	{
36 		auto configfile = File(fileName_, "w+");
37 
38 		foreach(key; sort(values_.keys))
39 		{
40 			configfile.writeln(key, separator_, values_[key]);
41 		}
42 	}
43 
44 	void processText(immutable string text) @trusted
45 	{
46 		auto lines = text.lineSplitter();
47 
48 		foreach(line; lines)
49 		{
50 			auto fields = split(line, separator_);
51 
52 			if(fields.length == 2)
53 			{
54 				values_[fields[0].strip] = fields[1].strip;
55 			}
56 		}
57 	}
58 public:
59 	this(immutable string fileName) @safe
60 	{
61 		fileName_ = fileName;
62 		load(fileName);
63 	}
64 
65 	~this() @safe
66 	{
67 		if(valuesModified_)
68 		{
69 			save();
70 		}
71 	}
72 
73 	bool hasValue(immutable string key) pure @safe
74 	{
75 		if(key in values_)
76 		{
77 			return true;
78 		}
79 		return false;
80 	}
81 
82 	Variant getValue(immutable string key) @trusted
83 	{
84 		return values_.get(key, Variant(0));
85 	}
86 
87 	Variant getValue(immutable string key, Variant defval) @trusted
88 	{
89 		return values_.get(key, Variant(defval));
90 	}
91 
92 	void setValue(immutable string key, Variant value) @trusted
93 	{
94 		values_[key] = value;
95 		valuesModified_ = true;
96 	}
97 
98 	bool remove(immutable string key) pure @safe
99 	{
100 		return values_.remove(key);
101 	}
102 
103 	Variant opIndex(immutable string key) @trusted
104 	{
105 		return getValue(key);
106 	}
107 
108 	void opIndexAssign(T)(T value, immutable string key) @trusted
109 	{
110 		setValue(key, Variant(value));
111 	}
112 
113 private:
114 	immutable char separator_ = '=';
115 	Variant[string] values_;
116 	immutable string fileName_; // Only used for saving
117 	bool valuesModified_;
118 }
119 
120 int toInt(Variant value) @trusted
121 {
122 	return value.coerce!(int);
123 }
124 
125 long toLong(Variant value) @trusted
126 {
127 	return value.coerce!(long);
128 }
129 
130 bool toBool(Variant value) @trusted
131 {
132 	return value.coerce!(bool);
133 }
134 
135 double toDouble(Variant value) @trusted
136 {
137 	return value.coerce!(double);
138 }
139 
140 real toReal(Variant value) @trusted
141 {
142 	return value.coerce!(real);
143 }
144 
145 string toStr(Variant value) @trusted // NOTE: Must be named toStr instead of toString or D buildin will override.
146 {
147 	if(value == 0)
148 	{
149 		return "";
150 	}
151 	return value.coerce!(string);
152 }
153 
154 unittest
155 {
156 	string test = "
157 		aBool=true
158 		float=3443.443
159 		number=12071
160 		sentence=This is a really long sentence to test for a really long value string!
161 		time=12:04
162 	";
163 
164 	auto config = VariantConfig(test);
165 	long number = config["number"].toLong;
166 	bool aBool = config["aBool"].toBool;
167 	string sentence = config["sentence"].toStr;
168 	string time = config["time"].toStr;
169 	double aDouble = config["aDouble"].toDouble;
170 
171 	assert(number == 12071);
172 	assert(aBool == true);
173 	assert(sentence == "This is a really long sentence to test for a really long value string!");
174 	assert(time == "12:04");
175 	assert(aDouble == 3443.443);
176 }