5 // Jonathan Pryor <jpryor@novell.com>
7 // Copyright (C) 2008 Novell (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections.Generic;
31 using System.ComponentModel;
32 using System.Globalization;
41 using NUnit.Framework;
44 namespace Tests.NDesk.Options
46 namespace Tests.Mono.Options
49 class FooConverter : TypeConverter {
50 public override bool CanConvertFrom (ITypeDescriptorContext context, Type sourceType)
52 if (sourceType == typeof (string))
54 return base.CanConvertFrom (context, sourceType);
57 public override object ConvertFrom (ITypeDescriptorContext context,
58 CultureInfo culture, object value)
60 string v = value as string;
63 case "A": return Foo.A;
64 case "B": return Foo.B;
68 return base.ConvertFrom (context, culture, value);
72 [TypeConverter (typeof(FooConverter))]
74 public static readonly Foo A = new Foo ("A");
75 public static readonly Foo B = new Foo ("B");
77 Foo (string s) { this.s = s; }
78 public override string ToString () {return s;}
82 public class OptionSetTest {
83 static IEnumerable<string> _ (params string[] a)
89 public void BundledValues ()
91 var defines = new List<string> ();
92 var libs = new List<string> ();
94 var p = new OptionSet () {
95 { "D|define=", v => defines.Add (v) },
96 { "L|library:", v => libs.Add (v) },
97 { "Debug", v => debug = v != null },
98 { "E", v => { /* ignore */ } },
100 p.Parse (_("-DNAME", "-D", "NAME2", "-Debug", "-L/foo", "-L", "/bar", "-EDNAME3"));
101 Assert.AreEqual (defines.Count, 3);
102 Assert.AreEqual (defines [0], "NAME");
103 Assert.AreEqual (defines [1], "NAME2");
104 Assert.AreEqual (defines [2], "NAME3");
105 Assert.AreEqual (debug, true);
106 Assert.AreEqual (libs.Count, 2);
107 Assert.AreEqual (libs [0], "/foo");
108 Assert.AreEqual (libs [1], null);
110 Utils.AssertException (typeof(OptionException),
111 "Cannot bundle unregistered option '-V'.",
112 p, v => { v.Parse (_("-EVALUENOTSUP")); });
116 public void RequiredValues ()
120 OptionSet p = new OptionSet () {
121 { "a=", v => a = v },
122 { "n=", (int v) => n = v },
124 List<string> extra = p.Parse (_("a", "-a", "s", "-n=42", "n"));
125 Assert.AreEqual (extra.Count, 2);
126 Assert.AreEqual (extra [0], "a");
127 Assert.AreEqual (extra [1], "n");
128 Assert.AreEqual (a, "s");
129 Assert.AreEqual (n, 42);
131 extra = p.Parse (_("-a="));
132 Assert.AreEqual (extra.Count, 0);
133 Assert.AreEqual (a, "");
137 public void OptionalValues ()
142 OptionSet p = new OptionSet () {
143 { "a:", v => a = v },
144 { "n:", (int? v) => n = v },
145 { "f:", (Foo v) => f = v },
148 Assert.AreEqual (a, "s");
150 Assert.AreEqual (a, null);
152 Assert.AreEqual (a, "");
154 p.Parse (_("-f", "A"));
155 Assert.AreEqual (f, null);
157 Assert.AreEqual (f, null);
159 Assert.AreEqual (f, Foo.A);
162 Assert.AreEqual (f, Foo.A);
165 Assert.AreEqual (n.Value, 42);
166 p.Parse (_("-n", "42"));
167 Assert.AreEqual (n.HasValue, false);
168 p.Parse (_("-n=42"));
169 Assert.AreEqual (n.Value, 42);
171 Assert.AreEqual (n.HasValue, false);
172 Utils.AssertException (typeof(OptionException),
173 "Could not convert string `' to type Int32 for option `-n'.",
174 p, v => { v.Parse (_("-n=")); });
178 public void BooleanValues ()
181 OptionSet p = new OptionSet () {
182 { "a", v => a = v != null },
185 Assert.AreEqual (a, true);
187 Assert.AreEqual (a, true);
189 Assert.AreEqual (a, false);
193 public void CombinationPlatter ()
196 string av = null, bv = null;
200 OptionSet p = new OptionSet () {
201 { "a=", v => { a = 1; av = v; } },
202 { "b", "desc", v => {b = 2; bv = v;} },
203 { "f=", (Foo v) => f = v },
204 { "v", v => { ++verbose; } },
205 { "h|?|help", (v) => { switch (v) {
206 case "h": help |= 0x1; break;
207 case "?": help |= 0x2; break;
208 case "help": help |= 0x4; break;
211 List<string> e = p.Parse (new string[]{"foo", "-v", "-a=42", "/b-",
212 "-a", "64", "bar", "--f", "B", "/h", "-?", "--help", "-v"});
214 Assert.AreEqual (e.Count, 2);
215 Assert.AreEqual (e[0], "foo");
216 Assert.AreEqual (e[1], "bar");
217 Assert.AreEqual (a, 1);
218 Assert.AreEqual (av, "64");
219 Assert.AreEqual (b, 2);
220 Assert.AreEqual (bv, null);
221 Assert.AreEqual (verbose, 2);
222 Assert.AreEqual (help, 0x7);
223 Assert.AreEqual (f, Foo.B);
227 public void Exceptions ()
230 var p = new OptionSet () {
231 { "a=", v => a = v },
234 { "n=", (int v) => { } },
235 { "f=", (Foo v) => { } },
238 Utils.AssertException (typeof(OptionException),
239 "Missing required value for option '-a'.",
240 p, v => { v.Parse (_("-a")); });
241 // another named option while expecting one -- follow Getopt::Long
242 Utils.AssertException (null, null,
243 p, v => { v.Parse (_("-a", "-a")); });
244 Assert.AreEqual (a, "-a");
245 // no exception when an unregistered named option follows.
246 Utils.AssertException (null, null,
247 p, v => { v.Parse (_("-a", "-b")); });
248 Assert.AreEqual (a, "-b");
249 Utils.AssertException (typeof(ArgumentNullException),
250 "Argument cannot be null.\nParameter name: option",
251 p, v => { v.Add (null); });
254 Utils.AssertException (typeof(OptionException),
255 "Could not convert string `value' to type Int32 for option `-n'.",
256 p, v => { v.Parse (_("-n", "value")); });
257 Utils.AssertException (typeof(OptionException),
258 "Could not convert string `invalid' to type Foo for option `--f'.",
259 p, v => { v.Parse (_("--f", "invalid")); });
261 // try to bundle with an option requiring a value
262 Utils.AssertException (typeof(OptionException),
263 "Cannot bundle unregistered option '-z'.",
264 p, v => { v.Parse (_("-cz", "extra")); });
266 Utils.AssertException (typeof(ArgumentNullException),
267 "Argument cannot be null.\nParameter name: action",
268 p, v => { v.Add ("foo", (Action<string>) null); });
269 Utils.AssertException (typeof(ArgumentException),
270 "Cannot provide maxValueCount of 2 for OptionValueType.None.\nParameter name: maxValueCount",
271 p, v => { v.Add ("foo", (k, val) => {/* ignore */}); });
275 public void WriteOptionDescriptions ()
277 var p = new OptionSet () {
278 { "p|indicator-style=", "append / indicator to directories", v => {} },
279 { "color:", "controls color info", v => {} },
280 { "color2:", "set {color}", v => {} },
281 { "rk=", "required key/value option", (k, v) => {} },
282 { "rk2=", "required {{foo}} {0:key}/{1:value} option", (k, v) => {} },
283 { "ok:", "optional key/value option", (k, v) => {} },
285 "This has a really\nlong, multi-line description that also\ntests\n" +
286 "the-builtin-supercalifragilisticexpialidicious-break-on-hyphen. " +
292 "IWantThisDescriptionToBreakInsideAWordGeneratingAutoWordHyphenation.",
295 "OnlyOnePeriod.AndNoWhitespaceShouldBeSupportedEvenWithLongDescriptions",
298 "Lots of spaces in the middle 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 and more until the end.",
301 "Lots of spaces in the middle - . - . - . - . - . - . - . - and more until the end.",
304 "The {DIRECTORY} to place the generated files and directories.\n\n" +
305 "If not specified, defaults to\n`dirname FILE`/cache/`basename FILE .tree`.",
307 { "h|?|help", "show help text", v => {} },
308 { "version", "output version information and exit", v => {} },
312 StringWriter expected = new StringWriter ();
313 expected.WriteLine (" -p, --indicator-style=VALUE");
314 expected.WriteLine (" append / indicator to directories");
315 expected.WriteLine (" --color[=VALUE] controls color info");
316 expected.WriteLine (" --color2[=color] set color");
317 expected.WriteLine (" --rk=VALUE1:VALUE2 required key/value option");
318 expected.WriteLine (" --rk2=key:value required {foo} key/value option");
319 expected.WriteLine (" --ok[=VALUE1:VALUE2] optional key/value option");
320 expected.WriteLine (" --long-desc This has a really");
321 expected.WriteLine (" long, multi-line description that also");
322 expected.WriteLine (" tests");
323 expected.WriteLine (" the-builtin-supercalifragilisticexpialidicious-");
324 expected.WriteLine (" break-on-hyphen. Also, a list:");
325 expected.WriteLine (" item 1");
326 expected.WriteLine (" item 2");
327 expected.WriteLine (" --long-desc2 IWantThisDescriptionToBreakInsideAWordGeneratingAu-");
328 expected.WriteLine (" toWordHyphenation.");
329 expected.WriteLine (" --long-desc3 OnlyOnePeriod.");
330 expected.WriteLine (" AndNoWhitespaceShouldBeSupportedEvenWithLongDesc-");
331 expected.WriteLine (" riptions");
332 expected.WriteLine (" --long-desc4 Lots of spaces in the middle 1 2 3 4 5 6 7 8 9 0 1");
333 expected.WriteLine (" 2 3 4 5 and more until the end.");
334 expected.WriteLine (" --long-desc5 Lots of spaces in the middle - . - . - . - . - . -");
335 expected.WriteLine (" . - . - and more until the end.");
336 expected.WriteLine (" -o, --out=DIRECTORY The DIRECTORY to place the generated files and");
337 expected.WriteLine (" directories.");
338 expected.WriteLine (" ");
339 expected.WriteLine (" If not specified, defaults to");
340 expected.WriteLine (" `dirname FILE`/cache/`basename FILE .tree`.");
341 expected.WriteLine (" -h, -?, --help show help text");
342 expected.WriteLine (" --version output version information and exit");
344 StringWriter actual = new StringWriter ();
345 p.WriteOptionDescriptions (actual);
347 Assert.AreEqual (expected.ToString (), actual.ToString ());
351 public void OptionBundling ()
354 a = b = c = f = null;
355 var p = new OptionSet () {
356 { "a", v => a = "a" },
357 { "b", v => b = "b" },
358 { "c", v => c = "c" },
359 { "f=", v => f = v },
361 List<string> extra = p.Parse (_ ("-abcf", "foo", "bar"));
362 Assert.AreEqual (extra.Count, 1);
363 Assert.AreEqual (extra [0], "bar");
364 Assert.AreEqual (a, "a");
365 Assert.AreEqual (b, "b");
366 Assert.AreEqual (c, "c");
367 Assert.AreEqual (f, "foo");
371 public void HaltProcessing ()
373 var p = new OptionSet () {
377 List<string> e = p.Parse (_ ("-a", "-b", "--", "-a", "-b"));
378 Assert.AreEqual (e.Count, 2);
379 Assert.AreEqual (e [0], "-a");
380 Assert.AreEqual (e [1], "-b");
384 public void KeyValueOptions ()
386 var a = new Dictionary<string, string> ();
387 var b = new Dictionary<int, char> ();
388 var p = new OptionSet () {
389 { "a=", (k,v) => a.Add (k, v) },
390 { "b=", (int k, char v) => b.Add (k, v) },
391 { "c:", (k, v) => {if (k != null) a.Add (k, v);} },
392 { "d={=>}{-->}", (k, v) => a.Add (k, v) },
393 { "e={}", (k, v) => a.Add (k, v) },
394 { "f=+/", (k, v) => a.Add (k, v) },
396 p.Parse (_("-a", "A", "B", "-a", "C", "D", "-a=E=F", "-a:G:H", "-aI=J", "-b", "1", "a", "-b", "2", "b"));
409 Assert.AreEqual (a.Count, 0);
410 p.Parse (_("-c", "a"));
411 Assert.AreEqual (a.Count, 0);
413 AssertDictionary (a, "a", null);
415 p.Parse (_("-ca=b"));
416 AssertDictionary (a, "a", "b");
419 p.Parse (_("-dA=>B", "-d", "C-->D", "-d:E", "F", "-d", "G", "H", "-dJ-->K"));
428 p.Parse (_("-eA=B", "-eC=D", "-eE", "F", "-e:G", "H"));
435 p.Parse (_("-f1/2", "-f=3/4", "-f:5+6", "-f7", "8", "-f9=10", "-f11=12"));
444 static void AssertDictionary<TKey, TValue> (Dictionary<TKey, TValue> dict, params string[] set)
446 TypeConverter k = TypeDescriptor.GetConverter (typeof (TKey));
447 TypeConverter v = TypeDescriptor.GetConverter (typeof (TValue));
449 Assert.AreEqual (dict.Count, set.Length / 2);
450 for (int i = 0; i < set.Length; i += 2) {
451 TKey key = (TKey) k.ConvertFromString (set [i]);
452 Assert.AreEqual (dict.ContainsKey (key), true);
453 if (set [i+1] == null)
454 Assert.AreEqual (dict [key], default (TValue));
456 Assert.AreEqual (dict [key], (TValue) v.ConvertFromString (set [i+1]));
460 class CustomOption : Option {
461 Action<OptionValueCollection> action;
463 public CustomOption (string p, string d, int c, Action<OptionValueCollection> a)
469 protected override void OnParseComplete (OptionContext c)
471 action (c.OptionValues);
476 public void CustomKeyValue ()
478 var a = new Dictionary<string, string> ();
479 var b = new Dictionary<string, string[]> ();
480 var p = new OptionSet () {
481 new CustomOption ("a==:", null, 2, v => a.Add (v [0], v [1])),
482 new CustomOption ("b==:", null, 3, v => b.Add (v [0], new string[]{v [1], v [2]})),
484 p.Parse (_("-a=b=c", "-a=d", "e", "-a:f=g", "-a:h:i", "-a", "j=k", "-a", "l:m"));
485 Assert.AreEqual (a.Count, 6);
486 Assert.AreEqual (a ["b"], "c");
487 Assert.AreEqual (a ["d"], "e");
488 Assert.AreEqual (a ["f"], "g");
489 Assert.AreEqual (a ["h"], "i");
490 Assert.AreEqual (a ["j"], "k");
491 Assert.AreEqual (a ["l"], "m");
493 Utils.AssertException (typeof(OptionException),
494 "Missing required value for option '-a'.",
495 p, v => {v.Parse (_("-a=b"));});
497 p.Parse (_("-b", "a", "b", "c", "-b:d:e:f", "-b=g=h:i", "-b:j=k:l"));
498 Assert.AreEqual (b.Count, 4);
499 Assert.AreEqual (b ["a"][0], "b");
500 Assert.AreEqual (b ["a"][1], "c");
501 Assert.AreEqual (b ["d"][0], "e");
502 Assert.AreEqual (b ["d"][1], "f");
503 Assert.AreEqual (b ["g"][0], "h");
504 Assert.AreEqual (b ["g"][1], "i");
505 Assert.AreEqual (b ["j"][0], "k");
506 Assert.AreEqual (b ["j"][1], "l");
510 public void Localization ()
512 var p = new OptionSet (f => "hello!") {
513 { "n=", (int v) => { } },
515 Utils.AssertException (typeof(OptionException), "hello!",
516 p, v => { v.Parse (_("-n=value")); });
518 StringWriter expected = new StringWriter ();
519 expected.WriteLine (" -nhello! hello!");
521 StringWriter actual = new StringWriter ();
522 p.WriteOptionDescriptions (actual);
524 Assert.AreEqual (actual.ToString (), expected.ToString ());
527 class CiOptionSet : OptionSet {
528 protected override void InsertItem (int index, Option item)
530 if (item.Prototype.ToLower () != item.Prototype)
531 throw new ArgumentException ("prototypes must be null!");
532 base.InsertItem (index, item);
535 protected override bool Parse (string option, OptionContext c)
537 if (c.Option != null)
538 return base.Parse (option, c);
540 if (!GetOptionParts (option, out f, out n, out s, out v)) {
541 return base.Parse (option, c);
543 return base.Parse (f + n.ToLower () + (v != null && s != null ? s + v : ""), c);
546 public new Option GetOptionForName (string n)
548 return base.GetOptionForName (n);
551 public void CheckOptionParts (string option, bool er, string ef, string en, string es, string ev)
554 bool r = GetOptionParts (option, out f, out n, out s, out v);
555 Assert.AreEqual (r, er);
556 Assert.AreEqual (f, ef);
557 Assert.AreEqual (n, en);
558 Assert.AreEqual (s, es);
559 Assert.AreEqual (v, ev);
564 public void DerivedType ()
567 var p = new CiOptionSet () {
568 { "h|help", v => help = v != null },
571 Assert.AreEqual (help, true);
573 p.Parse (_("-HELP"));
574 Assert.AreEqual (help, true);
576 Assert.AreEqual (p.GetOptionForName ("h"), p [0]);
577 Assert.AreEqual (p.GetOptionForName ("help"), p [0]);
578 Assert.AreEqual (p.GetOptionForName ("invalid"), null);
580 Utils.AssertException (typeof(ArgumentException), "prototypes must be null!",
581 p, v => { v.Add ("N|NUM=", (int n) => {}); });
582 Utils.AssertException (typeof(ArgumentNullException),
583 "Argument cannot be null.\nParameter name: option",
584 p, v => { v.GetOptionForName (null); });
588 public void OptionParts ()
590 var p = new CiOptionSet ();
591 p.CheckOptionParts ("A", false, null, null, null, null);
592 p.CheckOptionParts ("A=B", false, null, null, null, null);
593 p.CheckOptionParts ("-A=B", true, "-", "A", "=", "B");
594 p.CheckOptionParts ("-A:B", true, "-", "A", ":", "B");
595 p.CheckOptionParts ("--A=B", true, "--", "A", "=", "B");
596 p.CheckOptionParts ("--A:B", true, "--", "A", ":", "B");
597 p.CheckOptionParts ("/A=B", true, "/", "A", "=", "B");
598 p.CheckOptionParts ("/A:B", true, "/", "A", ":", "B");
599 p.CheckOptionParts ("-A=B=C", true, "-", "A", "=", "B=C");
600 p.CheckOptionParts ("-A:B=C", true, "-", "A", ":", "B=C");
601 p.CheckOptionParts ("-A:B:C", true, "-", "A", ":", "B:C");
602 p.CheckOptionParts ("--A=B=C", true, "--", "A", "=", "B=C");
603 p.CheckOptionParts ("--A:B=C", true, "--", "A", ":", "B=C");
604 p.CheckOptionParts ("--A:B:C", true, "--", "A", ":", "B:C");
605 p.CheckOptionParts ("/A=B=C", true, "/", "A", "=", "B=C");
606 p.CheckOptionParts ("/A:B=C", true, "/", "A", ":", "B=C");
607 p.CheckOptionParts ("/A:B:C", true, "/", "A", ":", "B:C");
608 p.CheckOptionParts ("-AB=C", true, "-", "AB", "=", "C");
609 p.CheckOptionParts ("-AB:C", true, "-", "AB", ":", "C");
612 class ContextCheckerOption : Option {
613 string eName, eValue;
616 public ContextCheckerOption (string p, string d, string eName, string eValue, int index)
620 this.eValue = eValue;
624 protected override void OnParseComplete (OptionContext c)
626 Assert.AreEqual (c.OptionValues.Count, 1);
627 Assert.AreEqual (c.OptionValues [0], eValue);
628 Assert.AreEqual (c.OptionName, eName);
629 Assert.AreEqual (c.OptionIndex, index);
630 Assert.AreEqual (c.Option, this);
631 Assert.AreEqual (c.Option.Description, base.Description);
636 public void OptionContext ()
638 var p = new OptionSet () {
639 new ContextCheckerOption ("a=", "a desc", "/a", "a-val", 1),
640 new ContextCheckerOption ("b", "b desc", "--b+", "--b+", 2),
641 new ContextCheckerOption ("c=", "c desc", "--c", "C", 3),
642 new ContextCheckerOption ("d", "d desc", "/d-", null, 4),
644 Assert.AreEqual (p.Count, 4);
645 p.Parse (_("/a", "a-val", "--b+", "--c=C", "/d-"));
649 public void DefaultHandler ()
651 var extra = new List<string> ();
652 var p = new OptionSet () {
653 { "<>", v => extra.Add (v) },
655 var e = p.Parse (_("-a", "b", "--c=D", "E"));
656 Assert.AreEqual (e.Count, 0);
657 Assert.AreEqual (extra.Count, 4);
658 Assert.AreEqual (extra [0], "-a");
659 Assert.AreEqual (extra [1], "b");
660 Assert.AreEqual (extra [2], "--c=D");
661 Assert.AreEqual (extra [3], "E");
665 public void MixedDefaultHandler ()
667 var tests = new List<string> ();
668 var p = new OptionSet () {
669 { "t|<>=", v => tests.Add (v) },
671 var e = p.Parse (_("-tA", "-t:B", "-t=C", "D", "--E=F"));
672 Assert.AreEqual (e.Count, 0);
673 Assert.AreEqual (tests.Count, 5);
674 Assert.AreEqual (tests [0], "A");
675 Assert.AreEqual (tests [1], "B");
676 Assert.AreEqual (tests [2], "C");
677 Assert.AreEqual (tests [3], "D");
678 Assert.AreEqual (tests [4], "--E=F");
682 public void DefaultHandlerRuns ()
684 var formats = new Dictionary<string, List<string>> ();
685 string format = "foo";
686 var p = new OptionSet () {
687 { "f|format=", v => format = v },
691 if (!formats.TryGetValue (format, out f)) {
692 f = new List<string> ();
693 formats.Add (format, f);
698 var e = p.Parse (_("a", "b", "-fbar", "c", "d", "--format=baz", "e", "f"));
699 Assert.AreEqual (e.Count, 0);
700 Assert.AreEqual (formats.Count, 3);
701 Assert.AreEqual (formats ["foo"].Count, 2);
702 Assert.AreEqual (formats ["foo"][0], "a");
703 Assert.AreEqual (formats ["foo"][1], "b");
704 Assert.AreEqual (formats ["bar"].Count, 2);
705 Assert.AreEqual (formats ["bar"][0], "c");
706 Assert.AreEqual (formats ["bar"][1], "d");
707 Assert.AreEqual (formats ["baz"].Count, 2);
708 Assert.AreEqual (formats ["baz"][0], "e");
709 Assert.AreEqual (formats ["baz"][1], "f");