1 /++ 2 Functions related to reading from a configuration file, broken out of 3 [kameloso.config] to avoid cyclic dependencies. 4 5 See_Also: 6 [kameloso.config] 7 8 Copyright: [JR](https://github.com/zorael) 9 License: [Boost Software License 1.0](https://www.boost.org/users/license.html) 10 11 Authors: 12 [JR](https://github.com/zorael) 13 +/ 14 module kameloso.configreader; 15 16 private: 17 18 import lu.traits : isStruct; 19 import std.meta : allSatisfy; 20 21 public: 22 23 24 // readConfigInto 25 /++ 26 Reads a configuration file and applies the settings therein to passed objects. 27 28 More than one object can be supplied; invalid ones for which there are no 29 settings in the configuration file will be silently ignored with no errors. 30 Orphan settings in the configuration file for which no appropriate 31 object was passed will be saved to `invalidEntries`. 32 33 Example: 34 --- 35 IRCClient client; 36 IRCServer server; 37 string[][string] missingEntries; 38 string[][string] invalidEntries; 39 40 "kameloso.conf".readConfigInto(missingEntries, invalidEntries, client, server); 41 --- 42 43 Params: 44 configFile = Filename of file to read from. 45 missingEntries = Reference to an associative array of string arrays 46 of expected configuration entries that were missing. 47 invalidEntries = Reference to an associative array of string arrays 48 of unexpected configuration entries that did not belong. 49 things = Reference variadic list of things to set values of, according 50 to the text in the configuration file. 51 +/ 52 void readConfigInto(T...) 53 (const string configFile, 54 ref string[][string] missingEntries, 55 ref string[][string] invalidEntries, 56 ref T things) 57 if (allSatisfy!(isStruct, T)) 58 { 59 import lu.serialisation : deserialise; 60 import std.algorithm.iteration : splitter; 61 62 return configFile 63 .configurationText 64 .splitter('\n') 65 .deserialise(missingEntries, invalidEntries, things); 66 } 67 68 69 // readConfigInto 70 /++ 71 Reads a configuration file and applies the settings therein to passed objects. 72 Merely wraps the other [readConfigInto] overload and distinguishes itself 73 from it by not taking the two `string[][string]` out parameters it does. 74 75 Params: 76 configFile = Filename of file to read from. 77 things = Reference variadic list of things to set values of, according 78 to the text in the configuration file. 79 +/ 80 void readConfigInto(T...)(const string configFile, ref T things) 81 if (allSatisfy!(isStruct, T)) 82 { 83 // Use two variables to satisfy -preview=dip1021 84 string[][string] ignore1; 85 string[][string] ignore2; 86 return configFile.readConfigInto(ignore1, ignore2, things); 87 } 88 89 90 // configurationText 91 /++ 92 Reads a configuration file into a string. 93 94 Example: 95 --- 96 string configText = "kameloso.conf".configurationText; 97 --- 98 99 Params: 100 configFile = Filename of file to read from. 101 102 Returns: 103 The contents of the supplied file. 104 105 Throws: 106 [lu.common.FileTypeMismatchException|FileTypeMismatchException] if the 107 configuration file is a directory, a character file or any other non-file 108 type we can't write to. 109 110 [lu.serialisation.ConfigurationFileReadFailureException|ConfigurationFileReadFailureException] 111 if the reading and decoding of the configuration file failed. 112 +/ 113 auto configurationText(const string configFile) 114 { 115 import std.file : exists, getAttributes, isFile, readText; 116 117 if (!configFile.exists) 118 { 119 return string.init; 120 } 121 else if (!configFile.isFile) 122 { 123 import lu.common : FileTypeMismatchException; 124 throw new FileTypeMismatchException( 125 "Configuration file is not a file", 126 configFile, 127 cast(ushort)getAttributes(configFile), 128 __FILE__); 129 } 130 131 try 132 { 133 import std.array : replace; 134 import std.string : chomp; 135 136 return configFile 137 .readText 138 .replace("[Votes]\n", "[Poll]\n") 139 .replace("[Votes]\r\n", "[Poll]\r\n") 140 .replace("[TwitchBot]\n", "[Twitch]\n") 141 .replace("[TwitchBot]\r\n", "[Twitch]\r\n") 142 .chomp; 143 } 144 catch (Exception e) 145 { 146 // catch Exception instead of UTFException, just in case there are more 147 // kinds of error than the normal "Invalid UTF-8 sequence". 148 throw new ConfigurationFileReadFailureException( 149 e.msg, 150 configFile, 151 __FILE__, 152 __LINE__); 153 } 154 } 155 156 157 // ConfigurationFileReadFailureException 158 /++ 159 Exception, to be thrown when the specified configuration file could not be 160 read, for whatever reason. 161 162 It is a normal [object.Exception|Exception] but with an attached filename string. 163 +/ 164 final class ConfigurationFileReadFailureException : Exception 165 { 166 @safe: 167 /// The name of the configuration file the exception refers to. 168 string filename; 169 170 /++ 171 Create a new [ConfigurationFileReadFailureException], without attaching a filename. 172 +/ 173 this( 174 const string message, 175 const string file = __FILE__, 176 const size_t line = __LINE__, 177 Throwable nextInChain = null) pure nothrow @nogc @safe 178 { 179 super(message, file, line, nextInChain); 180 } 181 182 /++ 183 Create a new [ConfigurationFileReadFailureException], attaching a filename. 184 +/ 185 this( 186 const string message, 187 const string filename, 188 const string file = __FILE__, 189 const size_t line = __LINE__, 190 Throwable nextInChain = null) pure nothrow @nogc @safe 191 { 192 this.filename = filename; 193 super(message, file, line, nextInChain); 194 } 195 }