1 module jcli.argparser.parser; 2 3 import std; 4 5 struct ArgParserSplitter 6 { 7 private 8 { 9 string[] _input; 10 size_t _elCursor; 11 size_t _arrCursor; 12 string _front; 13 bool _empty; 14 } 15 16 this(string[] input) 17 { 18 this._input = input; 19 this.popFront(); 20 } 21 22 @property @safe @nogc 23 string front() nothrow pure const 24 { 25 return this._front; 26 } 27 28 @property @safe @nogc 29 bool empty() nothrow pure const 30 { 31 return this._empty; 32 } 33 34 @safe @nogc 35 void popFront() nothrow 36 { 37 if(this._input.length == 0 || this._arrCursor == this._input.length) 38 { 39 this._empty = true; 40 return; 41 } 42 43 if(this._input[this._arrCursor].length == 0) 44 { 45 this._arrCursor++; 46 this._elCursor = 0; 47 this.popFront(); 48 return; 49 } 50 51 if(this._elCursor == 0 && this._input[this._arrCursor][0] != '-') 52 { 53 this._front = this._input[this._arrCursor++]; 54 return; 55 } 56 57 const start = this._elCursor; 58 while( 59 this._elCursor < this._input[this._arrCursor].length 60 && this._input[this._arrCursor][this._elCursor] != ' ' 61 && this._input[this._arrCursor][this._elCursor] != '=' 62 ) 63 this._elCursor++; 64 65 this._front = this._input[this._arrCursor][start..this._elCursor]; 66 if(this._elCursor == this._input[this._arrCursor].length) 67 { 68 this._elCursor = 0; 69 this._arrCursor++; 70 } 71 else 72 this._elCursor++; // Skip the delim 73 } 74 } 75 /// 76 unittest 77 { 78 assert( 79 ArgParserSplitter([ 80 "a", "b c", "--one", "-tw o", "--thr=ee" 81 ]).equal([ 82 "a", "b c", "--one", "-tw", "o", "--thr", "ee" 83 ]) 84 ); 85 } 86 87 struct ArgParser 88 { 89 static struct Result 90 { 91 enum Kind 92 { 93 rawText, 94 argument 95 } 96 97 string fullSlice; 98 string dashSlice; 99 string nameSlice; 100 Kind kind; 101 102 bool isShortHand() 103 { 104 return this.dashSlice.length == 1; 105 } 106 } 107 108 private 109 { 110 ArgParserSplitter _range; 111 bool _empty; 112 Result _front; 113 } 114 115 this(string[] args) 116 { 117 this._range = ArgParserSplitter(args); 118 this.popFront(); 119 } 120 121 @property @safe @nogc 122 Result front() nothrow pure const 123 { 124 return this._front; 125 } 126 127 @property @safe @nogc 128 bool empty() nothrow pure const 129 { 130 return this._empty; 131 } 132 133 @safe @nogc 134 void popFront() nothrow 135 { 136 scope(exit) this._range.popFront(); 137 138 this._front = Result.init; 139 if(this._range.empty) 140 { 141 this._empty = true; 142 return; 143 } 144 145 this._front.fullSlice = this._range.front; 146 if(this._front.fullSlice.length && this._front.fullSlice[0] == '-') 147 { 148 this._front.kind = Result.Kind.argument; 149 const start = 0; 150 auto end = 0; 151 while(end < this._front.fullSlice.length && this._front.fullSlice[end] == '-') 152 end++; 153 this._front.dashSlice = this._front.fullSlice[start..end]; 154 this._front.nameSlice = this._front.fullSlice[end..$]; 155 } 156 else 157 this._front.kind = Result.Kind.rawText; 158 } 159 160 @property @safe @nogc nothrow inout 161 auto remainingArgs() 162 { 163 return this._range; 164 } 165 } 166 /// 167 unittest 168 { 169 assert( 170 ArgParser([ 171 "dub", "run", "-b", "release", "--compiler=ldc", "--", "abc" 172 ]).equal([ 173 ArgParser.Result("dub", null, null, ArgParser.Result.Kind.rawText), 174 ArgParser.Result("run", null, null, ArgParser.Result.Kind.rawText), 175 ArgParser.Result("-b", "-", "b", ArgParser.Result.Kind.argument), 176 ArgParser.Result("release", null, null, ArgParser.Result.Kind.rawText), 177 ArgParser.Result("--compiler", "--", "compiler", ArgParser.Result.Kind.argument), 178 ArgParser.Result("ldc", null, null, ArgParser.Result.Kind.rawText), 179 ArgParser.Result("--", "--", "", ArgParser.Result.Kind.argument), 180 ArgParser.Result("abc", null, null, ArgParser.Result.Kind.rawText), 181 ]) 182 ); 183 }