1 // File: CommandLineOptions.cs
3 // This is a re-usable component to be used when you
4 // need to parse command-line options/parameters.
6 // Separates command line parameters from command line options.
7 // Uses reflection to populate member variables the derived class with the values
10 // An option can start with "/", "-" or "--".
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
17 // An example to explain:
18 // csc /nologo /t:exe myfile.cs
26 // Please see a short description of the CommandLineOptions class
27 // at http://codeblast.com/~gert/dotnet/sells.html
29 // Gert Lombard (gert@codeblast.com)
30 // James Newkirk (jim@nunit.org)
35 using System.Reflection;
36 using System.Collections;
43 [AttributeUsage(AttributeTargets.Field)]
44 public class OptionAttribute : Attribute
46 protected object optValue;
47 protected string optName;
48 protected string description;
52 get { return optName; }
53 set { optName = value; }
58 get { return optValue; }
59 set { optValue = value; }
62 public string Description
64 get { return description; }
65 set { description = value; }
70 // The CommandLineOptions members
73 public abstract class CommandLineOptions
75 protected ArrayList parameters;
76 private int optionCount;
78 public CommandLineOptions(string[] args)
80 optionCount = Init(args);
87 return ParameterCount == 0 && optionCount == 0;
91 public int Init(string[] args)
95 while (n < args.Length)
97 int pos = IsOption(args[n]);
101 if (GetOption(args, ref n, pos))
104 InvalidOption(args[Math.Min(n, args.Length-1)]);
109 if (parameters == null) parameters = new ArrayList();
110 parameters.Add(args[n]);
117 // An option starts with "/", "-" or "--":
118 protected virtual int IsOption(string opt)
125 else if (opt.Length > 2)
127 c = opt.ToCharArray(0, 3);
128 if (c[0] == '-' && c[1] == '-' && IsOptionNameChar(c[2])) return 2;
132 c = opt.ToCharArray(0, 2);
134 if ((c[0] == '-' || c[0] == '/') && IsOptionNameChar(c[1])) return 1;
138 protected virtual bool IsOptionNameChar(char c)
140 return Char.IsLetterOrDigit(c) || c == '?';
143 protected abstract void InvalidOption(string name);
145 protected virtual bool MatchShortName(FieldInfo field, string name)
147 object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true);
148 foreach (OptionAttribute att in atts)
150 if (string.Compare(att.Short, name, true) == 0) return true;
155 protected virtual FieldInfo GetMemberField(string name)
157 Type t = this.GetType();
158 FieldInfo[] fields = t.GetFields(BindingFlags.Instance|BindingFlags.Public);
159 foreach (FieldInfo field in fields)
161 if (string.Compare(field.Name, name, true) == 0) return field;
162 if (MatchShortName(field, name)) return field;
167 protected virtual object GetOptionValue(FieldInfo field)
169 object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true);
172 OptionAttribute att = (OptionAttribute)atts[0];
178 protected virtual bool GetOption(string[] args, ref int index, int pos)
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);
188 object value = GetOptionValue(field);
191 if (field.FieldType == typeof(bool))
192 value = true; // default for bool values is true
193 else if(field.FieldType == typeof(string))
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;
202 value = cmdLineVal != null ? cmdLineVal : args[++index];
204 field.SetValue(this, Convert.ChangeType(value, field.FieldType));
210 // Ignore exceptions like type conversion errors.
215 protected virtual void SplitOptionAndValue(ref string opt, ref object val)
217 // Look for ":" or "=" separator in the option:
218 int pos = opt.IndexOfAny( new char[] { ':', '=' } );
221 val = opt.Substring(pos+1);
222 opt = opt.Substring(0, pos);
225 // Parameter accessor:
226 public string this[int index]
230 if (parameters != null) return (string)parameters[index];
235 public ArrayList Parameters
237 get { return parameters; }
240 public int ParameterCount
244 return parameters == null ? 0 : parameters.Count;
248 public virtual void Help()
250 Console.WriteLine(GetHelpText());
253 public virtual string GetHelpText()
255 StringBuilder helpText = new StringBuilder();
257 Type t = this.GetType();
258 FieldInfo[] fields = t.GetFields(BindingFlags.Instance|BindingFlags.Public);
259 foreach (FieldInfo field in fields)
261 object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true);
264 OptionAttribute att = (OptionAttribute)atts[0];
265 if (att.Description != null)
268 if (att.Value == null)
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";
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 );
282 return helpText.ToString();