a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
317 lines
7.8 KiB
C#
317 lines
7.8 KiB
C#
// 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();
|
|
}
|
|
}
|
|
}
|