context: //
  // Options.cs
  //
  // Authors:
  //  Jonathan Pryor <jpryor@novell.com>
  //  Federico Di Gregorio <fog@initd.org>
  //
  // Copyright (C) 2008 Novell (http://www.novell.com)
  // Copyright (C) 2009 Federico Di Gregorio.
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  // Compile With:
  //   gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll
  //   gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll
  //
  // The LINQ version just changes the implementation of
  // OptionSet.Parse(IEnumerable<string>), and confers no semantic changes.
  
  //
  // A Getopt::Long-inspired option parsing library for C#.
  //
  // NDesk.Options.OptionSet is built upon a key/value table, where the
  // key is a option format string and the value is a delegate that is 
  // invoked when the format string is matched.
  //
  // Option format strings:
  //  Regex-like BNF Grammar: 
  //    name: .+
  //    type: [=:]
  //    sep: ( [^{}]+ | '{' .+ '}' )?
  //    aliases: ( name type sep ) ( '|' name type sep )*
  // 
  // Each '|'-delimited name is an alias for the associated action.  If the
  // format string ends in a '=', it has a required value.  If the format
  // string ends in a ':', it has an optional value.  If neither '=' or ':'
  // is present, no value is supported.  `=' or `:' need only be defined on one
  // alias, but if they are provided on more than one they must be consistent.
  //
  // Each alias portion may also end with a "key/value separator", which is used
  // to split option values if the option accepts > 1 value.  If not specified,
  // it defaults to '=' and ':'.  If specified, it can be any character except
  // '{' and '}' OR the *string* between '{' and '}'.  If no separator should be
  // used (i.e. the separate values should be distinct arguments), then "{}"
  // should be used as the separator.
  //
  // Options are extracted either from the current option by looking for
  // the option name followed by an '=' or ':', or is taken from the
  // following option IFF:
  //  - The current option does not contain a '=' or a ':'
  //  - The current option requires a value (i.e. not a Option type of ':')
  //
  // The `name' used in the option format string does NOT include any leading
  // option indicator, such as '-', '--', or '/'.  All three of these are
  // permitted/required on any named option.
  //
  // Option bundling is permitted so long as:
  //   - '-' is used to start the option group
  //   - all of the bundled options are a single character
  //   - at most one of the bundled options accepts a value, and the value
  //     provided starts from the next character to the end of the string.
  //
  // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value'
  // as '-Dname=value'.
  //
  // Option processing is disabled by specifying "--".  All options after "--"
  // are returned by OptionSet.Parse() unchanged and unprocessed.
  //
  // Unprocessed options are returned from OptionSet.Parse().
  //
  // Examples:
  //  int verbose = 0;
  //  OptionSet p = new OptionSet ()
  //    .Add ("v", v => ++verbose)
  //    .Add ("name=|value=", v => Console.WriteLine (v));
  //  p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"});
  //
  // The above would parse the argument string array, and would invoke the
  // lambda expression three times, setting `verbose' to 3 when complete.  
  // It would also print out "A" and "B" to standard output.
  // The returned array would contain the string "extra".
  //
  // C# 3.0 collection initializers are supported and encouraged:
  //  var p = new OptionSet () {
  //    { "h|?|help", v => ShowHelp () },
  //  };
  //
  // System.ComponentModel.TypeConverter is also supported, allowing the use of
  // custom data types in the callback type; TypeConverter.ConvertFromString()
  // is used to convert the value option to an instance of the specified
  // type:
  //
  //  var p = new OptionSet () {
  //    { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
  //  };
  //
  // Random other tidbits:
  //  - Boolean options (those w/o '=' or ':' in the option format string)
  //    are explicitly enabled if they are followed with '+', and explicitly
  //    disabled if they are followed with '-':
  //      string a = null;
  //      var p = new OptionSet () {
  //        { "a", s => a = s },
  //      };
  //      p.Parse (new string[]{"-a"});   // sets v != null
  //      p.Parse (new string[]{"-a+"});  // sets v != null
  //      p.Parse (new string[]{"-a-"});  // sets v == null
  //
  
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.ComponentModel;
  using System.Globalization;
  using System.IO;
  using System.Runtime.Serialization;
  using System.Security.Permissions;
  using System.Text;
  using System.Text.RegularExpressions;
  
  #if LINQ
  using System.Linq;
  #endif
  
  #if TEST
  using NDesk.Options;
  #endif
  
  #if NDESK_OPTIONS
  namespace NDesk.Options
  #else
  	namespace Mono.Options
  #endif
  {
  	static class StringCoda {
  		
  		public static IEnumerable<string> WrappedLines (string self, params int[] widths)
  		{
  			IEnumerable<int> w = widths;
  			return WrappedLines (self, w);
  		}
  		
  		public static IEnumerable<string> WrappedLines (string self, IEnumerable<int> widths)
  		{
  			if (widths == null)
  				throw new ArgumentNullException ("widths");
  			return CreateWrappedLinesIterator (self, widths);
  		}
  		
  		private static IEnumerable<string> CreateWrappedLinesIterator (string self, IEnumerable<int> widths)
  		{
  			if (string.IsNullOrEmpty (self)) {
  				yield return string.Empty;
  				yield break;
  			}
  			using (IEnumerator<int> ewidths = widths.GetEnumerator ()) {
  				bool? hw = null;
  				int width = GetNextWidth (ewidths, int.MaxValue, ref hw);
  				int start = 0, end;
  				do {
  					end = GetLineEnd (start, width, self);
  					char c = self [end-1];
  					if (char.IsWhiteSpace (c))
  						--end;
  					bool needContinuation = end != self.Length && !IsEolChar (c);
  					string continuation = "";
  					if (needContinuation) {
  						--end;
  						continuation = "-";
  					}
  					string line = self.Substring (start, end - start) + continuation;
  					yield return line;
  					start = end;
  					if (char.IsWhiteSpace (c))
  						++start;
  					width = GetNextWidth (ewidths, width, ref hw);
  				} while (start < self.Length);
  			}
  		}
  		
  		private static int GetNextWidth (IEnumerator<int> ewidths, int curWidth, ref bool? eValid)
  		{
  			if (!eValid.HasValue || (eValid.HasValue && eValid.Value)) {
  				curWidth = (eValid = ewidths.MoveNext ()).Value ? ewidths.Current : curWidth;
  				// '.' is any character, - is for a continuation
  				const string minWidth = ".-";
  				if (curWidth < minWidth.Length)
  					throw new ArgumentOutOfRangeException ("widths",
  					                                       string.Format ("Element must be >= {0}, was {1}.", minWidth.Length, curWidth));
  				return curWidth;
  			}
  			// no more elements, use the last element.
  			return curWidth;
  		}
  		
  		private static bool IsEolChar (char c)
  		{
  			return !char.IsLetterOrDigit (c);
  		}
  		
  		private static int GetLineEnd (int start, int length, string description)
  		{
  			int end = System.Math.Min (start + length, description.Length);
  			int sep = -1;
  			for (int i = start; i < end; ++i) {
  				if (description [i] == '\n')
  					return i+1;
  				if (IsEolChar (description [i]))
  					sep = i+1;
  			}
  			if (sep == -1 || end == description.Length)
  				return end;
  			return sep;
  		}
  	}
  	
  	public class OptionValueCollection : IList, IList<string> {
  		
  		List<string> values = new List<string> ();
  		OptionContext c;
  		
  		internal OptionValueCollection (OptionContext c)
  		{
  			this.c = c;
  		}
  		
  		#region ICollection
  		void ICollection.CopyTo (Array array, int index)  {(values as ICollection).CopyTo (array, index);}
  		bool ICollection.IsSynchronized                   {get {return (values as ICollection).IsSynchronized;}}
  		object ICollection.SyncRoot                       {get {return (values as ICollection).SyncRoot;}}
  #endregion
  		
  		#region ICollection<T>
  		public void Add (string item)                       {values.Add (item);}
  		public void Clear ()                                {values.Clear ();}
  		public bool Contains (string item)                  {return values.Contains (item);}
  		public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);}
  		public bool Remove (string item)                    {return values.Remove (item);}
  		public int Count                                    {get {return values.Count;}}
  		public bool IsReadOnly                              {get {return false;}}
  #endregion
  		
  		#region IEnumerable
  		IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();}
  #endregion
  		
  		#region IEnumerable<T>
  		public IEnumerator<string> GetEnumerator () {return values.GetEnumerator ();}
  #endregion
  		
  		#region IList
  		int IList.Add (object value)                {return (values as IList).Add (value);}
  		bool IList.Contains (object value)          {return (values as IList).Contains (value);}
  		int IList.IndexOf (object value)            {return (values as IList).IndexOf (value);}
  		void IList.Insert (int index, object value) {(values as IList).Insert (index, value);}
  		void IList.Remove (object value)            {(values as IList).Remove (value);}
  		void IList.RemoveAt (int index)             {(values as IList).RemoveAt (index);}
  		bool IList.IsFixedSize                      {get {return false;}}
  		object IList.this [int index]               {get {return this [index];} set {(values as IList)[index] = value;}}
  #endregion
  		
  		#region IList<T>
  		public int IndexOf (string item)            {return values.IndexOf (item);}
  		public void Insert (int index, string item) {values.Insert (index, item);}
  		public void RemoveAt (int index)            {values.RemoveAt (index);}
  		
  		private void AssertValid (int index)
  		{
  			if (c.Option == null)
  				throw new InvalidOperationException ("OptionContext.Option is null.");
  			if (index >= c.Option.MaxValueCount)
  				throw new ArgumentOutOfRangeException ("index");
  			if (c.Option.OptionValueType == OptionValueType.Required &&
  			    index >= values.Count)
  				throw new OptionException (string.Format (
  					c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), 
  				                           c.OptionName);
  		}
  		
  		public string this [int index] {
  			get {
  				AssertValid (index);
  				return index >= values.Count ? null : values [index];
  			}
  			set {
  				values [index] = value;
  			}
  		}
  #endregion
  		
  		public List<string> ToList ()
  		{
  			return new List<string> (values);
  		}
  		
  		public string[] ToArray ()
  		{
  			return values.ToArray ();
  		}
  		
  		public override string ToString ()
  		{
  			return string.Join (", ", values.ToArray ());
  		}
  	}
  	
  	public class OptionContext {
  		private Option                option;
  		private string                name;
  		private int                   index;
  		private OptionSet             set;
  		private OptionValueCollection c;
  		
  		public OptionContext (OptionSet set)
  		{
  			this.set = set;
  			this.c   = new OptionValueCollection (this);
  		}
  		
  		public Option Option {
  			get {return option;}
  			set {option = value;}
  		}
  		
  		public string OptionName { 
  			get {return name;}
  			set {name = value;}
  		}
  		
  		public int OptionIndex {
  			get {return index;}
  			set {index = value;}
  		}
  		
  		public OptionSet OptionSet {
  			get {return set;}
  		}
  		
  		public OptionValueCollection OptionValues {
  			get {return c;}
  		}
  	}
  	
  	public enum OptionValueType {
  		None, 
  		Optional,
  		Required,
  	}
  	
  	public abstract class Option {
  		string prototype, description;
  		string[] names;
  		OptionValueType type;
  		int count;
  		string[] separators;
  		
  		protected Option (string prototype, string description)
  			: this (prototype, description, 1)
  		{
  		}
  		
  		protected Option (string prototype, string description, int maxValueCount)
  		{
  			if (prototype == null)
  				throw new ArgumentNullException ("prototype");
  			if (prototype.Length == 0)
  				throw new ArgumentException ("Cannot be the empty string.", "prototype");
  			if (maxValueCount < 0)
  				throw new ArgumentOutOfRangeException ("maxValueCount");
  			
  			this.prototype   = prototype;
  			this.description = description;
  			this.count       = maxValueCount;
  			this.names       = (this is OptionSet.Category)
  				// append GetHashCode() so that "duplicate" categories have distinct
  				// names, e.g. adding multiple "" categories should be valid.
  			? new[]{prototype + this.GetHashCode ()}
  			: prototype.Split ('|');
  			
  			if (this is OptionSet.Category)
  				return;
  			
  			this.type        = ParsePrototype ();
  			
  			if (this.count == 0 && type != OptionValueType.None)
  				throw new ArgumentException (
  					"Cannot provide maxValueCount of 0 for OptionValueType.Required or " +
  					"OptionValueType.Optional.",
  					"maxValueCount");
  			if (this.type == OptionValueType.None && maxValueCount > 1)
  				throw new ArgumentException (
  					string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount),
  					"maxValueCount");
  			if (Array.IndexOf (names, "<>") >= 0 && 
  			    ((names.Length == 1 && this.type != OptionValueType.None) ||
  			 (names.Length > 1 && this.MaxValueCount > 1)))
  				throw new ArgumentException (
  					"The default option handler '<>' cannot require values.",
  					"prototype");
  		}
  		
  		public string           Prototype       {get {return prototype;}}
  		public string           Description     {get {return description;}}
  		public OptionValueType  OptionValueType {get {return type;}}
  		public int              MaxValueCount   {get {return count;}}
  		
  		public string[] GetNames ()
  		{
  			return (string[]) names.Clone ();
  		}
  		
  		public string[] GetValueSeparators ()
  		{
  			if (separators == null)
  				return new string [0];
  			return (string[]) separators.Clone ();
  		}
  		
  		protected static T Parse<T> (string value, OptionContext c)
  		{
  			Type tt = typeof (T);
  			bool nullable = tt.IsValueType && tt.IsGenericType && 
  				!tt.IsGenericTypeDefinition && 
  					tt.GetGenericTypeDefinition () == typeof (Nullable<>);
  			Type targetType = nullable ? tt.GetGenericArguments () [0] : typeof (T);
  			TypeConverter conv = TypeDescriptor.GetConverter (targetType);
  			T t = default (T);
  			try {
  				if (value != null)
  					t = (T) conv.ConvertFromString (value);
  			}
  			catch (Exception e) {
  				throw new OptionException (
  					string.Format (
  					c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
  					value, targetType.Name, c.OptionName),
  					c.OptionName, e);
  			}
  			return t;
  		}
  		
  		internal string[] Names           {get {return names;}}
  		internal string[] ValueSeparators {get {return separators;}}
  		
  		static readonly char[] NameTerminator = new char[]{'=', ':'};
  		
  		private OptionValueType ParsePrototype ()
  		{
  			char type = '\0';
  			List<string> seps = new List<string> ();
  			for (int i = 0; i < names.Length; ++i) {
  				string name = names [i];
  				if (name.Length == 0)
  					throw new ArgumentException ("Empty option names are not supported.", "prototype");
  				
  				int end = name.IndexOfAny (NameTerminator);
  				if (end == -1)
  					continue;
  				names [i] = name.Substring (0, end);
  				if (type == '\0' || type == name [end])
  					type = name [end];
  				else 
  					throw new ArgumentException (
  						string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]),
  						"prototype");
  				AddSeparators (name, end, seps);
  			}
  			
  			if (type == '\0')
  				return OptionValueType.None;
  			
  			if (count <= 1 && seps.Count != 0)
  				throw new ArgumentException (
  					string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count),
  					"prototype");
  			if (count > 1) {
  				if (seps.Count == 0)
  				this.separators = new string[]{":", "="};
  				else if (seps.Count == 1 && seps [0].Length == 0)
  					this.separators = null;
  				else
  					this.separators = seps.ToArray ();
  			}
  			
  			return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
  		}
  		
  		private static void AddSeparators (string name, int end, ICollection<string> seps)
  		{
  			int start = -1;
  			for (int i = end+1; i < name.Length; ++i) {
  				switch (name [i]) {
  					case '{':
  						if (start != -1)
  							throw new ArgumentException (
  								string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  								"prototype");
  						start = i+1;
  						break;
  					case '}':
  						if (start == -1)
  							throw new ArgumentException (
  								string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  								"prototype");
  						seps.Add (name.Substring (start, i-start));
  						start = -1;
  						break;
  					default:
  						if (start == -1)
  							seps.Add (name [i].ToString ());
  						break;
  				}
  			}
  			if (start != -1)
  				throw new ArgumentException (
  					string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
  					"prototype");
  		}
  		
  		public void Invoke (OptionContext c)
  		{
  			OnParseComplete (c);
  			c.OptionName  = null;
  			c.Option      = null;
  			c.OptionValues.Clear ();
  		}
  		
  		protected abstract void OnParseComplete (OptionContext c);
  		
  		public override string ToString ()
  		{
  			return Prototype;
  		}
  	}
  	
  	public abstract class ArgumentSource {
  		
  		protected ArgumentSource ()
  		{
  		}
  		
  		public abstract string[] GetNames ();
  		public abstract string Description { get; }
  		public abstract bool GetArguments (string value, out IEnumerable<string> replacement);
  		
  		public static IEnumerable<string> GetArgumentsFromFile (string file)
  		{
  			return GetArguments (File.OpenText (file), true);
  		}
  		
  		public static IEnumerable<string> GetArguments (TextReader reader)
  		{
  			return GetArguments (reader, false);
  		}
  		
  		// Cribbed from mcs/driver.cs:LoadArgs(string)
  		static IEnumerable<string> GetArguments (TextReader reader, bool close)
  		{
  			try {
  				StringBuilder arg = new StringBuilder ();
  				
  				string line;
  				while ((line = reader.ReadLine ()) != null) {
  					int t = line.Length;
  					
  					for (int i = 0; i < t; i++) {
  						char c = line [i];
  						
  						if (c == '"' || c == '\'') {
  							char end = c;
  							
  							for (i++; i < t; i++){
  								c = line [i];
  								
  								if (c == end)
  									break;
  								arg.Append (c);
  							}
  						} else if (c == ' ') {
  							if (arg.Length > 0) {
  								yield return arg.ToString ();
  								arg.Length = 0;
  							}
  						} else
  							arg.Append (c);
  					}
  					if (arg.Length > 0) {
  						yield return arg.ToString ();
  						arg.Length = 0;
  					}
  				}
  			}
  			finally {
  				if (close)
  					reader.Close ();
  			}
  		}
  	}
  	
  	public class ResponseFileSource : ArgumentSource {
  		
  		public override string[] GetNames ()
  		{
  			return new string[]{"@file"};
  		}
  		
  		public override string Description {
  			get {return "Read response file for more options.";}
  		}
  		
  		public override bool GetArguments (string value, out IEnumerable<string> replacement)
  		{
  			if (string.IsNullOrEmpty (value) || !value.StartsWith ("@")) {
  				replacement = null;
  				return false;
  			}
  			replacement = ArgumentSource.GetArgumentsFromFile (value.Substring (1));
  			return true;
  		}
  	}
  	
  	[Serializable]
  	public class OptionException : Exception {
  		private string option;
  		
  		public OptionException ()
  		{
  		}
  		
  		public OptionException (string message, string optionName)
  			: base (message)
  		{
  			this.option = optionName;
  		}
  		
  		public OptionException (string message, string optionName, Exception innerException)
  			: base (message, innerException)
  		{
  			this.option = optionName;
  		}
  		
  		protected OptionException (SerializationInfo info, StreamingContext context)
  			: base (info, context)
  		{
  			this.option = info.GetString ("OptionName");
  		}
  		
  		public string OptionName {
  			get {return this.option;}
  		}
  		
  		[SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
  		public override void GetObjectData (SerializationInfo info, StreamingContext context)
  		{
  			base.GetObjectData (info, context);
  			info.AddValue ("OptionName", option);
  		}
  	}
  	
  	public delegate void OptionAction<TKey, TValue> (TKey key, TValue value);
  	
  	public class OptionSet : KeyedCollection<string, Option>
  	{
  		public OptionSet ()
  			: this (delegate (string f) {return f;})
  		{
  		}
  		
  		public OptionSet (Converter<string, string> localizer)
  		{
  			this.localizer = localizer;
  			this.roSources = new ReadOnlyCollection<ArgumentSource>(sources);
  		}
  		
  		Converter<string, string> localizer;
  		
  		public Converter<string, string> MessageLocalizer {
  			get {return localizer;}
  		}
  		
  		List<ArgumentSource> sources = new List<ArgumentSource> ();
  		ReadOnlyCollection<ArgumentSource> roSources;
  		
  		public ReadOnlyCollection<ArgumentSource> ArgumentSources {
  			get {return roSources;}
  		}
  		
  		
  		protected override string GetKeyForItem (Option item)
  		{
  			if (item == null)
  				throw new ArgumentNullException ("option");
  			if (item.Names != null && item.Names.Length > 0)
  				return item.Names [0];
  			// This should never happen, as it's invalid for Option to be
  			// constructed w/o any names.
  			throw new InvalidOperationException ("Option has no names!");
  		}
  		
  		[Obsolete ("Use KeyedCollection.this[string]")]
  		protected Option GetOptionForName (string option)
  		{
  			if (option == null)
  				throw new ArgumentNullException ("option");
  			try {
  				return base [option];
  			}
  			catch (KeyNotFoundException) {
  				return null;
  			}
  		}
  		
  		protected override void InsertItem (int index, Option item)
  		{
  			base.InsertItem (index, item);
  			AddImpl (item);
  		}
  		
  		protected override void RemoveItem (int index)
  		{
  			Option p = Items [index];
  			base.RemoveItem (index);
  			// KeyedCollection.RemoveItem() handles the 0th item
  			for (int i = 1; i < p.Names.Length; ++i) {
  				Dictionary.Remove (p.Names [i]);
  			}
  		}
  		
  		protected override void SetItem (int index, Option item)
  		{
  			base.SetItem (index, item);
  			AddImpl (item);
  		}
  		
  		private void AddImpl (Option option)
  		{
  			if (option == null)
  				throw new ArgumentNullException ("option");
  			List<string> added = new List<string> (option.Names.Length);
  			try {
  				// KeyedCollection.InsertItem/SetItem handle the 0th name.
  				for (int i = 1; i < option.Names.Length; ++i) {
  					Dictionary.Add (option.Names [i], option);
  					added.Add (option.Names [i]);
  				}
  			}
  			catch (Exception) {
  				foreach (string name in added)
  					Dictionary.Remove (name);
  				throw;
  			}
  		}
  		
  		public OptionSet Add (string header)
  		{
  			if (header == null)
  				throw new ArgumentNullException ("header");
  			Add (new Category (header));
  			return this;
  		}
  		
  		internal sealed class Category : Option {
  			
  			// Prototype starts with '=' because this is an invalid prototype
  			// (see Option.ParsePrototype(), and thus it'll prevent Category
  			// instances from being accidentally used as normal options.
  			public Category (string description)
  				: base ("=:Category:= " + description, description)
  			{
  			}
  			
  			protected override void OnParseComplete (OptionContext c)
  			{
  				throw new NotSupportedException ("Category.OnParseComplete should not be invoked.");
  			}
  		}
  		
  		
  		public new OptionSet Add (Option option)
  		{
  			base.Add (option);
  			return this;
  		}
  		
  		sealed class ActionOption : Option {
  			Action<OptionValueCollection> action;
  			
  			public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action)
  				: base (prototype, description, count)
  			{
  				if (action == null)
  					throw new ArgumentNullException ("action");
  				this.action = action;
  			}
  			
  			protected override void OnParseComplete (OptionContext c)
  			{
  				action (c.OptionValues);
  			}
  		}
  		
  		public OptionSet Add (string prototype, Action<string> action)
  		{
  			return Add (prototype, null, action);
  		}
  		
  		public OptionSet Add (string prototype, string description, Action<string> action)
  		{
  			if (action == null)
  				throw new ArgumentNullException ("action");
  			Option p = new ActionOption (prototype, description, 1, 
  			                             delegate (OptionValueCollection v) { action (v [0]); });
  			base.Add (p);
  			return this;
  		}
  		
  		public OptionSet Add (string prototype, OptionAction<string, string> action)
  		{
  			return Add (prototype, null, action);
  		}
  		
  		public OptionSet Add (string prototype, string description, OptionAction<string, string> action)
  		{
  			if (action == null)
  				throw new ArgumentNullException ("action");
  			Option p = new ActionOption (prototype, description, 2, 
  			                             delegate (OptionValueCollection v) {action (v [0], v [1]);});
  			base.Add (p);
  			return this;
  		}
  		
  		sealed class ActionOption<T> : Option {
  			Action<T> action;
  			
  			public ActionOption (string prototype, string description, Action<T> action)
  				: base (prototype, description, 1)
  			{
  
gt: 				if (action == null)