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 }