BadScript 2
Loading...
Searching...
No Matches
Tokenizer.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;
6using System.Text.RegularExpressions;
7
9
10using CSharpx;
11
13
14namespace CommandLine.Core
15{
16 internal static class Tokenizer
17 {
18 public static Result<IEnumerable<Token>, Error> Tokenize(IEnumerable<string> arguments,
19 Func<string, NameLookupResult> nameLookup)
20 {
21 return Tokenize(arguments, nameLookup, tokens => tokens);
22 }
23
24 public static Result<IEnumerable<Token>, Error> Tokenize(IEnumerable<string> arguments,
25 Func<string, NameLookupResult> nameLookup,
26 Func<IEnumerable<Token>, IEnumerable<Token>> normalize)
27 {
28 List<Error> errors = new List<Error>();
29 Action<Error> onError = errors.Add;
30
31 IEnumerable<Token> tokens = (from arg in arguments
32 from token in !arg.StartsWith("-", StringComparison.Ordinal) ? new[]
33 {
34 Token.Value(arg),
35 } :
36 arg.StartsWith("--", StringComparison.Ordinal) ?
37 TokenizeLongName(arg, onError) :
38 TokenizeShortName(arg, nameLookup)
39 select token)
40 .Memoize();
41
42 IEnumerable<Token> normalized = normalize(tokens)
43 .Memoize();
44
45 IEnumerable<Token> unkTokens =
46 (from t in normalized where t.IsName() && nameLookup(t.Text) == NameLookupResult.NoOptionFound select t)
47 .Memoize();
48
49 return Result.Succeed(normalized.Where(x => !unkTokens.Contains(x)),
50 errors.Concat(from t in unkTokens select new UnknownOptionError(t.Text))
51 );
52 }
53
54 public static Result<IEnumerable<Token>, Error> PreprocessDashDash(IEnumerable<string> arguments,
55 Func<IEnumerable<string>,
56 Result<IEnumerable<Token>, Error>>
57 tokenizer)
58 {
59 if (arguments.Any(arg => arg.EqualsOrdinal("--")))
60 {
61 Result<IEnumerable<Token>, Error> tokenizerResult =
62 tokenizer(arguments.TakeWhile(arg => !arg.EqualsOrdinal("--")));
63
64 IEnumerable<Token> values = arguments.SkipWhile(arg => !arg.EqualsOrdinal("--"))
65 .Skip(1)
66 .Select(Token.ValueForced);
67
68 return tokenizerResult.Map(tokens => tokens.Concat(values));
69 }
70
71 return tokenizer(arguments);
72 }
73
75 Result<IEnumerable<Token>, Error> tokenizerResult,
76 Func<string, Maybe<char>> optionSequenceWithSeparatorLookup)
77 {
78 IEnumerable<Token> tokens = tokenizerResult.SucceededWith()
79 .Memoize();
80
81 List<Token> exploded = new List<Token>(tokens is ICollection<Token> coll ? coll.Count : tokens.Count());
82 Maybe<char> nothing = Maybe.Nothing<char>(); // Re-use same Nothing instance for efficiency
83 Maybe<char> separator = nothing;
84
85 foreach (Token token in tokens)
86 {
87 if (token.IsName())
88 {
89 separator = optionSequenceWithSeparatorLookup(token.Text);
90 exploded.Add(token);
91 }
92 else
93 {
94 // Forced values are never considered option values, so they should not be split
95 if (separator.MatchJust(out char sep) && sep != '\0' && !token.IsValueForced())
96 {
97 if (token.Text.Contains(sep))
98 {
99 exploded.AddRange(token.Text.Split(sep)
101 );
102 }
103 else
104 {
105 exploded.Add(token);
106 }
107 }
108 else
109 {
110 exploded.Add(token);
111 }
112
113 separator = nothing; // Only first value after a separator can possibly be split
114 }
115 }
116
117 return Result.Succeed(exploded as IEnumerable<Token>, tokenizerResult.SuccessMessages());
118 }
119
127 public static IEnumerable<Token> Normalize(IEnumerable<Token> tokens,
128 Func<string, bool> nameLookup)
129 {
130 IEnumerable<Tuple<Token, Token>> toExclude =
131 from i in
132 tokens.Select((t, i) =>
133 {
134 if (t.IsName() == false || nameLookup(t.Text))
135 {
136 return Maybe.Nothing<Tuple<Token, Token>>();
137 }
138
139 Maybe<Token> next = tokens.ElementAtOrDefault(i + 1)
140 .ToMaybe();
141
142 bool removeValue = next.MatchJust(out Token nextValue) &&
143 next.MapValueOrDefault(p => p.IsValue() &&
144 ((Value)p).ExplicitlyAssigned,
145 false
146 );
147
148 return Maybe.Just(new Tuple<Token, Token>(t, removeValue ? nextValue : null));
149 }
150 )
151 .Where(i => i.IsJust())
152 select i.FromJustOrFail();
153
154 IEnumerable<Token> normalized =
155 tokens.Where(t => toExclude.Any(e => ReferenceEquals(e.Item1, t) || ReferenceEquals(e.Item2, t)) ==
156 false
157 );
158
159 return normalized;
160 }
161
162 public static Func<
163 IEnumerable<string>,
164 IEnumerable<OptionSpecification>,
166 ConfigureTokenizer(StringComparer nameComparer,
167 bool ignoreUnknownArguments,
168 bool enableDashDash)
169 {
170 return (arguments, optionSpecs) =>
171 {
172 Func<IEnumerable<Token>, IEnumerable<Token>> normalize = ignoreUnknownArguments
173 ? toks => Normalize(toks,
174 name =>
175 NameLookup.Contains(name,
176 optionSpecs,
177 nameComparer
178 ) !=
179 NameLookupResult.NoOptionFound
180 )
181 : new Func<IEnumerable<Token>,
182 IEnumerable<Token>>(toks => toks);
183
184 Result<IEnumerable<Token>, Error> tokens = enableDashDash
185 ? PreprocessDashDash(arguments,
186 args =>
187 Tokenize(args,
188 name => NameLookup.Contains(name,
189 optionSpecs,
190 nameComparer
191 ),
192 normalize
193 )
194 )
195 : Tokenize(arguments,
196 name => NameLookup.Contains(name,
197 optionSpecs,
198 nameComparer
199 ),
200 normalize
201 );
202
203 Result<IEnumerable<Token>, Error> explodedTokens =
204 ExplodeOptionList(tokens, name => NameLookup.HavingSeparator(name, optionSpecs, nameComparer));
205
206 return explodedTokens;
207 };
208 }
209
210 private static IEnumerable<Token> TokenizeShortName(string value,
211 Func<string, NameLookupResult> nameLookup)
212 {
213 //Allow single dash as a value
214 if (value.Length == 1 && value[0] == '-')
215 {
216 yield return Token.Value(value);
217
218 yield break;
219 }
220
221 if (value.Length > 1 && value[0] == '-' && value[1] != '-')
222 {
223 string text = value.Substring(1);
224
225 if (char.IsDigit(text[0]))
226 {
227 yield return Token.Value(value);
228
229 yield break;
230 }
231
232 if (value.Length == 2)
233 {
234 yield return Token.Name(text);
235
236 yield break;
237 }
238
239 int i = 0;
240
241 foreach (char c in text)
242 {
243 string n = new string(c, 1);
244 NameLookupResult r = nameLookup(n);
245
246 // Assume first char is an option
247 if (i > 0 && r == NameLookupResult.NoOptionFound)
248 {
249 break;
250 }
251
252 i++;
253
254 yield return Token.Name(n);
255
256 // If option expects a value (other than a boolean), assume following chars are that value
257 if (r == NameLookupResult.OtherOptionFound)
258 {
259 break;
260 }
261 }
262
263 if (i < text.Length)
264 {
265 yield return Token.Value(text.Substring(i));
266 }
267 }
268 }
269
270 private static IEnumerable<Token> TokenizeLongName(string value,
271 Action<Error> onError)
272 {
273 if (value.Length > 2 && value.StartsWith("--", StringComparison.Ordinal))
274 {
275 string text = value.Substring(2);
276 int equalIndex = text.IndexOf('=');
277
278 if (equalIndex <= 0)
279 {
280 yield return Token.Name(text);
281
282 yield break;
283 }
284
285 if (equalIndex == 1) // "--="
286 {
287 onError(new BadFormatTokenError(value));
288
289 yield break;
290 }
291
292 Match tokenMatch = Regex.Match(text, "^([^=]+)=([^ ].*)$");
293
294 if (tokenMatch.Success)
295 {
296 yield return Token.Name(tokenMatch.Groups[1].Value);
297 yield return Token.Value(tokenMatch.Groups[2].Value, true);
298 }
299 else
300 {
301 onError(new BadFormatTokenError(value));
302 }
303 }
304 }
305 }
306}
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 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
static IEnumerable< Token > TokenizeShortName(string value, Func< string, NameLookupResult > nameLookup)
Definition Tokenizer.cs:210
static Result< IEnumerable< Token >, Error > ExplodeOptionList(Result< IEnumerable< Token >, Error > tokenizerResult, Func< string, Maybe< char > > optionSequenceWithSeparatorLookup)
Definition Tokenizer.cs:74
static Func< IEnumerable< string >, IEnumerable< OptionSpecification >, Result< IEnumerable< Token >, Error > > ConfigureTokenizer(StringComparer nameComparer, bool ignoreUnknownArguments, bool enableDashDash)
Definition Tokenizer.cs:166
static Result< IEnumerable< Token >, Error > Tokenize(IEnumerable< string > arguments, Func< string, NameLookupResult > nameLookup)
Definition Tokenizer.cs:18
static IEnumerable< Token > TokenizeLongName(string value, Action< Error > onError)
Definition Tokenizer.cs:270
static Result< IEnumerable< Token >, Error > Tokenize(IEnumerable< string > arguments, Func< string, NameLookupResult > nameLookup, Func< IEnumerable< Token >, IEnumerable< Token > > normalize)
Definition Tokenizer.cs:24
static IEnumerable< Token > Normalize(IEnumerable< Token > tokens, Func< string, bool > nameLookup)
Normalizes the given tokens .
Definition Tokenizer.cs:127
static Result< IEnumerable< Token >, Error > PreprocessDashDash(IEnumerable< string > arguments, Func< IEnumerable< string >, Result< IEnumerable< Token >, Error > > tokenizer)
Definition Tokenizer.cs:54
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.