5 // Jonathan Pryor <jpryor@novell.com>
7 // Copyright (C) 2008 Novell (http://www.novell.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
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
33 // The LINQ version just changes the implementation of
34 // OptionSet.Parse(IEnumerable<string>), and confers no semantic changes.
37 // A Getopt::Long-inspired option parsing library for C#.
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.
43 // Option format strings:
44 // Regex-like BNF Grammar:
47 // sep: ( [^{}]+ | '{' .+ '}' )?
48 // aliases: ( name type sep ) ( '|' name type sep )*
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.
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.
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 ':')
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.
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.
79 // This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value'
82 // Option processing is disabled by specifying "--". All options after "--"
83 // are returned by OptionSet.Parse() unchanged and unprocessed.
85 // Unprocessed options are returned from OptionSet.Parse().
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"});
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".
99 // C# 3.0 collection initializers are supported and encouraged:
100 // var p = new OptionSet () {
101 // { "h|?|help", v => ShowHelp () },
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
109 // var p = new OptionSet () {
110 // { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) },
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 '-':
118 // var p = new OptionSet () {
119 // { "a", s => a = s },
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
127 using System
.Collections
;
128 using System
.Collections
.Generic
;
129 using System
.Collections
.ObjectModel
;
130 using System
.ComponentModel
;
131 using System
.Globalization
;
133 using System
.Runtime
.Serialization
;
134 using System
.Security
.Permissions
;
136 using System
.Text
.RegularExpressions
;
147 namespace NDesk
.Options
149 namespace Mono
.Options
152 public class OptionValueCollection
: IList
, IList
<string> {
154 List
<string> values
= new List
<string> ();
157 internal OptionValueCollection (OptionContext c
)
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;}}
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;}}
179 IEnumerator IEnumerable
.GetEnumerator () {return values.GetEnumerator ();}
182 #region IEnumerable<T>
183 public IEnumerator
<string> GetEnumerator () {return values.GetEnumerator ();}
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;}}
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);}
202 private void AssertValid (int index
)
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
),
215 public string this [int index
] {
218 return index
>= values
.Count
? null : values
[index
];
221 values
[index
] = value;
226 public List
<string> ToList ()
228 return new List
<string> (values
);
231 public string[] ToArray ()
233 return values
.ToArray ();
236 public override string ToString ()
238 return string.Join (", ", values
.ToArray ());
242 public class OptionContext
{
243 private Option option
;
246 private OptionSet
set;
247 private OptionValueCollection c
;
249 public OptionContext (OptionSet
set)
252 this.c
= new OptionValueCollection (this);
255 public Option Option
{
257 set {option = value;}
260 public string OptionName
{
265 public int OptionIndex
{
270 public OptionSet OptionSet
{
274 public OptionValueCollection OptionValues
{
279 public enum OptionValueType
{
285 public abstract class Option
{
286 string prototype
, description
;
288 OptionValueType type
;
292 protected Option (string prototype
, string description
)
293 : this (prototype
, description
, 1)
297 protected Option (string prototype
, string description
, int maxValueCount
)
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");
306 this.prototype
= prototype
;
307 this.names
= prototype
.Split ('|');
308 this.description
= description
;
309 this.count
= maxValueCount
;
310 this.type
= ParsePrototype ();
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.",
317 if (this.type
== OptionValueType
.None
&& maxValueCount
> 1)
318 throw new ArgumentException (
319 string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", 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.",
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;}}
334 public string[] GetNames ()
336 return (string[]) names
.Clone ();
339 public string[] GetValueSeparators ()
341 if (separators
== null)
342 return new string [0];
343 return (string[]) separators
.Clone ();
346 protected static T Parse
<T
> (string value, OptionContext c
)
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
);
357 t
= (T
) conv
.ConvertFromString (value);
359 catch (Exception e
) {
360 throw new OptionException (
362 c
.OptionSet
.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."),
363 value, targetType
.Name
, c
.OptionName
),
369 internal string[] Names {get {return names;}}
370 internal string[] ValueSeparators {get {return separators;}}
372 static readonly char[] NameTerminator
= new char[]{'=', ':'}
;
374 private OptionValueType
ParsePrototype ()
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");
383 int end
= name
.IndexOfAny (NameTerminator
);
386 names
[i
] = name
.Substring (0, end
);
387 if (type
== '\0' || type
== name
[end
])
390 throw new ArgumentException (
391 string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type
, name
[end
]),
393 AddSeparators (name
, end
, seps
);
397 return OptionValueType
.None
;
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
),
405 this.separators
= new string[]{":", "="}
;
406 else if (seps
.Count
== 1 && seps
[0].Length
== 0)
407 this.separators
= null;
409 this.separators
= seps
.ToArray ();
412 return type
== '=' ? OptionValueType
.Required
: OptionValueType
.Optional
;
415 private static void AddSeparators (string name
, int end
, ICollection
<string> seps
)
418 for (int i
= end
+1; i
< name
.Length
; ++i
) {
422 throw new ArgumentException (
423 string.Format ("Ill-formed name/value separator found in \"{0}\".", name
),
429 throw new ArgumentException (
430 string.Format ("Ill-formed name/value separator found in \"{0}\".", name
),
432 seps
.Add (name
.Substring (start
, i
-start
));
437 seps
.Add (name
[i
].ToString ());
442 throw new ArgumentException (
443 string.Format ("Ill-formed name/value separator found in \"{0}\".", name
),
447 public void Invoke (OptionContext c
)
452 c
.OptionValues
.Clear ();
455 protected abstract void OnParseComplete (OptionContext c
);
457 public override string ToString ()
464 public class OptionException
: Exception
{
465 private string option
;
467 public OptionException ()
471 public OptionException (string message
, string optionName
)
474 this.option
= optionName
;
477 public OptionException (string message
, string optionName
, Exception innerException
)
478 : base (message
, innerException
)
480 this.option
= optionName
;
483 protected OptionException (SerializationInfo info
, StreamingContext context
)
484 : base (info
, context
)
486 this.option
= info
.GetString ("OptionName");
489 public string OptionName
{
490 get {return this.option;}
493 [SecurityPermission (SecurityAction
.LinkDemand
, SerializationFormatter
= true)]
494 public override void GetObjectData (SerializationInfo info
, StreamingContext context
)
496 base.GetObjectData (info
, context
);
497 info
.AddValue ("OptionName", option
);
501 public delegate void OptionAction
<TKey
, TValue
> (TKey key
, TValue
value);
503 public class OptionSet
: KeyedCollection
<string, Option
>
506 : this (delegate (string f
) {return f;}
)
510 public OptionSet (Converter
<string, string> localizer
)
512 this.localizer
= localizer
;
515 Converter
<string, string> localizer
;
517 public Converter
<string, string> MessageLocalizer
{
518 get {return localizer;}
521 protected override string GetKeyForItem (Option item
)
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!");
532 [Obsolete ("Use KeyedCollection.this[string]")]
533 protected Option
GetOptionForName (string option
)
536 throw new ArgumentNullException ("option");
538 return base [option
];
540 catch (KeyNotFoundException
) {
545 protected override void InsertItem (int index
, Option item
)
547 base.InsertItem (index
, item
);
551 protected override void RemoveItem (int index
)
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
]);
561 protected override void SetItem (int index
, Option item
)
563 base.SetItem (index
, item
);
568 private void AddImpl (Option option
)
571 throw new ArgumentNullException ("option");
572 List
<string> added
= new List
<string> (option
.Names
.Length
);
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
]);
581 foreach (string name
in added
)
582 Dictionary
.Remove (name
);
587 public new OptionSet
Add (Option option
)
593 sealed class ActionOption
: Option
{
594 Action
<OptionValueCollection
> action
;
596 public ActionOption (string prototype
, string description
, int count
, Action
<OptionValueCollection
> action
)
597 : base (prototype
, description
, count
)
600 throw new ArgumentNullException ("action");
601 this.action
= action
;
604 protected override void OnParseComplete (OptionContext c
)
606 action (c
.OptionValues
);
610 public OptionSet
Add (string prototype
, Action
<string> action
)
612 return Add (prototype
, null, action
);
615 public OptionSet
Add (string prototype
, string description
, Action
<string> action
)
618 throw new ArgumentNullException ("action");
619 Option p
= new ActionOption (prototype
, description
, 1,
620 delegate (OptionValueCollection v
) { action (v [0]); }
);
625 public OptionSet
Add (string prototype
, OptionAction
<string, string> action
)
627 return Add (prototype
, null, action
);
630 public OptionSet
Add (string prototype
, string description
, OptionAction
<string, string> action
)
633 throw new ArgumentNullException ("action");
634 Option p
= new ActionOption (prototype
, description
, 2,
635 delegate (OptionValueCollection v
) {action (v [0], v [1]);}
);
640 sealed class ActionOption
<T
> : Option
{
643 public ActionOption (string prototype
, string description
, Action
<T
> action
)
644 : base (prototype
, description
, 1)
647 throw new ArgumentNullException ("action");
648 this.action
= action
;
651 protected override void OnParseComplete (OptionContext c
)
653 action (Parse
<T
> (c
.OptionValues
[0], c
));
657 sealed class ActionOption
<TKey
, TValue
> : Option
{
658 OptionAction
<TKey
, TValue
> action
;
660 public ActionOption (string prototype
, string description
, OptionAction
<TKey
, TValue
> action
)
661 : base (prototype
, description
, 2)
664 throw new ArgumentNullException ("action");
665 this.action
= action
;
668 protected override void OnParseComplete (OptionContext c
)
671 Parse
<TKey
> (c
.OptionValues
[0], c
),
672 Parse
<TValue
> (c
.OptionValues
[1], c
));
676 public OptionSet Add
<T
> (string prototype
, Action
<T
> action
)
678 return Add (prototype
, null, action
);
681 public OptionSet Add
<T
> (string prototype
, string description
, Action
<T
> action
)
683 return Add (new ActionOption
<T
> (prototype
, description
, action
));
686 public OptionSet Add
<TKey
, TValue
> (string prototype
, OptionAction
<TKey
, TValue
> action
)
688 return Add (prototype
, null, action
);
691 public OptionSet Add
<TKey
, TValue
> (string prototype
, string description
, OptionAction
<TKey
, TValue
> action
)
693 return Add (new ActionOption
<TKey
, TValue
> (prototype
, description
, action
));
696 protected virtual OptionContext
CreateOptionContext ()
698 return new OptionContext (this);
702 public List
<string> Parse (IEnumerable
<string> arguments
)
705 OptionContext c
= CreateOptionContext ();
707 var def
= GetOptionForName ("<>");
709 from argument
in arguments
710 where
++c
.OptionIndex
>= 0 && (process
|| def
!= null)
714 : !Parse (argument
, c
)
716 ? Unprocessed (null, def
, c
, argument
)
720 ? Unprocessed (null, def
, c
, argument
)
724 List
<string> r
= unprocessed
.ToList ();
725 if (c
.Option
!= null)
730 public List
<string> Parse (IEnumerable
<string> arguments
)
732 OptionContext c
= CreateOptionContext ();
735 List
<string> unprocessed
= new List
<string> ();
736 Option def
= Contains ("<>") ? this ["<>"] : null;
737 foreach (string argument
in arguments
) {
739 if (argument
== "--") {
744 Unprocessed (unprocessed
, def
, c
, argument
);
747 if (!Parse (argument
, c
))
748 Unprocessed (unprocessed
, def
, c
, argument
);
750 if (c
.Option
!= null)
756 private static bool Unprocessed (ICollection
<string> extra
, Option def
, OptionContext c
, string argument
)
759 extra
.Add (argument
);
762 c
.OptionValues
.Add (argument
);
768 private readonly Regex ValueOption
= new Regex (
769 @"^(?<flag>--|-|/)(?<name>[^:=]+)((?<sep>[:=])(?<value>.*))?$");
771 protected bool GetOptionParts (string argument
, out string flag
, out string name
, out string sep
, out string value)
773 if (argument
== null)
774 throw new ArgumentNullException ("argument");
776 flag
= name
= sep
= value = null;
777 Match m
= ValueOption
.Match (argument
);
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
;
790 protected virtual bool Parse (string argument
, OptionContext c
)
792 if (c
.Option
!= null) {
793 ParseValue (argument
, c
);
798 if (!GetOptionParts (argument
, out f
, out n
, out s
, out v
))
804 c
.OptionName
= f
+ n
;
806 switch (p
.OptionValueType
) {
807 case OptionValueType
.None
:
808 c
.OptionValues
.Add (n
);
811 case OptionValueType
.Optional
:
812 case OptionValueType
.Required
:
818 // no match; is it a bool option?
819 if (ParseBool (argument
, n
, c
))
821 // is it a bundled option?
822 if (ParseBundledValue (f
, string.Concat (n
+ s
+ v
), c
))
828 private void ParseValue (string option
, OptionContext c
)
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
);
836 if (c
.OptionValues
.Count
== c
.Option
.MaxValueCount
||
837 c
.Option
.OptionValueType
== OptionValueType
.Optional
)
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
)),
847 private bool ParseBool (string option
, string n
, OptionContext c
)
851 if (n
.Length
>= 1 && (n
[n
.Length
-1] == '+' || n
[n
.Length
-1] == '-') &&
852 Contains ((rn
= n
.Substring (0, n
.Length
-1)))) {
854 string v
= n
[n
.Length
-1] == '+' ? option
: null;
855 c
.OptionName
= option
;
857 c
.OptionValues
.Add (v
);
864 private bool ParseBundledValue (string f
, string n
, OptionContext c
)
868 for (int i
= 0; i
< n
.Length
; ++i
) {
870 string opt
= f
+ n
[i
].ToString ();
871 string rn
= n
[i
].ToString ();
872 if (!Contains (rn
)) {
875 throw new OptionException (string.Format (localizer (
876 "Cannot bundle unregistered option '{0}'."), opt
), opt
);
879 switch (p
.OptionValueType
) {
880 case OptionValueType
.None
:
881 Invoke (c
, opt
, n
, p
);
883 case OptionValueType
.Optional
:
884 case OptionValueType
.Required
: {
885 string v
= n
.Substring (i
+1);
888 ParseValue (v
.Length
!= 0 ? v
: null, c
);
892 throw new InvalidOperationException ("Unknown OptionValueType: " + p
.OptionValueType
);
898 private static void Invoke (OptionContext c
, string name
, string value, Option option
)
902 c
.OptionValues
.Add (value);
906 private const int OptionWidth
= 29;
908 public void WriteOptionDescriptions (TextWriter o
)
910 foreach (Option p
in this) {
912 if (!WriteOptionPrototype (o
, p
, ref written
))
915 if (written
< OptionWidth
)
916 o
.Write (new string (' ', OptionWidth
- written
));
919 o
.Write (new string (' ', OptionWidth
));
922 List
<string> lines
= GetLines (localizer (GetDescription (p
.Description
)));
923 o
.WriteLine (lines
[0]);
924 string prefix
= new string (' ', OptionWidth
+2);
925 for (int i
= 1; i
< lines
.Count
; ++i
) {
927 o
.WriteLine (lines
[i
]);
932 bool WriteOptionPrototype (TextWriter o
, Option p
, ref int written
)
934 string[] names
= p
.Names
;
936 int i
= GetNextOptionIndex (names
, 0);
937 if (i
== names
.Length
)
940 if (names
[i
].Length
== 1) {
941 Write (o
, ref written
, " -");
942 Write (o
, ref written
, names
[0]);
945 Write (o
, ref written
, " --");
946 Write (o
, ref written
, names
[0]);
949 for ( i
= GetNextOptionIndex (names
, i
+1);
950 i
< names
.Length
; i
= GetNextOptionIndex (names
, i
+1)) {
951 Write (o
, ref written
, ", ");
952 Write (o
, ref written
, names
[i
].Length
== 1 ? "-" : "--");
953 Write (o
, ref written
, names
[i
]);
956 if (p
.OptionValueType
== OptionValueType
.Optional
||
957 p
.OptionValueType
== OptionValueType
.Required
) {
958 if (p
.OptionValueType
== OptionValueType
.Optional
) {
959 Write (o
, ref written
, localizer ("["));
961 Write (o
, ref written
, localizer ("=" + GetArgumentName (0, p
.MaxValueCount
, p
.Description
)));
962 string sep
= p
.ValueSeparators
!= null && p
.ValueSeparators
.Length
> 0
963 ? p
.ValueSeparators
[0]
965 for (int c
= 1; c
< p
.MaxValueCount
; ++c
) {
966 Write (o
, ref written
, localizer (sep
+ GetArgumentName (c
, p
.MaxValueCount
, p
.Description
)));
968 if (p
.OptionValueType
== OptionValueType
.Optional
) {
969 Write (o
, ref written
, localizer ("]"));
975 static int GetNextOptionIndex (string[] names
, int i
)
977 while (i
< names
.Length
&& names
[i
] == "<>") {
983 static void Write (TextWriter o
, ref int n
, string s
)
989 private static string GetArgumentName (int index
, int maxIndex
, string description
)
991 if (description
== null)
992 return maxIndex
== 1 ? "VALUE" : "VALUE" + (index
+ 1);
995 nameStart
= new string[]{"{0:", "{"}
;
997 nameStart
= new string[]{"{" + index + ":"}
;
998 for (int i
= 0; i
< nameStart
.Length
; ++i
) {
1001 start
= description
.IndexOf (nameStart
[i
], j
);
1002 } while (start
>= 0 && j
!= 0 ? description
[j
++ - 1] == '{' : false);
1005 int end
= description
.IndexOf ("}", start
);
1008 return description
.Substring (start
+ nameStart
[i
].Length
, end
- start
- nameStart
[i
].Length
);
1010 return maxIndex
== 1 ? "VALUE" : "VALUE" + (index
+ 1);
1013 private static string GetDescription (string description
)
1015 if (description
== null)
1016 return string.Empty
;
1017 StringBuilder sb
= new StringBuilder (description
.Length
);
1019 for (int i
= 0; i
< description
.Length
; ++i
) {
1020 switch (description
[i
]) {
1031 if ((i
+1) == description
.Length
|| description
[i
+1] != '}')
1032 throw new InvalidOperationException ("Invalid option description: " + description
);
1037 sb
.Append (description
.Substring (start
, i
- start
));
1048 sb
.Append (description
[i
]);
1052 return sb
.ToString ();
1055 private static List
<string> GetLines (string description
)
1057 List
<string> lines
= new List
<string> ();
1058 if (string.IsNullOrEmpty (description
)) {
1059 lines
.Add (string.Empty
);
1062 int length
= 80 - OptionWidth
- 2;
1065 end
= GetLineEnd (start
, length
, description
);
1067 if (end
< description
.Length
) {
1068 char c
= description
[end
];
1069 if (c
== '-' || (char.IsWhiteSpace (c
) && c
!= '\n'))
1071 else if (c
!= '\n') {
1076 lines
.Add (description
.Substring (start
, end
- start
));
1078 lines
[lines
.Count
-1] += "-";
1081 if (start
< description
.Length
&& description
[start
] == '\n')
1083 } while (end
< description
.Length
);
1087 private static int GetLineEnd (int start
, int length
, string description
)
1089 int end
= System
.Math
.Min (start
+ length
, description
.Length
);
1091 for (int i
= start
; i
< end
; ++i
) {
1092 switch (description
[i
]) {
1106 if (sep
== -1 || end
== description
.Length
)