1 module ddotenv.dotenv;
2 
3 private enum LineType { Whitespace, Directive };
4 
5 private struct Line
6 {
7     LineType lineType;
8     string key;
9     string value;
10 }
11 
12 public class DotEnvFormatException : Exception
13 {
14     pure @safe nothrow this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
15     {
16        super(".env format error", file, line, next);
17     }
18 }
19 
20 public void loadDotEnv()
21 {
22     loadDotEnv(".env", false);
23 }
24 
25 public void loadDotEnv(string path)
26 {
27     loadDotEnv(path, true);
28 }
29 
30 private void loadDotEnv(string path, bool throwOnError)
31 {
32     import std.stdio : File; 
33     import std.exception : ErrnoException;
34     File envFile;
35     try {
36         envFile = File(path, "r");
37     } catch(ErrnoException e) {
38       if( throwOnError ) throw e;
39       return;
40     }     
41       
42     foreach(line; envFile.byLine)
43     {
44         auto parsedLine = parseLine(line);
45         if( parsedLine.lineType == LineType.Directive )
46         {
47             import std.process : environment;
48             environment[parsedLine.key] = parsedLine.value;
49         }
50     }
51 }
52 
53 private @safe Line parseLine(const char[] line)
54 {
55    import std.regex : ctRegex, matchFirst;
56 
57    Line result;
58    result.lineType = LineType.Whitespace;
59 
60    auto patternForBlankLine = ctRegex!(`^\s*(?:#.*)?$`);
61    auto patternForSimpleLine = ctRegex!(`^\s*(?:export\s+)?(\S+)\s*=\s*([^#"]*)(?:#.*)?$`);
62    
63    if( matchFirst(line, patternForBlankLine) )
64    {
65        return result;
66    }
67 
68    auto simpleCaptures = matchFirst(line, patternForSimpleLine);
69    
70    if( simpleCaptures.empty() )
71    {
72        throw new DotEnvFormatException();
73    } 
74    
75    import std..string : stripRight;
76 
77    result.key = simpleCaptures[1].idup;
78    result.value = simpleCaptures[2].idup.stripRight();
79    result.lineType = LineType.Directive;
80  
81    return result;
82 }
83 
84 unittest 
85 {
86    assert( parseLine("# comment").lineType == LineType.Whitespace );
87    auto parsed = parseLine("KEY=VALUE");
88    assert( parsed.key == "KEY" && parsed.value == "VALUE" );
89 
90    parsed = parseLine("KEY=val # hello!");
91    assert( parsed.value == "val" );
92    
93    parsed = parseLine("  key=value");
94    assert( parsed.key == "key" );
95    
96 }
97 
98 
99