// File: CommandLineOptions.cs // // This is a re-usable component to be used when you // need to parse command-line options/parameters. // // Separates command line parameters from command line options. // Uses reflection to populate member variables the derived class with the values // of the options. // // An option can start with "-" or "--". On Windows systems, it can start with "/" as well. // // I define 3 types of "options": // 1. Boolean options (yes/no values), e.g: /r to recurse // 2. Value options, e.g: /loglevel=3 // 2. Parameters: standalone strings like file names // // An example to explain: // csc /nologo /t:exe myfile.cs // | | | // | | + parameter // | | // | + value option // | // + boolean option // // Please see a short description of the CommandLineOptions class // at http://codeblast.com/~gert/dotnet/sells.html // // Gert Lombard (gert@codeblast.com) // James Newkirk (jim@nunit.org) namespace Codeblast { using System; using System.Reflection; using System.Collections; using System.Text; // // The Attributes // [AttributeUsage(AttributeTargets.Field)] public class OptionAttribute : Attribute { protected object optValue; protected string optName; protected string description; public string Short { get { return optName; } set { optName = value; } } public object Value { get { return optValue; } set { optValue = value; } } public string Description { get { return description; } set { description = value; } } } // // The CommandLineOptions members // public abstract class CommandLineOptions { protected ArrayList parameters; protected bool isInvalid = false; private int optionCount; private ArrayList invalidArguments = new ArrayList(); private bool allowForwardSlash; public CommandLineOptions( string[] args ) : this( System.IO.Path.DirectorySeparatorChar != '/', args ) {} public CommandLineOptions( bool allowForwardSlash, string[] args ) { this.allowForwardSlash = allowForwardSlash; optionCount = Init( args ); } public IList InvalidArguments { get { return invalidArguments; } } public bool NoArgs { get { return ParameterCount == 0 && optionCount == 0; } } public bool AllowForwardSlash { get { return allowForwardSlash; } } public int Init(params string[] args) { int count = 0; int n = 0; while (n < args.Length) { int pos = IsOption(args[n]); if (pos > 0) { // It's an option: if (GetOption(args, ref n, pos)) count++; else InvalidOption(args[Math.Min(n, args.Length-1)]); } else { if (parameters == null) parameters = new ArrayList(); parameters.Add(args[n]); if ( !IsValidParameter(args[n]) ) InvalidOption( args[n] ); } n++; } return count; } // An option starts with "/", "-" or "--": protected virtual int IsOption(string opt) { char[] c = null; if (opt.Length < 2) { return 0; } else if (opt.Length > 2) { c = opt.ToCharArray(0, 3); if (c[0] == '-' && c[1] == '-' && IsOptionNameChar(c[2])) return 2; } else { c = opt.ToCharArray(0, 2); } if ((c[0] == '-' || c[0] == '/' && AllowForwardSlash) && IsOptionNameChar(c[1])) return 1; return 0; } protected virtual bool IsOptionNameChar(char c) { return Char.IsLetterOrDigit(c) || c == '?'; } protected virtual void InvalidOption(string name) { invalidArguments.Add( name ); isInvalid = true; } protected virtual bool IsValidParameter(string param) { return true; } protected virtual bool MatchShortName(FieldInfo field, string name) { object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true); foreach (OptionAttribute att in atts) { if (string.Compare(att.Short, name, true) == 0) return true; } return false; } protected virtual FieldInfo GetMemberField(string name) { Type t = this.GetType(); FieldInfo[] fields = t.GetFields(BindingFlags.Instance|BindingFlags.Public); foreach (FieldInfo field in fields) { if (string.Compare(field.Name, name, true) == 0) return field; if (MatchShortName(field, name)) return field; } return null; } protected virtual object GetOptionValue(FieldInfo field) { object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true); if (atts.Length > 0) { OptionAttribute att = (OptionAttribute)atts[0]; return att.Value; } return null; } protected virtual bool GetOption(string[] args, ref int index, int pos) { try { object cmdLineVal = null; string opt = args[index].Substring(pos, args[index].Length-pos); SplitOptionAndValue(ref opt, ref cmdLineVal); FieldInfo field = GetMemberField(opt); if (field != null) { object value = GetOptionValue(field); if (value == null) { if (field.FieldType == typeof(bool)) value = true; // default for bool values is true else if(field.FieldType == typeof(string)) { value = cmdLineVal != null ? cmdLineVal : args[++index]; field.SetValue(this, Convert.ChangeType(value, field.FieldType)); string stringValue = (string)value; if(stringValue == null || stringValue.Length == 0) return false; return true; } else if(field.FieldType.IsEnum) value = Enum.Parse( field.FieldType, (string)cmdLineVal, true ); else value = cmdLineVal != null ? cmdLineVal : args[++index]; } field.SetValue(this, Convert.ChangeType(value, field.FieldType)); return true; } } catch (Exception) { // Ignore exceptions like type conversion errors. } return false; } protected virtual void SplitOptionAndValue(ref string opt, ref object val) { // Look for ":" or "=" separator in the option: int pos = opt.IndexOfAny( new char[] { ':', '=' } ); if (pos < 1) return; val = opt.Substring(pos+1); opt = opt.Substring(0, pos); } // Parameter accessor: public string this[int index] { get { if (parameters != null) return (string)parameters[index]; return null; } } public ArrayList Parameters { get { return parameters; } } public int ParameterCount { get { return parameters == null ? 0 : parameters.Count; } } public virtual void Help() { Console.WriteLine(GetHelpText()); } public virtual string GetHelpText() { StringBuilder helpText = new StringBuilder(); Type t = this.GetType(); FieldInfo[] fields = t.GetFields(BindingFlags.Instance|BindingFlags.Public); char optChar = allowForwardSlash ? '/' : '-'; foreach (FieldInfo field in fields) { object[] atts = field.GetCustomAttributes(typeof(OptionAttribute), true); if (atts.Length > 0) { OptionAttribute att = (OptionAttribute)atts[0]; if (att.Description != null) { string valType = ""; if (att.Value == null) { if (field.FieldType == typeof(float)) valType = "=FLOAT"; else if (field.FieldType == typeof(string)) valType = "=STR"; else if (field.FieldType != typeof(bool)) valType = "=X"; } helpText.AppendFormat("{0}{1,-20}\t{2}", optChar, field.Name+valType, att.Description); if (att.Short != null) helpText.AppendFormat(" (Short format: {0}{1}{2})", optChar, att.Short, valType); helpText.Append( Environment.NewLine ); } } } return helpText.ToString(); } } }