Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add support for running tests unattended

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