PageRenderTime 100ms CodeModel.GetById 31ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 0ms

/SparkleLib/SparkleOptions.cs

http://github.com/hbons/SparkleShare
C# | 1101 lines | 859 code | 114 blank | 128 comment | 206 complexity | f710448eb8f1cd7797524d3424bd86b3 MD5 | raw file
   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
 126using System;
 127using System.Collections;
 128using System.Collections.Generic;
 129using System.Collections.ObjectModel;
 130using System.ComponentModel;
 131using System.Globalization;
 132using System.IO;
 133using System.Runtime.Serialization;
 134using System.Security.Permissions;
 135using System.Text;
 136using System.Text.RegularExpressions;
 137
 138#if LINQ
 139using System.Linq;
 140#endif
 141
 142#if TEST
 143using NDesk.Options;
 144#endif
 145
 146#if NDESK_OPTIONS
 147namespace NDesk.Options
 148#else
 149namespace SparkleLib.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		protected static T Parse<T> (string value, OptionContext c)
 347		{
 348			Type tt = typeof (T);
 349			bool nullable = tt.IsValueType && tt.IsGenericType && 
 350				!tt.IsGenericTypeDefinition && 
 351				tt.GetGenericTypeDefinition () == typeof (Nullable<>);
 352			Type targetType = nullable ? tt.GetGenericArguments () [0] : typeof (T);
 353			TypeConverter conv = TypeDescriptor.GetConverter (targetType);
 354			T t = default (T);
 355			try {
 356				if (value != null)
 357					t = (T) conv.ConvertFromString (value);
 358			}
 359			catch (Exception e) {
 360				throw new OptionException (
 361						string.Format (
 362							c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
 363							value, targetType.Name, c.OptionName),
 364						c.OptionName, e);
 365			}
 366			return t;
 367		}
 368
 369		internal string[] Names           {get {return names;}}
 370		internal string[] ValueSeparators {get {return separators;}}
 371
 372		static readonly char[] NameTerminator = new char[]{'=', ':'};
 373
 374		private OptionValueType ParsePrototype ()
 375		{
 376			char type = '\0';
 377			List<string> seps = new List<string> ();
 378			for (int i = 0; i < names.Length; ++i) {
 379				string name = names [i];
 380				if (name.Length == 0)
 381					throw new ArgumentException ("Empty option names are not supported.", "prototype");
 382
 383				int end = name.IndexOfAny (NameTerminator);
 384				if (end == -1)
 385					continue;
 386				names [i] = name.Substring (0, end);
 387				if (type == '\0' || type == name [end])
 388					type = name [end];
 389				else 
 390					throw new ArgumentException (
 391							string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]),
 392							"prototype");
 393				AddSeparators (name, end, seps);
 394			}
 395
 396			if (type == '\0')
 397				return OptionValueType.None;
 398
 399			if (count <= 1 && seps.Count != 0)
 400				throw new ArgumentException (
 401						string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count),
 402						"prototype");
 403			if (count > 1) {
 404				if (seps.Count == 0)
 405					this.separators = new string[]{":", "="};
 406				else if (seps.Count == 1 && seps [0].Length == 0)
 407					this.separators = null;
 408				else
 409					this.separators = seps.ToArray ();
 410			}
 411
 412			return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
 413		}
 414
 415		private static void AddSeparators (string name, int end, ICollection<string> seps)
 416		{
 417			int start = -1;
 418			for (int i = end+1; i < name.Length; ++i) {
 419				switch (name [i]) {
 420					case '{':
 421						if (start != -1)
 422							throw new ArgumentException (
 423									string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
 424									"prototype");
 425						start = i+1;
 426						break;
 427					case '}':
 428						if (start == -1)
 429							throw new ArgumentException (
 430									string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
 431									"prototype");
 432						seps.Add (name.Substring (start, i-start));
 433						start = -1;
 434						break;
 435					default:
 436						if (start == -1)
 437							seps.Add (name [i].ToString ());
 438						break;
 439				}
 440			}
 441			if (start != -1)
 442				throw new ArgumentException (
 443						string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
 444						"prototype");
 445		}
 446
 447		public void Invoke (OptionContext c)
 448		{
 449			OnParseComplete (c);
 450			c.OptionName  = null;
 451			c.Option      = null;
 452			c.OptionValues.Clear ();
 453		}
 454
 455		protected abstract void OnParseComplete (OptionContext c);
 456
 457		public override string ToString ()
 458		{
 459			return Prototype;
 460		}
 461	}
 462
 463	[Serializable]
 464	public class OptionException : Exception {
 465		private string option;
 466
 467		public OptionException ()
 468		{
 469		}
 470
 471		public OptionException (string message, string optionName)
 472			: base (message)
 473		{
 474			this.option = optionName;
 475		}
 476
 477		public OptionException (string message, string optionName, Exception innerException)
 478			: base (message, innerException)
 479		{
 480			this.option = optionName;
 481		}
 482
 483		protected OptionException (SerializationInfo info, StreamingContext context)
 484			: base (info, context)
 485		{
 486			this.option = info.GetString ("OptionName");
 487		}
 488
 489		public string OptionName {
 490			get {return this.option;}
 491		}
 492
 493		[SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
 494		public override void GetObjectData (SerializationInfo info, StreamingContext context)
 495		{
 496			base.GetObjectData (info, context);
 497			info.AddValue ("OptionName", option);
 498		}
 499	}
 500
 501	public delegate void OptionAction<TKey, TValue> (TKey key, TValue value);
 502
 503	public class OptionSet : KeyedCollection<string, Option>
 504	{
 505		public OptionSet ()
 506			: this (delegate (string f) {return f;})
 507		{
 508		}
 509
 510		public OptionSet (Converter<string, string> localizer)
 511		{
 512			this.localizer = localizer;
 513		}
 514
 515		Converter<string, string> localizer;
 516
 517		public Converter<string, string> MessageLocalizer {
 518			get {return localizer;}
 519		}
 520
 521		protected override string GetKeyForItem (Option item)
 522		{
 523			if (item == null)
 524				throw new ArgumentNullException ("option");
 525			if (item.Names != null && item.Names.Length > 0)
 526				return item.Names [0];
 527			// This should never happen, as it's invalid for Option to be
 528			// constructed w/o any names.
 529			throw new InvalidOperationException ("Option has no names!");
 530		}
 531
 532		[Obsolete ("Use KeyedCollection.this[string]")]
 533		protected Option GetOptionForName (string option)
 534		{
 535			if (option == null)
 536				throw new ArgumentNullException ("option");
 537			try {
 538				return base [option];
 539			}
 540			catch (KeyNotFoundException) {
 541				return null;
 542			}
 543		}
 544
 545		protected override void InsertItem (int index, Option item)
 546		{
 547			base.InsertItem (index, item);
 548			AddImpl (item);
 549		}
 550
 551		protected override void RemoveItem (int index)
 552		{
 553			base.RemoveItem (index);
 554			Option p = Items [index];
 555			// KeyedCollection.RemoveItem() handles the 0th item
 556			for (int i = 1; i < p.Names.Length; ++i) {
 557				Dictionary.Remove (p.Names [i]);
 558			}
 559		}
 560
 561		protected override void SetItem (int index, Option item)
 562		{
 563			base.SetItem (index, item);
 564			RemoveItem (index);
 565			AddImpl (item);
 566		}
 567
 568		private void AddImpl (Option option)
 569		{
 570			if (option == null)
 571				throw new ArgumentNullException ("option");
 572			List<string> added = new List<string> (option.Names.Length);
 573			try {
 574				// KeyedCollection.InsertItem/SetItem handle the 0th name.
 575				for (int i = 1; i < option.Names.Length; ++i) {
 576					Dictionary.Add (option.Names [i], option);
 577					added.Add (option.Names [i]);
 578				}
 579			}
 580			catch (Exception) {
 581				foreach (string name in added)
 582					Dictionary.Remove (name);
 583				throw;
 584			}
 585		}
 586
 587		public new OptionSet Add (Option option)
 588		{
 589			base.Add (option);
 590			return this;
 591		}
 592
 593		sealed class ActionOption : Option {
 594			Action<OptionValueCollection> action;
 595
 596			public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action)
 597				: base (prototype, description, count)
 598			{
 599				if (action == null)
 600					throw new ArgumentNullException ("action");
 601				this.action = action;
 602			}
 603
 604			protected override void OnParseComplete (OptionContext c)
 605			{
 606				action (c.OptionValues);
 607			}
 608		}
 609
 610		public OptionSet Add (string prototype, Action<string> action)
 611		{
 612			return Add (prototype, null, action);
 613		}
 614
 615		public OptionSet Add (string prototype, string description, Action<string> action)
 616		{
 617			if (action == null)
 618				throw new ArgumentNullException ("action");
 619			Option p = new ActionOption (prototype, description, 1, 
 620					delegate (OptionValueCollection v) { action (v [0]); });
 621			base.Add (p);
 622			return this;
 623		}
 624
 625		public OptionSet Add (string prototype, OptionAction<string, string> action)
 626		{
 627			return Add (prototype, null, action);
 628		}
 629
 630		public OptionSet Add (string prototype, string description, OptionAction<string, string> action)
 631		{
 632			if (action == null)
 633				throw new ArgumentNullException ("action");
 634			Option p = new ActionOption (prototype, description, 2, 
 635					delegate (OptionValueCollection v) {action (v [0], v [1]);});
 636			base.Add (p);
 637			return this;
 638		}
 639
 640		sealed class ActionOption<T> : Option {
 641			Action<T> action;
 642
 643			public ActionOption (string prototype, string description, Action<T> action)
 644				: base (prototype, description, 1)
 645			{
 646				if (action == null)
 647					throw new ArgumentNullException ("action");
 648				this.action = action;
 649			}
 650
 651			protected override void OnParseComplete (OptionContext c)
 652			{
 653				action (Parse<T> (c.OptionValues [0], c));
 654			}
 655		}
 656
 657		sealed class ActionOption<TKey, TValue> : Option {
 658			OptionAction<TKey, TValue> action;
 659
 660			public ActionOption (string prototype, string description, OptionAction<TKey, TValue> action)
 661				: base (prototype, description, 2)
 662			{
 663				if (action == null)
 664					throw new ArgumentNullException ("action");
 665				this.action = action;
 666			}
 667
 668			protected override void OnParseComplete (OptionContext c)
 669			{
 670				action (
 671						Parse<TKey> (c.OptionValues [0], c),
 672						Parse<TValue> (c.OptionValues [1], c));
 673			}
 674		}
 675
 676		public OptionSet Add<T> (string prototype, Action<T> action)
 677		{
 678			return Add (prototype, null, action);
 679		}
 680
 681		public OptionSet Add<T> (string prototype, string description, Action<T> action)
 682		{
 683			return Add (new ActionOption<T> (prototype, description, action));
 684		}
 685
 686		public OptionSet Add<TKey, TValue> (string prototype, OptionAction<TKey, TValue> action)
 687		{
 688			return Add (prototype, null, action);
 689		}
 690
 691		public OptionSet Add<TKey, TValue> (string prototype, string description, OptionAction<TKey, TValue> action)
 692		{
 693			return Add (new ActionOption<TKey, TValue> (prototype, description, action));
 694		}
 695
 696		protected virtual OptionContext CreateOptionContext ()
 697		{
 698			return new OptionContext (this);
 699		}
 700
 701#if LINQ
 702		public List<string> Parse (IEnumerable<string> arguments)
 703		{
 704			bool process = true;
 705			OptionContext c = CreateOptionContext ();
 706			c.OptionIndex = -1;
 707			var def = GetOptionForName ("<>");
 708			var unprocessed = 
 709				from argument in arguments
 710				where ++c.OptionIndex >= 0 && (process || def != null)
 711					? process
 712						? argument == "--" 
 713							? (process = false)
 714							: !Parse (argument, c)
 715								? def != null 
 716									? Unprocessed (null, def, c, argument) 
 717									: true
 718								: false
 719						: def != null 
 720							? Unprocessed (null, def, c, argument)
 721							: true
 722					: true
 723				select argument;
 724			List<string> r = unprocessed.ToList ();
 725			if (c.Option != null)
 726				c.Option.Invoke (c);
 727			return r;
 728		}
 729#else
 730		public List<string> Parse (IEnumerable<string> arguments)
 731		{
 732			OptionContext c = CreateOptionContext ();
 733			c.OptionIndex = -1;
 734			bool process = true;
 735			List<string> unprocessed = new List<string> ();
 736			Option def = Contains ("<>") ? this ["<>"] : null;
 737			foreach (string argument in arguments) {
 738				++c.OptionIndex;
 739				if (argument == "--") {
 740					process = false;
 741					continue;
 742				}
 743				if (!process) {
 744					Unprocessed (unprocessed, def, c, argument);
 745					continue;
 746				}
 747				if (!Parse (argument, c))
 748					Unprocessed (unprocessed, def, c, argument);
 749			}
 750			if (c.Option != null)
 751				c.Option.Invoke (c);
 752			return unprocessed;
 753		}
 754#endif
 755
 756		private static bool Unprocessed (ICollection<string> extra, Option def, OptionContext c, string argument)
 757		{
 758			if (def == null) {
 759				extra.Add (argument);
 760				return false;
 761			}
 762			c.OptionValues.Add (argument);
 763			c.Option = def;
 764			c.Option.Invoke (c);
 765			return false;
 766		}
 767
 768		private readonly Regex ValueOption = new Regex (
 769			@"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
 770
 771		protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value)
 772		{
 773			if (argument == null)
 774				throw new ArgumentNullException ("argument");
 775
 776			flag = name = sep = value = null;
 777			Match m = ValueOption.Match (argument);
 778			if (!m.Success) {
 779				return false;
 780			}
 781			flag  = m.Groups ["flag"].Value;
 782			name  = m.Groups ["name"].Value;
 783			if (m.Groups ["sep"].Success && m.Groups ["value"].Success) {
 784				sep   = m.Groups ["sep"].Value;
 785				value = m.Groups ["value"].Value;
 786			}
 787			return true;
 788		}
 789
 790		protected virtual bool Parse (string argument, OptionContext c)
 791		{
 792			if (c.Option != null) {
 793				ParseValue (argument, c);
 794				return true;
 795			}
 796
 797			string f, n, s, v;
 798			if (!GetOptionParts (argument, out f, out n, out s, out v))
 799				return false;
 800
 801			Option p;
 802			if (Contains (n)) {
 803				p = this [n];
 804				c.OptionName = f + n;
 805				c.Option     = p;
 806				switch (p.OptionValueType) {
 807					case OptionValueType.None:
 808						c.OptionValues.Add (n);
 809						c.Option.Invoke (c);
 810						break;
 811					case OptionValueType.Optional:
 812					case OptionValueType.Required: 
 813						ParseValue (v, c);
 814						break;
 815				}
 816				return true;
 817			}
 818			// no match; is it a bool option?
 819			if (ParseBool (argument, n, c))
 820				return true;
 821			// is it a bundled option?
 822			if (ParseBundledValue (f, string.Concat (n + s + v), c))
 823				return true;
 824
 825			return false;
 826		}
 827
 828		private void ParseValue (string option, OptionContext c)
 829		{
 830			if (option != null)
 831				foreach (string o in c.Option.ValueSeparators != null 
 832						? option.Split (c.Option.ValueSeparators, StringSplitOptions.None)
 833						: new string[]{option}) {
 834					c.OptionValues.Add (o);
 835				}
 836			if (c.OptionValues.Count == c.Option.MaxValueCount || 
 837					c.Option.OptionValueType == OptionValueType.Optional)
 838				c.Option.Invoke (c);
 839			else if (c.OptionValues.Count > c.Option.MaxValueCount) {
 840				throw new OptionException (localizer (string.Format (
 841								"Error: Found {0} option values when expecting {1}.", 
 842								c.OptionValues.Count, c.Option.MaxValueCount)),
 843						c.OptionName);
 844			}
 845		}
 846
 847		private bool ParseBool (string option, string n, OptionContext c)
 848		{
 849			Option p;
 850			string rn;
 851			if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') &&
 852					Contains ((rn = n.Substring (0, n.Length-1)))) {
 853				p = this [rn];
 854				string v = n [n.Length-1] == '+' ? option : null;
 855				c.OptionName  = option;
 856				c.Option      = p;
 857				c.OptionValues.Add (v);
 858				p.Invoke (c);
 859				return true;
 860			}
 861			return false;
 862		}
 863
 864		private bool ParseBundledValue (string f, string n, OptionContext c)
 865		{
 866			if (f != "-")
 867				return false;
 868			for (int i = 0; i < n.Length; ++i) {
 869				Option p;
 870				string opt = f + n [i].ToString ();
 871				string rn = n [i].ToString ();
 872				if (!Contains (rn)) {
 873					if (i == 0)
 874						return false;
 875					throw new OptionException (string.Format (localizer (
 876									"Cannot bundle unregistered option '{0}'."), opt), opt);
 877				}
 878				p = this [rn];
 879				switch (p.OptionValueType) {
 880					case OptionValueType.None:
 881						Invoke (c, opt, n, p);
 882						break;
 883					case OptionValueType.Optional:
 884					case OptionValueType.Required: {
 885						string v     = n.Substring (i+1);
 886						c.Option     = p;
 887						c.OptionName = opt;
 888						ParseValue (v.Length != 0 ? v : null, c);
 889						return true;
 890					}
 891					default:
 892						throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType);
 893				}
 894			}
 895			return true;
 896		}
 897
 898		private static void Invoke (OptionContext c, string name, string value, Option option)
 899		{
 900			c.OptionName  = name;
 901			c.Option      = option;
 902			c.OptionValues.Add (value);
 903			option.Invoke (c);
 904		}
 905
 906		private const int OptionWidth = 29;
 907
 908		public void WriteOptionDescriptions (TextWriter o)
 909		{
 910			foreach (Option p in this) {
 911				int written = 0;
 912				if (!WriteOptionPrototype (o, p, ref written))
 913					continue;
 914
 915				if (written < OptionWidth)
 916					o.Write (new string (' ', OptionWidth - written));
 917				else {
 918					o.WriteLine ();
 919					o.Write (new string (' ', OptionWidth));
 920				}
 921
 922				bool indent = false;
 923				string prefix = new string (' ', OptionWidth+2);
 924				foreach (string line in GetLines (localizer (GetDescription (p.Description)))) {
 925					if (indent) 
 926						o.Write (prefix);
 927					o.WriteLine (line);
 928					indent = true;
 929				}
 930			}
 931		}
 932
 933		bool WriteOptionPrototype (TextWriter o, Option p, ref int written)
 934		{
 935			string[] names = p.Names;
 936
 937			int i = GetNextOptionIndex (names, 0);
 938			if (i == names.Length)
 939				return false;
 940
 941			if (names [i].Length == 1) {
 942				Write (o, ref written, "  -");
 943				Write (o, ref written, names [0]);
 944			}
 945			else {
 946				Write (o, ref written, "      --");
 947				Write (o, ref written, names [0]);
 948			}
 949
 950			for ( i = GetNextOptionIndex (names, i+1); 
 951					i < names.Length; i = GetNextOptionIndex (names, i+1)) {
 952				Write (o, ref written, ", ");
 953				Write (o, ref written, names [i].Length == 1 ? "-" : "--");
 954				Write (o, ref written, names [i]);
 955			}
 956
 957			if (p.OptionValueType == OptionValueType.Optional ||
 958					p.OptionValueType == OptionValueType.Required) {
 959				if (p.OptionValueType == OptionValueType.Optional) {
 960					Write (o, ref written, localizer ("["));
 961				}
 962				Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description)));
 963				string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 
 964					? p.ValueSeparators [0]
 965					: " ";
 966				for (int c = 1; c < p.MaxValueCount; ++c) {
 967					Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description)));
 968				}
 969				if (p.OptionValueType == OptionValueType.Optional) {
 970					Write (o, ref written, localizer ("]"));
 971				}
 972			}
 973			return true;
 974		}
 975
 976		static int GetNextOptionIndex (string[] names, int i)
 977		{
 978			while (i < names.Length && names [i] == "<>") {
 979				++i;
 980			}
 981			return i;
 982		}
 983
 984		static void Write (TextWriter o, ref int n, string s)
 985		{
 986			n += s.Length;
 987			o.Write (s);
 988		}
 989
 990		private static string GetArgumentName (int index, int maxIndex, string description)
 991		{
 992			if (description == null)
 993				return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
 994			string[] nameStart;
 995			if (maxIndex == 1)
 996				nameStart = new string[]{"{0:", "{"};
 997			else
 998				nameStart = new string[]{"{" + index + ":"};
 999			for (int i = 0; i < nameStart.Length; ++i) {
1000				int start, j = 0;
1001				do {
1002					start = description.IndexOf (nameStart [i], j);
1003				} while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
1004				if (start == -1)
1005					continue;
1006				int end = description.IndexOf ("}", start);
1007				if (end == -1)
1008					continue;
1009				return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length);
1010			}
1011			return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
1012		}
1013
1014		private static string GetDescription (string description)
1015		{
1016			if (description == null)
1017				return string.Empty;
1018			StringBuilder sb = new StringBuilder (description.Length);
1019			int start = -1;
1020			for (int i = 0; i < description.Length; ++i) {
1021				switch (description [i]) {
1022					case '{':
1023						if (i == start) {
1024							sb.Append ('{');
1025							start = -1;
1026						}
1027						else if (start < 0)
1028							start = i + 1;
1029						break;
1030					case '}':
1031						if (start < 0) {
1032							if ((i+1) == description.Length || description [i+1] != '}')
1033								throw new InvalidOperationException ("Invalid option description: " + description);
1034							++i;
1035							sb.Append ("}");
1036						}
1037						else {
1038							sb.Append (description.Substring (start, i - start));
1039							start = -1;
1040						}
1041						break;
1042					case ':':
1043						if (start < 0)
1044							goto default;
1045						start = i + 1;
1046						break;
1047					default:
1048						if (start < 0)
1049							sb.Append (description [i]);
1050						break;
1051				}
1052			}
1053			return sb.ToString ();
1054		}
1055
1056		private static IEnumerable<string> GetLines (string description)
1057		{
1058			if (string.IsNullOrEmpty (description)) {
1059				yield return string.Empty;
1060				yield break;
1061			}
1062			int length = 80 - OptionWidth - 1;
1063			int start = 0, end;
1064			do {
1065				end = GetLineEnd (start, length, description);
1066				char c = description [end-1];
1067				if (char.IsWhiteSpace (c))
1068					--end;
1069				bool writeContinuation = end != description.Length && !IsEolChar (c);
1070				string line = description.Substring (start, end - start) +
1071						(writeContinuation ? "-" : "");
1072				yield return line;
1073				start = end;
1074				if (char.IsWhiteSpace (c))
1075					++start;
1076				length = 80 - OptionWidth - 2 - 1;
1077			} while (end < description.Length);
1078		}
1079
1080		private static bool IsEolChar (char c)
1081		{
1082			return !char.IsLetterOrDigit (c);
1083		}
1084
1085		private static int GetLineEnd (int start, int length, string description)
1086		{
1087			int end = System.Math.Min (start + length, description.Length);
1088			int sep = -1;
1089			for (int i = start; i < end; ++i) {
1090				if (description [i] == '\n')
1091					return i+1;
1092				if (IsEolChar (description [i]))
1093					sep = i+1;
1094			}
1095			if (sep == -1 || end == description.Length)
1096				return end;
1097			return sep;
1098		}
1099	}
1100}
1101