1 module scorpion.config;
2 
3 import std.conv : to;
4 import std.file : exists, read, write;
5 import std..string : split, strip, indexOf;
6 
7 private enum defaultConfiguration = import("default-configuration.properties");
8 
9 /**
10  * Stores the configuration key/values and the used profiles
11  * from the configuration files.
12  */
13 struct Config {
14 
15 	private string[] _profiles;
16 	private string[string] _values;
17 
18 	/**
19 	 * Gets the active profiles, read from the scorpion.profiles
20 	 * properties and from the `ProfilesConfiguration` configuration.
21 	 */
22 	public @property string[] profiles() {
23 		return _profiles;
24 	}
25 
26 	public void addProfiles(string[] profiles) {
27 		_profiles ~= profiles;
28 	}
29 
30 	/**
31 	 * Indicates whether a profile is active.
32 	 */
33 	public bool hasProfile(string profile) {
34 		foreach(p ; _profiles) {
35 			if(p == profile) return true;
36 		}
37 		return false;
38 	}
39 
40 	/**
41 	 * Indicates whether at least one of the profiles in the
42 	 * given array is active.
43 	 */
44 	public bool hasProfile(string[] profiles...) {
45 		foreach(profile ; profiles) {
46 			if(hasProfile(profile)) return true;
47 		}
48 		return false;
49 	}
50 
51 	/**
52 	 * Gets a configuration value from its key.
53 	 */
54 	public T get(T)(string key, lazy T defaultValue) {
55 		auto ptr = key in _values;
56 		if(ptr) return to!T(*ptr);
57 		else return defaultValue;
58 	}
59 
60 	public static Config load() {
61 		if(!exists("scorpion.properties")) write("scorpion.properties", defaultConfiguration);
62 		Config config;
63 		loadImpl(config, "scorpion");
64 		return config;
65 	}
66 
67 	private static void loadImpl(ref Config config, string file) {
68 		file ~= ".properties";
69 		if(exists(file)) {
70 			auto values = parseProperties(cast(string)read(file));
71 			foreach(key, value; values) config._values[key] = value;
72 			auto profiles = "scorpion.profiles" in values;
73 			if(profiles) {
74 				foreach(profile ; split(*profiles, ",")) {
75 					profile = profile.strip;
76 					config._profiles ~= profile;
77 					loadImpl(config, profile);
78 				}
79 			}
80 		}
81 	}
82 
83 }
84 
85 string[string] parseProperties(string data) {
86 	string[string] ret;
87 	foreach(line ; split(data, "\n")) {
88 		immutable sep = line.indexOf("=");
89 		if(sep != -1) {
90 			immutable key = line[0..sep].strip;
91 			if(key.length) {
92 				ret[key] = line[sep+1..$].strip;
93 			}
94 		}
95 	}
96 	return ret;
97 }
98 
99 /**
100  * Annotation for a configuration class. Does not take any argument.
101  * A configuration class should extend one or more of the configuration
102  * interfaces.
103  * Example:
104  * ---
105  * @Configuration
106  * class ExampleConfig : ProfilesConfig {
107  * 
108  *    override string[] defaultProfiles() {
109  *       return ["example"];
110  *    }
111  * 
112  * }
113  * ---
114  */
115 enum Configuration;
116 
117 /**
118  * Configuration for the language files.
119  * Language files use the format `key=value`.
120  */
121 interface LanguageConfiguration {
122 
123 	/**
124 	 * Returns a map of the language files, alredy read.
125 	 * Example:
126 	 * ---
127 	 * override string[string] loadLanguages() {
128 	 *    return ["en": cast(string)read("res/lang/en.lang")];
129 	 * }
130 	 * ---
131 	 */
132 	string[string] loadLanguages();
133 
134 }
135 
136 /**
137  * Configuration for default profiles.
138  * This configuration adds the profiles in the configuration
139  * files the ones returned by `defaultProfiles`;
140  */
141 interface ProfilesConfiguration {
142 
143 	/**
144 	 * Gets the default profiles.
145 	 */
146 	string[] defaultProfiles();
147 
148 }