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 }