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 }