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