// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace IncludeTool.Support { /// /// Attribute to markup fields which can be set by command line argument /// public class CommandLineOptionAttribute : Attribute { /// /// The argument name. Uses the field name if not set. /// public string Name; /// /// If set, marks that the argument is required. /// public bool IsRequired; /// /// For multi-value fields, indicates a separator character between arguments /// public char Separator; } /// /// Utility functions for parsing command line options /// static class CommandLine { /// /// Parse the options from the given command line into the given options, using CommandLineOption attributes. /// /// The arguments to parse /// Object to receive the parsed options /// True if all options were parsed, false otherwise public static bool Parse(string[] Args, object Options) { bool bResult = true; // Parse out all the arguments we can List RemainingArgs = new List(Args); foreach(FieldInfo Field in Options.GetType().GetFields()) { CommandLineOptionAttribute OptionAttribute = Field.GetCustomAttribute(); if(OptionAttribute != null) { string Name = OptionAttribute.Name ?? Field.Name; if (Field.FieldType == typeof(bool)) { string Flag = String.Format("-{0}", Name); if(RemainingArgs.RemoveAll(x => x.Equals(Flag, StringComparison.OrdinalIgnoreCase)) > 0) { Field.SetValue(Options, true); } } else { string Prefix = String.Format("-{0}=", Name); // Find all the values with this prefix List Values = new List(); for (int Idx = 0; Idx < RemainingArgs.Count; Idx++) { if (RemainingArgs[Idx].StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) { string Suffix = RemainingArgs[Idx].Substring(Prefix.Length); if (OptionAttribute.Separator != 0) { Values.AddRange(Suffix.Split(OptionAttribute.Separator)); } else { Values.Add(Suffix); } RemainingArgs.RemoveAt(Idx--); } } // Check if the field type implements ICollection<>. If so, we can take multiple values. Type CollectionType = null; foreach (Type InterfaceType in Field.FieldType.GetInterfaces()) { if (InterfaceType.IsGenericType && InterfaceType.GetGenericTypeDefinition() == typeof(ICollection<>)) { CollectionType = InterfaceType; break; } } // Assign the values to the field if(Values.Count == 0) { if(OptionAttribute.IsRequired) { Console.WriteLine("Missing parameter {0}...", Prefix); bResult = false; } } else { if (CollectionType == null) { if (Values.Count > 1) { Console.WriteLine("Multiple values specified for {0}...", Prefix); bResult = false; } else if(Field.FieldType.IsEnum) { try { object TypedValue = Enum.Parse(Field.FieldType, Values[0], true); Field.SetValue(Options, TypedValue); } catch(ArgumentException) { Console.WriteLine("Invalid argument for '{0}'. Expected {1}.", Name, String.Join("/", Enum.GetNames(Field.FieldType))); bResult = false; } } else { object TypedValue = Convert.ChangeType(Values[0], Field.FieldType); Field.SetValue(Options, TypedValue); } } else { foreach (string Value in Values) { object TypedValue = Convert.ChangeType(Value, CollectionType.GenericTypeArguments[0]); CollectionType.InvokeMember("Add", BindingFlags.InvokeMethod, null, Field.GetValue(Options), new object[] { TypedValue }); } } } } } } // Check there's nothing left over that we couldn't parse if (RemainingArgs.Count > 0) { foreach(string RemainingArg in RemainingArgs) { Console.WriteLine("Invalid argument: {0}", RemainingArg); } bResult = false; } return bResult; } } }