@@ -32,9 +32,9 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
32
32
Action < string > onUnknownOption = ignoreUnknownArguments ? doNothing : unknownOptionError ;
33
33
34
34
int consumeNext = 0 ;
35
+ Action < int > onConsumeNext = ( n => consumeNext = consumeNext + n ) ;
36
+
35
37
var tokens = new List < Token > ( ) ;
36
- Action < string > addValue = ( s => tokens . Add ( new Value ( s ) ) ) ;
37
- Action < string > addName = ( s => tokens . Add ( new Name ( s ) ) ) ;
38
38
39
39
var enumerator = arguments . GetEnumerator ( ) ;
40
40
while ( enumerator . MoveNext ( ) )
@@ -44,7 +44,7 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
44
44
break ;
45
45
46
46
case string arg when consumeNext > 0 :
47
- addValue ( arg ) ;
47
+ tokens . Add ( new Value ( arg ) ) ;
48
48
consumeNext = consumeNext - 1 ;
49
49
break ;
50
50
@@ -53,100 +53,25 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
53
53
break ;
54
54
55
55
case "--" :
56
- addValue ( "--" ) ;
56
+ tokens . Add ( new Value ( "--" ) ) ;
57
57
break ;
58
58
59
59
case "-" :
60
60
// A single hyphen is always a value (it usually means "read from stdin" or "write to stdout")
61
- addValue ( "-" ) ;
62
- break ;
63
-
64
- case string arg when arg . StartsWith ( "--" ) && arg . Contains ( "=" ) :
65
- string [ ] parts = arg . Substring ( 2 ) . Split ( new char [ ] { '=' } , 2 ) ;
66
- if ( String . IsNullOrWhiteSpace ( parts [ 0 ] ) || parts [ 0 ] . Contains ( " " ) )
67
- {
68
- onBadFormatToken ( arg ) ;
69
- }
70
- else
71
- {
72
- switch ( nameLookup ( parts [ 0 ] ) )
73
- {
74
- case NameLookupResult . NoOptionFound :
75
- onUnknownOption ( parts [ 0 ] ) ;
76
- break ;
77
-
78
- default :
79
- addName ( parts [ 0 ] ) ;
80
- addValue ( parts [ 1 ] ) ;
81
- break ;
82
- }
83
- }
61
+ tokens . Add ( new Value ( "-" ) ) ;
84
62
break ;
85
63
86
64
case string arg when arg . StartsWith ( "--" ) :
87
- var name = arg . Substring ( 2 ) ;
88
- switch ( nameLookup ( name ) )
89
- {
90
- case NameLookupResult . OtherOptionFound :
91
- addName ( name ) ;
92
- consumeNext = 1 ;
93
- break ;
94
-
95
- case NameLookupResult . NoOptionFound :
96
- // When ignoreUnknownArguments is true and AutoHelp is true, calling code is responsible for
97
- // setting up nameLookup so that it will return a known name for --help, so that we don't skip it here
98
- onUnknownOption ( name ) ;
99
- break ;
100
-
101
- default :
102
- addName ( name ) ;
103
- break ;
104
- }
65
+ tokens . AddRange ( TokenizeLongName ( arg , nameLookup , onBadFormatToken , onUnknownOption , onConsumeNext ) ) ;
105
66
break ;
106
67
107
68
case string arg when arg . StartsWith ( "-" ) :
108
- // First option char that requires a value means we swallow the rest of the string as the value
109
- // But if there is no rest of the string, then instead we swallow the next argument
110
- string chars = arg . Substring ( 1 ) ;
111
- int len = chars . Length ;
112
- if ( len > 0 && Char . IsDigit ( chars [ 0 ] ) )
113
- {
114
- // Assume it's a negative number
115
- addValue ( arg ) ;
116
- continue ;
117
- }
118
- for ( int i = 0 ; i < len ; i ++ )
119
- {
120
- var s = new String ( chars [ i ] , 1 ) ;
121
- switch ( nameLookup ( s ) )
122
- {
123
- case NameLookupResult . OtherOptionFound :
124
- addName ( s ) ;
125
- if ( i + 1 < len )
126
- {
127
- addValue ( chars . Substring ( i + 1 ) ) ;
128
- i = len ; // Can't use "break" inside a switch, so this breaks out of the loop
129
- }
130
- else
131
- {
132
- consumeNext = 1 ;
133
- }
134
- break ;
135
-
136
- case NameLookupResult . NoOptionFound :
137
- onUnknownOption ( s ) ;
138
- break ;
139
-
140
- default :
141
- addName ( s ) ;
142
- break ;
143
- }
144
- }
69
+ tokens . AddRange ( TokenizeShortName ( arg , nameLookup , onUnknownOption , onConsumeNext ) ) ;
145
70
break ;
146
71
147
72
case string arg :
148
73
// If we get this far, it's a plain value
149
- addValue ( arg ) ;
74
+ tokens . Add ( new Value ( arg ) ) ;
150
75
break ;
151
76
}
152
77
}
@@ -158,7 +83,6 @@ public static Result<IEnumerable<Token>, Error> ExplodeOptionList(
158
83
Result < IEnumerable < Token > , Error > tokenizerResult ,
159
84
Func < string , Maybe < char > > optionSequenceWithSeparatorLookup )
160
85
{
161
- // TODO: I don't like how this works. I don't want "-s foo;bar baz" to put three values into -s. Let's instead have a third token type, List, besides Name and Value.
162
86
var tokens = tokenizerResult . SucceededWith ( ) . Memoize ( ) ;
163
87
164
88
var replaces = tokens . Select ( ( t , i ) =>
@@ -192,5 +116,94 @@ public static Func<
192
116
return explodedTokens ;
193
117
} ;
194
118
}
119
+
120
+ private static IEnumerable < Token > TokenizeShortName (
121
+ string arg ,
122
+ Func < string , NameLookupResult > nameLookup ,
123
+ Action < string > onUnknownOption ,
124
+ Action < int > onConsumeNext )
125
+ {
126
+
127
+ // First option char that requires a value means we swallow the rest of the string as the value
128
+ // But if there is no rest of the string, then instead we swallow the next argument
129
+ string chars = arg . Substring ( 1 ) ;
130
+ int len = chars . Length ;
131
+ if ( len > 0 && Char . IsDigit ( chars [ 0 ] ) )
132
+ {
133
+ // Assume it's a negative number
134
+ yield return Token . Value ( arg ) ;
135
+ yield break ;
136
+ }
137
+ for ( int i = 0 ; i < len ; i ++ )
138
+ {
139
+ var s = new String ( chars [ i ] , 1 ) ;
140
+ switch ( nameLookup ( s ) )
141
+ {
142
+ case NameLookupResult . OtherOptionFound :
143
+ yield return Token . Name ( s ) ;
144
+
145
+ if ( i + 1 < len )
146
+ {
147
+ // Rest of this is the value (e.g. "-sfoo" where "-s" is a string-consuming arg)
148
+ yield return Token . Value ( chars . Substring ( i + 1 ) ) ;
149
+ yield break ;
150
+ }
151
+ else
152
+ {
153
+ // Value is in next param (e.g., "-s foo")
154
+ onConsumeNext ( 1 ) ;
155
+ }
156
+ break ;
157
+
158
+ case NameLookupResult . NoOptionFound :
159
+ onUnknownOption ( s ) ;
160
+ break ;
161
+
162
+ default :
163
+ yield return Token . Name ( s ) ;
164
+ break ;
165
+ }
166
+ }
167
+ }
168
+
169
+ private static IEnumerable < Token > TokenizeLongName (
170
+ string arg ,
171
+ Func < string , NameLookupResult > nameLookup ,
172
+ Action < string > onBadFormatToken ,
173
+ Action < string > onUnknownOption ,
174
+ Action < int > onConsumeNext )
175
+ {
176
+ string [ ] parts = arg . Substring ( 2 ) . Split ( new char [ ] { '=' } , 2 ) ;
177
+ string name = parts [ 0 ] ;
178
+ string value = ( parts . Length > 1 ) ? parts [ 1 ] : null ;
179
+ // A parameter like "--stringvalue=" is acceptable, and makes stringvalue be the empty string
180
+ if ( String . IsNullOrWhiteSpace ( name ) || name . Contains ( " " ) )
181
+ {
182
+ onBadFormatToken ( arg ) ;
183
+ yield break ;
184
+ }
185
+ switch ( nameLookup ( name ) )
186
+ {
187
+ case NameLookupResult . NoOptionFound :
188
+ onUnknownOption ( name ) ;
189
+ yield break ;
190
+
191
+ case NameLookupResult . OtherOptionFound :
192
+ yield return Token . Name ( name ) ;
193
+ if ( value == null ) // NOT String.IsNullOrEmpty
194
+ {
195
+ onConsumeNext ( 1 ) ;
196
+ }
197
+ else
198
+ {
199
+ yield return Token . Value ( value ) ;
200
+ }
201
+ break ;
202
+
203
+ default :
204
+ yield return Token . Name ( name ) ;
205
+ break ;
206
+ }
207
+ }
195
208
}
196
209
}
0 commit comments