1 /* 2 * Copyright (c) 2017-2018 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: 2017-2018 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/selery/source/selery/node/plugin/config.d, selery/node/plugin/config.d) 28 */ 29 module selery.node.plugin.config; 30 31 import std.ascii : newline; 32 import std.conv : to, ConvException; 33 import std.string : split, join, strip, indexOf; 34 import std.traits : isArray; 35 36 import selery.node.plugin.file; 37 38 enum _; 39 40 struct Value(T) { 41 42 alias Type = T; 43 44 string name; 45 T def; 46 47 } 48 49 Value!T value(T)(string name, T def) { 50 return Value!T(name, def); 51 } 52 53 /** 54 * Container for configuration files with compile-time format 55 * and members. 56 * 57 * The file is saved in a text file in format "field: value", 58 * one per line, with every value converted into a string. 59 * Supported values are the basic numeric types, string and 60 * arrays of these two. 61 * 62 * Use $(D value) to indicate a field with a static type, $(D _) to 63 * insert an empty line and a string to insert a comment. 64 * 65 * Example: 66 * --- 67 * Config!(Value!string("name"), _, Value!uint("players", 0), Value!uint("max", 256), _, Value!(uint[])("array")) example; 68 * assert(example.players == 0); 69 * assert(example.max == 256); 70 * 71 * alias Example = Config!("comment", value("field", "value"), _, value("int", 12u)); 72 * Example.init.save("example.txt"); 73 * assert(read("example.txt") == "# comment\nfield: value\n\nint: 12\n"); // with posix's line-endings 74 * --- 75 */ 76 struct Config(E...) if(areValidArgs!E) { 77 78 private string file; 79 80 mixin((){ 81 string ret; 82 foreach(immutable i, T; E) { 83 static if(!is(T == _) && !is(typeof(T) : string)) { 84 ret ~= "E[" ~ to!string(i) ~ "].Type " ~ T.name ~ "=E[" ~ to!string(i) ~ "].def;"; 85 } 86 } 87 return ret; 88 }()); 89 90 /** 91 * Loads the values from a file. 92 * Throws: 93 * ConvException if one of the value cannot be converted from string to the requested one 94 * FileException on file error 95 */ 96 public void load(string sep=":", string mod=__MODULE__)(string file) { 97 if(exists!mod(file)) { 98 string[] lines = (cast(string)read!mod(file)).split("\n"); 99 foreach(string line ; lines) { 100 if(line.length && line[0] != '#') { 101 auto index = line.indexOf(sep); 102 if(index > 0) { 103 string name = line[0..index].strip; 104 string value = line[index+1..$].strip; 105 foreach(immutable i, T; E) { 106 static if(!is(T == _) && !is(typeof(T) : string)) { 107 if(name == T.name) { 108 static if(!isArray!(T.Type) || is(T.Type : string)) { 109 try { 110 mixin("this." ~ T.name ~ " = to!(T.Type)(value);"); 111 } catch(ConvException) {} 112 } else { 113 foreach(el ; value.split(",")) { 114 try { 115 mixin("this." ~ T.name ~ " ~= to!(typeof(T.Type.init[0]))(el.strip);"); 116 } catch(ConvException) {} 117 } 118 } 119 } 120 } 121 } 122 } 123 } 124 } 125 } 126 this.file = file; 127 } 128 129 /** 130 * Saves the field's values into a file. If none is given 131 * the values are saved in the same file they have been 132 * loaded from (if the load method has been called), otherwise 133 * the file is not saved. 134 * Throws: 135 * FileException on file error 136 * Example: 137 * --- 138 * example.save("example.txt"); 139 * example.save("dir/test.txt"); 140 * assert(read("example.txt") == read("dir/test.txt")); 141 * --- 142 */ 143 public void save(string sep=":", string mod=__MODULE__)(string file) { 144 string data; 145 foreach(immutable i, T; E) { 146 static if(is(T == _)) { 147 data ~= newline; 148 } else static if(is(typeof(T) : string)) { 149 data ~= "# " ~ T ~ newline; 150 } else { 151 data ~= T.name ~ sep ~ " "; 152 static if(!isArray!(T.Type) || is(T.Type : string)) { 153 mixin("data ~= to!string(this." ~ T.name ~ ");"); 154 } else { 155 mixin("auto array = this." ~ T.name ~ ";"); 156 string[] d; 157 foreach(a ; array) d ~= to!string(a); 158 data ~= d.join(", "); 159 } 160 data ~= newline; 161 } 162 } 163 write!mod(file, data); 164 } 165 166 /// ditto 167 public void save(string sep=":", string mod=__MODULE__)() { 168 if(this.file.length) this.save!(sep, mod)(this.file); 169 } 170 171 } 172 173 private bool areValidArgs(E...)() { 174 foreach(T ; E) { 175 static if(!is(T == _) && !is(typeof(T) : string) && !is(T == struct) && !is(typeof(T.name) == string) && !is(typeof(T.def) == T.Type)) return false; 176 } 177 return true; 178 }