2006-08-15 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / nunit20 / util / CommandLineOptions.cs
1 // File: CommandLineOptions.cs
2 //
3 // This is a re-usable component to be used when you 
4 // need to parse command-line options/parameters.
5 //
6 // Separates command line parameters from command line options.
7 // Uses reflection to populate member variables the derived class with the values 
8 // of the options.
9 //
10 // An option can start with "/", "-" or "--".
11 //
12 // I define 3 types of "options":
13 //   1. Boolean options (yes/no values), e.g: /r to recurse
14 //   2. Value options, e.g: /loglevel=3
15 //   2. Parameters: standalone strings like file names
16 //
17 // An example to explain:
18 //   csc /nologo /t:exe myfile.cs
19 //       |       |      |
20 //       |       |      + parameter
21 //       |       |
22 //       |       + value option
23 //       |
24 //       + boolean option
25 //
26 // Please see a short description of the CommandLineOptions class
27 // at http://codeblast.com/~gert/dotnet/sells.html
28 // 
29 // Gert Lombard (gert@codeblast.com)
30 // James Newkirk (jim@nunit.org)
31
32 namespace Codeblast
33 {
34         using System;
35         using System.Reflection;
36         using System.Collections;
37         using System.Text;
38
39         //
40         // The Attributes
41         //
42
43         [AttributeUsage(AttributeTargets.Field)]
44         public class OptionAttribute : Attribute 
45         {
46                 protected object optValue;
47                 protected string optName;
48                 protected string description;
49
50                 public string Short 
51                 {
52                         get { return optName; }
53                         set { optName = value; }
54                 }
55
56                 public object Value
57                 {
58                         get { return optValue; }
59                         set { optValue = value; }
60                 }
61
62                 public string Description 
63                 {
64                         get { return description; }
65                         set { description = value; }
66                 }
67         }
68
69         //
70         // The CommandLineOptions members
71         //
72
73         public abstract class CommandLineOptions
74         {
75                 protected ArrayList parameters;
76                 private int optionCount;
77
78                 public CommandLineOptions(string[] args)
79                 {
80                         optionCount = Init(args);
81                 }
82
83                 public bool NoArgs
84                 {
85                         get 
86                         { 
87                                 return ParameterCount == 0 && optionCount == 0;
88                         }
89                 }
90
91                 public int Init(string[] args)
92                 {
93                         int count = 0;
94                         int n = 0;
95                         while (n < args.Length)
96                         {
97                                 int pos = IsOption(args[n]);
98                                 if (pos > 0)
99                                 {
100                                         // It's an option:
101                                         if (GetOption(args, ref n, pos))
102                                                 count++;
103                                         else
104                                                 InvalidOption(args[Math.Min(n, args.Length-1)]);
105                                 }
106                                 else
107                                 {
108                                         // It's a parameter:
109                                         if (parameters == null) parameters = new ArrayList();
110                                         parameters.Add(args[n]);
111                                 }
112                                 n++;
113                         }
114                         return count;
115                 }
116
117                 // An option starts with "/", "-" or "--":
118                 protected virtual int IsOption(string opt)
119                 {
120                         char[] c = null;
121                         if (opt.Length < 2) 
122                         {
123                                 return 0;
124                         }
125                         else if (opt.Length > 2)
126                         {
127                                 c = opt.ToCharArray(0, 3);
128                                 if (c[0] == '-' && c[1] == '-' && IsOptionNameChar(c[2])) return 2;
129                         }
130                         else
131                         {
132                                 c = opt.ToCharArray(0, 2);
133                         }
134                         if ((c[0] == '-' || c[0] == '/') && IsOptionNameChar(c[1])) return 1;
135                         return 0; 
136                 }
137
138                 protected virtual bool IsOptionNameChar(char c)
139                 {
140                         return Char.IsLetterOrDigit(c) || c == '?';
141                 }
142
143                 protected abstract void InvalidOption(string name);
144
145                 protected virtual bool MatchShortName(FieldInfo field, string name)
146                 {
147                         object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true);
148                         foreach (OptionAttribute att in atts)
149                         {
150                                 if (string.Compare(att.Short, name, true) == 0) return true;
151                         }
152                         return false;
153                 }
154
155                 protected virtual FieldInfo GetMemberField(string name)
156                 {
157                         Type t = this.GetType();
158                         FieldInfo[] fields = t.GetFields(BindingFlags.Instance|BindingFlags.Public);
159                         foreach (FieldInfo field in fields)
160                         {
161                                 if (string.Compare(field.Name, name, true) == 0) return field;
162                                 if (MatchShortName(field, name)) return field;
163                         }
164                         return null;
165                 }
166
167                 protected virtual object GetOptionValue(FieldInfo field)
168                 {
169                         object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true);
170                         if (atts.Length > 0)
171                         {
172                                 OptionAttribute att = (OptionAttribute)atts[0];
173                                 return att.Value;
174                         }
175                         return null;
176                 }
177
178                 protected virtual bool GetOption(string[] args, ref int index, int pos)
179                 {
180                         try
181                         {
182                                 object cmdLineVal = null;
183                                 string opt = args[index].Substring(pos, args[index].Length-pos);
184                                 SplitOptionAndValue(ref opt, ref cmdLineVal);
185                                 FieldInfo field = GetMemberField(opt);
186                                 if (field != null)
187                                 {
188                                         object value = GetOptionValue(field);
189                                         if (value == null)
190                                         {
191                                                 if (field.FieldType == typeof(bool))
192                                                         value = true; // default for bool values is true
193                                                 else if(field.FieldType == typeof(string))
194                                                 {
195                                                         value = cmdLineVal != null ? cmdLineVal : args[++index];
196                                                         field.SetValue(this, Convert.ChangeType(value, field.FieldType));
197                                                         string stringValue = (string)value;
198                                                         if(stringValue == null || stringValue.Length == 0) return false; 
199                                                         return true;
200                                                 }
201                                                 else
202                                                         value = cmdLineVal != null ? cmdLineVal : args[++index];
203                                         }
204                                         field.SetValue(this, Convert.ChangeType(value, field.FieldType));
205                                         return true;
206                                 }
207                         }
208                         catch (Exception) 
209                         {
210                                 // Ignore exceptions like type conversion errors.
211                         }
212                         return false;
213                 }
214
215                 protected virtual void SplitOptionAndValue(ref string opt, ref object val)
216                 {
217                         // Look for ":" or "=" separator in the option:
218                         int pos = opt.IndexOfAny( new char[] { ':', '=' } );
219                         if (pos < 1) return;
220
221                         val = opt.Substring(pos+1);
222                         opt = opt.Substring(0, pos);
223                 }
224
225                 // Parameter accessor:
226                 public string this[int index]
227                 {
228                         get
229                         {
230                                 if (parameters != null) return (string)parameters[index];
231                                 return null;
232                         }
233                 }
234
235                 public ArrayList Parameters
236                 {
237                         get { return parameters; }
238                 }
239
240                 public int ParameterCount
241                 {
242                         get
243                         {
244                                 return parameters == null ? 0 : parameters.Count;
245                         }
246                 }
247
248                 public virtual void Help()
249                 {
250                         Console.WriteLine(GetHelpText());
251                 }
252
253                 public virtual string GetHelpText()
254                 {
255                         StringBuilder helpText = new StringBuilder();
256
257                         Type t = this.GetType();
258                         FieldInfo[] fields = t.GetFields(BindingFlags.Instance|BindingFlags.Public);
259                         foreach (FieldInfo field in fields)
260                         {
261                                 object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true);
262                                 if (atts.Length > 0)
263                                 {
264                                         OptionAttribute att = (OptionAttribute)atts[0];
265                                         if (att.Description != null)
266                                         {
267                                                 string valType = "";
268                                                 if (att.Value == null)
269                                                 {
270                                                         if (field.FieldType == typeof(float)) valType = "=FLOAT";
271                                                         else if (field.FieldType == typeof(string)) valType = "=STR";
272                                                         else if (field.FieldType != typeof(bool)) valType = "=X";
273                                                 }
274
275                                                 helpText.AppendFormat("/{0,-20}{1}", field.Name+valType, att.Description);
276                                                 if (att.Short != null) 
277                                                         helpText.AppendFormat(" (Short format: /{0}{1})", att.Short, valType);
278                                                 helpText.Append( Environment.NewLine );
279                                         }
280                                 }
281                         }
282                         return helpText.ToString();
283                 }
284         }
285 }