417 lines
11 KiB
C#
417 lines
11 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using AutomationTool;
|
|
|
|
namespace Gauntlet
|
|
{
|
|
|
|
public interface IAutoParamNotifiable
|
|
{
|
|
void ParametersWereApplied(string[] Params);
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// An attribute that can be used to apply commandline options to fields or properties.
|
|
///
|
|
/// Simply tag properties or fields with the CommandLineOption, a name, and a default value, then
|
|
/// call CommandLineOption.Apply(obj, args) where args is a list of -switches or -key=value pairs
|
|
///
|
|
/// The main constraint is that your object type must be convertable from a string
|
|
///
|
|
/// </summary>
|
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
|
public class AutoParam : System.Attribute
|
|
{
|
|
/// <summary>
|
|
/// Default value
|
|
/// </summary>
|
|
protected object Default;
|
|
|
|
/// <summary>
|
|
/// Names that can refer to this param
|
|
/// </summary>
|
|
public string[] OptionNames { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// Constructor that takes nothing. Param option should be -MemberName or -MemberName=value.
|
|
/// Members with no matching param will be left as-is.
|
|
/// </summary>
|
|
public AutoParam()
|
|
{
|
|
this.OptionNames = null;
|
|
this.Default = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor that takes an array of of potential argument names, e.g. {"build","builds"}
|
|
/// Members with no matching param will be left as-is.
|
|
/// </summary>
|
|
/// <param name="OptionNames"></param>
|
|
protected AutoParam(params string[] OptionNames)
|
|
{
|
|
this.OptionNames = OptionNames;
|
|
this.Default = null;
|
|
}
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
/// Constructor that takes a default argument to use if no param is specified. Param option should be -MemberName or -MemberName=value.
|
|
/// Members with no matching param will be set to 'Default'
|
|
/// </summary>
|
|
/// <param name="Default"></param>
|
|
public AutoParam(object Default)
|
|
{
|
|
this.OptionNames = null;
|
|
this.Default = Default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor that takes an array of of potential argument names, e.g. {"build","builds"}
|
|
/// Members with no matching param will be set to 'Default'
|
|
/// </summary>
|
|
/// <param name="Default"></param>
|
|
/// <param name="OptionNames"></param>
|
|
protected AutoParam(object Default, params string[] OptionNames)
|
|
{
|
|
this.OptionNames = OptionNames;
|
|
this.Default = Default;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Checks whether Args contains a -Param statement, if so returns true else
|
|
/// returns Default
|
|
/// </summary>
|
|
/// <param name="Param"></param>
|
|
/// <param name="Args"></param>
|
|
/// <returns></returns>
|
|
static protected bool SwitchExists(string Param, string[] Args)
|
|
{
|
|
foreach (string Arg in Args)
|
|
{
|
|
string StringArg = Arg;
|
|
|
|
if (StringArg.StartsWith("-"))
|
|
{
|
|
StringArg = Arg.Substring(1);
|
|
}
|
|
|
|
if (StringArg.ToString().Equals(Param, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks Args for a -param=value statement and either returns value or the
|
|
/// provided default
|
|
/// </summary>
|
|
/// <param name="Param"></param>
|
|
/// <param name="ParamType"></param>
|
|
/// <param name="Args"></param>
|
|
/// <returns></returns>
|
|
static protected object ParaseAndCoerceParam(string Param, Type ParamType, string[] Args)
|
|
{
|
|
if (!Param.EndsWith("="))
|
|
{
|
|
Param += "=";
|
|
}
|
|
foreach (string Arg in Args)
|
|
{
|
|
string StringArg = Arg;
|
|
|
|
if (StringArg.StartsWith("-"))
|
|
{
|
|
StringArg = Arg.Substring(1);
|
|
}
|
|
|
|
if (StringArg.StartsWith(Param, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
string StringVal = StringArg.Substring(Param.Length);
|
|
|
|
if (ParamType.IsEnum)
|
|
{
|
|
var AllValues = Enum.GetValues(ParamType).Cast<object>();
|
|
|
|
var Enums = AllValues.Where(P => string.Equals(StringVal, P.ToString(), StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (Enums.Count() == 0)
|
|
{
|
|
Log.Error("Could not convert param {0} to enum of type {1}", StringVal, ParamType);
|
|
}
|
|
|
|
return Enums.First();
|
|
}
|
|
else
|
|
{
|
|
return Convert.ChangeType(StringVal, ParamType);
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if this type is considered a simple primitive (there is IsClass in c# but no IsStruct :()
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
static bool IsSimple(Type type)
|
|
{
|
|
return type.IsPrimitive
|
|
|| type.IsEnum
|
|
|| type.Equals(typeof(string))
|
|
|| type.Equals(typeof(decimal));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call to process all CommandLineOption attributes on an objects members and set them based on the
|
|
/// provided argument list
|
|
/// </summary>
|
|
/// <param name="Obj"></param>
|
|
/// <param name="Args"></param>
|
|
/// <param name="ApplyDefaults"></param>
|
|
protected static void ApplyParamsAndDefaultsInternal(object Obj, string[] Args, bool ApplyDefaults)
|
|
{
|
|
// get all field and property members
|
|
var Fields = Obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
var Properties = Obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
var AllMembers = Fields.Cast<MemberInfo>().Concat(Properties);
|
|
|
|
foreach (var Member in AllMembers)
|
|
{
|
|
Type MemberType = null;
|
|
|
|
// Get the type of the member (note - this is not Member.Type!)
|
|
if (Member is PropertyInfo)
|
|
{
|
|
MemberType = ((PropertyInfo)Member).PropertyType;
|
|
}
|
|
else if (Member is FieldInfo)
|
|
{
|
|
MemberType = ((FieldInfo)Member).FieldType;
|
|
}
|
|
|
|
// Go through all attributes
|
|
foreach (object Attrib in Member.GetCustomAttributes(true))
|
|
{
|
|
if (Attrib is AutoParam)
|
|
{
|
|
// If this is a struct then we want to recurse
|
|
if (IsSimple(MemberType) == false)
|
|
{
|
|
object Value = null;
|
|
|
|
// Get the reference value
|
|
if (Member is PropertyInfo)
|
|
{
|
|
Value = ((PropertyInfo)Member).GetValue(Obj);
|
|
}
|
|
else if (Member is FieldInfo)
|
|
{
|
|
Value = ((FieldInfo)Member).GetValue(Obj);
|
|
}
|
|
|
|
// if null create a new one (e.g. a new instance of a struct);
|
|
if (Value == null)
|
|
{
|
|
try
|
|
{
|
|
Value = Activator.CreateInstance(MemberType);
|
|
}
|
|
catch
|
|
{
|
|
throw new AutomationException("Add a default constructor to the class {0}!", MemberType);
|
|
}
|
|
|
|
// Set the new object as the refernce
|
|
if (Member is PropertyInfo)
|
|
{
|
|
((PropertyInfo)Member).SetValue(Obj, Value);
|
|
}
|
|
else if (Member is FieldInfo)
|
|
{
|
|
((FieldInfo)Member).SetValue(Obj, Value);
|
|
}
|
|
}
|
|
|
|
// Recurse into this struct
|
|
ApplyParamsAndDefaultsInternal(Value, Args, ApplyDefaults);
|
|
}
|
|
else
|
|
{
|
|
AutoParam Opt = Attrib as AutoParam;
|
|
|
|
// if the attribute had names provided use them, else use the name of the variable
|
|
string[] ParamNames = (Opt.OptionNames != null && Opt.OptionNames.Length > 0) ? Opt.OptionNames : new string[] { Member.Name };
|
|
|
|
// save the default
|
|
object DefaultValue = Opt.Default;
|
|
object NewValue = null;
|
|
|
|
if (DefaultValue != null && DefaultValue.GetType() != MemberType)
|
|
{
|
|
Log.Warning("AutoParam default value for member {0} is type {1}, not {2}", Member.Name, DefaultValue.GetType(), MemberType);
|
|
}
|
|
|
|
// Go through all params used to refer to this member
|
|
foreach (string Name in ParamNames)
|
|
{
|
|
// if default is a bool then just check if the switch exists
|
|
if (MemberType == typeof(bool))
|
|
{
|
|
if (SwitchExists(Name, Args))
|
|
{
|
|
NewValue = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// for all other types try to parse out the value
|
|
NewValue = ParaseAndCoerceParam(Name, MemberType, Args);
|
|
}
|
|
|
|
// stop as soon as we find something
|
|
if (NewValue != null)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no value was found, use the default
|
|
if (NewValue != null || (ApplyDefaults && DefaultValue != null))
|
|
{
|
|
if (NewValue == null)
|
|
{
|
|
NewValue = DefaultValue;
|
|
}
|
|
|
|
if (MemberType.IsEnum)
|
|
{
|
|
if (NewValue.GetType() != MemberType)
|
|
{
|
|
Log.Warning("Default for member {0} is an enum of an incorrect type!", Member.Name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Force a conversion - need to do this even for default values incase someone specified a double for a float
|
|
NewValue = Convert.ChangeType(NewValue, MemberType);
|
|
}
|
|
|
|
// Set the value on the member
|
|
if (Member is PropertyInfo)
|
|
{
|
|
((PropertyInfo)Member).SetValue(Obj, NewValue);
|
|
}
|
|
else if (Member is FieldInfo)
|
|
{
|
|
((FieldInfo)Member).SetValue(Obj, NewValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
IAutoParamNotifiable ParamNotifable = Obj as IAutoParamNotifiable;
|
|
|
|
if (ParamNotifable != null)
|
|
{
|
|
ParamNotifable.ParametersWereApplied(Args);
|
|
}
|
|
}
|
|
|
|
public static void ApplyDefaults(object Obj)
|
|
{
|
|
ApplyParamsAndDefaultsInternal(Obj, new string[0], true);
|
|
}
|
|
|
|
public static void ApplyParams(object Obj, string[] Args)
|
|
{
|
|
ApplyParamsAndDefaultsInternal(Obj, Args, false);
|
|
}
|
|
|
|
public static void ApplyParamsAndDefaults(object Obj, string[] Args)
|
|
{
|
|
ApplyParamsAndDefaultsInternal(Obj, Args, true);
|
|
}
|
|
|
|
/*public static string[] GetParams(object Obj)
|
|
{
|
|
List<string> CopiedParams = new List<string>();
|
|
|
|
var Fields = Obj.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
|
|
|
|
var Properties = Obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
|
|
var AllMembers = Fields.Cast<MemberInfo>().Concat(Properties);
|
|
|
|
foreach (var Member in AllMembers)
|
|
{
|
|
foreach (object Attrib in Member.GetCustomAttributes(true))
|
|
{
|
|
if (Attrib is AutoParam)
|
|
{
|
|
AutoParam Opt = Attrib as AutoParam;
|
|
|
|
string ParamName = string.IsNullOrEmpty(Opt.Name) ? Member.Name : Opt.Name;
|
|
|
|
object ParamValue = null;
|
|
|
|
if (Member is PropertyInfo)
|
|
{
|
|
ParamValue = ((PropertyInfo)Member).GetValue(Obj);
|
|
}
|
|
else if (Member is FieldInfo)
|
|
{
|
|
ParamValue = ((FieldInfo)Member).GetValue(Obj);
|
|
}
|
|
|
|
if (Opt.Default.GetType() == typeof(bool))
|
|
{
|
|
if ((bool)ParamValue)
|
|
{
|
|
CopiedParams.Add(ParamName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CopiedParams.Add(string.Format("{0}={1}", ParamName, ParamValue));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return CopiedParams.ToArray();
|
|
}*/
|
|
}
|
|
|
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
|
public class AutoParamWithNames : AutoParam
|
|
{
|
|
public AutoParamWithNames(object Default, params string[] OptionNames) :
|
|
base(Default, OptionNames)
|
|
{
|
|
}
|
|
|
|
public AutoParamWithNames(params string[] OptionNames) :
|
|
base(OptionNames)
|
|
{
|
|
}
|
|
}
|
|
}
|