5 // Jonathan Pryor <jpryor@novell.com>
6 // Federico Di Gregorio <fog@initd.org>
8 // Copyright (C) 2008 Novell (http://www.novell.com)
9 // Copyright (C) 2009 Federico Di Gregorio.
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
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
35 // The LINQ version just changes the implementation of
36 // OptionSet.Parse(IEnumerable<string>), and confers no semantic changes.
39 // A Getopt::Long-inspired option parsing library for C#.
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.
45 // Option format strings:
46 // Regex-like BNF Grammar:
49 // sep: ( [^{}]+ | '{' .+ '}' )?
50 // aliases: ( name type sep ) ( '|' name type sep )*
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.
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.
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 ':')
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.
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.
81 // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value'
84 // Option processing is disabled by specifying "--". All options after "--"
85 // are returned by OptionSet.Parse() unchanged and unprocessed.
87 // Unprocessed options are returned from OptionSet.Parse().
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"});
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".
101 // C# 3.0 collection initializers are supported and encouraged:
102 // var p = new OptionSet () {
103 // { "h|?|help", v => ShowHelp () },
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
111 // var p = new OptionSet () {
112 // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
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 '-':
120 // var p = new OptionSet () {
121 // { "a", s => a = s },
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
129 using System
.Collections
;
130 using System
.Collections
.Generic
;
131 using System
.Collections
.ObjectModel
;
132 using System
.ComponentModel
;
133 using System
.Globalization
;
135 using System
.Runtime
.Serialization
;
136 using System
.Security
.Permissions
;
138 using System
.Text
.RegularExpressions
;
149 namespace NDesk
.Options
151 namespace Mono
.Options
154 static class StringCoda
{
156 public static IEnumerable
<string> WrappedLines (string self
, params int[] widths
)
158 IEnumerable
<int> w
= widths
;
159 return WrappedLines (self
, w
);
162 public static IEnumerable
<string> WrappedLines (string self
, IEnumerable
<int> widths
)
165 throw new ArgumentNullException ("widths");
166 return CreateWrappedLinesIterator (self
, widths
);
169 private static IEnumerable
<string> CreateWrappedLinesIterator (string self
, IEnumerable
<int> widths
)
171 if (string.IsNullOrEmpty (self
)) {
172 yield return string.Empty
;
175 using (var ewidths
= widths
.GetEnumerator ()) {
177 int width
= GetNextWidth (ewidths
, int.MaxValue
, ref hw
);
180 end
= GetLineEnd (start
, width
, self
);
181 char c
= self
[end
-1];
182 if (char.IsWhiteSpace (c
))
184 bool needContinuation
= end
!= self
.Length
&& !IsEolChar (c
);
185 string continuation
= "";
186 if (needContinuation
) {
190 string line
= self
.Substring (start
, end
- start
) + continuation
;
193 if (char.IsWhiteSpace (c
))
195 width
= GetNextWidth (ewidths
, width
, ref hw
);
196 } while (end
< self
.Length
);
200 private static int GetNextWidth (IEnumerator
<int> ewidths
, int curWidth
, ref bool? eValid
)
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
));
211 // no more elements, use the last element.
215 private static bool IsEolChar (char c
)
217 return !char.IsLetterOrDigit (c
);
220 private static int GetLineEnd (int start
, int length
, string description
)
222 int end
= System
.Math
.Min (start
+ length
, description
.Length
);
224 for (int i
= start
; i
< end
; ++i
) {
225 if (description
[i
] == '\n')
227 if (IsEolChar (description
[i
]))
230 if (sep
== -1 || end
== description
.Length
)
236 public class OptionValueCollection
: IList
, IList
<string> {
238 List
<string> values
= new List
<string> ();
241 internal OptionValueCollection (OptionContext c
)
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;}}
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;}}
263 IEnumerator IEnumerable
.GetEnumerator () {return values.GetEnumerator ();}
266 #region IEnumerable<T>
267 public IEnumerator
<string> GetEnumerator () {return values.GetEnumerator ();}
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;}}
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);}
286 private void AssertValid (int index
)
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
),
299 public string this [int index
] {
302 return index
>= values
.Count
? null : values
[index
];
305 values
[index
] = value;
310 public List
<string> ToList ()
312 return new List
<string> (values
);
315 public string[] ToArray ()
317 return values
.ToArray ();
320 public override string ToString ()
322 return string.Join (", ", values
.ToArray ());
326 public class OptionContext
{
327 private Option option
;
330 private OptionSet
set;
331 private OptionValueCollection c
;
333 public OptionContext (OptionSet
set)
336 this.c
= new OptionValueCollection (this);
339 public Option Option
{
341 set {option = value;}
344 public string OptionName
{
349 public int OptionIndex
{
354 public OptionSet OptionSet
{
358 public OptionValueCollection OptionValues
{
363 public enum OptionValueType
{
369 public abstract class Option
{
370 string prototype
, description
;
372 OptionValueType type
;
376 protected Option (string prototype
, string description
)
377 : this (prototype
, description
, 1)
381 protected Option (string prototype
, string description
, int maxValueCount
)
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");
390 this.prototype
= prototype
;
391 this.names
= prototype
.Split ('|');
392 this.description
= description
;
393 this.count
= maxValueCount
;
394 this.type
= ParsePrototype ();
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.",
401 if (this.type
== OptionValueType
.None
&& maxValueCount
> 1)
402 throw new ArgumentException (
403 string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", 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.",
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;}}
418 public string[] GetNames ()
420 return (string[]) names
.Clone ();
423 public string[] GetValueSeparators ()
425 if (separators
== null)
426 return new string [0];
427 return (string[]) separators
.Clone ();
430 protected static T Parse
<T
> (string value, OptionContext c
)
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
);
441 t
= (T
) conv
.ConvertFromString (value);
443 catch (Exception e
) {
444 throw new OptionException (
446 c
.OptionSet
.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
447 value, targetType
.Name
, c
.OptionName
),
453 internal string[] Names {get {return names;}}
454 internal string[] ValueSeparators {get {return separators;}}
456 static readonly char[] NameTerminator
= new char[]{'=', ':'}
;
458 private OptionValueType
ParsePrototype ()
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");
467 int end
= name
.IndexOfAny (NameTerminator
);
470 names
[i
] = name
.Substring (0, end
);
471 if (type
== '\0' || type
== name
[end
])
474 throw new ArgumentException (
475 string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type
, name
[end
]),
477 AddSeparators (name
, end
, seps
);
481 return OptionValueType
.None
;
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
),
489 this.separators
= new string[]{":", "="}
;
490 else if (seps
.Count
== 1 && seps
[0].Length
== 0)
491 this.separators
= null;
493 this.separators
= seps
.ToArray ();
496 return type
== '=' ? OptionValueType
.Required
: OptionValueType
.Optional
;
499 private static void AddSeparators (string name
, int end
, ICollection
<string> seps
)
502 for (int i
= end
+1; i
< name
.Length
; ++i
) {
506 throw new ArgumentException (
507 string.Format ("Ill-formed name/value separator found in \"{0}\".", name
),
513 throw new ArgumentException (
514 string.Format ("Ill-formed name/value separator found in \"{0}\".", name
),
516 seps
.Add (name
.Substring (start
, i
-start
));
521 seps
.Add (name
[i
].ToString ());
526 throw new ArgumentException (
527 string.Format ("Ill-formed name/value separator found in \"{0}\".", name
),
531 public void Invoke (OptionContext c
)
536 c
.OptionValues
.Clear ();
539 protected abstract void OnParseComplete (OptionContext c
);
541 public override string ToString ()
548 public class OptionException
: Exception
{
549 private string option
;
551 public OptionException ()
555 public OptionException (string message
, string optionName
)
558 this.option
= optionName
;
561 public OptionException (string message
, string optionName
, Exception innerException
)
562 : base (message
, innerException
)
564 this.option
= optionName
;
567 protected OptionException (SerializationInfo info
, StreamingContext context
)
568 : base (info
, context
)
570 this.option
= info
.GetString ("OptionName");
573 public string OptionName
{
574 get {return this.option;}
577 [SecurityPermission (SecurityAction
.LinkDemand
, SerializationFormatter
= true)]
578 public override void GetObjectData (SerializationInfo info
, StreamingContext context
)
580 base.GetObjectData (info
, context
);
581 info
.AddValue ("OptionName", option
);
585 public delegate void OptionAction
<TKey
, TValue
> (TKey key
, TValue
value);
587 public class OptionSet
: KeyedCollection
<string, Option
>
590 : this (delegate (string f
) {return f;}
)
594 public OptionSet (Converter
<string, string> localizer
)
596 this.localizer
= localizer
;
599 Converter
<string, string> localizer
;
601 public Converter
<string, string> MessageLocalizer
{
602 get {return localizer;}
605 protected override string GetKeyForItem (Option item
)
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!");
616 [Obsolete ("Use KeyedCollection.this[string]")]
617 protected Option
GetOptionForName (string option
)
620 throw new ArgumentNullException ("option");
622 return base [option
];
624 catch (KeyNotFoundException
) {
629 protected override void InsertItem (int index
, Option item
)
631 base.InsertItem (index
, item
);
635 protected override void RemoveItem (int index
)
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
]);
645 protected override void SetItem (int index
, Option item
)
647 base.SetItem (index
, item
);
652 private void AddImpl (Option option
)
655 throw new ArgumentNullException ("option");
656 List
<string> added
= new List
<string> (option
.Names
.Length
);
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
]);
665 foreach (string name
in added
)
666 Dictionary
.Remove (name
);
671 public new OptionSet
Add (Option option
)
677 sealed class ActionOption
: Option
{
678 Action
<OptionValueCollection
> action
;
680 public ActionOption (string prototype
, string description
, int count
, Action
<OptionValueCollection
> action
)
681 : base (prototype
, description
, count
)
684 throw new ArgumentNullException ("action");
685 this.action
= action
;
688 protected override void OnParseComplete (OptionContext c
)
690 action (c
.OptionValues
);
694 public OptionSet
Add (string prototype
, Action
<string> action
)
696 return Add (prototype
, null, action
);
699 public OptionSet
Add (string prototype
, string description
, Action
<string> action
)
702 throw new ArgumentNullException ("action");
703 Option p
= new ActionOption (prototype
, description
, 1,
704 delegate (OptionValueCollection v
) { action (v [0]); }
);
709 public OptionSet
Add (string prototype
, OptionAction
<string, string> action
)
711 return Add (prototype
, null, action
);
714 public OptionSet
Add (string prototype
, string description
, OptionAction
<string, string> action
)
717 throw new ArgumentNullException ("action");
718 Option p
= new ActionOption (prototype
, description
, 2,
719 delegate (OptionValueCollection v
) {action (v [0], v [1]);}
);
724 sealed class ActionOption
<T
> : Option
{
727 public ActionOption (string prototype
, string description
, Action
<T
> action
)
728 : base (prototype
, description
, 1)
731 throw new ArgumentNullException ("action");
732 this.action
= action
;
735 protected override void OnParseComplete (OptionContext c
)
737 action (Parse
<T
> (c
.OptionValues
[0], c
));
741 sealed class ActionOption
<TKey
, TValue
> : Option
{
742 OptionAction
<TKey
, TValue
> action
;
744 public ActionOption (string prototype
, string description
, OptionAction
<TKey
, TValue
> action
)
745 : base (prototype
, description
, 2)
748 throw new ArgumentNullException ("action");
749 this.action
= action
;
752 protected override void OnParseComplete (OptionContext c
)
755 Parse
<TKey
> (c
.OptionValues
[0], c
),
756 Parse
<TValue
> (c
.OptionValues
[1], c
));
760 public OptionSet Add
<T
> (string prototype
, Action
<T
> action
)
762 return Add (prototype
, null, action
);
765 public OptionSet Add
<T
> (string prototype
, string description
, Action
<T
> action
)
767 return Add (new ActionOption
<T
> (prototype
, description
, action
));
770 public OptionSet Add
<TKey
, TValue
> (string prototype
, OptionAction
<TKey
, TValue
> action
)
772 return Add (prototype
, null, action
);
775 public OptionSet Add
<TKey
, TValue
> (string prototype
, string description
, OptionAction
<TKey
, TValue
> action
)
777 return Add (new ActionOption
<TKey
, TValue
> (prototype
, description
, action
));
780 protected virtual OptionContext
CreateOptionContext ()
782 return new OptionContext (this);
786 public List
<string> Parse (IEnumerable
<string> arguments
)
789 OptionContext c
= CreateOptionContext ();
791 var def
= GetOptionForName ("<>");
793 from argument
in arguments
794 where
++c
.OptionIndex
>= 0 && (process
|| def
!= null)
798 : !Parse (argument
, c
)
800 ? Unprocessed (null, def
, c
, argument
)
804 ? Unprocessed (null, def
, c
, argument
)
808 List
<string> r
= unprocessed
.ToList ();
809 if (c
.Option
!= null)
814 public List
<string> Parse (IEnumerable
<string> arguments
)
816 OptionContext c
= CreateOptionContext ();
819 List
<string> unprocessed
= new List
<string> ();
820 Option def
= Contains ("<>") ? this ["<>"] : null;
821 foreach (string argument
in arguments
) {
823 if (argument
== "--") {
828 Unprocessed (unprocessed
, def
, c
, argument
);
831 if (!Parse (argument
, c
))
832 Unprocessed (unprocessed
, def
, c
, argument
);
834 if (c
.Option
!= null)
840 private static bool Unprocessed (ICollection
<string> extra
, Option def
, OptionContext c
, string argument
)
843 extra
.Add (argument
);
846 c
.OptionValues
.Add (argument
);
852 private readonly Regex ValueOption
= new Regex (
853 @"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
855 protected bool GetOptionParts (string argument
, out string flag
, out string name
, out string sep
, out string value)
857 if (argument
== null)
858 throw new ArgumentNullException ("argument");
860 flag
= name
= sep
= value = null;
861 Match m
= ValueOption
.Match (argument
);
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
;
874 protected virtual bool Parse (string argument
, OptionContext c
)
876 if (c
.Option
!= null) {
877 ParseValue (argument
, c
);
882 if (!GetOptionParts (argument
, out f
, out n
, out s
, out v
))
888 c
.OptionName
= f
+ n
;
890 switch (p
.OptionValueType
) {
891 case OptionValueType
.None
:
892 c
.OptionValues
.Add (n
);
895 case OptionValueType
.Optional
:
896 case OptionValueType
.Required
:
902 // no match; is it a bool option?
903 if (ParseBool (argument
, n
, c
))
905 // is it a bundled option?
906 if (ParseBundledValue (f
, string.Concat (n
+ s
+ v
), c
))
912 private void ParseValue (string option
, OptionContext c
)
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
);
920 if (c
.OptionValues
.Count
== c
.Option
.MaxValueCount
||
921 c
.Option
.OptionValueType
== OptionValueType
.Optional
)
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
)),
931 private bool ParseBool (string option
, string n
, OptionContext c
)
935 if (n
.Length
>= 1 && (n
[n
.Length
-1] == '+' || n
[n
.Length
-1] == '-') &&
936 Contains ((rn
= n
.Substring (0, n
.Length
-1)))) {
938 string v
= n
[n
.Length
-1] == '+' ? option
: null;
939 c
.OptionName
= option
;
941 c
.OptionValues
.Add (v
);
948 private bool ParseBundledValue (string f
, string n
, OptionContext c
)
952 for (int i
= 0; i
< n
.Length
; ++i
) {
954 string opt
= f
+ n
[i
].ToString ();
955 string rn
= n
[i
].ToString ();
956 if (!Contains (rn
)) {
959 throw new OptionException (string.Format (localizer (
960 "Cannot bundle unregistered option '{0}'."), opt
), opt
);
963 switch (p
.OptionValueType
) {
964 case OptionValueType
.None
:
965 Invoke (c
, opt
, n
, p
);
967 case OptionValueType
.Optional
:
968 case OptionValueType
.Required
: {
969 string v
= n
.Substring (i
+1);
972 ParseValue (v
.Length
!= 0 ? v
: null, c
);
976 throw new InvalidOperationException ("Unknown OptionValueType: " + p
.OptionValueType
);
982 private static void Invoke (OptionContext c
, string name
, string value, Option option
)
986 c
.OptionValues
.Add (value);
990 private const int OptionWidth
= 29;
992 public void WriteOptionDescriptions (TextWriter o
)
994 foreach (Option p
in this) {
996 if (!WriteOptionPrototype (o
, p
, ref written
))
999 if (written
< OptionWidth
)
1000 o
.Write (new string (' ', OptionWidth
- written
));
1003 o
.Write (new string (' ', OptionWidth
));
1006 bool indent
= false;
1007 string prefix
= new string (' ', OptionWidth
+2);
1008 foreach (string line
in GetLines (localizer (GetDescription (p
.Description
)))) {
1017 bool WriteOptionPrototype (TextWriter o
, Option p
, ref int written
)
1019 string[] names
= p
.Names
;
1021 int i
= GetNextOptionIndex (names
, 0);
1022 if (i
== names
.Length
)
1025 if (names
[i
].Length
== 1) {
1026 Write (o
, ref written
, " -");
1027 Write (o
, ref written
, names
[0]);
1030 Write (o
, ref written
, " --");
1031 Write (o
, ref written
, names
[0]);
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
]);
1041 if (p
.OptionValueType
== OptionValueType
.Optional
||
1042 p
.OptionValueType
== OptionValueType
.Required
) {
1043 if (p
.OptionValueType
== OptionValueType
.Optional
) {
1044 Write (o
, ref written
, localizer ("["));
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]
1050 for (int c
= 1; c
< p
.MaxValueCount
; ++c
) {
1051 Write (o
, ref written
, localizer (sep
+ GetArgumentName (c
, p
.MaxValueCount
, p
.Description
)));
1053 if (p
.OptionValueType
== OptionValueType
.Optional
) {
1054 Write (o
, ref written
, localizer ("]"));
1060 static int GetNextOptionIndex (string[] names
, int i
)
1062 while (i
< names
.Length
&& names
[i
] == "<>") {
1068 static void Write (TextWriter o
, ref int n
, string s
)
1074 private static string GetArgumentName (int index
, int maxIndex
, string description
)
1076 if (description
== null)
1077 return maxIndex
== 1 ? "VALUE" : "VALUE" + (index
+ 1);
1080 nameStart
= new string[]{"{0:", "{"}
;
1082 nameStart
= new string[]{"{" + index + ":"}
;
1083 for (int i
= 0; i
< nameStart
.Length
; ++i
) {
1086 start
= description
.IndexOf (nameStart
[i
], j
);
1087 } while (start
>= 0 && j
!= 0 ? description
[j
++ - 1] == '{' : false);
1090 int end
= description
.IndexOf ("}", start
);
1093 return description
.Substring (start
+ nameStart
[i
].Length
, end
- start
- nameStart
[i
].Length
);
1095 return maxIndex
== 1 ? "VALUE" : "VALUE" + (index
+ 1);
1098 private static string GetDescription (string description
)
1100 if (description
== null)
1101 return string.Empty
;
1102 StringBuilder sb
= new StringBuilder (description
.Length
);
1104 for (int i
= 0; i
< description
.Length
; ++i
) {
1105 switch (description
[i
]) {
1116 if ((i
+1) == description
.Length
|| description
[i
+1] != '}')
1117 throw new InvalidOperationException ("Invalid option description: " + description
);
1122 sb
.Append (description
.Substring (start
, i
- start
));
1133 sb
.Append (description
[i
]);
1137 return sb
.ToString ();
1140 private static IEnumerable
<string> GetLines (string description
)
1142 return StringCoda
.WrappedLines (description
,
1144 80 - OptionWidth
- 2);