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 }