1 | //
|
---|
2 | // Options.cs
|
---|
3 | //
|
---|
4 | // Authors:
|
---|
5 | // Jonathan Pryor <jpryor@novell.com>
|
---|
6 | // Federico Di Gregorio <fog@initd.org>
|
---|
7 | //
|
---|
8 | // Copyright (C) 2008 Novell (http://www.novell.com)
|
---|
9 | // Copyright (C) 2009 Federico Di Gregorio.
|
---|
10 | //
|
---|
11 | // Permission is hereby granted, free of charge, to any person obtaining
|
---|
12 | // a copy of this software and associated documentation files (the
|
---|
13 | // "Software"), to deal in the Software without restriction, including
|
---|
14 | // without limitation the rights to use, copy, modify, merge, publish,
|
---|
15 | // distribute, sublicense, and/or sell copies of the Software, and to
|
---|
16 | // permit persons to whom the Software is furnished to do so, subject to
|
---|
17 | // the following conditions:
|
---|
18 | //
|
---|
19 | // The above copyright notice and this permission notice shall be
|
---|
20 | // included in all copies or substantial portions of the Software.
|
---|
21 | //
|
---|
22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
---|
23 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
---|
24 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
---|
25 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
---|
26 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
---|
27 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
---|
28 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
---|
29 | //
|
---|
30 |
|
---|
31 | // Compile With:
|
---|
32 | // gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll
|
---|
33 | // gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll
|
---|
34 | //
|
---|
35 | // The LINQ version just changes the implementation of
|
---|
36 | // OptionSet.Parse(IEnumerable<string>), and confers no semantic changes.
|
---|
37 |
|
---|
38 | //
|
---|
39 | // A Getopt::Long-inspired option parsing library for C#.
|
---|
40 | //
|
---|
41 | // NDesk.Options.OptionSet is built upon a key/value table, where the
|
---|
42 | // key is a option format string and the value is a delegate that is
|
---|
43 | // invoked when the format string is matched.
|
---|
44 | //
|
---|
45 | // Option format strings:
|
---|
46 | // Regex-like BNF Grammar:
|
---|
47 | // name: .+
|
---|
48 | // type: [=:]
|
---|
49 | // sep: ( [^{}]+ | '{' .+ '}' )?
|
---|
50 | // aliases: ( name type sep ) ( '|' name type sep )*
|
---|
51 | //
|
---|
52 | // Each '|'-delimited name is an alias for the associated action. If the
|
---|
53 | // format string ends in a '=', it has a required value. If the format
|
---|
54 | // string ends in a ':', it has an optional value. If neither '=' or ':'
|
---|
55 | // is present, no value is supported. `=' or `:' need only be defined on one
|
---|
56 | // alias, but if they are provided on more than one they must be consistent.
|
---|
57 | //
|
---|
58 | // Each alias portion may also end with a "key/value separator", which is used
|
---|
59 | // to split option values if the option accepts > 1 value. If not specified,
|
---|
60 | // it defaults to '=' and ':'. If specified, it can be any character except
|
---|
61 | // '{' and '}' OR the *string* between '{' and '}'. If no separator should be
|
---|
62 | // used (i.e. the separate values should be distinct arguments), then "{}"
|
---|
63 | // should be used as the separator.
|
---|
64 | //
|
---|
65 | // Options are extracted either from the current option by looking for
|
---|
66 | // the option name followed by an '=' or ':', or is taken from the
|
---|
67 | // following option IFF:
|
---|
68 | // - The current option does not contain a '=' or a ':'
|
---|
69 | // - The current option requires a value (i.e. not a Option type of ':')
|
---|
70 | //
|
---|
71 | // The `name' used in the option format string does NOT include any leading
|
---|
72 | // option indicator, such as '-', '--', or '/'. All three of these are
|
---|
73 | // permitted/required on any named option.
|
---|
74 | //
|
---|
75 | // Option bundling is permitted so long as:
|
---|
76 | // - '-' is used to start the option group
|
---|
77 | // - all of the bundled options are a single character
|
---|
78 | // - at most one of the bundled options accepts a value, and the value
|
---|
79 | // provided starts from the next character to the end of the string.
|
---|
80 | //
|
---|
81 | // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value'
|
---|
82 | // as '-Dname=value'.
|
---|
83 | //
|
---|
84 | // Option processing is disabled by specifying "--". All options after "--"
|
---|
85 | // are returned by OptionSet.Parse() unchanged and unprocessed.
|
---|
86 | //
|
---|
87 | // Unprocessed options are returned from OptionSet.Parse().
|
---|
88 | //
|
---|
89 | // Examples:
|
---|
90 | // int verbose = 0;
|
---|
91 | // OptionSet p = new OptionSet ()
|
---|
92 | // .Add ("v", v => ++verbose)
|
---|
93 | // .Add ("name=|value=", v => Console.WriteLine (v));
|
---|
94 | // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"});
|
---|
95 | //
|
---|
96 | // The above would parse the argument string array, and would invoke the
|
---|
97 | // lambda expression three times, setting `verbose' to 3 when complete.
|
---|
98 | // It would also print out "A" and "B" to standard output.
|
---|
99 | // The returned array would contain the string "extra".
|
---|
100 | //
|
---|
101 | // C# 3.0 collection initializers are supported and encouraged:
|
---|
102 | // var p = new OptionSet () {
|
---|
103 | // { "h|?|help", v => ShowHelp () },
|
---|
104 | // };
|
---|
105 | //
|
---|
106 | // System.ComponentModel.TypeConverter is also supported, allowing the use of
|
---|
107 | // custom data types in the callback type; TypeConverter.ConvertFromString()
|
---|
108 | // is used to convert the value option to an instance of the specified
|
---|
109 | // type:
|
---|
110 | //
|
---|
111 | // var p = new OptionSet () {
|
---|
112 | // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
|
---|
113 | // };
|
---|
114 | //
|
---|
115 | // Random other tidbits:
|
---|
116 | // - Boolean options (those w/o '=' or ':' in the option format string)
|
---|
117 | // are explicitly enabled if they are followed with '+', and explicitly
|
---|
118 | // disabled if they are followed with '-':
|
---|
119 | // string a = null;
|
---|
120 | // var p = new OptionSet () {
|
---|
121 | // { "a", s => a = s },
|
---|
122 | // };
|
---|
123 | // p.Parse (new string[]{"-a"}); // sets v != null
|
---|
124 | // p.Parse (new string[]{"-a+"}); // sets v != null
|
---|
125 | // p.Parse (new string[]{"-a-"}); // sets v == null
|
---|
126 | //
|
---|
127 |
|
---|
128 | using System;
|
---|
129 | using System.Collections;
|
---|
130 | using System.Collections.Generic;
|
---|
131 | using System.Collections.ObjectModel;
|
---|
132 | using System.ComponentModel;
|
---|
133 | using System.Globalization;
|
---|
134 | using System.IO;
|
---|
135 | using System.Runtime.Serialization;
|
---|
136 | using System.Security.Permissions;
|
---|
137 | using System.Text;
|
---|
138 | using System.Text.RegularExpressions;
|
---|
139 |
|
---|
140 | #if LINQ
|
---|
141 | using System.Linq;
|
---|
142 | #endif
|
---|
143 |
|
---|
144 | #if TEST
|
---|
145 | using NDesk.Options;
|
---|
146 | #endif
|
---|
147 |
|
---|
148 | #if NDESK_OPTIONS
|
---|
149 | namespace NDesk.Options
|
---|
150 | #else
|
---|
151 | namespace Mono.Options
|
---|
152 | #endif
|
---|
153 | {
|
---|
154 | static class StringCoda {
|
---|
155 |
|
---|
156 | public static IEnumerable<string> WrappedLines (string self, params int[] widths)
|
---|
157 | {
|
---|
158 | IEnumerable<int> w = widths;
|
---|
159 | return WrappedLines (self, w);
|
---|
160 | }
|
---|
161 |
|
---|
162 | public static IEnumerable<string> WrappedLines (string self, IEnumerable<int> widths)
|
---|
163 | {
|
---|
164 | if (widths == null)
|
---|
165 | throw new ArgumentNullException ("widths");
|
---|
166 | return CreateWrappedLinesIterator (self, widths);
|
---|
167 | }
|
---|
168 |
|
---|
169 | private static IEnumerable<string> CreateWrappedLinesIterator (string self, IEnumerable<int> widths)
|
---|
170 | {
|
---|
171 | if (string.IsNullOrEmpty (self)) {
|
---|
172 | yield return string.Empty;
|
---|
173 | yield break;
|
---|
174 | }
|
---|
175 | using (var ewidths = widths.GetEnumerator ()) {
|
---|
176 | bool? hw = null;
|
---|
177 | int width = GetNextWidth (ewidths, int.MaxValue, ref hw);
|
---|
178 | int start = 0, end;
|
---|
179 | do {
|
---|
180 | end = GetLineEnd (start, width, self);
|
---|
181 | char c = self [end-1];
|
---|
182 | if (char.IsWhiteSpace (c))
|
---|
183 | --end;
|
---|
184 | bool needContinuation = end != self.Length && !IsEolChar (c);
|
---|
185 | string continuation = "";
|
---|
186 | if (needContinuation) {
|
---|
187 | --end;
|
---|
188 | continuation = "-";
|
---|
189 | }
|
---|
190 | string line = self.Substring (start, end - start) + continuation;
|
---|
191 | yield return line;
|
---|
192 | start = end;
|
---|
193 | if (char.IsWhiteSpace (c))
|
---|
194 | ++start;
|
---|
195 | width = GetNextWidth (ewidths, width, ref hw);
|
---|
196 | } while (end < self.Length);
|
---|
197 | }
|
---|
198 | }
|
---|
199 |
|
---|
200 | private static int GetNextWidth (IEnumerator<int> ewidths, int curWidth, ref bool? eValid)
|
---|
201 | {
|
---|
202 | if (!eValid.HasValue || (eValid.HasValue && eValid.Value)) {
|
---|
203 | curWidth = (eValid = ewidths.MoveNext ()).Value ? ewidths.Current : curWidth;
|
---|
204 | // '.' is any character, - is for a continuation
|
---|
205 | const string minWidth = ".-";
|
---|
206 | if (curWidth < minWidth.Length)
|
---|
207 | throw new ArgumentOutOfRangeException ("widths",
|
---|
208 | string.Format ("Element must be >= {0}, was {1}.", minWidth.Length, curWidth));
|
---|
209 | return curWidth;
|
---|
210 | }
|
---|
211 | // no more elements, use the last element.
|
---|
212 | return curWidth;
|
---|
213 | }
|
---|
214 |
|
---|
215 | private static bool IsEolChar (char c)
|
---|
216 | {
|
---|
217 | return !char.IsLetterOrDigit (c);
|
---|
218 | }
|
---|
219 |
|
---|
220 | private static int GetLineEnd (int start, int length, string description)
|
---|
221 | {
|
---|
222 | int end = System.Math.Min (start + length, description.Length);
|
---|
223 | int sep = -1;
|
---|
224 | for (int i = start; i < end; ++i) {
|
---|
225 | if (description [i] == '\n')
|
---|
226 | return i+1;
|
---|
227 | if (IsEolChar (description [i]))
|
---|
228 | sep = i+1;
|
---|
229 | }
|
---|
230 | if (sep == -1 || end == description.Length)
|
---|
231 | return end;
|
---|
232 | return sep;
|
---|
233 | }
|
---|
234 | }
|
---|
235 |
|
---|
236 | public class OptionValueCollection : IList, IList<string> {
|
---|
237 |
|
---|
238 | List<string> values = new List<string> ();
|
---|
239 | OptionContext c;
|
---|
240 |
|
---|
241 | internal OptionValueCollection (OptionContext c)
|
---|
242 | {
|
---|
243 | this.c = c;
|
---|
244 | }
|
---|
245 |
|
---|
246 | #region ICollection
|
---|
247 | void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);}
|
---|
248 | bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}}
|
---|
249 | object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}}
|
---|
250 | #endregion
|
---|
251 |
|
---|
252 | #region ICollection<T>
|
---|
253 | public void Add (string item) {values.Add (item);}
|
---|
254 | public void Clear () {values.Clear ();}
|
---|
255 | public bool Contains (string item) {return values.Contains (item);}
|
---|
256 | public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);}
|
---|
257 | public bool Remove (string item) {return values.Remove (item);}
|
---|
258 | public int Count {get {return values.Count;}}
|
---|
259 | public bool IsReadOnly {get {return false;}}
|
---|
260 | #endregion
|
---|
261 |
|
---|
262 | #region IEnumerable
|
---|
263 | IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();}
|
---|
264 | #endregion
|
---|
265 |
|
---|
266 | #region IEnumerable<T>
|
---|
267 | public IEnumerator<string> GetEnumerator () {return values.GetEnumerator ();}
|
---|
268 | #endregion
|
---|
269 |
|
---|
270 | #region IList
|
---|
271 | int IList.Add (object value) {return (values as IList).Add (value);}
|
---|
272 | bool IList.Contains (object value) {return (values as IList).Contains (value);}
|
---|
273 | int IList.IndexOf (object value) {return (values as IList).IndexOf (value);}
|
---|
274 | void IList.Insert (int index, object value) {(values as IList).Insert (index, value);}
|
---|
275 | void IList.Remove (object value) {(values as IList).Remove (value);}
|
---|
276 | void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);}
|
---|
277 | bool IList.IsFixedSize {get {return false;}}
|
---|
278 | object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}}
|
---|
279 | #endregion
|
---|
280 |
|
---|
281 | #region IList<T>
|
---|
282 | public int IndexOf (string item) {return values.IndexOf (item);}
|
---|
283 | public void Insert (int index, string item) {values.Insert (index, item);}
|
---|
284 | public void RemoveAt (int index) {values.RemoveAt (index);}
|
---|
285 |
|
---|
286 | private void AssertValid (int index)
|
---|
287 | {
|
---|
288 | if (c.Option == null)
|
---|
289 | throw new InvalidOperationException ("OptionContext.Option is null.");
|
---|
290 | if (index >= c.Option.MaxValueCount)
|
---|
291 | throw new ArgumentOutOfRangeException ("index");
|
---|
292 | if (c.Option.OptionValueType == OptionValueType.Required &&
|
---|
293 | index >= values.Count)
|
---|
294 | throw new OptionException (string.Format (
|
---|
295 | c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName),
|
---|
296 | c.OptionName);
|
---|
297 | }
|
---|
298 |
|
---|
299 | public string this [int index] {
|
---|
300 | get {
|
---|
301 | AssertValid (index);
|
---|
302 | return index >= values.Count ? null : values [index];
|
---|
303 | }
|
---|
304 | set {
|
---|
305 | values [index] = value;
|
---|
306 | }
|
---|
307 | }
|
---|
308 | #endregion
|
---|
309 |
|
---|
310 | public List<string> ToList ()
|
---|
311 | {
|
---|
312 | return new List<string> (values);
|
---|
313 | }
|
---|
314 |
|
---|
315 | public string[] ToArray ()
|
---|
316 | {
|
---|
317 | return values.ToArray ();
|
---|
318 | }
|
---|
319 |
|
---|
320 | public override string ToString ()
|
---|
321 | {
|
---|
322 | return string.Join (", ", values.ToArray ());
|
---|
323 | }
|
---|
324 | }
|
---|
325 |
|
---|
326 | public class OptionContext {
|
---|
327 | private Option option;
|
---|
328 | private string name;
|
---|
329 | private int index;
|
---|
330 | private OptionSet set;
|
---|
331 | private OptionValueCollection c;
|
---|
332 |
|
---|
333 | public OptionContext (OptionSet set)
|
---|
334 | {
|
---|
335 | this.set = set;
|
---|
336 | this.c = new OptionValueCollection (this);
|
---|
337 | }
|
---|
338 |
|
---|
339 | public Option Option {
|
---|
340 | get {return option;}
|
---|
341 | set {option = value;}
|
---|
342 | }
|
---|
343 |
|
---|
344 | public string OptionName {
|
---|
345 | get {return name;}
|
---|
346 | set {name = value;}
|
---|
347 | }
|
---|
348 |
|
---|
349 | public int OptionIndex {
|
---|
350 | get {return index;}
|
---|
351 | set {index = value;}
|
---|
352 | }
|
---|
353 |
|
---|
354 | public OptionSet OptionSet {
|
---|
355 | get {return set;}
|
---|
356 | }
|
---|
357 |
|
---|
358 | public OptionValueCollection OptionValues {
|
---|
359 | get {return c;}
|
---|
360 | }
|
---|
361 | }
|
---|
362 |
|
---|
363 | public enum OptionValueType {
|
---|
364 | None,
|
---|
365 | Optional,
|
---|
366 | Required,
|
---|
367 | }
|
---|
368 |
|
---|
369 | public abstract class Option {
|
---|
370 | string prototype, description;
|
---|
371 | string[] names;
|
---|
372 | OptionValueType type;
|
---|
373 | int count;
|
---|
374 | string[] separators;
|
---|
375 |
|
---|
376 | protected Option (string prototype, string description)
|
---|
377 | : this (prototype, description, 1)
|
---|
378 | {
|
---|
379 | }
|
---|
380 |
|
---|
381 | protected Option (string prototype, string description, int maxValueCount)
|
---|
382 | {
|
---|
383 | if (prototype == null)
|
---|
384 | throw new ArgumentNullException ("prototype");
|
---|
385 | if (prototype.Length == 0)
|
---|
386 | throw new ArgumentException ("Cannot be the empty string.", "prototype");
|
---|
387 | if (maxValueCount < 0)
|
---|
388 | throw new ArgumentOutOfRangeException ("maxValueCount");
|
---|
389 |
|
---|
390 | this.prototype = prototype;
|
---|
391 | this.names = prototype.Split ('|');
|
---|
392 | this.description = description;
|
---|
393 | this.count = maxValueCount;
|
---|
394 | this.type = ParsePrototype ();
|
---|
395 |
|
---|
396 | if (this.count == 0 && type != OptionValueType.None)
|
---|
397 | throw new ArgumentException (
|
---|
398 | "Cannot provide maxValueCount of 0 for OptionValueType.Required or " +
|
---|
399 | "OptionValueType.Optional.",
|
---|
400 | "maxValueCount");
|
---|
401 | if (this.type == OptionValueType.None && maxValueCount > 1)
|
---|
402 | throw new ArgumentException (
|
---|
403 | string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount),
|
---|
404 | "maxValueCount");
|
---|
405 | if (Array.IndexOf (names, "<>") >= 0 &&
|
---|
406 | ((names.Length == 1 && this.type != OptionValueType.None) ||
|
---|
407 | (names.Length > 1 && this.MaxValueCount > 1)))
|
---|
408 | throw new ArgumentException (
|
---|
409 | "The default option handler '<>' cannot require values.",
|
---|
410 | "prototype");
|
---|
411 | }
|
---|
412 |
|
---|
413 | public string Prototype {get {return prototype;}}
|
---|
414 | public string Description {get {return description;}}
|
---|
415 | public OptionValueType OptionValueType {get {return type;}}
|
---|
416 | public int MaxValueCount {get {return count;}}
|
---|
417 |
|
---|
418 | public string[] GetNames ()
|
---|
419 | {
|
---|
420 | return (string[]) names.Clone ();
|
---|
421 | }
|
---|
422 |
|
---|
423 | public string[] GetValueSeparators ()
|
---|
424 | {
|
---|
425 | if (separators == null)
|
---|
426 | return new string [0];
|
---|
427 | return (string[]) separators.Clone ();
|
---|
428 | }
|
---|
429 |
|
---|
430 | protected static T Parse<T> (string value, OptionContext c)
|
---|
431 | {
|
---|
432 | Type tt = typeof (T);
|
---|
433 | bool nullable = tt.IsValueType && tt.IsGenericType &&
|
---|
434 | !tt.IsGenericTypeDefinition &&
|
---|
435 | tt.GetGenericTypeDefinition () == typeof (Nullable<>);
|
---|
436 | Type targetType = nullable ? tt.GetGenericArguments () [0] : typeof (T);
|
---|
437 | TypeConverter conv = TypeDescriptor.GetConverter (targetType);
|
---|
438 | T t = default (T);
|
---|
439 | try {
|
---|
440 | if (value != null)
|
---|
441 | t = (T) conv.ConvertFromString (value);
|
---|
442 | }
|
---|
443 | catch (Exception e) {
|
---|
444 | throw new OptionException (
|
---|
445 | string.Format (
|
---|
446 | c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
|
---|
447 | value, targetType.Name, c.OptionName),
|
---|
448 | c.OptionName, e);
|
---|
449 | }
|
---|
450 | return t;
|
---|
451 | }
|
---|
452 |
|
---|
453 | internal string[] Names {get {return names;}}
|
---|
454 | internal string[] ValueSeparators {get {return separators;}}
|
---|
455 |
|
---|
456 | static readonly char[] NameTerminator = new char[]{'=', ':'};
|
---|
457 |
|
---|
458 | private OptionValueType ParsePrototype ()
|
---|
459 | {
|
---|
460 | char type = '\0';
|
---|
461 | List<string> seps = new List<string> ();
|
---|
462 | for (int i = 0; i < names.Length; ++i) {
|
---|
463 | string name = names [i];
|
---|
464 | if (name.Length == 0)
|
---|
465 | throw new ArgumentException ("Empty option names are not supported.", "prototype");
|
---|
466 |
|
---|
467 | int end = name.IndexOfAny (NameTerminator);
|
---|
468 | if (end == -1)
|
---|
469 | continue;
|
---|
470 | names [i] = name.Substring (0, end);
|
---|
471 | if (type == '\0' || type == name [end])
|
---|
472 | type = name [end];
|
---|
473 | else
|
---|
474 | throw new ArgumentException (
|
---|
475 | string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]),
|
---|
476 | "prototype");
|
---|
477 | AddSeparators (name, end, seps);
|
---|
478 | }
|
---|
479 |
|
---|
480 | if (type == '\0')
|
---|
481 | return OptionValueType.None;
|
---|
482 |
|
---|
483 | if (count <= 1 && seps.Count != 0)
|
---|
484 | throw new ArgumentException (
|
---|
485 | string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count),
|
---|
486 | "prototype");
|
---|
487 | if (count > 1) {
|
---|
488 | if (seps.Count == 0)
|
---|
489 | this.separators = new string[]{":", "="};
|
---|
490 | else if (seps.Count == 1 && seps [0].Length == 0)
|
---|
491 | this.separators = null;
|
---|
492 | else
|
---|
493 | this.separators = seps.ToArray ();
|
---|
494 | }
|
---|
495 |
|
---|
496 | return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
|
---|
497 | }
|
---|
498 |
|
---|
499 | private static void AddSeparators (string name, int end, ICollection<string> seps)
|
---|
500 | {
|
---|
501 | int start = -1;
|
---|
502 | for (int i = end+1; i < name.Length; ++i) {
|
---|
503 | switch (name [i]) {
|
---|
504 | case '{':
|
---|
505 | if (start != -1)
|
---|
506 | throw new ArgumentException (
|
---|
507 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
|
---|
508 | "prototype");
|
---|
509 | start = i+1;
|
---|
510 | break;
|
---|
511 | case '}':
|
---|
512 | if (start == -1)
|
---|
513 | throw new ArgumentException (
|
---|
514 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
|
---|
515 | "prototype");
|
---|
516 | seps.Add (name.Substring (start, i-start));
|
---|
517 | start = -1;
|
---|
518 | break;
|
---|
519 | default:
|
---|
520 | if (start == -1)
|
---|
521 | seps.Add (name [i].ToString ());
|
---|
522 | break;
|
---|
523 | }
|
---|
524 | }
|
---|
525 | if (start != -1)
|
---|
526 | throw new ArgumentException (
|
---|
527 | string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
|
---|
528 | "prototype");
|
---|
529 | }
|
---|
530 |
|
---|
531 | public void Invoke (OptionContext c)
|
---|
532 | {
|
---|
533 | OnParseComplete (c);
|
---|
534 | c.OptionName = null;
|
---|
535 | c.Option = null;
|
---|
536 | c.OptionValues.Clear ();
|
---|
537 | }
|
---|
538 |
|
---|
539 | protected abstract void OnParseComplete (OptionContext c);
|
---|
540 |
|
---|
541 | public override string ToString ()
|
---|
542 | {
|
---|
543 | return Prototype;
|
---|
544 | }
|
---|
545 | }
|
---|
546 |
|
---|
547 | [Serializable]
|
---|
548 | public class OptionException : Exception {
|
---|
549 | private string option;
|
---|
550 |
|
---|
551 | public OptionException ()
|
---|
552 | {
|
---|
553 | }
|
---|
554 |
|
---|
555 | public OptionException (string message, string optionName)
|
---|
556 | : base (message)
|
---|
557 | {
|
---|
558 | this.option = optionName;
|
---|
559 | }
|
---|
560 |
|
---|
561 | public OptionException (string message, string optionName, Exception innerException)
|
---|
562 | : base (message, innerException)
|
---|
563 | {
|
---|
564 | this.option = optionName;
|
---|
565 | }
|
---|
566 |
|
---|
567 | protected OptionException (SerializationInfo info, StreamingContext context)
|
---|
568 | : base (info, context)
|
---|
569 | {
|
---|
570 | this.option = info.GetString ("OptionName");
|
---|
571 | }
|
---|
572 |
|
---|
573 | public string OptionName {
|
---|
574 | get {return this.option;}
|
---|
575 | }
|
---|
576 |
|
---|
577 | [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
|
---|
578 | public override void GetObjectData (SerializationInfo info, StreamingContext context)
|
---|
579 | {
|
---|
580 | base.GetObjectData (info, context);
|
---|
581 | info.AddValue ("OptionName", option);
|
---|
582 | }
|
---|
583 | }
|
---|
584 |
|
---|
585 | public delegate void OptionAction<TKey, TValue> (TKey key, TValue value);
|
---|
586 |
|
---|
587 | public class OptionSet : KeyedCollection<string, Option>
|
---|
588 | {
|
---|
589 | public OptionSet ()
|
---|
590 | : this (delegate (string f) {return f;})
|
---|
591 | {
|
---|
592 | }
|
---|
593 |
|
---|
594 | public OptionSet (Converter<string, string> localizer)
|
---|
595 | {
|
---|
596 | this.localizer = localizer;
|
---|
597 | }
|
---|
598 |
|
---|
599 | Converter<string, string> localizer;
|
---|
600 |
|
---|
601 | public Converter<string, string> MessageLocalizer {
|
---|
602 | get {return localizer;}
|
---|
603 | }
|
---|
604 |
|
---|
605 | protected override string GetKeyForItem (Option item)
|
---|
606 | {
|
---|
607 | if (item == null)
|
---|
608 | throw new ArgumentNullException ("option");
|
---|
609 | if (item.Names != null && item.Names.Length > 0)
|
---|
610 | return item.Names [0];
|
---|
611 | // This should never happen, as it's invalid for Option to be
|
---|
612 | // constructed w/o any names.
|
---|
613 | throw new InvalidOperationException ("Option has no names!");
|
---|
614 | }
|
---|
615 |
|
---|
616 | [Obsolete ("Use KeyedCollection.this[string]")]
|
---|
617 | protected Option GetOptionForName (string option)
|
---|
618 | {
|
---|
619 | if (option == null)
|
---|
620 | throw new ArgumentNullException ("option");
|
---|
621 | try {
|
---|
622 | return base [option];
|
---|
623 | }
|
---|
624 | catch (KeyNotFoundException) {
|
---|
625 | return null;
|
---|
626 | }
|
---|
627 | }
|
---|
628 |
|
---|
629 | protected override void InsertItem (int index, Option item)
|
---|
630 | {
|
---|
631 | base.InsertItem (index, item);
|
---|
632 | AddImpl (item);
|
---|
633 | }
|
---|
634 |
|
---|
635 | protected override void RemoveItem (int index)
|
---|
636 | {
|
---|
637 | base.RemoveItem (index);
|
---|
638 | Option p = Items [index];
|
---|
639 | // KeyedCollection.RemoveItem() handles the 0th item
|
---|
640 | for (int i = 1; i < p.Names.Length; ++i) {
|
---|
641 | Dictionary.Remove (p.Names [i]);
|
---|
642 | }
|
---|
643 | }
|
---|
644 |
|
---|
645 | protected override void SetItem (int index, Option item)
|
---|
646 | {
|
---|
647 | base.SetItem (index, item);
|
---|
648 | RemoveItem (index);
|
---|
649 | AddImpl (item);
|
---|
650 | }
|
---|
651 |
|
---|
652 | private void AddImpl (Option option)
|
---|
653 | {
|
---|
654 | if (option == null)
|
---|
655 | throw new ArgumentNullException ("option");
|
---|
656 | List<string> added = new List<string> (option.Names.Length);
|
---|
657 | try {
|
---|
658 | // KeyedCollection.InsertItem/SetItem handle the 0th name.
|
---|
659 | for (int i = 1; i < option.Names.Length; ++i) {
|
---|
660 | Dictionary.Add (option.Names [i], option);
|
---|
661 | added.Add (option.Names [i]);
|
---|
662 | }
|
---|
663 | }
|
---|
664 | catch (Exception) {
|
---|
665 | foreach (string name in added)
|
---|
666 | Dictionary.Remove (name);
|
---|
667 | throw;
|
---|
668 | }
|
---|
669 | }
|
---|
670 |
|
---|
671 | public new OptionSet Add (Option option)
|
---|
672 | {
|
---|
673 | base.Add (option);
|
---|
674 | return this;
|
---|
675 | }
|
---|
676 |
|
---|
677 | sealed class ActionOption : Option {
|
---|
678 | Action<OptionValueCollection> action;
|
---|
679 |
|
---|
680 | public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action)
|
---|
681 | : base (prototype, description, count)
|
---|
682 | {
|
---|
683 | if (action == null)
|
---|
684 | throw new ArgumentNullException ("action");
|
---|
685 | this.action = action;
|
---|
686 | }
|
---|
687 |
|
---|
688 | protected override void OnParseComplete (OptionContext c)
|
---|
689 | {
|
---|
690 | action (c.OptionValues);
|
---|
691 | }
|
---|
692 | }
|
---|
693 |
|
---|
694 | public OptionSet Add (string prototype, Action<string> action)
|
---|
695 | {
|
---|
696 | return Add (prototype, null, action);
|
---|
697 | }
|
---|
698 |
|
---|
699 | public OptionSet Add (string prototype, string description, Action<string> action)
|
---|
700 | {
|
---|
701 | if (action == null)
|
---|
702 | throw new ArgumentNullException ("action");
|
---|
703 | Option p = new ActionOption (prototype, description, 1,
|
---|
704 | delegate (OptionValueCollection v) { action (v [0]); });
|
---|
705 | base.Add (p);
|
---|
706 | return this;
|
---|
707 | }
|
---|
708 |
|
---|
709 | public OptionSet Add (string prototype, OptionAction<string, string> action)
|
---|
710 | {
|
---|
711 | return Add (prototype, null, action);
|
---|
712 | }
|
---|
713 |
|
---|
714 | public OptionSet Add (string prototype, string description, OptionAction<string, string> action)
|
---|
715 | {
|
---|
716 | if (action == null)
|
---|
717 | throw new ArgumentNullException ("action");
|
---|
718 | Option p = new ActionOption (prototype, description, 2,
|
---|
719 | delegate (OptionValueCollection v) {action (v [0], v [1]);});
|
---|
720 | base.Add (p);
|
---|
721 | return this;
|
---|
722 | }
|
---|
723 |
|
---|
724 | sealed class ActionOption<T> : Option {
|
---|
725 | Action<T> action;
|
---|
726 |
|
---|
727 | public ActionOption (string prototype, string description, Action<T> action)
|
---|
728 | : base (prototype, description, 1)
|
---|
729 | {
|
---|
730 | if (action == null)
|
---|
731 | throw new ArgumentNullException ("action");
|
---|
732 | this.action = action;
|
---|
733 | }
|
---|
734 |
|
---|
735 | protected override void OnParseComplete (OptionContext c)
|
---|
736 | {
|
---|
737 | action (Parse<T> (c.OptionValues [0], c));
|
---|
738 | }
|
---|
739 | }
|
---|
740 |
|
---|
741 | sealed class ActionOption<TKey, TValue> : Option {
|
---|
742 | OptionAction<TKey, TValue> action;
|
---|
743 |
|
---|
744 | public ActionOption (string prototype, string description, OptionAction<TKey, TValue> action)
|
---|
745 | : base (prototype, description, 2)
|
---|
746 | {
|
---|
747 | if (action == null)
|
---|
748 | throw new ArgumentNullException ("action");
|
---|
749 | this.action = action;
|
---|
750 | }
|
---|
751 |
|
---|
752 | protected override void OnParseComplete (OptionContext c)
|
---|
753 | {
|
---|
754 | action (
|
---|
755 | Parse<TKey> (c.OptionValues [0], c),
|
---|
756 | Parse<TValue> (c.OptionValues [1], c));
|
---|
757 | }
|
---|
758 | }
|
---|
759 |
|
---|
760 | public OptionSet Add<T> (string prototype, Action<T> action)
|
---|
761 | {
|
---|
762 | return Add (prototype, null, action);
|
---|
763 | }
|
---|
764 |
|
---|
765 | public OptionSet Add<T> (string prototype, string description, Action<T> action)
|
---|
766 | {
|
---|
767 | return Add (new ActionOption<T> (prototype, description, action));
|
---|
768 | }
|
---|
769 |
|
---|
770 | public OptionSet Add<TKey, TValue> (string prototype, OptionAction<TKey, TValue> action)
|
---|
771 | {
|
---|
772 | return Add (prototype, null, action);
|
---|
773 | }
|
---|
774 |
|
---|
775 | public OptionSet Add<TKey, TValue> (string prototype, string description, OptionAction<TKey, TValue> action)
|
---|
776 | {
|
---|
777 | return Add (new ActionOption<TKey, TValue> (prototype, description, action));
|
---|
778 | }
|
---|
779 |
|
---|
780 | protected virtual OptionContext CreateOptionContext ()
|
---|
781 | {
|
---|
782 | return new OptionContext (this);
|
---|
783 | }
|
---|
784 |
|
---|
785 | #if LINQ
|
---|
786 | public List<string> Parse (IEnumerable<string> arguments)
|
---|
787 | {
|
---|
788 | bool process = true;
|
---|
789 | OptionContext c = CreateOptionContext ();
|
---|
790 | c.OptionIndex = -1;
|
---|
791 | var def = GetOptionForName ("<>");
|
---|
792 | var unprocessed =
|
---|
793 | from argument in arguments
|
---|
794 | where ++c.OptionIndex >= 0 && (process || def != null)
|
---|
795 | ? process
|
---|
796 | ? argument == "--"
|
---|
797 | ? (process = false)
|
---|
798 | : !Parse (argument, c)
|
---|
799 | ? def != null
|
---|
800 | ? Unprocessed (null, def, c, argument)
|
---|
801 | : true
|
---|
802 | : false
|
---|
803 | : def != null
|
---|
804 | ? Unprocessed (null, def, c, argument)
|
---|
805 | : true
|
---|
806 | : true
|
---|
807 | select argument;
|
---|
808 | List<string> r = unprocessed.ToList ();
|
---|
809 | if (c.Option != null)
|
---|
810 | c.Option.Invoke (c);
|
---|
811 | return r;
|
---|
812 | }
|
---|
813 | #else
|
---|
814 | public List<string> Parse (IEnumerable<string> arguments)
|
---|
815 | {
|
---|
816 | OptionContext c = CreateOptionContext ();
|
---|
817 | c.OptionIndex = -1;
|
---|
818 | bool process = true;
|
---|
819 | List<string> unprocessed = new List<string> ();
|
---|
820 | Option def = Contains ("<>") ? this ["<>"] : null;
|
---|
821 | foreach (string argument in arguments) {
|
---|
822 | ++c.OptionIndex;
|
---|
823 | if (argument == "--") {
|
---|
824 | process = false;
|
---|
825 | continue;
|
---|
826 | }
|
---|
827 | if (!process) {
|
---|
828 | Unprocessed (unprocessed, def, c, argument);
|
---|
829 | continue;
|
---|
830 | }
|
---|
831 | if (!Parse (argument, c))
|
---|
832 | Unprocessed (unprocessed, def, c, argument);
|
---|
833 | }
|
---|
834 | if (c.Option != null)
|
---|
835 | c.Option.Invoke (c);
|
---|
836 | return unprocessed;
|
---|
837 | }
|
---|
838 | #endif
|
---|
839 |
|
---|
840 | private static bool Unprocessed (ICollection<string> extra, Option def, OptionContext c, string argument)
|
---|
841 | {
|
---|
842 | if (def == null) {
|
---|
843 | extra.Add (argument);
|
---|
844 | return false;
|
---|
845 | }
|
---|
846 | c.OptionValues.Add (argument);
|
---|
847 | c.Option = def;
|
---|
848 | c.Option.Invoke (c);
|
---|
849 | return false;
|
---|
850 | }
|
---|
851 |
|
---|
852 | private readonly Regex ValueOption = new Regex (
|
---|
853 | @"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
|
---|
854 |
|
---|
855 | protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value)
|
---|
856 | {
|
---|
857 | if (argument == null)
|
---|
858 | throw new ArgumentNullException ("argument");
|
---|
859 |
|
---|
860 | flag = name = sep = value = null;
|
---|
861 | Match m = ValueOption.Match (argument);
|
---|
862 | if (!m.Success) {
|
---|
863 | return false;
|
---|
864 | }
|
---|
865 | flag = m.Groups ["flag"].Value;
|
---|
866 | name = m.Groups ["name"].Value;
|
---|
867 | if (m.Groups ["sep"].Success && m.Groups ["value"].Success) {
|
---|
868 | sep = m.Groups ["sep"].Value;
|
---|
869 | value = m.Groups ["value"].Value;
|
---|
870 | }
|
---|
871 | return true;
|
---|
872 | }
|
---|
873 |
|
---|
874 | protected virtual bool Parse (string argument, OptionContext c)
|
---|
875 | {
|
---|
876 | if (c.Option != null) {
|
---|
877 | ParseValue (argument, c);
|
---|
878 | return true;
|
---|
879 | }
|
---|
880 |
|
---|
881 | string f, n, s, v;
|
---|
882 | if (!GetOptionParts (argument, out f, out n, out s, out v))
|
---|
883 | return false;
|
---|
884 |
|
---|
885 | Option p;
|
---|
886 | if (Contains (n)) {
|
---|
887 | p = this [n];
|
---|
888 | c.OptionName = f + n;
|
---|
889 | c.Option = p;
|
---|
890 | switch (p.OptionValueType) {
|
---|
891 | case OptionValueType.None:
|
---|
892 | c.OptionValues.Add (n);
|
---|
893 | c.Option.Invoke (c);
|
---|
894 | break;
|
---|
895 | case OptionValueType.Optional:
|
---|
896 | case OptionValueType.Required:
|
---|
897 | ParseValue (v, c);
|
---|
898 | break;
|
---|
899 | }
|
---|
900 | return true;
|
---|
901 | }
|
---|
902 | // no match; is it a bool option?
|
---|
903 | if (ParseBool (argument, n, c))
|
---|
904 | return true;
|
---|
905 | // is it a bundled option?
|
---|
906 | if (ParseBundledValue (f, string.Concat (n + s + v), c))
|
---|
907 | return true;
|
---|
908 |
|
---|
909 | return false;
|
---|
910 | }
|
---|
911 |
|
---|
912 | private void ParseValue (string option, OptionContext c)
|
---|
913 | {
|
---|
914 | if (option != null)
|
---|
915 | foreach (string o in c.Option.ValueSeparators != null
|
---|
916 | ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None)
|
---|
917 | : new string[]{option}) {
|
---|
918 | c.OptionValues.Add (o);
|
---|
919 | }
|
---|
920 | if (c.OptionValues.Count == c.Option.MaxValueCount ||
|
---|
921 | c.Option.OptionValueType == OptionValueType.Optional)
|
---|
922 | c.Option.Invoke (c);
|
---|
923 | else if (c.OptionValues.Count > c.Option.MaxValueCount) {
|
---|
924 | throw new OptionException (localizer (string.Format (
|
---|
925 | "Error: Found {0} option values when expecting {1}.",
|
---|
926 | c.OptionValues.Count, c.Option.MaxValueCount)),
|
---|
927 | c.OptionName);
|
---|
928 | }
|
---|
929 | }
|
---|
930 |
|
---|
931 | private bool ParseBool (string option, string n, OptionContext c)
|
---|
932 | {
|
---|
933 | Option p;
|
---|
934 | string rn;
|
---|
935 | if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') &&
|
---|
936 | Contains ((rn = n.Substring (0, n.Length-1)))) {
|
---|
937 | p = this [rn];
|
---|
938 | string v = n [n.Length-1] == '+' ? option : null;
|
---|
939 | c.OptionName = option;
|
---|
940 | c.Option = p;
|
---|
941 | c.OptionValues.Add (v);
|
---|
942 | p.Invoke (c);
|
---|
943 | return true;
|
---|
944 | }
|
---|
945 | return false;
|
---|
946 | }
|
---|
947 |
|
---|
948 | private bool ParseBundledValue (string f, string n, OptionContext c)
|
---|
949 | {
|
---|
950 | if (f != "-")
|
---|
951 | return false;
|
---|
952 | for (int i = 0; i < n.Length; ++i) {
|
---|
953 | Option p;
|
---|
954 | string opt = f + n [i].ToString ();
|
---|
955 | string rn = n [i].ToString ();
|
---|
956 | if (!Contains (rn)) {
|
---|
957 | if (i == 0)
|
---|
958 | return false;
|
---|
959 | throw new OptionException (string.Format (localizer (
|
---|
960 | "Cannot bundle unregistered option '{0}'."), opt), opt);
|
---|
961 | }
|
---|
962 | p = this [rn];
|
---|
963 | switch (p.OptionValueType) {
|
---|
964 | case OptionValueType.None:
|
---|
965 | Invoke (c, opt, n, p);
|
---|
966 | break;
|
---|
967 | case OptionValueType.Optional:
|
---|
968 | case OptionValueType.Required: {
|
---|
969 | string v = n.Substring (i+1);
|
---|
970 | c.Option = p;
|
---|
971 | c.OptionName = opt;
|
---|
972 | ParseValue (v.Length != 0 ? v : null, c);
|
---|
973 | return true;
|
---|
974 | }
|
---|
975 | default:
|
---|
976 | throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType);
|
---|
977 | }
|
---|
978 | }
|
---|
979 | return true;
|
---|
980 | }
|
---|
981 |
|
---|
982 | private static void Invoke (OptionContext c, string name, string value, Option option)
|
---|
983 | {
|
---|
984 | c.OptionName = name;
|
---|
985 | c.Option = option;
|
---|
986 | c.OptionValues.Add (value);
|
---|
987 | option.Invoke (c);
|
---|
988 | }
|
---|
989 |
|
---|
990 | private const int OptionWidth = 29;
|
---|
991 |
|
---|
992 | public void WriteOptionDescriptions (TextWriter o)
|
---|
993 | {
|
---|
994 | foreach (Option p in this) {
|
---|
995 | int written = 0;
|
---|
996 | if (!WriteOptionPrototype (o, p, ref written))
|
---|
997 | continue;
|
---|
998 |
|
---|
999 | if (written < OptionWidth)
|
---|
1000 | o.Write (new string (' ', OptionWidth - written));
|
---|
1001 | else {
|
---|
1002 | o.WriteLine ();
|
---|
1003 | o.Write (new string (' ', OptionWidth));
|
---|
1004 | }
|
---|
1005 |
|
---|
1006 | bool indent = false;
|
---|
1007 | string prefix = new string (' ', OptionWidth+2);
|
---|
1008 | foreach (string line in GetLines (localizer (GetDescription (p.Description)))) {
|
---|
1009 | if (indent)
|
---|
1010 | o.Write (prefix);
|
---|
1011 | o.WriteLine (line);
|
---|
1012 | indent = true;
|
---|
1013 | }
|
---|
1014 | }
|
---|
1015 | }
|
---|
1016 |
|
---|
1017 | bool WriteOptionPrototype (TextWriter o, Option p, ref int written)
|
---|
1018 | {
|
---|
1019 | string[] names = p.Names;
|
---|
1020 |
|
---|
1021 | int i = GetNextOptionIndex (names, 0);
|
---|
1022 | if (i == names.Length)
|
---|
1023 | return false;
|
---|
1024 |
|
---|
1025 | if (names [i].Length == 1) {
|
---|
1026 | Write (o, ref written, " -");
|
---|
1027 | Write (o, ref written, names [0]);
|
---|
1028 | }
|
---|
1029 | else {
|
---|
1030 | Write (o, ref written, " --");
|
---|
1031 | Write (o, ref written, names [0]);
|
---|
1032 | }
|
---|
1033 |
|
---|
1034 | for ( i = GetNextOptionIndex (names, i+1);
|
---|
1035 | i < names.Length; i = GetNextOptionIndex (names, i+1)) {
|
---|
1036 | Write (o, ref written, ", ");
|
---|
1037 | Write (o, ref written, names [i].Length == 1 ? "-" : "--");
|
---|
1038 | Write (o, ref written, names [i]);
|
---|
1039 | }
|
---|
1040 |
|
---|
1041 | if (p.OptionValueType == OptionValueType.Optional ||
|
---|
1042 | p.OptionValueType == OptionValueType.Required) {
|
---|
1043 | if (p.OptionValueType == OptionValueType.Optional) {
|
---|
1044 | Write (o, ref written, localizer ("["));
|
---|
1045 | }
|
---|
1046 | Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description)));
|
---|
1047 | string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0
|
---|
1048 | ? p.ValueSeparators [0]
|
---|
1049 | : " ";
|
---|
1050 | for (int c = 1; c < p.MaxValueCount; ++c) {
|
---|
1051 | Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description)));
|
---|
1052 | }
|
---|
1053 | if (p.OptionValueType == OptionValueType.Optional) {
|
---|
1054 | Write (o, ref written, localizer ("]"));
|
---|
1055 | }
|
---|
1056 | }
|
---|
1057 | return true;
|
---|
1058 | }
|
---|
1059 |
|
---|
1060 | static int GetNextOptionIndex (string[] names, int i)
|
---|
1061 | {
|
---|
1062 | while (i < names.Length && names [i] == "<>") {
|
---|
1063 | ++i;
|
---|
1064 | }
|
---|
1065 | return i;
|
---|
1066 | }
|
---|
1067 |
|
---|
1068 | static void Write (TextWriter o, ref int n, string s)
|
---|
1069 | {
|
---|
1070 | n += s.Length;
|
---|
1071 | o.Write (s);
|
---|
1072 | }
|
---|
1073 |
|
---|
1074 | private static string GetArgumentName (int index, int maxIndex, string description)
|
---|
1075 | {
|
---|
1076 | if (description == null)
|
---|
1077 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
|
---|
1078 | string[] nameStart;
|
---|
1079 | if (maxIndex == 1)
|
---|
1080 | nameStart = new string[]{"{0:", "{"};
|
---|
1081 | else
|
---|
1082 | nameStart = new string[]{"{" + index + ":"};
|
---|
1083 | for (int i = 0; i < nameStart.Length; ++i) {
|
---|
1084 | int start, j = 0;
|
---|
1085 | do {
|
---|
1086 | start = description.IndexOf (nameStart [i], j);
|
---|
1087 | } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
|
---|
1088 | if (start == -1)
|
---|
1089 | continue;
|
---|
1090 | int end = description.IndexOf ("}", start);
|
---|
1091 | if (end == -1)
|
---|
1092 | continue;
|
---|
1093 | return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length);
|
---|
1094 | }
|
---|
1095 | return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
|
---|
1096 | }
|
---|
1097 |
|
---|
1098 | private static string GetDescription (string description)
|
---|
1099 | {
|
---|
1100 | if (description == null)
|
---|
1101 | return string.Empty;
|
---|
1102 | StringBuilder sb = new StringBuilder (description.Length);
|
---|
1103 | int start = -1;
|
---|
1104 | for (int i = 0; i < description.Length; ++i) {
|
---|
1105 | switch (description [i]) {
|
---|
1106 | case '{':
|
---|
1107 | if (i == start) {
|
---|
1108 | sb.Append ('{');
|
---|
1109 | start = -1;
|
---|
1110 | }
|
---|
1111 | else if (start < 0)
|
---|
1112 | start = i + 1;
|
---|
1113 | break;
|
---|
1114 | case '}':
|
---|
1115 | if (start < 0) {
|
---|
1116 | if ((i+1) == description.Length || description [i+1] != '}')
|
---|
1117 | throw new InvalidOperationException ("Invalid option description: " + description);
|
---|
1118 | ++i;
|
---|
1119 | sb.Append ("}");
|
---|
1120 | }
|
---|
1121 | else {
|
---|
1122 | sb.Append (description.Substring (start, i - start));
|
---|
1123 | start = -1;
|
---|
1124 | }
|
---|
1125 | break;
|
---|
1126 | case ':':
|
---|
1127 | if (start < 0)
|
---|
1128 | goto default;
|
---|
1129 | start = i + 1;
|
---|
1130 | break;
|
---|
1131 | default:
|
---|
1132 | if (start < 0)
|
---|
1133 | sb.Append (description [i]);
|
---|
1134 | break;
|
---|
1135 | }
|
---|
1136 | }
|
---|
1137 | return sb.ToString ();
|
---|
1138 | }
|
---|
1139 |
|
---|
1140 | private static IEnumerable<string> GetLines (string description)
|
---|
1141 | {
|
---|
1142 | return StringCoda.WrappedLines (description,
|
---|
1143 | 80 - OptionWidth,
|
---|
1144 | 80 - OptionWidth - 2);
|
---|
1145 | }
|
---|
1146 | }
|
---|
1147 | }
|
---|
1148 |
|
---|