1 2 module dotenv; 3 4 import std.algorithm; 5 import std.conv; 6 import std.string; 7 import std.uni; 8 9 bool isNameChar(T)(T ch) 10 { 11 return ch.isUpper || !ch.isAlpha; 12 } 13 14 /++ 15 + Stores and accesses loaded environment variables. 16 + All environment variables are case-insensitive, and their names are stored in upper case. 17 ++/ 18 shared struct Env 19 { 20 private: 21 static string[string] _cache; 22 23 public: 24 static void clear() 25 { 26 _cache = typeof(_cache).init; 27 } 28 29 @property 30 static bool empty() 31 { 32 return _cache.length == 0; 33 } 34 35 @property 36 static size_t length() 37 { 38 return _cache.length; 39 } 40 41 @property 42 static const(string[]) keys() 43 { 44 return _cache.keys; 45 } 46 47 @property 48 static const(string[]) values() 49 { 50 return _cache.values; 51 } 52 53 static int opApply(scope int delegate(string) dg) 54 { 55 foreach(value; _cache) 56 { 57 if(int result = dg(value)) 58 { 59 return result; 60 } 61 } 62 63 return 0; 64 } 65 66 static int opApply(scope int delegate(string, string) dg) 67 { 68 foreach(key, value; _cache) 69 { 70 if(int result = dg(key, value)) 71 { 72 return result; 73 } 74 } 75 76 return 0; 77 } 78 79 static string[string] opIndex() 80 { 81 return _cache.dup; 82 } 83 84 static string opIndex(string name) 85 { 86 if(string* variable = name.toUpper in _cache) 87 { 88 return *variable; 89 } 90 91 return null; 92 } 93 94 static string opIndexAssign(T)(T value, string name) 95 { 96 return _cache[name.toUpper] = to!string(value); 97 } 98 99 template opDispatch(string name) if(name.map!isNameChar.all) 100 { 101 @property 102 static T opDispatch(T = string, Args...)(Args args) if(Args.length == 0) 103 { 104 if(string variable = typeof(this)[name]) 105 { 106 return to!T(variable); 107 } 108 109 return T.init; 110 } 111 112 @property 113 static T opDispatch(T = string, Args...)(Args args) if(Args.length == 1) 114 { 115 typeof(this)[name] = args[0]; 116 117 return args[0]; 118 } 119 } 120 121 static bool remove(string name) 122 { 123 return _cache.remove(name.toUpper); 124 } 125 126 /++ 127 + Loads system environment variables (but not the dotenv file). 128 ++/ 129 static void loadSystem() 130 { 131 import std.process : environment; 132 133 // Copy the environment first. 134 foreach(name, value; environment.toAA) 135 { 136 if(name !in _cache) 137 { 138 _cache[name] = value; 139 } 140 } 141 } 142 143 /++ 144 + Copies environment variables, then loads the dotenv file, if present. 145 + Variables declared in the .env file will override system environment variables. 146 + All variable names are case-insensitive, and are stored as upper case. 147 + 148 + Params: 149 + handlers = An optional list of exception handlers. 150 + fileName = The name of the dovenv file (".env" by default). 151 + copySystem = If true, system environment variables are loaded as well. 152 ++/ 153 static void load(handlers...)(string fileName = ".env", bool copySystem = true) 154 if(__traits(compiles, { 155 Exception e = void; 156 foreach(handler; handlers) 157 { 158 handler(e); 159 } 160 })) 161 { 162 import std.exception; 163 import std.stdio, std.string; 164 165 try 166 { 167 // Optionally copy environment. 168 if(copySystem) loadSystem; 169 170 // Open the .env file if it exists. 171 File file = File(fileName, "r"); 172 scope(exit) file.close; 173 174 // Read and store variables. 175 foreach(line; file.byLineCopy.map!strip) 176 { 177 // Skip empty lines or line comments. 178 if(line.length == 0 || line[0] == '#') continue; 179 180 auto result = line.split("="); 181 if(result.length < 1) continue; 182 183 // Convert all names to upper case. 184 string name = result[0].strip.toUpper; 185 string value = ""; 186 187 if(result.length > 0) 188 { 189 // Recostruct the right side of the assignment. 190 value = result[1 .. $].join("=").strip; 191 } 192 193 typeof(this)[name] = value; 194 } 195 } 196 catch(Exception e) 197 { 198 foreach(handler; handlers) 199 { 200 handler(e); 201 } 202 } 203 } 204 }