4 // Author: Rafael Teixeira (rafaelteixeirabr@hotmail.com)
6 // (C) 2002 Rafael Teixeira
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 namespace Mono
.GetOptions
33 using System
.Collections
;
34 using System
.Reflection
;
36 public enum WhatToDoNext
42 internal enum OptionProcessingResult
46 OptionConsumedParameter
49 internal class OptionDetails
: IComparable
51 public string ShortForm
;
52 public string LongForm
;
53 public string AlternateForm
;
54 public string ShortDescription
;
55 public bool NeedsParameter
;
56 public int MaxOccurs
; // negative means there is no limit
58 public bool BooleanOption
;
59 public Options OptionBundle
;
60 public MemberInfo MemberInfo
;
61 public ArrayList Values
;
62 public Type ParameterType
;
63 public string paramName
= null;
64 public bool VBCStyleBoolean
;
65 public bool SecondLevelHelp
;
68 public OptionDetails NextAlternate
= null;
70 private string ExtractParamName(string shortDescription
)
72 int whereBegins
= shortDescription
.IndexOf("{");
77 int whereEnds
= shortDescription
.IndexOf("}");
78 if (whereEnds
< whereBegins
)
79 whereEnds
= shortDescription
.Length
+ 1;
81 paramName
= shortDescription
.Substring(whereBegins
+ 1, whereEnds
- whereBegins
- 1);
83 shortDescription
.Substring(0, whereBegins
) +
85 shortDescription
.Substring(whereEnds
+ 1);
87 return shortDescription
;
90 public string ParamName
92 get { return paramName; }
95 private bool verboseParsing
97 get { return OptionBundle.VerboseParsingOfOptions || OptionBundle.DebuggingOfOptions; }
100 // private bool debugOptions { get { return OptionBundle.DebuggingOfOptions; } }
102 private OptionsParsingMode parsingMode
104 get { return OptionBundle.ParsingMode; }
107 private bool useGNUFormat
109 get { return (parsingMode & OptionsParsingMode.GNU_DoubleDash) == OptionsParsingMode.GNU_DoubleDash; }
112 private bool dontSplitOnCommas
114 get { return OptionBundle.DontSplitOnCommas; }
117 private string linuxLongPrefix
119 get { return (useGNUFormat ? "--" : "-"); }
122 public string DefaultForm
126 string shortPrefix
= "-";
127 string longPrefix
= linuxLongPrefix
;
128 if (parsingMode
== OptionsParsingMode
.Windows
)
133 if (ShortForm
!= string.Empty
)
134 return shortPrefix
+ ShortForm
;
136 return longPrefix
+ LongForm
;
140 private string optionHelp
= null;
142 public override string ToString()
144 if (optionHelp
== null)
148 bool hasLongForm
= (LongForm
!= null && LongForm
!= string.Empty
);
149 if (OptionBundle
.ParsingMode
== OptionsParsingMode
.Windows
)
157 longPrefix
= linuxLongPrefix
;
160 optionHelp
+= (ShortForm
!= string.Empty
) ? shortPrefix
+ ShortForm
+ " " : " ";
161 optionHelp
+= hasLongForm
? longPrefix
+ LongForm
: "";
166 optionHelp
+= ParamName
;
168 else if (BooleanOption
&& VBCStyleBoolean
)
170 optionHelp
+= "[+|-]";
173 if (AlternateForm
!= string.Empty
&& AlternateForm
!= null)
174 optionHelp
+= "Also " + shortPrefix
+ AlternateForm
+ (NeedsParameter
? (":" + ParamName
) : "") + ". ";
175 optionHelp
+= ShortDescription
;
180 private static Type
TypeOfMember(MemberInfo memberInfo
)
182 if ((memberInfo
.MemberType
== MemberTypes
.Field
&& memberInfo
is FieldInfo
))
183 return ((FieldInfo
) memberInfo
).FieldType
;
185 if ((memberInfo
.MemberType
== MemberTypes
.Property
&& memberInfo
is PropertyInfo
))
186 return ((PropertyInfo
) memberInfo
).PropertyType
;
188 if ((memberInfo
.MemberType
== MemberTypes
.Method
&& memberInfo
is MethodInfo
))
190 if (((MethodInfo
) memberInfo
).ReturnType
.FullName
!= typeof(WhatToDoNext
).FullName
)
191 throw new NotSupportedException("Option method must return '" + typeof(WhatToDoNext
).FullName
+ "'");
193 ParameterInfo
[] parameters
= ((MethodInfo
) memberInfo
).GetParameters();
194 if ((parameters
== null) || (parameters
.Length
== 0))
197 return parameters
[0].ParameterType
;
200 throw new NotSupportedException("'" + memberInfo
.MemberType
+ "' memberType is not supported");
203 public OptionDetails(MemberInfo memberInfo
, OptionAttribute option
, Options optionBundle
)
205 ShortForm
= ("" + option
.ShortForm
).Trim();
206 if (option
.LongForm
== null)
207 LongForm
= string.Empty
;
209 LongForm
= (option
.LongForm
== string.Empty
) ? memberInfo
.Name
: option
.LongForm
;
210 AlternateForm
= option
.AlternateForm
;
211 ShortDescription
= ExtractParamName(option
.ShortDescription
);
213 OptionBundle
= optionBundle
;
214 BooleanOption
= false;
215 MemberInfo
= memberInfo
;
216 NeedsParameter
= false;
219 VBCStyleBoolean
= option
.VBCStyleBoolean
;
220 SecondLevelHelp
= option
.SecondLevelHelp
;
221 Hidden
= false; // TODO: check other attributes
223 ParameterType
= TypeOfMember(memberInfo
);
225 if (ParameterType
!= null)
227 if (ParameterType
.FullName
!= "System.Boolean")
229 if (LongForm
.IndexOf(':') >= 0)
230 throw new InvalidOperationException(
231 "Options with an embedded colon (':') in their visible name must be boolean!!! [" +
232 MemberInfo
.ToString() + " isn't]");
234 NeedsParameter
= true;
236 if (option
.MaxOccurs
!= 1)
238 if (ParameterType
.IsArray
)
240 Values
= new ArrayList();
241 MaxOccurs
= option
.MaxOccurs
;
245 if (MemberInfo
is MethodInfo
|| MemberInfo
is PropertyInfo
)
246 MaxOccurs
= option
.MaxOccurs
;
248 throw new InvalidOperationException("MaxOccurs set to non default value (" + option
.MaxOccurs
+ ") for a [" +
249 MemberInfo
.ToString() + "] option");
255 BooleanOption
= true;
256 if (option
.MaxOccurs
!= 1)
258 if (MemberInfo
is MethodInfo
|| MemberInfo
is PropertyInfo
)
259 MaxOccurs
= option
.MaxOccurs
;
261 throw new InvalidOperationException("MaxOccurs set to non default value (" + option
.MaxOccurs
+ ") for a [" +
262 MemberInfo
.ToString() + "] option");
274 string ShortID
= ShortForm
.ToUpper();
275 if (ShortID
== string.Empty
)
277 return ShortID
+ " " + LongForm
;
280 return LongForm
+ " " + ShortForm
;
284 int IComparable
.CompareTo(object other
)
286 return Key
.CompareTo(((OptionDetails
) other
).Key
);
289 public void TransferValues()
293 if (MemberInfo
is FieldInfo
)
295 ((FieldInfo
) MemberInfo
).SetValue(OptionBundle
, Values
.ToArray(ParameterType
.GetElementType()));
299 if (MemberInfo
is PropertyInfo
)
301 ((PropertyInfo
) MemberInfo
).SetValue(OptionBundle
, Values
.ToArray(ParameterType
.GetElementType()), null);
307 ((MethodInfo
) MemberInfo
).Invoke(OptionBundle
, new object[] {Values.ToArray(ParameterType.GetElementType())}
) ==
308 WhatToDoNext
.AbandonProgram
)
313 private int HowManyBeforeExceedingMaxOccurs(int howMany
)
315 if (MaxOccurs
> 0 && (Occurs
+ howMany
) > MaxOccurs
)
317 Console
.Error
.WriteLine("Option " + LongForm
+ " can be used at most " + MaxOccurs
+ " times. Ignoring extras...");
318 howMany
= MaxOccurs
- Occurs
;
324 private bool AddingOneMoreExceedsMaxOccurs
326 get { return HowManyBeforeExceedingMaxOccurs(1) < 1; }
329 private void DoIt(bool setValue
)
331 if (AddingOneMoreExceedsMaxOccurs
)
335 Console
.WriteLine("<{0}> set to [{1}]", LongForm
, setValue
);
337 if (MemberInfo
is FieldInfo
)
339 ((FieldInfo
) MemberInfo
).SetValue(OptionBundle
, setValue
);
342 if (MemberInfo
is PropertyInfo
)
344 ((PropertyInfo
) MemberInfo
).SetValue(OptionBundle
, setValue
, null);
347 if ((WhatToDoNext
) ((MethodInfo
) MemberInfo
).Invoke(OptionBundle
, null) == WhatToDoNext
.AbandonProgram
)
351 private void DoIt(string parameterValue
)
353 if (parameterValue
== null)
356 string[] parameterValues
;
358 if (dontSplitOnCommas
|| MaxOccurs
== 1)
359 parameterValues
= new string[] {parameterValue}
;
361 parameterValues
= parameterValue
.Split(',');
363 int waitingToBeProcessed
= HowManyBeforeExceedingMaxOccurs(parameterValues
.Length
);
365 foreach(string parameter
in parameterValues
)
367 if (waitingToBeProcessed
-- <= 0)
370 object convertedParameter
= null;
373 Console
.WriteLine("<" + LongForm
+ "> set to [" + parameter
+ "]");
375 if (Values
!= null && parameter
!= null)
379 convertedParameter
= Convert
.ChangeType(parameter
, ParameterType
.GetElementType());
384 String
.Format("The value '{0}' is not convertible to the appropriate type '{1}' for the {2} option", parameter
,
385 ParameterType
.GetElementType().Name
, DefaultForm
));
387 Values
.Add(convertedParameter
);
391 if (parameter
!= null)
395 convertedParameter
= Convert
.ChangeType(parameter
, ParameterType
);
400 String
.Format("The value '{0}' is not convertible to the appropriate type '{1}' for the {2} option", parameter
,
401 ParameterType
.Name
, DefaultForm
));
406 if (MemberInfo
is FieldInfo
)
408 ((FieldInfo
) MemberInfo
).SetValue(OptionBundle
, convertedParameter
);
412 if (MemberInfo
is PropertyInfo
)
414 ((PropertyInfo
) MemberInfo
).SetValue(OptionBundle
, convertedParameter
, null);
418 if ((WhatToDoNext
) ((MethodInfo
) MemberInfo
).Invoke(OptionBundle
, new object[] {convertedParameter}
) ==
419 WhatToDoNext
.AbandonProgram
)
424 private bool IsThisOption(string arg
)
426 if (arg
!= null && arg
!= string.Empty
)
428 arg
= arg
.TrimStart('-', '/');
430 arg
= arg
.TrimEnd('-', '+');
431 return (arg
== ShortForm
|| arg
== LongForm
|| arg
== AlternateForm
);
436 public static void LinkAlternatesInsideList(ArrayList list
)
438 Hashtable baseForms
= new Hashtable(list
.Count
);
439 foreach(OptionDetails option
in list
)
441 if (option
.LongForm
!= null && option
.LongForm
.Trim().Length
> 0)
443 string[] parts
= option
.LongForm
.Split(':');
444 if (parts
.Length
< 2)
446 baseForms
.Add(option
.LongForm
, option
);
450 OptionDetails baseForm
= (OptionDetails
) baseForms
[parts
[0]];
451 if (baseForm
!= null)
453 // simple linked list
454 option
.NextAlternate
= baseForm
.NextAlternate
;
455 baseForm
.NextAlternate
= option
;
462 private bool IsAlternate(string compoundArg
)
464 OptionDetails next
= NextAlternate
;
467 if (next
.IsThisOption(compoundArg
))
469 next
= next
.NextAlternate
;
474 public OptionProcessingResult
ProcessArgument(string arg
, string nextArg
)
476 if (IsAlternate(arg
+ ":" + nextArg
))
477 return OptionProcessingResult
.NotThisOption
;
479 if (IsThisOption(arg
))
483 if (VBCStyleBoolean
&& arg
.EndsWith("-"))
487 return OptionProcessingResult
.OptionAlone
;
492 return OptionProcessingResult
.OptionConsumedParameter
;
496 if (IsThisOption(arg
+ ":" + nextArg
))
499 return OptionProcessingResult
.OptionConsumedParameter
;
502 return OptionProcessingResult
.NotThisOption
;