PageRenderTime 234ms CodeModel.GetById 61ms app.highlight 129ms RepoModel.GetById 32ms app.codeStats 0ms

/WorldView/Utilities/Options.cs

#
C# | 1103 lines | 861 code | 114 blank | 128 comment | 209 complexity | 4dc01a58dec9c9082646f7d76ef2ba6a 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
 146namespace NDesk.Options {
 147
 148	public class OptionValueCollection : IList, IList<string> {
 149
 150		List<string> values = new List<string> ();
 151		OptionContext c;
 152
 153		internal OptionValueCollection (OptionContext c)
 154		{
 155			this.c = c;
 156		}
 157
 158		#region ICollection
 159		void ICollection.CopyTo (Array array, int index)  {(values as ICollection).CopyTo (array, index);}
 160		bool ICollection.IsSynchronized                   {get {return (values as ICollection).IsSynchronized;}}
 161		object ICollection.SyncRoot                       {get {return (values as ICollection).SyncRoot;}}
 162		#endregion
 163
 164		#region ICollection<T>
 165		public void Add (string item)                       {values.Add (item);}
 166		public void Clear ()                                {values.Clear ();}
 167		public bool Contains (string item)                  {return values.Contains (item);}
 168		public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);}
 169		public bool Remove (string item)                    {return values.Remove (item);}
 170		public int Count                                    {get {return values.Count;}}
 171		public bool IsReadOnly                              {get {return false;}}
 172		#endregion
 173
 174		#region IEnumerable
 175		IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();}
 176		#endregion
 177
 178		#region IEnumerable<T>
 179		public IEnumerator<string> GetEnumerator () {return values.GetEnumerator ();}
 180		#endregion
 181
 182		#region IList
 183		int IList.Add (object value)                {return (values as IList).Add (value);}
 184		bool IList.Contains (object value)          {return (values as IList).Contains (value);}
 185		int IList.IndexOf (object value)            {return (values as IList).IndexOf (value);}
 186		void IList.Insert (int index, object value) {(values as IList).Insert (index, value);}
 187		void IList.Remove (object value)            {(values as IList).Remove (value);}
 188		void IList.RemoveAt (int index)             {(values as IList).RemoveAt (index);}
 189		bool IList.IsFixedSize                      {get {return false;}}
 190		object IList.this [int index]               {get {return this [index];} set {(values as IList)[index] = value;}}
 191		#endregion
 192
 193		#region IList<T>
 194		public int IndexOf (string item)            {return values.IndexOf (item);}
 195		public void Insert (int index, string item) {values.Insert (index, item);}
 196		public void RemoveAt (int index)            {values.RemoveAt (index);}
 197
 198		private void AssertValid (int index)
 199		{
 200			if (c.Option == null)
 201				throw new InvalidOperationException ("OptionContext.Option is null.");
 202			if (index >= c.Option.MaxValueCount)
 203				throw new ArgumentOutOfRangeException ("index");
 204			if (c.Option.OptionValueType == OptionValueType.Required &&
 205					index >= values.Count)
 206				throw new OptionException (string.Format (
 207							c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), 
 208						c.OptionName);
 209		}
 210
 211		public string this [int index] {
 212			get {
 213				AssertValid (index);
 214				return index >= values.Count ? null : values [index];
 215			}
 216			set {
 217				values [index] = value;
 218			}
 219		}
 220		#endregion
 221
 222		public List<string> ToList ()
 223		{
 224			return new List<string> (values);
 225		}
 226
 227		public string[] ToArray ()
 228		{
 229			return values.ToArray ();
 230		}
 231
 232		public override string ToString ()
 233		{
 234			return string.Join (", ", values.ToArray ());
 235		}
 236	}
 237
 238	public class OptionContext {
 239		private Option                option;
 240		private string                name;
 241		private int                   index;
 242		private OptionSet             set;
 243		private OptionValueCollection c;
 244
 245		public OptionContext (OptionSet set)
 246		{
 247			this.set = set;
 248			this.c   = new OptionValueCollection (this);
 249		}
 250
 251		public Option Option {
 252			get {return option;}
 253			set {option = value;}
 254		}
 255
 256		public string OptionName { 
 257			get {return name;}
 258			set {name = value;}
 259		}
 260
 261		public int OptionIndex {
 262			get {return index;}
 263			set {index = value;}
 264		}
 265
 266		public OptionSet OptionSet {
 267			get {return set;}
 268		}
 269
 270		public OptionValueCollection OptionValues {
 271			get {return c;}
 272		}
 273	}
 274
 275	public enum OptionValueType {
 276		None, 
 277		Optional,
 278		Required,
 279	}
 280
 281	public abstract class Option {
 282		string prototype, description;
 283		string[] names;
 284		OptionValueType type;
 285		int count;
 286		string[] separators;
 287
 288		protected Option (string prototype, string description)
 289			: this (prototype, description, 1)
 290		{
 291		}
 292
 293		protected Option (string prototype, string description, int maxValueCount)
 294		{
 295			if (prototype == null)
 296				throw new ArgumentNullException ("prototype");
 297			if (prototype.Length == 0)
 298				throw new ArgumentException ("Cannot be the empty string.", "prototype");
 299			if (maxValueCount < 0)
 300				throw new ArgumentOutOfRangeException ("maxValueCount");
 301
 302			this.prototype   = prototype;
 303			this.names       = prototype.Split ('|');
 304			this.description = description;
 305			this.count       = maxValueCount;
 306			this.type        = ParsePrototype ();
 307
 308			if (this.count == 0 && type != OptionValueType.None)
 309				throw new ArgumentException (
 310						"Cannot provide maxValueCount of 0 for OptionValueType.Required or " +
 311							"OptionValueType.Optional.",
 312						"maxValueCount");
 313			if (this.type == OptionValueType.None && maxValueCount > 1)
 314				throw new ArgumentException (
 315						string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount),
 316						"maxValueCount");
 317			if (Array.IndexOf (names, "<>") >= 0 && 
 318					((names.Length == 1 && this.type != OptionValueType.None) ||
 319					 (names.Length > 1 && this.MaxValueCount > 1)))
 320				throw new ArgumentException (
 321						"The default option handler '<>' cannot require values.",
 322						"prototype");
 323		}
 324
 325		public string           Prototype       {get {return prototype;}}
 326		public string           Description     {get {return description;}}
 327		public OptionValueType  OptionValueType {get {return type;}}
 328		public int              MaxValueCount   {get {return count;}}
 329
 330		public string[] GetNames ()
 331		{
 332			return (string[]) names.Clone ();
 333		}
 334
 335		public string[] GetValueSeparators ()
 336		{
 337			if (separators == null)
 338				return new string [0];
 339			return (string[]) separators.Clone ();
 340		}
 341
 342		protected static T Parse<T> (string value, OptionContext c)
 343		{
 344			TypeConverter conv = TypeDescriptor.GetConverter (typeof (T));
 345			T t = default (T);
 346			try {
 347				if (value != null)
 348					t = (T) conv.ConvertFromString (value);
 349			}
 350			catch (Exception e) {
 351				throw new OptionException (
 352						string.Format (
 353							c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
 354							value, typeof (T).Name, c.OptionName),
 355						c.OptionName, e);
 356			}
 357			return t;
 358		}
 359
 360		internal string[] Names           {get {return names;}}
 361		internal string[] ValueSeparators {get {return separators;}}
 362
 363		static readonly char[] NameTerminator = new char[]{'=', ':'};
 364
 365		private OptionValueType ParsePrototype ()
 366		{
 367			char type = '\0';
 368			List<string> seps = new List<string> ();
 369			for (int i = 0; i < names.Length; ++i) {
 370				string name = names [i];
 371				if (name.Length == 0)
 372					throw new ArgumentException ("Empty option names are not supported.", "prototype");
 373
 374				int end = name.IndexOfAny (NameTerminator);
 375				if (end == -1)
 376					continue;
 377				names [i] = name.Substring (0, end);
 378				if (type == '\0' || type == name [end])
 379					type = name [end];
 380				else 
 381					throw new ArgumentException (
 382							string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]),
 383							"prototype");
 384				AddSeparators (name, end, seps);
 385			}
 386
 387			if (type == '\0')
 388				return OptionValueType.None;
 389
 390			if (count <= 1 && seps.Count != 0)
 391				throw new ArgumentException (
 392						string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count),
 393						"prototype");
 394			if (count > 1) {
 395				if (seps.Count == 0)
 396					this.separators = new string[]{":", "="};
 397				else if (seps.Count == 1 && seps [0].Length == 0)
 398					this.separators = null;
 399				else
 400					this.separators = seps.ToArray ();
 401			}
 402
 403			return type == '=' ? OptionValueType.Required : OptionValueType.Optional;
 404		}
 405
 406		private static void AddSeparators (string name, int end, ICollection<string> seps)
 407		{
 408			int start = -1;
 409			for (int i = end+1; i < name.Length; ++i) {
 410				switch (name [i]) {
 411					case '{':
 412						if (start != -1)
 413							throw new ArgumentException (
 414									string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
 415									"prototype");
 416						start = i+1;
 417						break;
 418					case '}':
 419						if (start == -1)
 420							throw new ArgumentException (
 421									string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
 422									"prototype");
 423						seps.Add (name.Substring (start, i-start));
 424						start = -1;
 425						break;
 426					default:
 427						if (start == -1)
 428							seps.Add (name [i].ToString ());
 429						break;
 430				}
 431			}
 432			if (start != -1)
 433				throw new ArgumentException (
 434						string.Format ("Ill-formed name/value separator found in \"{0}\".", name),
 435						"prototype");
 436		}
 437
 438		public void Invoke (OptionContext c)
 439		{
 440			OnParseComplete (c);
 441			c.OptionName  = null;
 442			c.Option      = null;
 443			c.OptionValues.Clear ();
 444		}
 445
 446		protected abstract void OnParseComplete (OptionContext c);
 447
 448		public override string ToString ()
 449		{
 450			return Prototype;
 451		}
 452	}
 453
 454	[Serializable]
 455	public class OptionException : Exception {
 456		private string option;
 457
 458		public OptionException ()
 459		{
 460		}
 461
 462		public OptionException (string message, string optionName)
 463			: base (message)
 464		{
 465			this.option = optionName;
 466		}
 467
 468		public OptionException (string message, string optionName, Exception innerException)
 469			: base (message, innerException)
 470		{
 471			this.option = optionName;
 472		}
 473
 474		protected OptionException (SerializationInfo info, StreamingContext context)
 475			: base (info, context)
 476		{
 477			this.option = info.GetString ("OptionName");
 478		}
 479
 480		public string OptionName {
 481			get {return this.option;}
 482		}
 483
 484		[SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
 485		public override void GetObjectData (SerializationInfo info, StreamingContext context)
 486		{
 487			base.GetObjectData (info, context);
 488			info.AddValue ("OptionName", option);
 489		}
 490	}
 491
 492	public delegate void OptionAction<TKey, TValue> (TKey key, TValue value);
 493
 494	public class OptionSet : KeyedCollection<string, Option>
 495	{
 496		public OptionSet ()
 497			: this (delegate (string f) {return f;})
 498		{
 499		}
 500
 501		public OptionSet (Converter<string, string> localizer)
 502		{
 503			this.localizer = localizer;
 504		}
 505
 506		Converter<string, string> localizer;
 507
 508		public Converter<string, string> MessageLocalizer {
 509			get {return localizer;}
 510		}
 511
 512		protected override string GetKeyForItem (Option item)
 513		{
 514			if (item == null)
 515				throw new ArgumentNullException ("option");
 516			if (item.Names != null && item.Names.Length > 0)
 517				return item.Names [0];
 518			// This should never happen, as it's invalid for Option to be
 519			// constructed w/o any names.
 520			throw new InvalidOperationException ("Option has no names!");
 521		}
 522
 523		[Obsolete ("Use KeyedCollection.this[string]")]
 524		protected Option GetOptionForName (string option)
 525		{
 526			if (option == null)
 527				throw new ArgumentNullException ("option");
 528			try {
 529				return base [option];
 530			}
 531			catch (KeyNotFoundException) {
 532				return null;
 533			}
 534		}
 535
 536		protected override void InsertItem (int index, Option item)
 537		{
 538			base.InsertItem (index, item);
 539			AddImpl (item);
 540		}
 541
 542		protected override void RemoveItem (int index)
 543		{
 544			base.RemoveItem (index);
 545			Option p = Items [index];
 546			// KeyedCollection.RemoveItem() handles the 0th item
 547			for (int i = 1; i < p.Names.Length; ++i) {
 548				Dictionary.Remove (p.Names [i]);
 549			}
 550		}
 551
 552		protected override void SetItem (int index, Option item)
 553		{
 554			base.SetItem (index, item);
 555			RemoveItem (index);
 556			AddImpl (item);
 557		}
 558
 559		private void AddImpl (Option option)
 560		{
 561			if (option == null)
 562				throw new ArgumentNullException ("option");
 563			List<string> added = new List<string> (option.Names.Length);
 564			try {
 565				// KeyedCollection.InsertItem/SetItem handle the 0th name.
 566				for (int i = 1; i < option.Names.Length; ++i) {
 567					Dictionary.Add (option.Names [i], option);
 568					added.Add (option.Names [i]);
 569				}
 570			}
 571			catch (Exception) {
 572				foreach (string name in added)
 573					Dictionary.Remove (name);
 574				throw;
 575			}
 576		}
 577
 578		public new OptionSet Add (Option option)
 579		{
 580			base.Add (option);
 581			return this;
 582		}
 583
 584		sealed class ActionOption : Option {
 585			Action<OptionValueCollection> action;
 586
 587			public ActionOption (string prototype, string description, int count, Action<OptionValueCollection> action)
 588				: base (prototype, description, count)
 589			{
 590				if (action == null)
 591					throw new ArgumentNullException ("action");
 592				this.action = action;
 593			}
 594
 595			protected override void OnParseComplete (OptionContext c)
 596			{
 597				action (c.OptionValues);
 598			}
 599		}
 600
 601		public OptionSet Add (string prototype, Action<string> action)
 602		{
 603			return Add (prototype, null, action);
 604		}
 605
 606		public OptionSet Add (string prototype, string description, Action<string> action)
 607		{
 608			if (action == null)
 609				throw new ArgumentNullException ("action");
 610			Option p = new ActionOption (prototype, description, 1, 
 611					delegate (OptionValueCollection v) { action (v [0]); });
 612			base.Add (p);
 613			return this;
 614		}
 615
 616		public OptionSet Add (string prototype, OptionAction<string, string> action)
 617		{
 618			return Add (prototype, null, action);
 619		}
 620
 621		public OptionSet Add (string prototype, string description, OptionAction<string, string> action)
 622		{
 623			if (action == null)
 624				throw new ArgumentNullException ("action");
 625			Option p = new ActionOption (prototype, description, 2, 
 626					delegate (OptionValueCollection v) {action (v [0], v [1]);});
 627			base.Add (p);
 628			return this;
 629		}
 630
 631		sealed class ActionOption<T> : Option {
 632			Action<T> action;
 633
 634			public ActionOption (string prototype, string description, Action<T> action)
 635				: base (prototype, description, 1)
 636			{
 637				if (action == null)
 638					throw new ArgumentNullException ("action");
 639				this.action = action;
 640			}
 641
 642			protected override void OnParseComplete (OptionContext c)
 643			{
 644				action (Parse<T> (c.OptionValues [0], c));
 645			}
 646		}
 647
 648		sealed class ActionOption<TKey, TValue> : Option {
 649			OptionAction<TKey, TValue> action;
 650
 651			public ActionOption (string prototype, string description, OptionAction<TKey, TValue> action)
 652				: base (prototype, description, 2)
 653			{
 654				if (action == null)
 655					throw new ArgumentNullException ("action");
 656				this.action = action;
 657			}
 658
 659			protected override void OnParseComplete (OptionContext c)
 660			{
 661				action (
 662						Parse<TKey> (c.OptionValues [0], c),
 663						Parse<TValue> (c.OptionValues [1], c));
 664			}
 665		}
 666
 667		public OptionSet Add<T> (string prototype, Action<T> action)
 668		{
 669			return Add (prototype, null, action);
 670		}
 671
 672		public OptionSet Add<T> (string prototype, string description, Action<T> action)
 673		{
 674			return Add (new ActionOption<T> (prototype, description, action));
 675		}
 676
 677		public OptionSet Add<TKey, TValue> (string prototype, OptionAction<TKey, TValue> action)
 678		{
 679			return Add (prototype, null, action);
 680		}
 681
 682		public OptionSet Add<TKey, TValue> (string prototype, string description, OptionAction<TKey, TValue> action)
 683		{
 684			return Add (new ActionOption<TKey, TValue> (prototype, description, action));
 685		}
 686
 687		protected virtual OptionContext CreateOptionContext ()
 688		{
 689			return new OptionContext (this);
 690		}
 691
 692#if LINQ
 693		public List<string> Parse (IEnumerable<string> arguments)
 694		{
 695			bool process = true;
 696			OptionContext c = CreateOptionContext ();
 697			c.OptionIndex = -1;
 698			var def = GetOptionForName ("<>");
 699			var unprocessed = 
 700				from argument in arguments
 701				where ++c.OptionIndex >= 0 && (process || def != null)
 702					? process
 703						? argument == "--" 
 704							? (process = false)
 705							: !Parse (argument, c)
 706								? def != null 
 707									? Unprocessed (null, def, c, argument) 
 708									: true
 709								: false
 710						: def != null 
 711							? Unprocessed (null, def, c, argument)
 712							: true
 713					: true
 714				select argument;
 715			List<string> r = unprocessed.ToList ();
 716			if (c.Option != null)
 717				c.Option.Invoke (c);
 718			return r;
 719		}
 720#else
 721		public List<string> Parse (IEnumerable<string> arguments)
 722		{
 723			OptionContext c = CreateOptionContext ();
 724			c.OptionIndex = -1;
 725			bool process = true;
 726			List<string> unprocessed = new List<string> ();
 727			Option def = Contains ("<>") ? this ["<>"] : null;
 728			foreach (string argument in arguments) {
 729				++c.OptionIndex;
 730				if (argument == "--") {
 731					process = false;
 732					continue;
 733				}
 734				if (!process) {
 735					Unprocessed (unprocessed, def, c, argument);
 736					continue;
 737				}
 738				if (!Parse (argument, c))
 739					Unprocessed (unprocessed, def, c, argument);
 740			}
 741			if (c.Option != null)
 742				c.Option.Invoke (c);
 743			return unprocessed;
 744		}
 745#endif
 746
 747		private static bool Unprocessed (ICollection<string> extra, Option def, OptionContext c, string argument)
 748		{
 749			if (def == null) {
 750				extra.Add (argument);
 751				return false;
 752			}
 753			c.OptionValues.Add (argument);
 754			c.Option = def;
 755			c.Option.Invoke (c);
 756			return false;
 757		}
 758
 759		private readonly Regex ValueOption = new Regex (
 760			@"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
 761
 762		protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value)
 763		{
 764			if (argument == null)
 765				throw new ArgumentNullException ("argument");
 766
 767			flag = name = sep = value = null;
 768			Match m = ValueOption.Match (argument);
 769			if (!m.Success) {
 770				return false;
 771			}
 772			flag  = m.Groups ["flag"].Value;
 773			name  = m.Groups ["name"].Value;
 774			if (m.Groups ["sep"].Success && m.Groups ["value"].Success) {
 775				sep   = m.Groups ["sep"].Value;
 776				value = m.Groups ["value"].Value;
 777			}
 778			return true;
 779		}
 780
 781		protected virtual bool Parse (string argument, OptionContext c)
 782		{
 783			if (c.Option != null) {
 784				ParseValue (argument, c);
 785				return true;
 786			}
 787
 788			string f, n, s, v;
 789			if (!GetOptionParts (argument, out f, out n, out s, out v))
 790				return false;
 791
 792			Option p;
 793			if (Contains (n)) {
 794				p = this [n];
 795				c.OptionName = f + n;
 796				c.Option     = p;
 797				switch (p.OptionValueType) {
 798					case OptionValueType.None:
 799						c.OptionValues.Add (n);
 800						c.Option.Invoke (c);
 801						break;
 802					case OptionValueType.Optional:
 803					case OptionValueType.Required: 
 804						ParseValue (v, c);
 805						break;
 806				}
 807				return true;
 808			}
 809			// no match; is it a bool option?
 810			if (ParseBool (argument, n, c))
 811				return true;
 812			// is it a bundled option?
 813			if (ParseBundledValue (f, string.Concat (n + s + v), c))
 814				return true;
 815
 816			return false;
 817		}
 818
 819		private void ParseValue (string option, OptionContext c)
 820		{
 821			if (option != null)
 822				foreach (string o in c.Option.ValueSeparators != null 
 823						? option.Split (c.Option.ValueSeparators, StringSplitOptions.None)
 824						: new string[]{option}) {
 825					c.OptionValues.Add (o);
 826				}
 827			if (c.OptionValues.Count == c.Option.MaxValueCount || 
 828					c.Option.OptionValueType == OptionValueType.Optional)
 829				c.Option.Invoke (c);
 830			else if (c.OptionValues.Count > c.Option.MaxValueCount) {
 831				throw new OptionException (localizer (string.Format (
 832								"Error: Found {0} option values when expecting {1}.", 
 833								c.OptionValues.Count, c.Option.MaxValueCount)),
 834						c.OptionName);
 835			}
 836		}
 837
 838		private bool ParseBool (string option, string n, OptionContext c)
 839		{
 840			Option p;
 841			string rn;
 842			if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') &&
 843					Contains ((rn = n.Substring (0, n.Length-1)))) {
 844				p = this [rn];
 845				string v = n [n.Length-1] == '+' ? option : null;
 846				c.OptionName  = option;
 847				c.Option      = p;
 848				c.OptionValues.Add (v);
 849				p.Invoke (c);
 850				return true;
 851			}
 852			return false;
 853		}
 854
 855		private bool ParseBundledValue (string f, string n, OptionContext c)
 856		{
 857			if (f != "-")
 858				return false;
 859			for (int i = 0; i < n.Length; ++i) {
 860				Option p;
 861				string opt = f + n [i].ToString ();
 862				string rn = n [i].ToString ();
 863				if (!Contains (rn)) {
 864					if (i == 0)
 865						return false;
 866					throw new OptionException (string.Format (localizer (
 867									"Cannot bundle unregistered option '{0}'."), opt), opt);
 868				}
 869				p = this [rn];
 870				switch (p.OptionValueType) {
 871					case OptionValueType.None:
 872						Invoke (c, opt, n, p);
 873						break;
 874					case OptionValueType.Optional:
 875					case OptionValueType.Required: {
 876						string v     = n.Substring (i+1);
 877						c.Option     = p;
 878						c.OptionName = opt;
 879						ParseValue (v.Length != 0 ? v : null, c);
 880						return true;
 881					}
 882					default:
 883						throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType);
 884				}
 885			}
 886			return true;
 887		}
 888
 889		private static void Invoke (OptionContext c, string name, string value, Option option)
 890		{
 891			c.OptionName  = name;
 892			c.Option      = option;
 893			c.OptionValues.Add (value);
 894			option.Invoke (c);
 895		}
 896
 897		private const int OptionWidth = 29;
 898
 899		public void WriteOptionDescriptions (TextWriter o)
 900		{
 901			foreach (Option p in this) {
 902				int written = 0;
 903				if (!WriteOptionPrototype (o, p, ref written))
 904					continue;
 905
 906				if (written < OptionWidth)
 907					o.Write (new string (' ', OptionWidth - written));
 908				else {
 909					o.WriteLine ();
 910					o.Write (new string (' ', OptionWidth));
 911				}
 912
 913				List<string> lines = GetLines (localizer (GetDescription (p.Description)));
 914				o.WriteLine (lines [0]);
 915				string prefix = new string (' ', OptionWidth+2);
 916				for (int i = 1; i < lines.Count; ++i) {
 917					o.Write (prefix);
 918					o.WriteLine (lines [i]);
 919				}
 920			}
 921		}
 922
 923		bool WriteOptionPrototype (TextWriter o, Option p, ref int written)
 924		{
 925			string[] names = p.Names;
 926
 927			int i = GetNextOptionIndex (names, 0);
 928			if (i == names.Length)
 929				return false;
 930
 931			if (names [i].Length == 1) {
 932				Write (o, ref written, "  -");
 933				Write (o, ref written, names [0]);
 934			}
 935			else {
 936				Write (o, ref written, "      --");
 937				Write (o, ref written, names [0]);
 938			}
 939
 940			for ( i = GetNextOptionIndex (names, i+1); 
 941					i < names.Length; i = GetNextOptionIndex (names, i+1)) {
 942				Write (o, ref written, ", ");
 943				Write (o, ref written, names [i].Length == 1 ? "-" : "--");
 944				Write (o, ref written, names [i]);
 945			}
 946
 947			if (p.OptionValueType == OptionValueType.Optional ||
 948					p.OptionValueType == OptionValueType.Required) {
 949				if (p.OptionValueType == OptionValueType.Optional) {
 950					Write (o, ref written, localizer ("["));
 951				}
 952				Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description)));
 953				string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 
 954					? p.ValueSeparators [0]
 955					: " ";
 956				for (int c = 1; c < p.MaxValueCount; ++c) {
 957					Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description)));
 958				}
 959				if (p.OptionValueType == OptionValueType.Optional) {
 960					Write (o, ref written, localizer ("]"));
 961				}
 962			}
 963			return true;
 964		}
 965
 966		static int GetNextOptionIndex (string[] names, int i)
 967		{
 968			while (i < names.Length && names [i] == "<>") {
 969				++i;
 970			}
 971			return i;
 972		}
 973
 974		static void Write (TextWriter o, ref int n, string s)
 975		{
 976			n += s.Length;
 977			o.Write (s);
 978		}
 979
 980		private static string GetArgumentName (int index, int maxIndex, string description)
 981		{
 982			if (description == null)
 983				return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
 984			string[] nameStart;
 985			if (maxIndex == 1)
 986				nameStart = new string[]{"{0:", "{"};
 987			else
 988				nameStart = new string[]{"{" + index + ":"};
 989			for (int i = 0; i < nameStart.Length; ++i) {
 990				int start, j = 0;
 991				do {
 992					start = description.IndexOf (nameStart [i], j);
 993				} while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
 994				if (start == -1)
 995					continue;
 996				int end = description.IndexOf ("}", start);
 997				if (end == -1)
 998					continue;
 999				return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length);
1000			}
1001			return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
1002		}
1003
1004		private static string GetDescription (string description)
1005		{
1006			if (description == null)
1007				return string.Empty;
1008			StringBuilder sb = new StringBuilder (description.Length);
1009			int start = -1;
1010			for (int i = 0; i < description.Length; ++i) {
1011				switch (description [i]) {
1012					case '{':
1013						if (i == start) {
1014							sb.Append ('{');
1015							start = -1;
1016						}
1017						else if (start < 0)
1018							start = i + 1;
1019						break;
1020					case '}':
1021						if (start < 0) {
1022							if ((i+1) == description.Length || description [i+1] != '}')
1023								throw new InvalidOperationException ("Invalid option description: " + description);
1024							++i;
1025							sb.Append ("}");
1026						}
1027						else {
1028							sb.Append (description.Substring (start, i - start));
1029							start = -1;
1030						}
1031						break;
1032					case ':':
1033						if (start < 0)
1034							goto default;
1035						start = i + 1;
1036						break;
1037					default:
1038						if (start < 0)
1039							sb.Append (description [i]);
1040						break;
1041				}
1042			}
1043			return sb.ToString ();
1044		}
1045
1046		private static List<string> GetLines (string description)
1047		{
1048			List<string> lines = new List<string> ();
1049			if (string.IsNullOrEmpty (description)) {
1050				lines.Add (string.Empty);
1051				return lines;
1052			}
1053			int length = 80 - OptionWidth - 2;
1054			int start = 0, end;
1055			do {
1056				end = GetLineEnd (start, length, description);
1057				bool cont = false;
1058				if (end < description.Length) {
1059					char c = description [end];
1060					if (c == '-' || (char.IsWhiteSpace (c) && c != '\n'))
1061						++end;
1062					else if (c != '\n') {
1063						cont = true;
1064						--end;
1065					}
1066				}
1067				lines.Add (description.Substring (start, end - start));
1068				if (cont) {
1069					lines [lines.Count-1] += "-";
1070				}
1071				start = end;
1072				if (start < description.Length && description [start] == '\n')
1073					++start;
1074			} while (end < description.Length);
1075			return lines;
1076		}
1077
1078		private static int GetLineEnd (int start, int length, string description)
1079		{
1080			int end = Math.Min (start + length, description.Length);
1081			int sep = -1;
1082			for (int i = start; i < end; ++i) {
1083				switch (description [i]) {
1084					case ' ':
1085					case '\t':
1086					case '\v':
1087					case '-':
1088					case ',':
1089					case '.':
1090					case ';':
1091						sep = i;
1092						break;
1093					case '\n':
1094						return i;
1095				}
1096			}
1097			if (sep == -1 || end == description.Length)
1098				return end;
1099			return sep;
1100		}
1101	}
1102}
1103