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