Add copyright notices to all source files and put a license in LICENSE.
[versaplex.git] / wvdotnet / ndesk-options.cs
blob166dd05947a91c13732dbef68da4df3ed4b57005
1 /*
2 * Versaplex:
3 * Copyright (C)2007-2008 Versabanq Innovations Inc. and contributors.
4 * See the included file named LICENSE for license information.
5 */
6 //
7 // Options.cs
8 //
9 // Authors:
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:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
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.
34 // Compile With:
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 [=:]? )+
47 //
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().
75 // Examples:
76 // int verbose = 0;
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 () },
90 // };
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
95 // type:
97 // var p = new OptionSet () {
98 // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
99 // };
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 '-':
105 // string a = null;
106 // var p = new OptionSet () {
107 // { "a", s => a = s },
108 // };
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
113 #define LINQ
115 using System;
116 using System.Collections;
117 using System.Collections.Generic;
118 using System.Collections.ObjectModel;
119 using System.ComponentModel;
120 using System.Globalization;
121 using System.IO;
122 using System.Runtime.Serialization;
123 using System.Text.RegularExpressions;
126 #if LINQ
127 using System.Linq;
128 #endif
130 #if TEST
131 using Wv.NDesk.Options;
132 #endif
134 #if !LINQ
135 namespace System {
136 public delegate void Action<T1,T2> (T1 a, T2 b);
138 #endif
140 namespace Wv.NDesk.Options {
142 public enum OptionValueType {
143 None,
144 Optional,
145 Required,
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;
161 string[] names;
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 ()
191 char type = '\0';
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);
198 if (end > 0) {
199 names [i] = name.Substring (0, end);
200 if (type == '\0' || type == name [end])
201 type = name [end];
202 else
203 throw new ArgumentException (
204 string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]),
205 "prototype");
208 if (type == '\0')
209 return OptionValueType.None;
210 return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
213 public void Invoke (OptionContext c)
215 OnParseComplete (c);
216 c.OptionName = null;
217 c.OptionValue = null;
218 c.Option = null;
221 protected abstract void OnParseComplete (OptionContext c);
223 public override string ToString ()
225 return Prototype;
229 [Serializable]
230 public class OptionException : Exception {
231 private string option;
233 public OptionException (string message, string optionName)
234 : base (message)
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>
264 public OptionSet ()
265 : this (f => f)
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)
279 if (option == null)
280 throw new ArgumentNullException ("option");
281 Option v;
282 if (options.TryGetValue (option, out v))
283 return v;
284 return null;
287 protected override void ClearItems ()
289 this.options.Clear ();
292 protected override void InsertItem (int index, Option item)
294 Add (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)
309 RemoveItem (index);
310 Add (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)
320 if (action == null)
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)
333 if (option == null)
334 throw new ArgumentNullException ("option");
335 List<string> added = new List<string> ();
336 try {
337 foreach (string name in option.Names) {
338 this.options.Add (name, option);
341 catch (Exception) {
342 foreach (string name in added)
343 this.options.Remove (name);
344 throw;
346 return this;
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)
361 if (action == null)
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);
369 base.Add (p);
370 return this;
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) {
392 T t = default(T);
393 try {
394 if (s != null)
395 t = (T) conv.ConvertFromString (s);
397 catch (Exception e) {
398 throw new OptionException (
399 string.Format (
400 localizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
401 s, typeof(T).Name, c.OptionName),
402 c.OptionName, e);
404 action (t, c);
406 return Add (options, description, a);
409 protected virtual OptionContext CreateOptionContext ()
411 return new OptionContext ();
414 #if LINQ
415 public List<string> Parse (IEnumerable<string> options)
417 bool process = true;
418 OptionContext c = CreateOptionContext ();
419 c.OptionIndex = -1;
420 var unprocessed =
421 from option in options
422 where ++c.OptionIndex >= 0 && process
423 ? option == "--"
424 ? (process = false)
425 : !Parse (option, c)
426 : true
427 select option;
428 List<string> r = unprocessed.ToList ();
429 if (c.Option != null)
430 NoValue (c);
431 return r;
433 #else
434 public List<string> Parse (IEnumerable<string> options)
436 OptionContext c = CreateOptionContext ();
437 c.OptionIndex = -1;
438 bool process = true;
439 List<string> unprocessed = new List<string> ();
440 foreach (string option in options) {
441 ++c.OptionIndex;
442 if (option == "--") {
443 process = false;
444 continue;
446 if (!process) {
447 unprocessed.Add (option);
448 continue;
450 if (!Parse (option, c))
451 unprocessed.Add (option);
453 if (c.Option != null)
454 NoValue (c);
455 return unprocessed;
457 #endif
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);
465 if (!m.Success) {
466 flag = name = value = null;
467 return false;
469 flag = m.Groups ["flag"].Value;
470 name = m.Groups ["name"].Value;
471 value = !m.Groups ["value"].Success ? null : m.Groups ["value"].Value;
472 return true;
475 protected virtual bool Parse (string option, OptionContext c)
477 if (c.Option != null) {
478 c.OptionValue = option;
479 c.Option.Invoke (c);
480 return true;
483 string f, n, v;
484 if (!GetOptionParts (option, out f, out n, out v))
485 return false;
487 Option p;
488 if (this.options.TryGetValue (n, out p)) {
489 c.OptionName = f + n;
490 c.Option = p;
491 switch (p.OptionValueType) {
492 case OptionValueType.None:
493 c.OptionValue = n;
494 c.Option.Invoke (c);
495 break;
496 case OptionValueType.Optional:
497 case OptionValueType.Required:
498 if (v != null) {
499 c.OptionValue = v;
500 c.Option.Invoke (c);
502 break;
504 return true;
506 // no match; is it a bool option?
507 if (ParseBool (option, n, c))
508 return true;
509 // is it a bundled option?
510 if (ParseBundled (f, n, c))
511 return true;
513 return false;
516 private bool ParseBool (string option, string n, OptionContext c)
518 Option p;
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;
523 c.OptionValue = v;
524 c.Option = p;
525 p.Invoke (c);
526 return true;
528 return false;
531 private bool ParseBundled (string f, string n, OptionContext c)
533 Option p;
534 if (f == "-" && this.options.TryGetValue (n [0].ToString (), out p)) {
535 int i = 0;
536 do {
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),
541 opt);
543 c.OptionName = opt;
544 c.OptionValue = n;
545 c.Option = p;
546 p.Invoke (c);
547 } while (++i < n.Length && this.options.TryGetValue (n [i].ToString (), out p));
548 return true;
550 return false;
553 private void NoValue (OptionContext c)
555 c.OptionValue = null;
556 Option p = c.Option;
557 if (p != null && p.OptionValueType == OptionValueType.Optional) {
558 p.Invoke (c);
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),
563 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);
574 int written = 0;
575 if (names [0].Length == 1) {
576 Write (o, ref written, " -");
577 Write (o, ref written, names [0]);
579 else {
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));
597 else {
598 o.WriteLine ();
599 o.Write (new string (' ', OptionWidth));
602 o.WriteLine (localizer (p.Description));
606 static void Write (TextWriter o, ref int n, string s)
608 n += s.Length;
609 o.Write (s);
614 #if TEST
615 namespace Tests.NDesk.Options {
617 using System.Linq;
619 class FooConverter : TypeConverter {
620 public override bool CanConvertFrom (ITypeDescriptorContext context, Type sourceType)
622 if (sourceType == typeof (string))
623 return true;
624 return base.CanConvertFrom (context, sourceType);
627 public override object ConvertFrom (ITypeDescriptorContext context,
628 CultureInfo culture, object value)
630 string v = value as string;
631 if (v != null) {
632 switch (v) {
633 case "A": return Foo.A;
634 case "B": return Foo.B;
638 return base.ConvertFrom (context, culture, value);
642 [TypeConverter (typeof(FooConverter))]
643 class Foo {
644 public static readonly Foo A = new Foo ("A");
645 public static readonly Foo B = new Foo ("B");
646 string s;
647 Foo (string s) { this.s = s; }
648 public override string ToString () {return s;}
651 class Test {
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 () },
667 bool run = true;
668 bool help = false;
669 var p = new OptionSet () {
670 { "t|test=",
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 },
676 p.Parse (args);
677 if (help) {
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);
682 } else if (run) {
683 foreach (Action a in tests.Values)
684 a ();
688 static IEnumerable<string> _ (params string[] a)
690 return a;
693 static void CheckRequired ()
695 string a = null;
696 int n = 0;
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");
705 Assert (a, "s");
706 Assert (n, 42);
708 extra = p.Parse (_("-a="));
709 Assert (extra.Count, 0);
710 Assert (a, "");
713 static void CheckOptional ()
715 string a = null;
716 int n = -1;
717 Foo f = null;
718 OptionSet p = new OptionSet () {
719 { "a:", v => a = v },
720 { "n:", (int v) => n = v },
721 { "f:", (Foo v) => f = v },
723 p.Parse (_("-a=s"));
724 Assert (a, "s");
725 p.Parse (_("-a"));
726 Assert (a, null);
727 p.Parse (_("-a="));
728 Assert (a, "");
730 p.Parse (_("-f", "A"));
731 Assert (f, Foo.A);
732 p.Parse (_("-f"));
733 Assert (f, null);
735 p.Parse (_("-n", "42"));
736 Assert (n, 42);
737 p.Parse (_("-n"));
738 Assert (n, 0);
741 static void CheckBoolean ()
743 bool a = false;
744 OptionSet p = new OptionSet () {
745 { "a", v => a = v != null },
747 p.Parse (_("-a"));
748 Assert (a, true);
749 p.Parse (_("-a+"));
750 Assert (a, true);
751 p.Parse (_("-a-"));
752 Assert (a, false);
755 static void CheckMany ()
757 int a = -1, b = -1;
758 string av = null, bv = null;
759 Foo f = null;
760 int help = 0;
761 int verbose = 0;
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;
771 } } },
773 List<string> e = p.Parse (new string[]{"foo", "-v", "-a=42", "/b-",
774 "-a", "64", "bar", "--f", "B", "/h", "-?", "--help", "-v"});
776 Assert (e.Count, 2);
777 Assert (e[0], "foo");
778 Assert (e[1], "bar");
779 Assert (a, 1);
780 Assert (av, "64");
781 Assert (b, 2);
782 Assert (bv, null);
783 Assert (verbose, 2);
784 Assert (help, 0x7);
785 Assert (f, Foo.B);
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 ()
809 string a = null;
810 var p = new OptionSet () {
811 { "a=", v => a = v },
812 { "c", v => { } },
813 { "n=", (int v) => { } },
814 { "f=", (Foo v) => { } },
816 // missing argument
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")); });
823 Assert (a, "-a");
824 // no exception when an unregistered named option follows.
825 AssertException (null, null,
826 p, v => { v.Parse (_("-a", "-b")); });
827 Assert (a, "-b");
828 AssertException (typeof(ArgumentNullException),
829 "Argument cannot be null.\nParameter name: option",
830 p, v => { v.Add (null); });
832 // bad type
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;
868 string stack = null;
869 string actualMessage = null;
870 try {
871 action (a);
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 ()
914 string a, b, c;
915 a = b = c = null;
916 var p = new OptionSet () {
917 { "a", v => a = "a" },
918 { "b", v => b = "b" },
919 { "c", v => c = "c" },
921 p.Parse (_ ("-abc"));
922 Assert (a, "a");
923 Assert (b, "b");
924 Assert (c, "c");
927 static void CheckHaltProcessing ()
929 var p = new OptionSet () {
930 { "a", v => {} },
931 { "b", v => {} },
933 List<string> e = p.Parse (_ ("-a", "-b", "--", "-a", "-b"));
934 Assert (e.Count, 2);
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);
968 string f, n, v;
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 ()
983 bool help = false;
984 var p = new CiOptionSet () {
985 { "h|help", v => help = v != null },
987 p.Parse (_("-H"));
988 Assert (help, true);
989 help = false;
990 p.Parse (_("-HELP"));
991 Assert (help, true);
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);
1013 } },
1014 { "b", "b desc", (v, c) => {
1015 Assert (v, "--b+");
1016 Assert (c.Option.Description, "b desc");
1017 Assert (c.OptionName, "--b+");
1018 Assert (c.OptionIndex, 2);
1019 Assert (c.OptionValue, v);
1020 } },
1021 { "c=", "c desc", (v, c) => {
1022 Assert (v, "C");
1023 Assert (c.Option.Description, "c desc");
1024 Assert (c.OptionName, "--c");
1025 Assert (c.OptionIndex, 3);
1026 Assert (c.OptionValue, v);
1027 } },
1028 { "d", "d desc", (v, c) => {
1029 Assert (v, null);
1030 Assert (c.Option.Description, "d desc");
1031 Assert (c.OptionName, "/d-");
1032 Assert (c.OptionIndex, 4);
1033 Assert (c.OptionValue, v);
1034 } },
1036 p.Parse (_("/a", "a-val", "--b+", "--c=C", "/d-"));
1040 #endif