1 module simpleconfig.args;
2 
3 import simpleconfig.attributes;
4 
5 /// See `simpleconfig.readConfiguration`
6 public string[] readConfiguration (S) (ref S dst)
7 {
8     static assert (is(S == struct), "Only structs are supported as configuration target");
9 
10     import core.runtime;    
11     return readConfigurationImpl(dst, Runtime.args());
12 }
13 
14 private template resolveName (alias Field)
15 {
16     import std.traits;
17 
18     enum resolveName = getUDAs!(Field, CLI)[0].full.length
19         ? getUDAs!(Field, CLI)[0]
20         : CLI(__traits(identifier, Field));
21 }
22 
23 unittest
24 {
25     struct S
26     {
27         @cli
28         string field1;
29         @cli("renamed|r")
30         string field2;
31     }
32 
33     static assert (resolveName!(S.field1).full == "field1");
34     static assert (resolveName!(S.field1).single == dchar.init);
35     static assert (resolveName!(S.field2).full == "renamed");
36     static assert (resolveName!(S.field2).single == 'r');
37 }
38 
39 private string[] readConfigurationImpl (S) (ref S dst, string[] src)
40 {
41     import std.traits;
42     import std.algorithm;
43     import std.range.primitives;
44     import std.conv;
45     import std.string;
46 
47     string[] remaining_args;
48     bool skip = false;
49 
50     rt: foreach (idx, arg; src)
51     {
52         string key;
53         string value;
54 
55         if (skip)
56         {
57             // skip argument of already processed flag
58             skip = false;
59             continue;
60         }
61 
62         // check if this is an argument
63 
64         if (arg.startsWith("--"))
65             key = arg[2 .. $];
66         else if (arg.startsWith("-"))
67             key = arg[1 .. $];
68         else
69         {
70             remaining_args ~= arg;
71             continue;
72         }
73 
74         // check if this is '-k v' or '-k=v' format
75 
76         auto pos = key.indexOf('=');
77 
78         if (pos != -1)
79         {
80             value = key[pos + 1 .. $];
81             key = key[0 .. pos];
82         }
83         else
84         {
85             value = src[idx + 1];
86         }
87 
88         // check if it matches attributed fields
89 
90         static foreach (Field; getSymbolsByUDA!(S, CLI))
91         {
92             if (   resolveName!Field.full == key
93                 || resolveName!Field.single == key.front)
94             {
95                 auto pfield = &__traits(getMember, dst, __traits(identifier, Field));
96                 *pfield = to!(typeof(*pfield))(value);
97                 // skip next arg if this was a space-delimited key-valye pair
98                 skip = pos == -1;
99                 continue rt;
100             }
101         }
102 
103         remaining_args ~= arg;
104     }
105 
106     return remaining_args;
107 }
108 
109 unittest
110 {
111     struct Config
112     {
113         @cli
114         string field1;
115         @cli("alias")
116         int field2;
117         @cli("long|l")
118         string field3;
119         string field4;
120         @cli
121         string field5;
122     }
123 
124     Config config;
125 
126     auto remaining = readConfigurationImpl(config, [
127         "--field1", "value1",
128         "--alias", "42",
129         "-l", "value3",
130         "--field4", "ignored",
131         "--field5=syntax",
132     ]);
133 
134     assert(config.field1 == "value1");
135     assert(config.field2 == 42);
136     assert(config.field3 == "value3");
137     assert(config.field4 == "");
138     assert(config.field5 == "syntax");
139 
140     assert(remaining == [ "--field4", "ignored" ]);
141 }