1 /** 2 * The module contains the objects and the properties of control functions 3 * 4 * Copyright: (c) 2015-2020, Milofon Project. 5 * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file. 6 * Author: <m.galanin@milofon.pro> Maksim Galanin 7 * Date: 2020-03-09 8 */ 9 10 module uniconf.core; 11 12 private 13 { 14 import std.exception : enforce; 15 16 import uninode.node : isUniNode; 17 import uninode.tree : UniTree; 18 } 19 20 21 /// Config node 22 alias UniConf = UniTree; 23 24 25 /** 26 * Thrown when an unhandled type is encountered. 27 */ 28 class UniConfException : Exception 29 { 30 /** 31 * common constructor 32 */ 33 pure nothrow @safe @nogc 34 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) 35 { 36 super(msg, file, line, next); 37 } 38 } 39 40 /// delegate reader config file 41 alias ConfigReader = string delegate(string filePath); 42 43 /// delegate loader config file 44 alias ConfigLoader = UniConf delegate(string data); 45 46 47 /** 48 * Setting the function to read configuration files 49 * 50 * Params: 51 * reader = Delegate reader 52 */ 53 void setConfigReader(ConfigReader reader) 54 { 55 _configReader = reader; 56 } 57 58 59 /** 60 * Register config file loader delegate 61 * 62 * Params: 63 * fileExtensions = File extension to delermite the type loader 64 * loader = Loader delegate 65 */ 66 void registerConfigLoader(string[] fileExtensions, ConfigLoader loader) 67 { 68 import std.algorithm.searching : canFind; 69 70 ConfigFileLoader nextLoader = _configLoader; 71 72 _configLoader = (string fileExt, string data) { 73 if (fileExtensions.canFind(fileExt)) 74 return loader(data); 75 else 76 return nextLoader(fileExt, data); 77 }; 78 } 79 80 81 /** 82 * The function loads the configuration object from a file 83 * Params: 84 * 85 * filePath = File path 86 */ 87 UniConf loadConfig(string filePath) @safe 88 { 89 import std.path : extension; 90 91 string source = () @trusted { 92 if (_configReader is null) 93 { 94 import std.file : readText, FileException; 95 try 96 return readText(filePath); 97 catch (FileException e) 98 throw new UniConfException("Error loading config from a file '" 99 ~ filePath ~ "'", e.file, e.line, e); 100 } 101 else 102 return _configReader(filePath); 103 } (); 104 105 return () @trusted { 106 return _configLoader(filePath.extension, source); 107 } (); 108 } 109 110 111 @("Should work load config") 112 @system unittest 113 { 114 import std.exception : collectExceptionMsg; 115 const msg = collectExceptionMsg(loadConfig("dummy://file.txt")); 116 assert (msg == "Error loading config from a file 'dummy://file.txt'"); 117 setConfigReader((string) { 118 return `{"client": {"host": "localhost", "port": 44}}`; 119 }); 120 121 registerConfigLoader([".yaml"], (string) { 122 return UniConf(2); 123 }); 124 125 registerConfigLoader([".json"], (string) { 126 return UniConf(1); 127 }); 128 129 assert (loadConfig("dummy://file.yaml") == UniConf(2)); 130 assert (loadConfig("dummy://file.json") == UniConf(1)); 131 } 132 133 134 package: 135 136 137 alias enforceUniConf = enforce!UniConfException; 138 139 140 private: 141 142 143 alias ConfigFileLoader = UniConf delegate(string fileExt, string data); 144 145 146 /** 147 * Configuration file reader 148 */ 149 __gshared ConfigReader _configReader; 150 151 /** 152 * Configuration file loader 153 */ 154 __gshared ConfigFileLoader _configLoader = (string fileExt, string data) { 155 throw new UniConfException("Not defined loader for '" ~ fileExt ~ "' format"); 156 }; 157