1 module simpleconfig.file; 2 3 import simpleconfig.attributes; 4 5 /// See `simpleconfig.readConfiguration` 6 public void readConfiguration (S) (ref S dst) 7 { 8 static assert (is(S == struct), "Only structs are supported as configuration target"); 9 10 import std.process : environment; 11 import std.exception : ifThrown; 12 import std.path : dirName; 13 import std.file : thisExePath; 14 15 auto executable = thisExePath(); 16 17 string[] locations = [ 18 ".", 19 dirName(executable) 20 ]; 21 22 version (Windows) 23 locations ~= environment["LOCALAPPDATA"]; 24 else version (Posix) 25 locations ~= environment["XDG_CONFIG_HOME"].ifThrown("~/.config"); 26 27 foreach (location; locations) 28 { 29 import std.path : buildPath, baseName; 30 import std.file : exists, readText; 31 32 auto filename = buildPath(location, baseName(executable, ".exe") ~ ".cfg"); 33 if (exists(filename)) 34 { 35 auto content = readText(filename); 36 readConfigurationImpl(dst, content); 37 return; 38 } 39 } 40 } 41 42 private template resolveName (alias Field) 43 { 44 import std.traits; 45 46 enum resolveName = getUDAs!(Field, CFG)[0].key.length 47 ? getUDAs!(Field, CFG)[0].key 48 : __traits(identifier, Field); 49 } 50 51 unittest 52 { 53 struct S 54 { 55 @cfg 56 string field1; 57 @cfg("renamed") 58 string field2; 59 } 60 61 static assert (resolveName!(S.field1) == "field1"); 62 static assert (resolveName!(S.field2) == "renamed"); 63 } 64 65 private string[2] extractKV (string line) 66 { 67 import std.algorithm : findSplit; 68 import std.string : strip; 69 70 auto separated = line.findSplit("="); 71 string key = strip(separated[0]); 72 string value = strip(separated[2]); 73 if (value[0] == '"' && value[$-1] == '"') 74 value = value[1 .. $ -1]; 75 76 return [ key, value ]; 77 } 78 79 unittest 80 { 81 assert(extractKV(" key = value ") == [ "key", "value" ]); 82 assert(extractKV(" key = \"value value\"") == [ "key", "value value" ]); 83 } 84 85 private void readConfigurationImpl (S) (ref S dst, string src) 86 { 87 import std.traits; 88 import std.algorithm; 89 import std.range; 90 import std.conv; 91 92 rt: foreach (line; src.splitter("\n")) 93 { 94 if (line.length == 0 || line.startsWith("#")) 95 continue; 96 97 auto kv = extractKV(line); 98 bool assign = false; 99 100 static foreach (Field; getSymbolsByUDA!(S, CFG)) 101 { 102 if (kv[0] == resolveName!Field) 103 { 104 auto pfield = &__traits(getMember, dst, __traits(identifier, Field)); 105 *pfield = to!(typeof(*pfield))(kv[1]); 106 continue rt; 107 } 108 } 109 } 110 } 111 112 unittest 113 { 114 struct Config 115 { 116 @cfg 117 string field1; 118 @cfg("alias") 119 int field2; 120 @cfg("with space") 121 string field3; 122 string field4; 123 } 124 125 Config config; 126 127 readConfigurationImpl(config, ` 128 # this is a comment 129 field1 = value1 130 alias = 42 131 with space = value3 132 field4 = ignored` 133 ); 134 135 assert(config.field1 == "value1"); 136 assert(config.field2 == 42); 137 assert(config.field3 == "value3"); 138 assert(config.field4 == ""); 139 }