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 }