0
|
1 // Written in the D programming language
|
|
2 // www.digitalmars.com/d/
|
|
3
|
|
4 /*
|
|
5 * The contents of this file are subject to the Mozilla Public License Version
|
|
6 * 1.1 (the "License"); you may not use this file except in compliance with
|
|
7 * the License. You may obtain a copy of the License at
|
|
8 * http://www.mozilla.org/MPL/
|
|
9 *
|
|
10 * Software distributed under the License is distributed on an "AS IS" basis,
|
|
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
12 * for the specific language governing rights and limitations under the
|
|
13 * License.
|
|
14 *
|
|
15 * The Original Code is the Dynamin library.
|
|
16 *
|
|
17 * The Initial Developer of the Original Code is Jordan Miner.
|
|
18 * Portions created by the Initial Developer are Copyright (C) 2007-2009
|
|
19 * the Initial Developer. All Rights Reserved.
|
|
20 *
|
|
21 * Contributor(s):
|
|
22 * Jordan Miner <jminer7@gmail.com>
|
|
23 *
|
|
24 */
|
|
25
|
|
26 module dynamin.core.settings;
|
|
27
|
|
28 import dynamin.core.string;
|
|
29 import tango.io.device.Conduit;
|
|
30 import tango.io.device.File;
|
|
31 import tango.io.device.Array;
|
|
32 import tango.io.stream.Text;
|
|
33 import tango.io.Stdout;
|
|
34 import tango.util.Convert;
|
|
35 import tango.core.Exception;
|
|
36
|
|
37 // TODO:
|
|
38 align(1)
|
|
39 struct Pixel32 {
|
|
40 version(BigEndian) {
|
|
41 ubyte A;
|
|
42 ubyte R;
|
|
43 ubyte G;
|
|
44 ubyte B;
|
|
45 } else {
|
|
46 ubyte B;
|
|
47 ubyte G;
|
|
48 ubyte R;
|
|
49 ubyte A;
|
|
50 }
|
|
51 }
|
|
52 class Color2 {
|
|
53 Pixel32 ToPixel32() {
|
|
54 Pixel32 px;
|
|
55 return px;
|
|
56 }
|
|
57 string ToSetting() {
|
|
58 auto px = ToPixel32();
|
|
59 return format("{}, {}, {}", px.R, px.G, px.B);
|
|
60 }
|
|
61 static Color2 FromSetting(string str) {
|
|
62 // allow "#AB00F2", "171, 0, 242"
|
|
63 return null;
|
|
64 }
|
|
65 }
|
|
66
|
|
67 string test = r"
|
|
68 ; Here is a comment
|
|
69 [Main]
|
|
70 UndoLevels=500
|
|
71 # Here is another comment
|
|
72 CaretColor=255, 0, 0
|
|
73 LineNumsVisible=true
|
|
74
|
|
75 [RubyMode]
|
|
76 TabSize=4
|
|
77 ";
|
|
78 unittest {
|
|
79 auto settings = new Settings;
|
|
80 settings.loadFromString(test);
|
|
81 assert(settings.get("UndoLevels") == "500");
|
|
82 assert(settings.get("TabSize", "RubyMode") == "4");
|
|
83 assert(getSetting!(int)(settings, "UndoLevels") == 500);
|
|
84 assert(getSetting!(bool)(settings, "LineNumsVisible") == true);
|
|
85 Stdout(settings.saveToString()).newline;
|
|
86 }
|
|
87
|
|
88 /**
|
|
89 * This class will read from and write to what is basically a Windows INI file.
|
|
90 * Here is what a file would look like:
|
|
91 * Example:
|
|
92 * -----
|
|
93 * ; Here is a comment
|
|
94 * [Main]
|
|
95 * UndoLevels=500
|
|
96 * # Here is another comment
|
|
97 * CaretColor=255, 0, 0
|
|
98 * LineNumsVisible=true
|
|
99 *
|
|
100 * [RubyMode]
|
|
101 * TabSize=4
|
|
102 * -----
|
|
103 */
|
|
104 class Settings {
|
|
105 protected:
|
|
106 string[string][string] sections;
|
|
107 string[] comment;
|
|
108 void loadFromStream(InputStream stream) {
|
|
109 auto input = new TextInput(stream);
|
|
110 string section = MainSectionName;
|
|
111 bool inStartComment = true;
|
|
112 int lineNum = 0;
|
|
113 foreach(string line; input) {
|
|
114 lineNum++;
|
|
115 // check for a line with just whitespace
|
|
116 if(line.trim().length == 0)
|
|
117 continue;
|
|
118
|
|
119 // check for a comment
|
|
120 if(line.trimLeft().startsWith(";") ||
|
|
121 line.trimLeft().startsWith("#")) {
|
|
122 if(inStartComment) {
|
|
123 comment.length = comment.length + 1;
|
|
124 comment[$-1] = line.trimLeft()[1..$].dup;
|
|
125 }
|
|
126 continue;
|
|
127 }
|
|
128 inStartComment = false;
|
|
129
|
|
130 // check for a section header
|
|
131 if(line.startsWith("[")) {
|
|
132 if(!line.endsWith("]") || line.length < 3)
|
|
133 throw new Exception("Invalid section on line " ~ to!(string)(lineNum));
|
|
134 section = line[1..$-1].dup;
|
|
135 continue;
|
|
136 }
|
|
137
|
|
138 // parse key=value line (quickly)
|
|
139 int eqIndex;
|
|
140 for(eqIndex = 0; eqIndex < line.length; ++eqIndex)
|
|
141 if(line[eqIndex] == '=')
|
|
142 break;
|
|
143 if(eqIndex == line.length)
|
|
144 throw new Exception("Invalid format on line " ~ to!(string)(lineNum));
|
|
145 string value = line[eqIndex+1..$].unescape();
|
|
146 sections[section][line[0..eqIndex].dup] = value;
|
|
147 }
|
|
148 }
|
|
149 void saveToStream(OutputStream stream) {
|
|
150 auto output = new TextOutput(stream);
|
|
151 foreach(line; comment)
|
|
152 output(';')(line).newline;
|
|
153 foreach(string sectionName, string[string] sectionData; sections) {
|
|
154 output('[')(sectionName)(']').newline;// TODO: newline before each section
|
|
155 foreach(key, value; sectionData)
|
|
156 output(key)('=')(value).newline; // TODO: escape?
|
|
157 }
|
|
158 }
|
|
159 public:
|
|
160 const string MainSectionName = "Main";
|
|
161 /**
|
|
162 * Parses the specified file.
|
|
163 */
|
|
164 void load(string file) {
|
|
165 scope f = new File(file);
|
|
166 loadFromStream(f);
|
|
167 }
|
|
168 void loadFromString(string str) {
|
|
169 scope a = new Array(str);
|
|
170 loadFromStream(a);
|
|
171 }
|
|
172 string saveToString() {
|
|
173 scope a = new Array(256, 80);
|
|
174 saveToStream(a);
|
|
175 return cast(string)a.slice(a.readable);
|
|
176 }
|
|
177 /**
|
|
178 *
|
|
179 * Examples:
|
|
180 * -----
|
|
181 * settings.get("UndoLevels");
|
|
182 * settings.get("TabSize", "RubyMode");
|
|
183 * -----
|
|
184 */
|
|
185 string get(string name, string section = MainSectionName) {
|
|
186 auto sect = section in sections;
|
|
187 if(sect) {
|
|
188 auto val = name in *sect;
|
|
189 if(val)
|
|
190 return *val;
|
|
191 }
|
|
192 return "";
|
|
193 }
|
|
194
|
|
195 /**
|
|
196 * Examples:
|
|
197 * -----
|
|
198 * settings.set("UndoLevels", "500")
|
|
199 * settings.set("TabSize", "4", "RubyMode");
|
|
200 * -----
|
|
201 */
|
|
202 void set(string name, string value, string section = MainSectionName) {
|
|
203 if(section.contains('[') || section.contains(']'))
|
|
204 throw new IllegalArgumentException(
|
|
205 "Section name cannot contain a bracket");
|
|
206 if(name.contains('='))
|
|
207 throw new IllegalArgumentException(
|
|
208 "name cannot contain an equal sign");
|
|
209 sections[section][name] = value;
|
|
210 }
|
|
211 /**
|
|
212 * Gets or sets the comment that appears at the beginning of the settings
|
|
213 * file. This is set when a file is read in.
|
|
214 */
|
|
215 string[] commentLines() {
|
|
216 return comment;
|
|
217 }
|
|
218 /// ditto
|
|
219 void commentLines(string[] lines) {
|
|
220 comment = lines;
|
|
221 }
|
|
222 }
|
|
223
|
|
224 /**
|
|
225 * Examples:
|
|
226 * -----
|
|
227 * getSetting!(int)(settings, "UndoLevels");
|
|
228 * getSetting!(int)(settings, "TabSize", "RubyMode");
|
|
229 * -----
|
|
230 */
|
|
231 T getSetting(T)(Settings settings,
|
|
232 string name, string section = Settings.MainSectionName) {
|
|
233 string str = settings.get(name, section);
|
|
234 static if(is(T == bool))
|
|
235 return str == "true" || str == "yes" || str == "on";
|
|
236 else static if(is(T : long))
|
|
237 return cast(T)to!(T)(str);
|
|
238 else
|
|
239 return T.FromSetting(str);
|
|
240 }
|
|
241
|
|
242 /**
|
|
243 * Examples:
|
|
244 * -----
|
|
245 * setSetting!(int)(settings, "UndoLevels", 500);
|
|
246 * setSetting!(int)(settings, "TabSize", 4, "RubyMode");
|
|
247 * -----
|
|
248 */
|
|
249 void setSetting(T)(Settings settings,
|
|
250 string name, T value, string section = Settings.MainSectionName) {
|
|
251 string str;
|
|
252 static if(is(T == bool))
|
|
253 str = value ? "true" : "false";
|
|
254 else static if(is(T : long))
|
|
255 str = to!(string)(value);
|
|
256 else
|
|
257 str = T.ToSetting();
|
|
258
|
|
259 settings.Set(name, str, section);
|
|
260 }
|
|
261
|