BadScript 2
Loading...
Searching...
No Matches
GetoptTokenizer.cs
Go to the documentation of this file.
1// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.
2
3using System;
4using System.Collections.Generic;
5using System.Linq;
6
7using CSharpx;
8
10
11namespace CommandLine.Core
12{
13 internal static class GetoptTokenizer
14 {
15 public static Result<IEnumerable<Token>, Error> Tokenize(IEnumerable<string> arguments,
16 Func<string, NameLookupResult> nameLookup)
17 {
18 return Tokenize(arguments, nameLookup, false, true, false);
19 }
20
21 public static Result<IEnumerable<Token>, Error> Tokenize(IEnumerable<string> arguments,
22 Func<string, NameLookupResult> nameLookup,
23 bool ignoreUnknownArguments,
24 bool allowDashDash,
25 bool posixlyCorrect)
26 {
27 List<Error> errors = new List<Error>();
28 Action<string> onBadFormatToken = arg => errors.Add(new BadFormatTokenError(arg));
29 Action<string> unknownOptionError = name => errors.Add(new UnknownOptionError(name));
30 Action<string> doNothing = name => { };
31 Action<string> onUnknownOption = ignoreUnknownArguments ? doNothing : unknownOptionError;
32
33 int consumeNext = 0;
34 Action<int> onConsumeNext = n => consumeNext = consumeNext + n;
35 bool forceValues = false;
36
37 List<Token> tokens = new List<Token>();
38
39 IEnumerator<string> enumerator = arguments.GetEnumerator();
40
41 while (enumerator.MoveNext())
42 {
43 switch (enumerator.Current)
44 {
45 case null:
46 break;
47
48 case string arg when forceValues:
49 tokens.Add(Token.ValueForced(arg));
50
51 break;
52
53 case string arg when consumeNext > 0:
54 tokens.Add(Token.Value(arg));
55 consumeNext = consumeNext - 1;
56
57 break;
58
59 case "--" when allowDashDash:
60 forceValues = true;
61
62 break;
63
64 case "--":
65 tokens.Add(Token.Value("--"));
66
67 if (posixlyCorrect)
68 {
69 forceValues = true;
70 }
71
72 break;
73
74 case "-":
75 // A single hyphen is always a value (it usually means "read from stdin" or "write to stdout")
76 tokens.Add(Token.Value("-"));
77
78 if (posixlyCorrect)
79 {
80 forceValues = true;
81 }
82
83 break;
84
85 case string arg when arg.StartsWith("--"):
86 tokens.AddRange(TokenizeLongName(arg,
87 nameLookup,
88 onBadFormatToken,
89 onUnknownOption,
90 onConsumeNext
91 )
92 );
93
94 break;
95
96 case string arg when arg.StartsWith("-"):
97 tokens.AddRange(TokenizeShortName(arg, nameLookup, onUnknownOption, onConsumeNext));
98
99 break;
100
101 case string arg:
102 // If we get this far, it's a plain value
103 tokens.Add(Token.Value(arg));
104
105 if (posixlyCorrect)
106 {
107 forceValues = true;
108 }
109
110 break;
111 }
112 }
113
114 return Result.Succeed(tokens.AsEnumerable(), errors.AsEnumerable());
115 }
116
118 Result<IEnumerable<Token>, Error> tokenizerResult,
119 Func<string, Maybe<char>> optionSequenceWithSeparatorLookup)
120 {
121 IEnumerable<Token> tokens = tokenizerResult.SucceededWith()
122 .Memoize();
123
124 List<Token> exploded = new List<Token>(tokens is ICollection<Token> coll ? coll.Count : tokens.Count());
125 Maybe<char> nothing = Maybe.Nothing<char>(); // Re-use same Nothing instance for efficiency
126 Maybe<char> separator = nothing;
127
128 foreach (Token token in tokens)
129 {
130 if (token.IsName())
131 {
132 separator = optionSequenceWithSeparatorLookup(token.Text);
133 exploded.Add(token);
134 }
135 else
136 {
137 // Forced values are never considered option values, so they should not be split
138 if (separator.MatchJust(out char sep) && sep != '\0' && !token.IsValueForced())
139 {
140 if (token.Text.Contains(sep))
141 {
142 exploded.AddRange(token.Text.Split(sep)
144 );
145 }
146 else
147 {
148 exploded.Add(token);
149 }
150 }
151 else
152 {
153 exploded.Add(token);
154 }
155
156 separator = nothing; // Only first value after a separator can possibly be split
157 }
158 }
159
160 return Result.Succeed(exploded as IEnumerable<Token>, tokenizerResult.SuccessMessages());
161 }
162
163 public static Func<
164 IEnumerable<string>,
165 IEnumerable<OptionSpecification>,
167 ConfigureTokenizer(StringComparer nameComparer,
168 bool ignoreUnknownArguments,
169 bool enableDashDash,
170 bool posixlyCorrect)
171 {
172 return (arguments, optionSpecs) =>
173 {
174 Result<IEnumerable<Token>, Error> tokens = Tokenize(arguments,
175 name => NameLookup.Contains(name,
176 optionSpecs,
177 nameComparer
178 ),
179 ignoreUnknownArguments,
180 enableDashDash,
181 posixlyCorrect
182 );
183
184 Result<IEnumerable<Token>, Error> explodedTokens =
185 ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer));
186
187 return explodedTokens;
188 };
189 }
190
191 private static IEnumerable<Token> TokenizeShortName(string arg,
192 Func<string, NameLookupResult> nameLookup,
193 Action<string> onUnknownOption,
194 Action<int> onConsumeNext)
195 {
196 // First option char that requires a value means we swallow the rest of the string as the value
197 // But if there is no rest of the string, then instead we swallow the next argument
198 string chars = arg.Substring(1);
199 int len = chars.Length;
200
201 if (len > 0 && char.IsDigit(chars[0]))
202 {
203 // Assume it's a negative number
204 yield return Token.Value(arg);
205
206 yield break;
207 }
208
209 for (int i = 0; i < len; i++)
210 {
211 string s = new string(chars[i], 1);
212
213 switch (nameLookup(s))
214 {
215 case NameLookupResult.OtherOptionFound:
216 yield return Token.Name(s);
217
218 if (i + 1 < len)
219 {
220 // Rest of this is the value (e.g. "-sfoo" where "-s" is a string-consuming arg)
221 yield return Token.Value(chars.Substring(i + 1));
222
223 yield break;
224 }
225
226 // Value is in next param (e.g., "-s foo")
227 onConsumeNext(1);
228
229 break;
230
231 case NameLookupResult.NoOptionFound:
232 onUnknownOption(s);
233
234 break;
235
236 default:
237 yield return Token.Name(s);
238
239 break;
240 }
241 }
242 }
243
244 private static IEnumerable<Token> TokenizeLongName(string arg,
245 Func<string, NameLookupResult> nameLookup,
246 Action<string> onBadFormatToken,
247 Action<string> onUnknownOption,
248 Action<int> onConsumeNext)
249 {
250 string[] parts = arg.Substring(2)
251 .Split(new[] { '=' },
252 2
253 );
254 string name = parts[0];
255 string value = parts.Length > 1 ? parts[1] : null;
256
257 // A parameter like "--stringvalue=" is acceptable, and makes stringvalue be the empty string
258 if (string.IsNullOrWhiteSpace(name) || name.Contains(" "))
259 {
260 onBadFormatToken(arg);
261
262 yield break;
263 }
264
265 switch (nameLookup(name))
266 {
267 case NameLookupResult.NoOptionFound:
268 onUnknownOption(name);
269
270 yield break;
271
272 case NameLookupResult.OtherOptionFound:
273 yield return Token.Name(name);
274
275 if (value == null) // NOT String.IsNullOrEmpty
276 {
277 onConsumeNext(1);
278 }
279 else
280 {
281 yield return Token.Value(value);
282 }
283
284 break;
285
286 default:
287 yield return Token.Name(name);
288
289 break;
290 }
291 }
292 }
293}
The Maybe type models an optional value. A value of type Maybe a either contains a value of type a (r...
Definition Maybe.cs:33
Models an error generated when an invalid token is detected.
Definition Error.cs:286
static Result< IEnumerable< Token >, Error > Tokenize(IEnumerable< string > arguments, Func< string, NameLookupResult > nameLookup)
static Result< IEnumerable< Token >, Error > Tokenize(IEnumerable< string > arguments, Func< string, NameLookupResult > nameLookup, bool ignoreUnknownArguments, bool allowDashDash, bool posixlyCorrect)
static IEnumerable< Token > TokenizeLongName(string arg, Func< string, NameLookupResult > nameLookup, Action< string > onBadFormatToken, Action< string > onUnknownOption, Action< int > onConsumeNext)
static Func< IEnumerable< string >, IEnumerable< OptionSpecification >, Result< IEnumerable< Token >, Error > > ConfigureTokenizer(StringComparer nameComparer, bool ignoreUnknownArguments, bool enableDashDash, bool posixlyCorrect)
static IEnumerable< Token > TokenizeShortName(string arg, Func< string, NameLookupResult > nameLookup, Action< string > onUnknownOption, Action< int > onConsumeNext)
static Result< IEnumerable< Token >, Error > ExplodeOptionList(Result< IEnumerable< Token >, Error > tokenizerResult, Func< string, Maybe< char > > optionSequenceWithSeparatorLookup)
static NameLookupResult Contains(string name, IEnumerable< OptionSpecification > specifications, StringComparer comparer)
Definition NameLookup.cs:20
static Maybe< char > HavingSeparator(string name, IEnumerable< OptionSpecification > specifications, StringComparer comparer)
Definition NameLookup.cs:37
static Token Value(string text)
Definition Token.cs:30
static Token Name(string text)
Definition Token.cs:25
static Token ValueForced(string text)
Definition Token.cs:40
static Token ValueFromSeparator(string text)
Definition Token.cs:45
Base type of all errors.
Definition Error.cs:110
Models an error generated when an unknown option is detected.
Definition Error.cs:383
Represents the result of a computation.