3 * Copyright (C)2007-2008 Versabanq Innovations Inc. and contributors.
4 * See the included file named LICENSE for license information.
10 // Jonathan Pryor <jpryor@novell.com>
12 // Copyright (C) 2008 Novell (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 // gmcs -debug+ -d:TEST -r:System.Core Options.cs
36 // gmcs -debug+ -d:LINQ -d:TEST -r:System.Core Options.cs
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 an Action<string>
43 // delegate that is invoked when the format string is matched.
45 // Option format strings:
46 // BNF Grammar: ( name [=:]? ) ( '|' name [=:]? )+
48 // Each '|'-delimited name is an alias for the associated action. If the
49 // format string ends in a '=', it has a required value. If the format
50 // string ends in a ':', it has an optional value. If neither '=' or ':'
51 // is present, no value is supported.
53 // Options are extracted either from the current option by looking for
54 // the option name followed by an '=' or ':', or is taken from the
55 // following option IFF:
56 // - The current option does not contain a '=' or a ':'
57 // - The following option is not a registered named option
59 // The `name' used in the option format string does NOT include any leading
60 // option indicator, such as '-', '--', or '/'. All three of these are
61 // permitted/required on any named option.
63 // Option bundling is permitted so long as:
64 // - '-' is used to start the option group
65 // - all of the bundled options do not require values
66 // - all of the bundled options are a single character
68 // This allows specifying '-a -b -c' as '-abc'.
70 // Option processing is disabled by specifying "--". All options after "--"
71 // are returned by OptionSet.Parse() unchanged and unprocessed.
73 // Unprocessed options are returned from OptionSet.Parse().
77 // OptionSet p = new OptionSet ()
78 // .Add ("v", v => ++verbose)
79 // .Add ("name=|value=", v => Console.WriteLine (v));
80 // p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"});
82 // The above would parse the argument string array, and would invoke the
83 // lambda expression three times, setting `verbose' to 3 when complete.
84 // It would also print out "A" and "B" to standard output.
85 // The returned array would contain the string "extra".
87 // C# 3.0 collection initializers are supported:
88 // var p = new OptionSet () {
89 // { "h|?|help", v => ShowHelp () },
92 // System.ComponentModel.TypeConverter is also supported, allowing the use of
93 // custom data types in the callback type; TypeConverter.ConvertFromString()
94 // is used to convert the value option to an instance of the specified
97 // var p = new OptionSet () {
98 // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
101 // Random other tidbits:
102 // - Boolean options (those w/o '=' or ':' in the option format string)
103 // are explicitly enabled if they are followed with '+', and explicitly
104 // disabled if they are followed with '-':
106 // var p = new OptionSet () {
107 // { "a", s => a = s },
109 // p.Parse (new string[]{"-a"}); // sets v != null
110 // p.Parse (new string[]{"-a+"}); // sets v != null
111 // p.Parse (new string[]{"-a-"}); // sets v == null
116 using System
.Collections
;
117 using System
.Collections
.Generic
;
118 using System
.Collections
.ObjectModel
;
119 using System
.ComponentModel
;
120 using System
.Globalization
;
122 using System
.Runtime
.Serialization
;
123 using System
.Text
.RegularExpressions
;
131 using Wv
.NDesk
.Options
;
136 public delegate void Action
<T1
,T2
> (T1 a
, T2 b
);
140 namespace Wv
.NDesk
.Options
{
142 public enum OptionValueType
{
148 public class OptionContext
{
149 public OptionContext ()
153 public Option Option { get; set; }
154 public string OptionName { get; set; }
155 public int OptionIndex { get; set; }
156 public string OptionValue { get; set; }
159 public abstract class Option
{
160 string prototype
, description
;
162 OptionValueType type
;
164 protected Option (string prototype
, string description
)
166 if (prototype
== null)
167 throw new ArgumentNullException ("prototype");
168 if (prototype
.Length
== 0)
169 throw new ArgumentException ("Cannot be the empty string.", "prototype");
171 this.prototype
= prototype
;
172 this.names
= prototype
.Split ('|');
173 this.description
= description
;
174 this.type
= ValidateNames ();
177 public string Prototype { get { return prototype; }
}
178 public string Description { get { return description; }
}
179 public OptionValueType OptionValueType { get { return type; }
}
181 public string[] GetNames ()
183 return (string[]) names
.Clone ();
186 internal string[] Names { get { return names; }
}
188 static readonly char[] NameTerminator
= new char[]{'=', ':'}
;
189 private OptionValueType
ValidateNames ()
192 for (int i
= 0; i
< names
.Length
; ++i
) {
193 string name
= names
[i
];
194 if (name
.Length
== 0)
195 throw new ArgumentException ("Empty option names are not supported.", "prototype");
197 int end
= name
.IndexOfAny (NameTerminator
);
199 names
[i
] = name
.Substring (0, end
);
200 if (type
== '\0' || type
== name
[end
])
203 throw new ArgumentException (
204 string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type
, name
[end
]),
209 return OptionValueType
.None
;
210 return type
== '=' ? OptionValueType
.Required
: OptionValueType
.Optional
;
213 public void Invoke (OptionContext c
)
217 c
.OptionValue
= null;
221 protected abstract void OnParseComplete (OptionContext c
);
223 public override string ToString ()
230 public class OptionException
: Exception
{
231 private string option
;
233 public OptionException (string message
, string optionName
)
236 this.option
= optionName
;
239 public OptionException (string message
, string optionName
, Exception innerException
)
240 : base (message
, innerException
)
242 this.option
= optionName
;
245 protected OptionException (SerializationInfo info
, StreamingContext context
)
246 : base (info
, context
)
248 this.option
= info
.GetString ("OptionName");
251 public string OptionName
{
252 get {return this.option;}
255 public override void GetObjectData (SerializationInfo info
, StreamingContext context
)
257 base.GetObjectData (info
, context
);
258 info
.AddValue ("OptionName", option
);
262 public class OptionSet
: Collection
<Option
>
269 public OptionSet (Converter
<string, string> localizer
)
271 this.localizer
= localizer
;
274 Dictionary
<string, Option
> options
= new Dictionary
<string, Option
> ();
275 Converter
<string, string> localizer
;
277 protected Option
GetOptionForName (string option
)
280 throw new ArgumentNullException ("option");
282 if (options
.TryGetValue (option
, out v
))
287 protected override void ClearItems ()
289 this.options
.Clear ();
292 protected override void InsertItem (int index
, Option item
)
295 base.InsertItem (index
, item
);
298 protected override void RemoveItem (int index
)
300 Option p
= Items
[index
];
301 foreach (string name
in p
.Names
) {
302 this.options
.Remove (name
);
304 base.RemoveItem (index
);
307 protected override void SetItem (int index
, Option item
)
311 base.SetItem (index
, item
);
314 class ActionOption
: Option
{
315 Action
<string, OptionContext
> action
;
317 public ActionOption (string prototype
, string description
, Action
<string, OptionContext
> action
)
318 : base (prototype
, description
)
321 throw new ArgumentNullException ("action");
322 this.action
= action
;
325 protected override void OnParseComplete (OptionContext c
)
327 action (c
.OptionValue
, c
);
331 public new OptionSet
Add (Option option
)
334 throw new ArgumentNullException ("option");
335 List
<string> added
= new List
<string> ();
337 foreach (string name
in option
.Names
) {
338 this.options
.Add (name
, option
);
342 foreach (string name
in added
)
343 this.options
.Remove (name
);
349 public OptionSet
Add (string options
, Action
<string> action
)
351 return Add (options
, null, action
);
354 public OptionSet
Add (string options
, Action
<string, OptionContext
> action
)
356 return Add (options
, null, action
);
359 public OptionSet
Add (string options
, string description
, Action
<string> action
)
362 throw new ArgumentNullException ("action");
363 return Add (options
, description
, (v
,c
) => {action (v);}
);
366 public OptionSet
Add (string options
, string description
, Action
<string, OptionContext
> action
)
368 Option p
= new ActionOption (options
, description
, action
);
373 public OptionSet Add
<T
> (string options
, Action
<T
> action
)
375 return Add (options
, null, action
);
378 public OptionSet Add
<T
> (string options
, Action
<T
, OptionContext
> action
)
380 return Add (options
, null, action
);
383 public OptionSet Add
<T
> (string options
, string description
, Action
<T
> action
)
385 return Add (options
, description
, (T v
, OptionContext c
) => {action (v);}
);
388 public OptionSet Add
<T
> (string options
, string description
, Action
<T
, OptionContext
> action
)
390 TypeConverter conv
= TypeDescriptor
.GetConverter (typeof(T
));
391 Action
<string, OptionContext
> a
= delegate (string s
, OptionContext c
) {
395 t
= (T
) conv
.ConvertFromString (s
);
397 catch (Exception e
) {
398 throw new OptionException (
400 localizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
401 s
, typeof(T
).Name
, c
.OptionName
),
406 return Add (options
, description
, a
);
409 protected virtual OptionContext
CreateOptionContext ()
411 return new OptionContext ();
415 public List
<string> Parse (IEnumerable
<string> options
)
418 OptionContext c
= CreateOptionContext ();
421 from option
in options
422 where
++c
.OptionIndex
>= 0 && process
428 List
<string> r
= unprocessed
.ToList ();
429 if (c
.Option
!= null)
434 public List
<string> Parse (IEnumerable
<string> options
)
436 OptionContext c
= CreateOptionContext ();
439 List
<string> unprocessed
= new List
<string> ();
440 foreach (string option
in options
) {
442 if (option
== "--") {
447 unprocessed
.Add (option
);
450 if (!Parse (option
, c
))
451 unprocessed
.Add (option
);
453 if (c
.Option
!= null)
459 private readonly Regex ValueOption
= new Regex (
460 @"^(?<flag>--|-|/)(?<name>[^:=]+)([:=](?<value>.*))?$");
462 protected bool GetOptionParts (string option
, out string flag
, out string name
, out string value)
464 Match m
= ValueOption
.Match (option
);
466 flag
= name
= value = null;
469 flag
= m
.Groups
["flag"].Value
;
470 name
= m
.Groups
["name"].Value
;
471 value = !m
.Groups
["value"].Success
? null : m
.Groups
["value"].Value
;
475 protected virtual bool Parse (string option
, OptionContext c
)
477 if (c
.Option
!= null) {
478 c
.OptionValue
= option
;
484 if (!GetOptionParts (option
, out f
, out n
, out v
))
488 if (this.options
.TryGetValue (n
, out p
)) {
489 c
.OptionName
= f
+ n
;
491 switch (p
.OptionValueType
) {
492 case OptionValueType
.None
:
496 case OptionValueType
.Optional
:
497 case OptionValueType
.Required
:
506 // no match; is it a bool option?
507 if (ParseBool (option
, n
, c
))
509 // is it a bundled option?
510 if (ParseBundled (f
, n
, c
))
516 private bool ParseBool (string option
, string n
, OptionContext c
)
519 if (n
.Length
>= 1 && (n
[n
.Length
-1] == '+' || n
[n
.Length
-1] == '-') &&
520 this.options
.TryGetValue (n
.Substring (0, n
.Length
-1), out p
)) {
521 string v
= n
[n
.Length
-1] == '+' ? option
: null;
522 c
.OptionName
= option
;
531 private bool ParseBundled (string f
, string n
, OptionContext c
)
534 if (f
== "-" && this.options
.TryGetValue (n
[0].ToString (), out p
)) {
537 string opt
= "-" + n
[i
].ToString ();
538 if (p
.OptionValueType
!= OptionValueType
.None
) {
539 throw new OptionException (string.Format (
540 localizer ("Cannot bundle option '{0}' that requires a value."), opt
),
547 } while (++i
< n
.Length
&& this.options
.TryGetValue (n
[i
].ToString (), out p
));
553 private void NoValue (OptionContext c
)
555 c
.OptionValue
= null;
557 if (p
!= null && p
.OptionValueType
== OptionValueType
.Optional
) {
560 else if (p
!= null && p
.OptionValueType
== OptionValueType
.Required
) {
561 throw new OptionException (string.Format (
562 localizer ("Missing required value for option '{0}'."), c
.OptionName
),
567 private const int OptionWidth
= 29;
569 public void WriteOptionDescriptions (TextWriter o
)
571 foreach (Option p
in this) {
572 List
<string> names
= new List
<string> (p
.Names
);
575 if (names
[0].Length
== 1) {
576 Write (o
, ref written
, " -");
577 Write (o
, ref written
, names
[0]);
580 Write (o
, ref written
, " --");
581 Write (o
, ref written
, names
[0]);
584 for (int i
= 1; i
< names
.Count
; ++i
) {
585 Write (o
, ref written
, ", ");
586 Write (o
, ref written
, names
[i
].Length
== 1 ? "-" : "--");
587 Write (o
, ref written
, names
[i
]);
590 if (p
.OptionValueType
== OptionValueType
.Optional
)
591 Write (o
, ref written
, localizer ("[=VALUE]"));
592 else if (p
.OptionValueType
== OptionValueType
.Required
)
593 Write (o
, ref written
, localizer ("=VALUE"));
595 if (written
< OptionWidth
)
596 o
.Write (new string (' ', OptionWidth
- written
));
599 o
.Write (new string (' ', OptionWidth
));
602 o
.WriteLine (localizer (p
.Description
));
606 static void Write (TextWriter o
, ref int n
, string s
)
615 namespace Tests
.NDesk
.Options
{
619 class FooConverter
: TypeConverter
{
620 public override bool CanConvertFrom (ITypeDescriptorContext context
, Type sourceType
)
622 if (sourceType
== typeof (string))
624 return base.CanConvertFrom (context
, sourceType
);
627 public override object ConvertFrom (ITypeDescriptorContext context
,
628 CultureInfo culture
, object value)
630 string v
= value as string;
633 case "A": return Foo
.A
;
634 case "B": return Foo
.B
;
638 return base.ConvertFrom (context
, culture
, value);
642 [TypeConverter (typeof(FooConverter
))]
644 public static readonly Foo A
= new Foo ("A");
645 public static readonly Foo B
= new Foo ("B");
647 Foo (string s
) { this.s = s; }
648 public override string ToString () {return s;}
652 public static void Main (string[] args
)
654 var tests
= new Dictionary
<string, Action
> () {
655 { "boolean", () => CheckBoolean () }
,
656 { "bundling", () => CheckOptionBundling () }
,
657 { "context", () => CheckOptionContext () }
,
658 { "descriptions", () => CheckWriteOptionDescriptions () }
,
659 { "exceptions", () => CheckExceptions () }
,
660 { "halt", () => CheckHaltProcessing () }
,
661 { "localization", () => CheckLocalization () }
,
662 { "many", () => CheckMany () }
,
663 { "optional", () => CheckOptional () }
,
664 { "required", () => CheckRequired () }
,
665 { "derived-type", () => CheckDerivedType () }
,
669 var p
= new OptionSet () {
671 "Run the specified test. Valid tests:\n" + new string (' ', 32) +
672 string.Join ("\n" + new string (' ', 32), tests
.Keys
.OrderBy (s
=> s
).ToArray ()),
673 v
=> { run = false; Console.WriteLine (v); tests [v] (); }
},
674 { "h|?|help", "Show this message and exit", (v) => help = v != null }
,
678 Console
.WriteLine ("usage: Options.exe [OPTION]+\n");
679 Console
.WriteLine ("Options unit test program.");
680 Console
.WriteLine ("Valid options include:");
681 p
.WriteOptionDescriptions (Console
.Out
);
683 foreach (Action a
in tests
.Values
)
688 static IEnumerable
<string> _ (params string[] a
)
693 static void CheckRequired ()
697 OptionSet p
= new OptionSet () {
698 { "a=", v => a = v }
,
699 { "n=", (int v) => n = v }
,
701 List
<string> extra
= p
.Parse (_("a", "-a", "s", "-n=42", "n"));
702 Assert (extra
.Count
, 2);
703 Assert (extra
[0], "a");
704 Assert (extra
[1], "n");
708 extra
= p
.Parse (_("-a="));
709 Assert (extra
.Count
, 0);
713 static void CheckOptional ()
718 OptionSet p
= new OptionSet () {
719 { "a:", v => a = v }
,
720 { "n:", (int v) => n = v }
,
721 { "f:", (Foo v) => f = v }
,
730 p
.Parse (_("-f", "A"));
735 p
.Parse (_("-n", "42"));
741 static void CheckBoolean ()
744 OptionSet p
= new OptionSet () {
745 { "a", v => a = v != null }
,
755 static void CheckMany ()
758 string av
= null, bv
= null;
762 OptionSet p
= new OptionSet () {
763 { "a=", v => { a = 1; av = v; }
},
764 { "b", "desc", v => {b = 2; bv = v;}
},
765 { "f=", (Foo v) => f = v }
,
766 { "v", v => { ++verbose; }
},
767 { "h|?|help", (v
) => { switch (v
) {
768 case "h": help
|= 0x1; break;
769 case "?": help
|= 0x2; break;
770 case "help": help
|= 0x4; break;
773 List
<string> e
= p
.Parse (new string[]{"foo", "-v", "-a=42", "/b-",
774 "-a", "64", "bar", "--f", "B", "/h", "-?", "--help", "-v"});
777 Assert (e
[0], "foo");
778 Assert (e
[1], "bar");
788 static void Assert
<T
>(T actual
, T expected
)
790 if (!object.Equals (actual
, expected
))
791 throw new InvalidOperationException (
792 string.Format ("Assertion failed: {0} != {1}", actual
, expected
));
795 class DefaultOption
: Option
{
796 public DefaultOption (string prototypes
, string description
)
797 : base (prototypes
, description
)
801 protected override void OnParseComplete (OptionContext c
)
803 throw new NotImplementedException ();
807 static void CheckExceptions ()
810 var p
= new OptionSet () {
811 { "a=", v => a = v }
,
813 { "n=", (int v) => { }
},
814 { "f=", (Foo v) => { }
},
817 AssertException (typeof(OptionException
),
818 "Missing required value for option '-a'.",
819 p
, v
=> { v.Parse (_("-a")); }
);
820 // another named option while expecting one -- follow Getopt::Long
821 AssertException (null, null,
822 p
, v
=> { v.Parse (_("-a", "-a")); }
);
824 // no exception when an unregistered named option follows.
825 AssertException (null, null,
826 p
, v
=> { v.Parse (_("-a", "-b")); }
);
828 AssertException (typeof(ArgumentNullException
),
829 "Argument cannot be null.\nParameter name: option",
830 p
, v
=> { v.Add (null); }
);
833 AssertException (typeof(OptionException
),
834 "Could not convert string `value' to type Int32 for option `-n'.",
835 p
, v
=> { v.Parse (_("-n", "value")); }
);
836 AssertException (typeof(OptionException
),
837 "Could not convert string `invalid' to type Foo for option `--f'.",
838 p
, v
=> { v.Parse (_("--f", "invalid")); }
);
840 // try to bundle with an option requiring a value
841 AssertException (typeof(OptionException
),
842 "Cannot bundle option '-a' that requires a value.",
843 p
, v
=> { v.Parse (_("-ca", "value")); }
);
845 AssertException (typeof(ArgumentNullException
),
846 "Argument cannot be null.\nParameter name: prototype",
847 p
, v
=> { new DefaultOption (null, null); }
);
848 AssertException (typeof(ArgumentException
),
849 "Cannot be the empty string.\nParameter name: prototype",
850 p
, v
=> { new DefaultOption ("", null); }
);
851 AssertException (typeof(ArgumentException
),
852 "Empty option names are not supported.\nParameter name: prototype",
853 p
, v
=> { new DefaultOption ("a|b||c=", null); }
);
854 AssertException (typeof(ArgumentException
),
855 "Conflicting option types: '=' vs. ':'.\nParameter name: prototype",
856 p
, v
=> { new DefaultOption ("a=|b:", null); }
);
857 AssertException (typeof(ArgumentNullException
),
858 "Argument cannot be null.\nParameter name: action",
859 p
, v
=> { v.Add ("foo", (Action<string>) null); }
);
860 AssertException (typeof(ArgumentNullException
),
861 "Argument cannot be null.\nParameter name: action",
862 p
, v
=> { v.Add ("foo", (Action<string, OptionContext>) null); }
);
865 static void AssertException
<T
> (Type exception
, string message
, T a
, Action
<T
> action
)
867 Type actualType
= null;
869 string actualMessage
= null;
873 catch (Exception e
) {
874 actualType
= e
.GetType ();
875 actualMessage
= e
.Message
;
876 if (!object.Equals (actualType
, exception
))
877 stack
= e
.ToString ();
879 if (!object.Equals (actualType
, exception
)) {
880 throw new InvalidOperationException (
881 string.Format ("Assertion failed: Expected Exception Type {0}, got {1}.\n" +
882 "Actual Exception: {2}", exception
, actualType
, stack
));
884 if (!object.Equals (actualMessage
, message
))
885 throw new InvalidOperationException (
886 string.Format ("Assertion failed:\n\tExpected: {0}\n\t Actual: {1}",
887 message
, actualMessage
));
890 static void CheckWriteOptionDescriptions ()
892 var p
= new OptionSet () {
893 { "p|indicator-style=", "append / indicator to directories", v => {}
},
894 { "color:", "controls color info", v => {}
},
895 { "h|?|help", "show help text", v => {}
},
896 { "version", "output version information and exit", v => {}
},
899 StringWriter expected
= new StringWriter ();
900 expected
.WriteLine (" -p, --indicator-style=VALUE");
901 expected
.WriteLine (" append / indicator to directories");
902 expected
.WriteLine (" --color[=VALUE] controls color info");
903 expected
.WriteLine (" -h, -?, --help show help text");
904 expected
.WriteLine (" --version output version information and exit");
906 StringWriter actual
= new StringWriter ();
907 p
.WriteOptionDescriptions (actual
);
909 Assert (actual
.ToString (), expected
.ToString ());
912 static void CheckOptionBundling ()
916 var p
= new OptionSet () {
917 { "a", v => a = "a" }
,
918 { "b", v => b = "b" }
,
919 { "c", v => c = "c" }
,
921 p
.Parse (_ ("-abc"));
927 static void CheckHaltProcessing ()
929 var p
= new OptionSet () {
933 List
<string> e
= p
.Parse (_ ("-a", "-b", "--", "-a", "-b"));
935 Assert (e
[0], "-a");
936 Assert (e
[1], "-b");
939 static void CheckLocalization ()
941 var p
= new OptionSet (f
=> "hello!") {
942 { "n=", (int v) => { }
},
944 AssertException (typeof(OptionException
), "hello!",
945 p
, v
=> { v.Parse (_("-n=value")); }
);
947 StringWriter expected
= new StringWriter ();
948 expected
.WriteLine (" -nhello! hello!");
950 StringWriter actual
= new StringWriter ();
951 p
.WriteOptionDescriptions (actual
);
953 Assert (actual
.ToString (), expected
.ToString ());
956 class CiOptionSet
: OptionSet
{
957 protected override void InsertItem (int index
, Option item
)
959 if (item
.Prototype
.ToLower () != item
.Prototype
)
960 throw new ArgumentException ("prototypes must be null!");
961 base.InsertItem (index
, item
);
964 protected override bool Parse (string option
, OptionContext c
)
966 if (c
.Option
!= null)
967 return base.Parse (option
, c
);
969 if (!GetOptionParts (option
, out f
, out n
, out v
)) {
970 return base.Parse (option
, c
);
972 return base.Parse (f
+ n
.ToLower () + (v
!= null ? "=" + v
: ""), c
);
975 public new Option
GetOptionForName (string n
)
977 return base.GetOptionForName (n
);
981 static void CheckDerivedType ()
984 var p
= new CiOptionSet () {
985 { "h|help", v => help = v != null }
,
990 p
.Parse (_("-HELP"));
993 Assert (p
.GetOptionForName ("h"), p
[0]);
994 Assert (p
.GetOptionForName ("help"), p
[0]);
995 Assert (p
.GetOptionForName ("invalid"), null);
997 AssertException (typeof(ArgumentException
), "prototypes must be null!",
998 p
, v
=> { v.Add ("N|NUM=", (int n) => {}
); });
999 AssertException (typeof(ArgumentNullException
),
1000 "Argument cannot be null.\nParameter name: option",
1001 p
, v
=> { v.GetOptionForName (null); }
);
1004 static void CheckOptionContext ()
1006 var p
= new OptionSet () {
1007 { "a=", "a desc", (v
,c
) => {
1008 Assert (v
, "a-val");
1009 Assert (c
.Option
.Description
, "a desc");
1010 Assert (c
.OptionName
, "/a");
1011 Assert (c
.OptionIndex
, 1);
1012 Assert (c
.OptionValue
, v
);
1014 { "b", "b desc", (v
, c
) => {
1016 Assert (c
.Option
.Description
, "b desc");
1017 Assert (c
.OptionName
, "--b+");
1018 Assert (c
.OptionIndex
, 2);
1019 Assert (c
.OptionValue
, v
);
1021 { "c=", "c desc", (v
, c
) => {
1023 Assert (c
.Option
.Description
, "c desc");
1024 Assert (c
.OptionName
, "--c");
1025 Assert (c
.OptionIndex
, 3);
1026 Assert (c
.OptionValue
, v
);
1028 { "d", "d desc", (v
, c
) => {
1030 Assert (c
.Option
.Description
, "d desc");
1031 Assert (c
.OptionName
, "/d-");
1032 Assert (c
.OptionIndex
, 4);
1033 Assert (c
.OptionValue
, v
);
1036 p
.Parse (_("/a", "a-val", "--b+", "--c=C", "/d-"));