source: Scheduling/trunk/cs/bsdx0200GUISourceCode/Options.cs@ 1154

Last change on this file since 1154 was 772, checked in by Sam Habiel, 15 years ago

Removal of Crystal Reports
Partial Rework of Clinic Patient List report
Other reports that used Crystal don't work yet.
Fixes for Strongly typed DataTables (change the RESOURCEID from uint to int) to support table merge from untyped table.
Support for command line arguments: /s= for server /p= for port /a= for access code /v= for verify code
Only the following combinations work: none; /s and /p; /s, /p, /a, /v

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