Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

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();
}
}
}