New test.
[mono.git] / mcs / class / Mono.GetOptions / Mono.GetOptions / OptionDetails.cs
1 //
2 // OptionDetails.cs
3 //
4 // Author: Rafael Teixeira (rafaelteixeirabr@hotmail.com)
5 //
6 // (C) 2002 Rafael Teixeira
7 //
8
9 //
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:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
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.
28 //
29
30 using System;
31 using System.Collections;
32 using System.IO;
33 using System.Reflection;
34
35 namespace Mono.GetOptions
36 {
37         public enum WhatToDoNext
38         {
39                 AbandonProgram,
40                 GoAhead
41         }
42         
43         internal enum OptionProcessingResult
44         {
45                 NotThisOption,
46                 OptionAlone,
47                 OptionConsumedParameter
48         }
49
50         internal class OptionDetails : IComparable
51         {
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
58                 public int Occurs;
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;
67                 public bool Hidden;
68                 
69                 public OptionDetails NextAlternate  = null;
70
71                 private string ExtractParamName(string shortDescription)
72                 {
73                         int whereBegins = shortDescription.IndexOf("{");
74                         if (whereBegins < 0)
75                                 paramName = "PARAM";
76                         else {
77                                 int whereEnds = shortDescription.IndexOf("}");
78                                 if (whereEnds < whereBegins)
79                                         whereEnds = shortDescription.Length+1;
80                                                 
81                                 paramName = shortDescription.Substring(whereBegins + 1, whereEnds - whereBegins - 1);
82                                 shortDescription = 
83                                         shortDescription.Substring(0, whereBegins) + 
84                                         paramName +
85                                         shortDescription.Substring(whereEnds + 1);
86                         }
87                         return shortDescription;
88                 }
89
90                 public string ParamName { get { return paramName; } }
91                                 
92                 private bool verboseParsing { get { return OptionBundle.VerboseParsingOfOptions || OptionBundle.DebuggingOfOptions; } }
93
94 //              private bool debugOptions { get { return OptionBundle.DebuggingOfOptions; } }
95
96                 private OptionsParsingMode parsingMode { get { return OptionBundle.ParsingMode; } } 
97                 
98                 private bool useGNUFormat { get { return (parsingMode & OptionsParsingMode.GNU_DoubleDash) == OptionsParsingMode.GNU_DoubleDash; } } 
99                 
100                 private bool dontSplitOnCommas { get { return OptionBundle.DontSplitOnCommas; } } 
101
102                 private string linuxLongPrefix {
103                         get { 
104                                 return (useGNUFormat? "--":"-"); 
105                         } 
106                 }
107                 
108                 public string DefaultForm
109                 {
110                         get {
111                                 string shortPrefix = "-";
112                                 string longPrefix = linuxLongPrefix;
113                                 if (parsingMode == OptionsParsingMode.Windows) {
114                                         shortPrefix = "/";
115                                         longPrefix = "/";
116                                 }
117                                 if (this.ShortForm != string.Empty)
118                                         return shortPrefix+this.ShortForm;
119                                 else
120                                         return longPrefix+this.LongForm;
121                         }
122                 }
123
124                 private string optionHelp = null;
125                 
126                 public override string ToString()
127                 {
128                         if      (optionHelp == null)
129                         {
130                                 string shortPrefix;
131                                 string longPrefix;
132                                 bool hasLongForm = (this.LongForm != null && this.LongForm != string.Empty);
133                                 if (this.OptionBundle.ParsingMode == OptionsParsingMode.Windows) {
134                                         shortPrefix = "/";
135                                         longPrefix = "/";
136                                 } else {
137                                         shortPrefix = "-";
138                                         longPrefix = linuxLongPrefix;
139                                 }
140                                 optionHelp = "  ";
141                                 optionHelp += (this.ShortForm != string.Empty) ? shortPrefix+this.ShortForm+" " : "   ";
142                                 optionHelp += hasLongForm ? longPrefix+this.LongForm : "";
143                                 if (NeedsParameter)     {
144                                         if (hasLongForm)
145                                                 optionHelp += ":"; 
146                                         optionHelp += ParamName; 
147                                 } else if (BooleanOption && VBCStyleBoolean) {
148                                         optionHelp += "[+|-]";
149                                 }
150                                 optionHelp += "\t";
151                                 if (this.AlternateForm != string.Empty && this.AlternateForm != null)
152                                         optionHelp += "Also "+ shortPrefix + this.AlternateForm + (NeedsParameter?(":"+ParamName):"") +". ";
153                                 optionHelp += this.ShortDescription;
154                         }
155                         return optionHelp;
156                 }
157
158                 private static System.Type TypeOfMember(MemberInfo memberInfo)
159                 {
160                         if ((memberInfo.MemberType == MemberTypes.Field && memberInfo is FieldInfo))
161                                 return ((FieldInfo)memberInfo).FieldType;
162
163                         if ((memberInfo.MemberType == MemberTypes.Property && memberInfo is PropertyInfo))
164                                 return ((PropertyInfo)memberInfo).PropertyType;
165
166                         if ((memberInfo.MemberType == MemberTypes.Method && memberInfo is MethodInfo))
167                         {
168                                 if (((MethodInfo)memberInfo).ReturnType.FullName != typeof(WhatToDoNext).FullName)
169                                         throw new NotSupportedException("Option method must return '" + typeof(WhatToDoNext).FullName + "'");
170
171                                 ParameterInfo[] parameters = ((MethodInfo)memberInfo).GetParameters();
172                                 if ((parameters == null) || (parameters.Length == 0))
173                                         return null;
174                                 else
175                                         return parameters[0].ParameterType;
176                         }
177
178                         throw new NotSupportedException("'" + memberInfo.MemberType + "' memberType is not supported");
179                 }
180
181                 public OptionDetails(MemberInfo memberInfo, OptionAttribute option, Options optionBundle)
182                 {
183                         this.ShortForm = ("" + option.ShortForm).Trim();
184                         if (option.LongForm == null)
185                                 this.LongForm = string.Empty;
186                         else
187                                 this.LongForm = (option.LongForm == string.Empty)? memberInfo.Name:option.LongForm;
188                         this.AlternateForm = option.AlternateForm;
189                         this.ShortDescription = ExtractParamName(option.ShortDescription);
190                         this.Occurs = 0;
191                         this.OptionBundle = optionBundle; 
192                         this.BooleanOption = false;
193                         this.MemberInfo = memberInfo;
194                         this.NeedsParameter = false;
195                         this.Values = null;
196                         this.MaxOccurs = 1;
197                         this.VBCStyleBoolean = option.VBCStyleBoolean;
198                         this.SecondLevelHelp = option.SecondLevelHelp;
199                         this.Hidden = false; // TODO: check other attributes
200                         
201                         this.ParameterType = TypeOfMember(memberInfo);
202
203                         if (this.ParameterType != null)
204                         {
205                                 if (this.ParameterType.FullName != "System.Boolean")
206                                 {
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]");
210                                 
211                                         this.NeedsParameter = true;
212
213                                         if (option.MaxOccurs != 1)
214                                         {
215                                                 if (this.ParameterType.IsArray)
216                                                 {
217                                                         this.Values = new ArrayList();
218                                                         this.MaxOccurs = option.MaxOccurs;
219                                                 }
220                                                 else
221                                                 {
222                                                         if (this.MemberInfo is MethodInfo || this.MemberInfo is PropertyInfo)
223                                                                 this.MaxOccurs = option.MaxOccurs;
224                                                         else
225                                                                 throw new InvalidOperationException("MaxOccurs set to non default value (" + option.MaxOccurs + ") for a [" + 
226                                                                                         this.MemberInfo.ToString() + "] option");
227                                                 }
228                                         }
229                                 }
230                                 else
231                                 {
232                                         this.BooleanOption = true;
233                                         if (option.MaxOccurs != 1)
234                                         {                       
235                                                 if (this.MemberInfo is MethodInfo || this.MemberInfo is PropertyInfo)
236                                                         this.MaxOccurs = option.MaxOccurs;
237                                                 else
238                                                         throw new InvalidOperationException("MaxOccurs set to non default value (" + option.MaxOccurs + ") for a [" + 
239                                                                                 this.MemberInfo.ToString() + "] option");
240                                         }
241                                 }
242                         }
243                 }
244
245                 internal string Key
246                 {
247                         get { 
248                                 if (useGNUFormat) {                             
249                                         string ShortID = this.ShortForm.ToUpper();
250                                         if (ShortID == string.Empty)
251                                                 ShortID = "ZZ";
252                                         return  ShortID + " " + this.LongForm; 
253                                 } else
254                                         return this.LongForm + " " + this.ShortForm; 
255                         }
256                 }
257
258                 int IComparable.CompareTo(object other)
259                 {
260                         return Key.CompareTo(((OptionDetails)other).Key);
261                 }
262
263                 public void TransferValues()
264                 {
265                         if (Values != null)
266                         {
267                                 if (MemberInfo is FieldInfo)
268                                 {
269                                         ((FieldInfo)MemberInfo).SetValue(OptionBundle, Values.ToArray(ParameterType.GetElementType()));
270                                         return;
271                                 }
272
273                                 if (MemberInfo is PropertyInfo) 
274                                 {
275                                         ((PropertyInfo)MemberInfo).SetValue(OptionBundle, Values.ToArray(ParameterType.GetElementType()), null);
276                                         return;
277                                 }
278
279                                 if ((WhatToDoNext)((MethodInfo)MemberInfo).Invoke(OptionBundle, new object[] { Values.ToArray(ParameterType.GetElementType()) }) == WhatToDoNext.AbandonProgram)
280                                         System.Environment.Exit(1);
281                         }
282                 }
283
284                 private int HowManyBeforeExceedingMaxOccurs(int howMany)
285                 {
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;
289                         }
290                         Occurs += howMany;
291                         return howMany;
292                 }
293                 
294                 private bool AddingOneMoreExceedsMaxOccurs { get { return HowManyBeforeExceedingMaxOccurs(1) < 1; } }
295
296                 private void DoIt(bool setValue)
297                 {
298                         if (AddingOneMoreExceedsMaxOccurs) 
299                                 return;
300
301                         if (verboseParsing)
302                                 Console.WriteLine("<{0}> set to [{1}]", this.LongForm, setValue);
303
304                         if (MemberInfo is FieldInfo)
305                         {
306                                 ((FieldInfo)MemberInfo).SetValue(OptionBundle, setValue);
307                                 return;
308                         }
309                         if (MemberInfo is PropertyInfo)
310                         {
311                                 ((PropertyInfo)MemberInfo).SetValue(OptionBundle, setValue, null);
312                                 return;
313                         }
314                         if ((WhatToDoNext)((MethodInfo)MemberInfo).Invoke(OptionBundle, null) == WhatToDoNext.AbandonProgram)
315                                 System.Environment.Exit(1);
316                 }
317                 
318                 private void DoIt(string parameterValue)
319                 {
320                         if (parameterValue == null)
321                                 parameterValue = "";
322
323                         string[] parameterValues;
324                         
325                         if (dontSplitOnCommas || MaxOccurs == 1)
326                                 parameterValues = new string[] { parameterValue };
327                         else
328                                 parameterValues = parameterValue.Split(',');
329
330                         int waitingToBeProcessed = HowManyBeforeExceedingMaxOccurs(parameterValues.Length);
331
332                         foreach (string parameter in parameterValues)
333                         {
334                                 if (waitingToBeProcessed-- <= 0)
335                                         break;
336                                         
337                                 object convertedParameter = null;
338
339                                 if (verboseParsing)
340                                         Console.WriteLine("<" + this.LongForm + "> set to [" + parameter + "]");
341
342                                 if (Values != null && parameter != null) {
343                                         try {
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));                                           
347                                         }
348                                         Values.Add(convertedParameter);
349                                         continue;
350                                 }
351
352                                 if (parameter != null) {        
353                                         try {
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));                                                                                            
357                                                 continue;
358                                         }
359                                 }
360
361                                 if (MemberInfo is FieldInfo) {
362                                         ((FieldInfo)MemberInfo).SetValue(OptionBundle, convertedParameter);
363                                         continue;
364                                 }
365
366                                 if (MemberInfo is PropertyInfo) {
367                                         ((PropertyInfo)MemberInfo).SetValue(OptionBundle, convertedParameter, null);
368                                         continue;
369                                 }
370
371                                 if ((WhatToDoNext)((MethodInfo)MemberInfo).Invoke(OptionBundle, new object[] { convertedParameter }) == WhatToDoNext.AbandonProgram)
372                                         System.Environment.Exit(1);
373                         }
374                 }
375
376                 private bool IsThisOption(string arg)
377                 {
378                         if (arg != null && arg != string.Empty)
379                         {
380                                 arg = arg.TrimStart('-', '/');                  
381                                 if (VBCStyleBoolean)
382                                         arg = arg.TrimEnd('-', '+');    
383                                 return (arg == ShortForm || arg == LongForm || arg == AlternateForm);
384                         }
385                         return false;
386                 }
387
388                 public static void LinkAlternatesInsideList(ArrayList list)
389                 {
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);
396                                         } else {
397                                                 OptionDetails baseForm = (OptionDetails)baseForms[parts[0]];
398                                                 if (baseForm != null) {
399                                                         // simple linked list
400                                                         option.NextAlternate = baseForm.NextAlternate;
401                                                         baseForm.NextAlternate = option;
402                                                 }
403                                         }
404                                 }
405                         }
406                 }
407
408                 private bool IsAlternate(string compoundArg)
409                 {
410                         OptionDetails next = NextAlternate;
411                         while (next != null) {
412                                 if (next.IsThisOption(compoundArg))
413                                         return true;
414                                 next = next.NextAlternate;
415                         }
416                         return false;
417                 }
418
419                 public OptionProcessingResult ProcessArgument(string arg, string nextArg)
420                 {
421                         if (IsAlternate(arg + ":" + nextArg))
422                                 return OptionProcessingResult.NotThisOption;
423                                 
424                         if (IsThisOption(arg))
425                         {
426                                 if (!NeedsParameter)
427                                 {
428                                         if (VBCStyleBoolean && arg.EndsWith("-"))
429                                                 DoIt(false);
430                                         else
431                                                 DoIt(true);
432                                         return OptionProcessingResult.OptionAlone;
433                                 }
434                                 else
435                                 {
436                                         DoIt(nextArg);
437                                         return OptionProcessingResult.OptionConsumedParameter;
438                                 }
439                         }
440
441                         if (IsThisOption(arg + ":" + nextArg))
442                         {
443                                 DoIt(true);
444                                 return OptionProcessingResult.OptionConsumedParameter;
445                         }
446
447                         return OptionProcessingResult.NotThisOption;
448                 }
449         }
450 }