Mercurial > projects > dynamin
view dynamin/core/settings.d @ 106:acdbb30fee7e
Port to D2.
Most of the effort was dealing with immutable and const.
author | Jordan Miner <jminer7@gmail.com> |
---|---|
date | Mon, 17 Dec 2012 23:41:50 -0600 |
parents | 73060bc3f004 |
children |
line wrap: on
line source
/* * Copyright Jordan Miner * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file BOOST_LICENSE.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * */ module dynamin.core.settings; import dynamin.core.string; import tango.io.device.Conduit; import tango.io.device.File; import tango.io.device.Array; import tango.io.stream.Text; import tango.io.Stdout; import tango.util.Convert; import tango.core.Exception; // TODO: align(1) struct Pixel32 { version(BigEndian) { ubyte A; ubyte R; ubyte G; ubyte B; } else { ubyte B; ubyte G; ubyte R; ubyte A; } } class Color2 { Pixel32 toPixel32() { Pixel32 px; return px; } string toSetting() { auto px = toPixel32(); return format("{}, {}, {}", px.R, px.G, px.B); } static Color2 fromSetting(cstring str) { // allow "#AB00F2", "171, 0, 242" return null; } } string test = r" ; Here is a comment [Main] UndoLevels=500 # Here is another comment CaretColor=255, 0, 0 LineNumsVisible=true [RubyMode] TabSize=4 "; unittest { // test saving to the file auto settings = new Settings; settings.loadFromString(test); test = settings.saveToString().idup; // test reading from the file settings = new Settings; settings.loadFromString(test); assert(settings.get("UndoLevels") == "500"); assert(settings.get("TabSize", "RubyMode") == "4"); assert(getSetting!(int)(settings, "UndoLevels") == 500); assert(getSetting!(bool)(settings, "LineNumsVisible") == true); } /** * This class will read from and write to what is basically a Windows INI file. * Here is what a file would look like: * Example: * ----- * ; Here is a comment * [Main] * UndoLevels=500 * # Here is another comment * CaretColor=255, 0, 0 * LineNumsVisible=true * * [RubyMode] * TabSize=4 * ----- */ class Settings { protected: string[string][string] sections; string[] comment; void loadFromStream(InputStream stream) { auto input = new TextInput(stream); string section = MainSectionName; bool inStartComment = true; int lineNum = 0; foreach(line; input) { lineNum++; // check for a line with just whitespace if(line.trim().length == 0) continue; // check for a comment if(line.trimLeft().startsWith(";") || line.trimLeft().startsWith("#")) { if(inStartComment) { comment.length = comment.length + 1; comment[$-1] = line.trimLeft()[1..$].idup; } continue; } inStartComment = false; // check for a section header if(line.startsWith("[")) { if(!line.endsWith("]") || line.length < 3) throw new Exception("Invalid section on line " ~ to!(string)(lineNum)); section = line[1..$-1].idup; continue; } // parse key=value line (quickly) int eqIndex; for(eqIndex = 0; eqIndex < line.length; ++eqIndex) if(line[eqIndex] == '=') break; if(eqIndex == line.length) throw new Exception("Invalid format on line " ~ to!(string)(lineNum)); string value = cast(immutable)(line[eqIndex+1..$].unescape()); sections[section][line[0..eqIndex].idup] = value; } } void saveToStream(OutputStream stream) { auto output = new TextOutput(stream); foreach(line; comment) output(';')(line).newline; foreach(string sectionName, string[string] sectionData; sections) { output('[')(sectionName)(']').newline;// TODO: newline before each section foreach(key, value; sectionData) output(key)('=')(value).newline; // TODO: escape? } } public: enum string MainSectionName = "Main"; /** * Parses the specified file. */ void load(cstring file) { scope f = new File(file); loadFromStream(f); } void loadFromString(cstring str) { auto a = new Array(cast(void[])str); loadFromStream(a); } mstring saveToString() { scope a = new Array(256, 80); saveToStream(a); return cast(mstring)a.slice(a.readable); } /** * * Examples: * ----- * settings.get("UndoLevels"); * settings.get("TabSize", "RubyMode"); * ----- */ string get(cstring name, cstring section = MainSectionName) { auto sect = section in sections; if(sect) { auto val = name in *sect; if(val) return *val; } return ""; } /** * Examples: * ----- * settings.set("UndoLevels", "500") * settings.set("TabSize", "4", "RubyMode"); * ----- */ void set(string name, string value, string section = MainSectionName) { if(section.contains('[') || section.contains(']')) throw new IllegalArgumentException( "Section name cannot contain a bracket"); if(name.contains('=')) throw new IllegalArgumentException( "name cannot contain an equal sign"); sections[section][name] = value; } /** * Gets or sets the comment that appears at the beginning of the settings * file. This is set when a file is read in. */ string[] commentLines() { return comment; } /// ditto void commentLines(string[] lines) { comment = lines; } } /** * Examples: * ----- * getSetting!(int)(settings, "UndoLevels"); * getSetting!(int)(settings, "TabSize", "RubyMode"); * ----- */ T getSetting(T)(Settings settings, string name, string section = Settings.MainSectionName) { string str = settings.get(name, section); static if(is(T == bool)) return str == "true" || str == "yes" || str == "on"; else static if(is(T : long)) return cast(T)to!(T)(str); else return T.fromSetting(str); } /** * Examples: * ----- * setSetting!(int)(settings, "UndoLevels", 500); * setSetting!(int)(settings, "TabSize", 4, "RubyMode"); * ----- */ void setSetting(T)(Settings settings, string name, T value, string section = Settings.MainSectionName) { mstring str; static if(is(T == bool)) str = value ? "true" : "false"; else static if(is(T : long)) str = to!(mstring)(value); else str = T.toSetting(); settings.set(name, str, section); }