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.
31 using System.Collections;
33 using System.Reflection;
35 namespace Mono.GetOptions
37 public enum WhatToDoNext
43 internal enum OptionProcessingResult
47 OptionConsumedParameter
50 internal class OptionDetails : IComparable
52 public string ShortForm;
53 public string LongForm;
54 public string AlternateForm;
55 public string ShortDescription;
56 public bool NeedsParameter;
57 public int MaxOccurs; // negative means there is no limit
59 public bool BooleanOption;
60 public Options OptionBundle;
61 public MemberInfo MemberInfo;
62 public ArrayList Values;
63 public System.Type ParameterType;
64 public string paramName = null;
65 public bool VBCStyleBoolean;
66 public bool SecondLevelHelp;
69 public OptionDetails NextAlternate = null;
71 private string ExtractParamName(string shortDescription)
73 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 { get { return paramName; } }
92 private bool verboseParsing { get { return OptionBundle.VerboseParsingOfOptions || OptionBundle.DebuggingOfOptions; } }
94 // private bool debugOptions { get { return OptionBundle.DebuggingOfOptions; } }
96 private OptionsParsingMode parsingMode { get { return OptionBundle.ParsingMode; } }
98 private bool useGNUFormat { get { return (parsingMode & OptionsParsingMode.GNU_DoubleDash) == OptionsParsingMode.GNU_DoubleDash; } }
100 private bool dontSplitOnCommas { get { return OptionBundle.DontSplitOnCommas; } }
102 private string linuxLongPrefix {
104 return (useGNUFormat? "--":"-");
108 public string DefaultForm
111 string shortPrefix = "-";
112 string longPrefix = linuxLongPrefix;
113 if (parsingMode == OptionsParsingMode.Windows) {
117 if (this.ShortForm != string.Empty)
118 return shortPrefix+this.ShortForm;
120 return longPrefix+this.LongForm;
124 private string optionHelp = null;
126 public override string ToString()
128 if (optionHelp == null)
132 bool hasLongForm = (this.LongForm != null && this.LongForm != string.Empty);
133 if (this.OptionBundle.ParsingMode == OptionsParsingMode.Windows) {
138 longPrefix = linuxLongPrefix;
141 optionHelp += (this.ShortForm != string.Empty) ? shortPrefix+this.ShortForm+" " : " ";
142 optionHelp += hasLongForm ? longPrefix+this.LongForm : "";
143 if (NeedsParameter) {
146 optionHelp += ParamName;
147 } else if (BooleanOption && VBCStyleBoolean) {
148 optionHelp += "[+|-]";
151 if (this.AlternateForm != string.Empty && this.AlternateForm != null)
152 optionHelp += "Also "+ shortPrefix + this.AlternateForm + (NeedsParameter?(":"+ParamName):"") +". ";
153 optionHelp += this.ShortDescription;
158 private static System.Type TypeOfMember(MemberInfo memberInfo)
160 if ((memberInfo.MemberType == MemberTypes.Field && memberInfo is FieldInfo))
161 return ((FieldInfo)memberInfo).FieldType;
163 if ((memberInfo.MemberType == MemberTypes.Property && memberInfo is PropertyInfo))
164 return ((PropertyInfo)memberInfo).PropertyType;
166 if ((memberInfo.MemberType == MemberTypes.Method && memberInfo is MethodInfo))
168 if (((MethodInfo)memberInfo).ReturnType.FullName != typeof(WhatToDoNext).FullName)
169 throw new NotSupportedException("Option method must return '" + typeof(WhatToDoNext).FullName + "'");
171 ParameterInfo[] parameters = ((MethodInfo)memberInfo).GetParameters();
172 if ((parameters == null) || (parameters.Length == 0))
175 return parameters[0].ParameterType;
178 throw new NotSupportedException("'" + memberInfo.MemberType + "' memberType is not supported");
181 public OptionDetails(MemberInfo memberInfo, OptionAttribute option, Options optionBundle)
183 this.ShortForm = ("" + option.ShortForm).Trim();
184 if (option.LongForm == null)
185 this.LongForm = string.Empty;
187 this.LongForm = (option.LongForm == string.Empty)? memberInfo.Name:option.LongForm;
188 this.AlternateForm = option.AlternateForm;
189 this.ShortDescription = ExtractParamName(option.ShortDescription);
191 this.OptionBundle = optionBundle;
192 this.BooleanOption = false;
193 this.MemberInfo = memberInfo;
194 this.NeedsParameter = false;
197 this.VBCStyleBoolean = option.VBCStyleBoolean;
198 this.SecondLevelHelp = option.SecondLevelHelp;
199 this.Hidden = false; // TODO: check other attributes
201 this.ParameterType = TypeOfMember(memberInfo);
203 if (this.ParameterType != null)
205 if (this.ParameterType.FullName != "System.Boolean")
207 if (this.LongForm.IndexOf(':') >= 0)
208 throw new InvalidOperationException("Options with an embedded colon (':') in their visible name must be boolean!!! [" +
209 this.MemberInfo.ToString() + " isn't]");
211 this.NeedsParameter = true;
213 if (option.MaxOccurs != 1)
215 if (this.ParameterType.IsArray)
217 this.Values = new ArrayList();
218 this.MaxOccurs = option.MaxOccurs;
222 if (this.MemberInfo is MethodInfo || this.MemberInfo is PropertyInfo)
223 this.MaxOccurs = option.MaxOccurs;
225 throw new InvalidOperationException("MaxOccurs set to non default value (" + option.MaxOccurs + ") for a [" +
226 this.MemberInfo.ToString() + "] option");
232 this.BooleanOption = true;
233 if (option.MaxOccurs != 1)
235 if (this.MemberInfo is MethodInfo || this.MemberInfo is PropertyInfo)
236 this.MaxOccurs = option.MaxOccurs;
238 throw new InvalidOperationException("MaxOccurs set to non default value (" + option.MaxOccurs + ") for a [" +
239 this.MemberInfo.ToString() + "] option");
249 string ShortID = this.ShortForm.ToUpper();
250 if (ShortID == string.Empty)
252 return ShortID + " " + this.LongForm;
254 return this.LongForm + " " + this.ShortForm;
258 int IComparable.CompareTo(object other)
260 return Key.CompareTo(((OptionDetails)other).Key);
263 public void TransferValues()
267 if (MemberInfo is FieldInfo)
269 ((FieldInfo)MemberInfo).SetValue(OptionBundle, Values.ToArray(ParameterType.GetElementType()));
273 if (MemberInfo is PropertyInfo)
275 ((PropertyInfo)MemberInfo).SetValue(OptionBundle, Values.ToArray(ParameterType.GetElementType()), null);
279 if ((WhatToDoNext)((MethodInfo)MemberInfo).Invoke(OptionBundle, new object[] { Values.ToArray(ParameterType.GetElementType()) }) == WhatToDoNext.AbandonProgram)
280 System.Environment.Exit(1);
284 private int HowManyBeforeExceedingMaxOccurs(int howMany)
286 if (MaxOccurs > 0 && (Occurs + howMany) > MaxOccurs) {
287 System.Console.Error.WriteLine("Option " + LongForm + " can be used at most " + MaxOccurs + " times. Ignoring extras...");
288 howMany = MaxOccurs - Occurs;
294 private bool AddingOneMoreExceedsMaxOccurs { get { return HowManyBeforeExceedingMaxOccurs(1) < 1; } }
296 private void DoIt(bool setValue)
298 if (AddingOneMoreExceedsMaxOccurs)
302 Console.WriteLine("<{0}> set to [{1}]", this.LongForm, setValue);
304 if (MemberInfo is FieldInfo)
306 ((FieldInfo)MemberInfo).SetValue(OptionBundle, setValue);
309 if (MemberInfo is PropertyInfo)
311 ((PropertyInfo)MemberInfo).SetValue(OptionBundle, setValue, null);
314 if ((WhatToDoNext)((MethodInfo)MemberInfo).Invoke(OptionBundle, null) == WhatToDoNext.AbandonProgram)
315 System.Environment.Exit(1);
318 private void DoIt(string parameterValue)
320 if (parameterValue == null)
323 string[] parameterValues;
325 if (dontSplitOnCommas || MaxOccurs == 1)
326 parameterValues = new string[] { parameterValue };
328 parameterValues = parameterValue.Split(',');
330 int waitingToBeProcessed = HowManyBeforeExceedingMaxOccurs(parameterValues.Length);
332 foreach (string parameter in parameterValues)
334 if (waitingToBeProcessed-- <= 0)
337 object convertedParameter = null;
340 Console.WriteLine("<" + this.LongForm + "> set to [" + parameter + "]");
342 if (Values != null && parameter != null) {
344 convertedParameter = Convert.ChangeType(parameter, ParameterType.GetElementType());
345 } catch (Exception ex) {
346 Console.WriteLine(String.Format("The value '{0}' is not convertible to the appropriate type '{1}' for the {2} option", parameter, ParameterType.GetElementType().Name, DefaultForm));
348 Values.Add(convertedParameter);
352 if (parameter != null) {
354 convertedParameter = Convert.ChangeType(parameter, ParameterType);
355 } catch (Exception ex) {
356 Console.WriteLine(String.Format("The value '{0}' is not convertible to the appropriate type '{1}' for the {2} option", parameter, ParameterType.Name, DefaultForm));
361 if (MemberInfo is FieldInfo) {
362 ((FieldInfo)MemberInfo).SetValue(OptionBundle, convertedParameter);
366 if (MemberInfo is PropertyInfo) {
367 ((PropertyInfo)MemberInfo).SetValue(OptionBundle, convertedParameter, null);
371 if ((WhatToDoNext)((MethodInfo)MemberInfo).Invoke(OptionBundle, new object[] { convertedParameter }) == WhatToDoNext.AbandonProgram)
372 System.Environment.Exit(1);
376 private bool IsThisOption(string arg)
378 if (arg != null && arg != string.Empty)
380 arg = arg.TrimStart('-', '/');
382 arg = arg.TrimEnd('-', '+');
383 return (arg == ShortForm || arg == LongForm || arg == AlternateForm);
388 public static void LinkAlternatesInsideList(ArrayList list)
390 Hashtable baseForms = new Hashtable(list.Count);
391 foreach (OptionDetails option in list) {
392 if (option.LongForm != null && option.LongForm.Trim().Length > 0) {
393 string[] parts = option.LongForm.Split(':');
394 if (parts.Length < 2) {
395 baseForms.Add(option.LongForm, option);
397 OptionDetails baseForm = (OptionDetails)baseForms[parts[0]];
398 if (baseForm != null) {
399 // simple linked list
400 option.NextAlternate = baseForm.NextAlternate;
401 baseForm.NextAlternate = option;
408 private bool IsAlternate(string compoundArg)
410 OptionDetails next = NextAlternate;
411 while (next != null) {
412 if (next.IsThisOption(compoundArg))
414 next = next.NextAlternate;
419 public OptionProcessingResult ProcessArgument(string arg, string nextArg)
421 if (IsAlternate(arg + ":" + nextArg))
422 return OptionProcessingResult.NotThisOption;
424 if (IsThisOption(arg))
428 if (VBCStyleBoolean && arg.EndsWith("-"))
432 return OptionProcessingResult.OptionAlone;
437 return OptionProcessingResult.OptionConsumedParameter;
441 if (IsThisOption(arg + ":" + nextArg))
444 return OptionProcessingResult.OptionConsumedParameter;
447 return OptionProcessingResult.NotThisOption;