////////////////////////////////////////////////////////////////////////////// // Command Line Argument Parser // ---------------------------- // // Author: peterhal@microsoft.com // // Shared Source License for Command Line Parser Library // // This license governs use of the accompanying software ('Software'), and your // use of the Software constitutes acceptance of this license. // // You may use the Software for any commercial or noncommercial purpose, // including distributing derivative works. // // In return, we simply require that you agree: // // 1. Not to remove any copyright or other notices from the Software. // 2. That if you distribute the Software in source code form you do so only // under this license (i.e. you must include a complete copy of this license // with your distribution), and if you distribute the Software solely in // object form you only do so under a license that complies with this // license. // 3. That the Software comes "as is", with no warranties. None whatsoever. // This means no express, implied or statutory warranty, including without // limitation, warranties of merchantability or fitness for a particular // purpose or any warranty of title or non-infringement. Also, you must pass // this disclaimer on whenever you distribute the Software or derivative // works. // 4. That no contributor to the Software will be liable for any of those types // of damages known as indirect, special, consequential, or incidental // related to the Software or this license, to the maximum extent the law // permits, no matter what legal theory it’s based on. Also, you must pass // this limitation of liability on whenever you distribute the Software or // derivative works. // 5. That if you sue anyone over patents that you think may apply to the // Software for a person's use of the Software, your license to the Software // ends automatically. // 6. That the patent rights, if any, granted in this license only apply to the // Software, not to any derivative works you make. // 7. That the Software is subject to U.S. export jurisdiction at the time it // is licensed to you, and it may be subject to additional export or import // laws in other places. You agree to comply with all such laws and // regulations that may apply to the Software after delivery of the software // to you. // 8. That if you are an agency of the U.S. Government, (i) Software provided // pursuant to a solicitation issued on or after December 1, 1995, is // provided with the commercial license rights set forth in this license, // and (ii) Software provided pursuant to a solicitation issued prior to // December 1, 1995, is provided with “Restricted Rights” as set forth in // FAR, 48 C.F.R. 52.227-14 (June 1987) or DFAR, 48 C.F.R. 252.227-7013 // (Oct 1988), as applicable. // 9. That your rights under this License end automatically if you breach it in // any way. // 10.That all rights not expressly granted to you in this license are reserved. // // Usage // ----- // // Parsing command line arguments to a console application is a common problem. // This library handles the common task of reading arguments from a command line // and filling in the values in a type. // // To use this library, define a class whose fields represent the data that your // application wants to receive from arguments on the command line. Then call // CommandLine.ParseArguments() to fill the object with the data // from the command line. Each field in the class defines a command line argument. // The type of the field is used to validate the data read from the command line. // The name of the field defines the name of the command line option. // // The parser can handle fields of the following types: // // - string // - int // - uint // - bool // - enum // - array of the above type // // For example, suppose you want to read in the argument list for wc (word count). // wc takes three optional boolean arguments: -l, -w, and -c and a list of files. // // You could parse these arguments using the following code: // // class WCArguments // { // public bool lines; // public bool words; // public bool chars; // public string[] files; // } // // class WC // { // static void Main(string[] args) // { // if (CommandLine.ParseArgumentsWithUsage(args, parsedArgs)) // { // // insert application code here // } // } // } // // So you could call this aplication with the following command line to count // lines in the foo and bar files: // // wc.exe /lines /files:foo /files:bar // // The program will display the following usage message when bad command line // arguments are used: // // wc.exe -x // // Unrecognized command line argument '-x' // /lines[+|-] short form /l // /words[+|-] short form /w // /chars[+|-] short form /c // /files: short form /f // @ Read response file for more options // // That was pretty easy. However, you realy want to omit the "/files:" for the // list of files. The details of field parsing can be controled using custom // attributes. The attributes which control parsing behaviour are: // // ArgumentAttribute // - controls short name, long name, required, allow duplicates, default value // and help text // DefaultArgumentAttribute // - allows omition of the "/name". // - This attribute is allowed on only one field in the argument class. // // So for the wc.exe program we want this: // // using System; // using Utilities; // // class WCArguments // { // [Argument(ArgumentType.AtMostOnce, HelpText="Count number of lines in the input text.")] // public bool lines; // [Argument(ArgumentType.AtMostOnce, HelpText="Count number of words in the input text.")] // public bool words; // [Argument(ArgumentType.AtMostOnce, HelpText="Count number of chars in the input text.")] // public bool chars; // [DefaultArgument(ArgumentType.MultipleUnique, HelpText="Input files to count.")] // public string[] files; // } // // class WC // { // static void Main(string[] args) // { // WCArguments parsedArgs = new WCArguments(); // if (CommandLine.ParseArgumentsWithUsage(args, parsedArgs)) // { // // insert application code here // } // } // } // // // // So now we have the command line we want: // // wc.exe /lines foo bar // // This will set lines to true and will set files to an array containing the // strings "foo" and "bar". // // The new usage message becomes: // // wc.exe -x // // Unrecognized command line argument '-x' // /lines[+|-] Count number of lines in the input text. (short form /l) // /words[+|-] Count number of words in the input text. (short form /w) // /chars[+|-] Count number of chars in the input text. (short form /c) // @ Read response file for more options // Input files to count. (short form /f) // // If you want more control over how error messages are reported, how /help is // dealt with, etc you can instantiate the CommandLine.Parser class. // // // // Cheers, // Peter Hallam // C# Compiler Developer // Microsoft Corp. // // // // // Release Notes // ------------- // // 10/02/2002 Initial Release // 10/14/2002 Bug Fix // 01/08/2003 Bug Fix in @ include files // 10/23/2004 Added user specified help text, formatting of help text to // screen width. Added ParseHelp for /?. // 11/23/2004 Added support for default values. ////////////////////////////////////////////////////////////////////////////// namespace CommandLine { using System; using System.Diagnostics; using System.Reflection; using System.Collections; using System.IO; using System.Text; using System.Runtime.InteropServices; /// /// Used to control parsing of command line arguments. /// [Flags] public enum ArgumentType { /// /// Indicates that this field is required. An error will be displayed /// if it is not present when parsing arguments. /// Required = 0x01, /// /// Only valid in conjunction with Multiple. /// Duplicate values will result in an error. /// Unique = 0x02, /// /// Inidicates that the argument may be specified more than once. /// Only valid if the argument is a collection /// Multiple = 0x04, /// /// The default type for non-collection arguments. /// The argument is not required, but an error will be reported if it is specified more than once. /// AtMostOnce = 0x00, /// /// For non-collection arguments, when the argument is specified more than /// once no error is reported and the value of the argument is the last /// value which occurs in the argument list. /// LastOccurenceWins = Multiple, /// /// The default type for collection arguments. /// The argument is permitted to occur multiple times, but duplicate /// values will cause an error to be reported. /// MultipleUnique = Multiple | Unique, } /// /// Allows control of command line parsing. /// Attach this attribute to instance fields of types used /// as the destination of command line argument parsing. /// [AttributeUsage(AttributeTargets.Field)] public class ArgumentAttribute : Attribute { /// /// Allows control of command line parsing. /// /// Specifies the error checking to be done on the argument. public ArgumentAttribute(ArgumentType type) { this.type = type; } /// /// The error checking to be done on the argument. /// public ArgumentType Type { get { return this.type; } } /// /// Returns true if the argument did not have an explicit short name specified. /// public bool DefaultShortName { get { return null == this.shortName; } } /// /// The short name of the argument. /// public string ShortName { get { return this.shortName; } set { this.shortName = value; } } /// /// Returns true if the argument did not have an explicit long name specified. /// public bool DefaultLongName { get { return null == this.longName; } } /// /// The long name of the argument. /// public string LongName { get { Debug.Assert(!this.DefaultLongName); return this.longName; } set { this.longName = value; } } /// /// The default value of the argument. /// public object DefaultValue { get { return this.defaultValue; } set { this.defaultValue = value; } } /// /// Returns true if the argument has a default value. /// public bool HasDefaultValue { get { return null != this.defaultValue; } } /// /// Returns true if the argument has help text specified. /// public bool HasHelpText { get { return null != this.helpText; } } /// /// The help text for the argument. /// public string HelpText { get { return this.helpText; } set { this.helpText = value; } } private string shortName; private string longName; private string helpText; private object defaultValue; private ArgumentType type; } /// /// Indicates that this argument is the default argument. /// '/' or '-' prefix only the argument value is specified. /// [AttributeUsage(AttributeTargets.Field)] public class DefaultArgumentAttribute : ArgumentAttribute { /// /// Indicates that this argument is the default argument. /// /// Specifies the error checking to be done on the argument. public DefaultArgumentAttribute(ArgumentType type) : base (type) { } } /// /// A delegate used in error reporting. /// public delegate void ErrorReporter(string message); /// /// Parser for command line arguments. /// /// The parser specification is infered from the instance fields of the object /// specified as the destination of the parse. /// Valid argument types are: int, uint, string, bool, enums /// Also argument types of Array of the above types are also valid. /// /// Error checking options can be controlled by adding a ArgumentAttribute /// to the instance fields of the destination object. /// /// At most one field may be marked with the DefaultArgumentAttribute /// indicating that arguments without a '-' or '/' prefix will be parsed as that argument. /// /// If not specified then the parser will infer default options for parsing each /// instance field. The default long name of the argument is the field name. The /// default short name is the first character of the long name. Long names and explicitly /// specified short names must be unique. Default short names will be used provided that /// the default short name does not conflict with a long name or an explicitly /// specified short name. /// /// Arguments which are array types are collection arguments. Collection /// arguments can be specified multiple times. /// public sealed class Parser { /// /// The System Defined new line string. /// public const string NewLine = "\r\n"; /// /// Don't ever call this. /// private Parser() { } /// /// Parses Command Line Arguments. Displays usage message to Console.Out /// if /?, /help or invalid arguments are encounterd. /// Errors are output on Console.Error. /// Use ArgumentAttributes to control parsing behaviour. /// /// The actual arguments. /// The resulting parsed arguments. /// true if no errors were detected. public static bool ParseArgumentsWithUsage(string [] arguments, object destination) { if (Parser.ParseHelp(arguments) || !Parser.ParseArguments(arguments, destination)) { // error encountered in arguments. Display usage message System.Console.Write(Parser.ArgumentsUsage(destination.GetType())); return false; } return true; } /// /// Parses Command Line Arguments. /// Errors are output on Console.Error. /// Use ArgumentAttributes to control parsing behaviour. /// /// The actual arguments. /// The resulting parsed arguments. /// true if no errors were detected. public static bool ParseArguments(string [] arguments, object destination) { return Parser.ParseArguments(arguments, destination, new ErrorReporter(Console.Error.WriteLine)); } /// /// Parses Command Line Arguments. /// Use ArgumentAttributes to control parsing behaviour. /// /// The actual arguments. /// The resulting parsed arguments. /// The destination for parse errors. /// true if no errors were detected. public static bool ParseArguments(string[] arguments, object destination, ErrorReporter reporter) { Parser parser = new Parser(destination.GetType(), reporter); return parser.Parse(arguments, destination); } private static void NullErrorReporter(string message) { } private class HelpArgument { [ArgumentAttribute(ArgumentType.AtMostOnce, ShortName="?")] public bool help = false; } /// /// Checks if a set of arguments asks for help. /// /// Args to check for help. /// Returns true if args contains /? or /help. public static bool ParseHelp(string[] args) { Parser helpParser = new Parser(typeof(HelpArgument), new ErrorReporter(NullErrorReporter)); HelpArgument helpArgument = new HelpArgument(); helpParser.Parse(args, helpArgument); return helpArgument.help; } /// /// Returns a Usage string for command line argument parsing. /// Use ArgumentAttributes to control parsing behaviour. /// Formats the output to the width of the current console window. /// /// The type of the arguments to display usage for. /// Printable string containing a user friendly description of command line arguments. public static string ArgumentsUsage(Type argumentType) { int screenWidth = Parser.GetConsoleWindowWidth(); if (screenWidth == 0) screenWidth = 80; return ArgumentsUsage(argumentType, screenWidth); } /// /// Returns a Usage string for command line argument parsing. /// Use ArgumentAttributes to control parsing behaviour. /// /// The type of the arguments to display usage for. /// The number of columns to format the output to. /// Printable string containing a user friendly description of command line arguments. public static string ArgumentsUsage(Type argumentType, int columns) { return (new Parser(argumentType, null)).GetUsageString(columns); } private const int STD_OUTPUT_HANDLE = -11; private struct COORD { internal Int16 x; internal Int16 y; } private struct SMALL_RECT { internal Int16 Left; internal Int16 Top; internal Int16 Right; internal Int16 Bottom; } private struct CONSOLE_SCREEN_BUFFER_INFO { internal COORD dwSize; internal COORD dwCursorPosition; internal Int16 wAttributes; internal SMALL_RECT srWindow; internal COORD dwMaximumWindowSize; } [DllImport("kernel32.dll", EntryPoint="GetStdHandle", SetLastError=true, CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)] private static extern int GetStdHandle(int nStdHandle); [DllImport("kernel32.dll", EntryPoint="GetConsoleScreenBufferInfo", SetLastError=true, CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)] private static extern int GetConsoleScreenBufferInfo(int hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo); /// /// Returns the number of columns in the current console window /// /// Returns the number of columns in the current console window public static int GetConsoleWindowWidth() { int screenWidth; CONSOLE_SCREEN_BUFFER_INFO csbi = new CONSOLE_SCREEN_BUFFER_INFO(); int rc; rc = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), ref csbi); screenWidth = csbi.dwSize.x; return screenWidth; } /// /// Searches a StringBuilder for a character /// /// The text to search. /// The character value to search for. /// The index to stat searching at. /// The index of the first occurence of value or -1 if it is not found. public static int IndexOf(StringBuilder text, char value, int startIndex) { for (int index = startIndex; index < text.Length; index++) { if (text[index] == value) return index; } return -1; } /// /// Searches a StringBuilder for a character in reverse /// /// The text to search. /// The character to search for. /// The index to start the search at. /// The index of the last occurence of value in text or -1 if it is not found. public static int LastIndexOf(StringBuilder text, char value, int startIndex) { for (int index = Math.Min(startIndex, text.Length - 1); index >= 0; index --) { if (text[index] == value) return index; } return -1; } private const int spaceBeforeParam = 2; /// /// Creates a new command line argument parser. /// /// The type of object to parse. /// The destination for parse errors. public Parser(Type argumentSpecification, ErrorReporter reporter) { this.reporter = reporter; this.arguments = new ArrayList(); this.argumentMap = new Hashtable(); foreach (FieldInfo field in argumentSpecification.GetFields()) { if (!field.IsStatic && !field.IsInitOnly && !field.IsLiteral) { ArgumentAttribute attribute = GetAttribute(field); if (attribute is DefaultArgumentAttribute) { Debug.Assert(this.defaultArgument == null); this.defaultArgument = new Argument(attribute, field, reporter); } else { this.arguments.Add(new Argument(attribute, field, reporter)); } } } // add explicit names to map foreach (Argument argument in this.arguments) { Debug.Assert(!argumentMap.ContainsKey(argument.LongName)); this.argumentMap[argument.LongName] = argument; if (argument.ExplicitShortName) { if (argument.ShortName != null && argument.ShortName.Length > 0) { Debug.Assert(!argumentMap.ContainsKey(argument.ShortName)); this.argumentMap[argument.ShortName] = argument; } else { argument.ClearShortName(); } } } // add implicit names which don't collide to map foreach (Argument argument in this.arguments) { if (!argument.ExplicitShortName) { if (argument.ShortName != null && argument.ShortName.Length > 0 && !argumentMap.ContainsKey(argument.ShortName)) this.argumentMap[argument.ShortName] = argument; else argument.ClearShortName(); } } } private static ArgumentAttribute GetAttribute(FieldInfo field) { object[] attributes = field.GetCustomAttributes(typeof(ArgumentAttribute), false); if (attributes.Length == 1) return (ArgumentAttribute) attributes[0]; Debug.Assert(attributes.Length == 0); return null; } private void ReportUnrecognizedArgument(string argument) { this.reporter(string.Format("Unrecognized command line argument '{0}'", argument)); } /// /// Parses an argument list into an object /// /// /// /// true if an error occurred private bool ParseArgumentList(string[] args, object destination) { bool hadError = false; if (args != null) { foreach (string argument in args) { if (argument.Length > 0) { switch (argument[0]) { case '-': case '/': int endIndex = argument.IndexOfAny(new char[] {':', '+', '-'}, 1); string option = argument.Substring(1, endIndex == -1 ? argument.Length - 1 : endIndex - 1); string optionArgument; if (option.Length + 1 == argument.Length) { optionArgument = null; } else if (argument.Length > 1 + option.Length && argument[1 + option.Length] == ':') { optionArgument = argument.Substring(option.Length + 2); } else { optionArgument = argument.Substring(option.Length + 1); } Argument arg = (Argument) this.argumentMap[option]; if (arg == null) { ReportUnrecognizedArgument(argument); hadError = true; } else { hadError |= !arg.SetValue(optionArgument, destination); } break; case '@': string[] nestedArguments; hadError |= LexFileArguments(argument.Substring(1), out nestedArguments); hadError |= ParseArgumentList(nestedArguments, destination); break; default: if (this.defaultArgument != null) { hadError |= !this.defaultArgument.SetValue(argument, destination); } else { ReportUnrecognizedArgument(argument); hadError = true; } break; } } } } return hadError; } /// /// Parses an argument list. /// /// The arguments to parse. /// The destination of the parsed arguments. /// true if no parse errors were encountered. public bool Parse(string[] args, object destination) { bool hadError = ParseArgumentList(args, destination); // check for missing required arguments foreach (Argument arg in this.arguments) { hadError |= arg.Finish(destination); } if (this.defaultArgument != null) { hadError |= this.defaultArgument.Finish(destination); } return !hadError; } private struct ArgumentHelpStrings { public ArgumentHelpStrings(string syntax, string help) { this.syntax = syntax; this.help = help; } public string syntax; public string help; } /// /// A user firendly usage string describing the command line argument syntax. /// public string GetUsageString(int screenWidth) { ArgumentHelpStrings[] strings = GetAllHelpStrings(); int maxParamLen = 0; foreach (ArgumentHelpStrings helpString in strings) { maxParamLen = Math.Max(maxParamLen, helpString.syntax.Length); } const int minimumNumberOfCharsForHelpText = 10; const int minimumHelpTextColumn = 5; const int minimumScreenWidth = minimumHelpTextColumn + minimumNumberOfCharsForHelpText; int helpTextColumn; int idealMinimumHelpTextColumn = maxParamLen + spaceBeforeParam; screenWidth = Math.Max(screenWidth, minimumScreenWidth); if (screenWidth < (idealMinimumHelpTextColumn + minimumNumberOfCharsForHelpText)) helpTextColumn = minimumHelpTextColumn; else helpTextColumn = idealMinimumHelpTextColumn; const string newLine = "\n"; StringBuilder builder = new StringBuilder(); foreach (ArgumentHelpStrings helpStrings in strings) { // add syntax string int syntaxLength = helpStrings.syntax.Length; builder.Append(helpStrings.syntax); // start help text on new line if syntax string is too long int currentColumn = syntaxLength; if (syntaxLength >= helpTextColumn) { builder.Append(newLine); currentColumn = 0; } // add help text broken on spaces int charsPerLine = screenWidth - helpTextColumn; int index = 0; while (index < helpStrings.help.Length) { // tab to start column builder.Append(' ', helpTextColumn - currentColumn); currentColumn = helpTextColumn; // find number of chars to display on this line int endIndex = index + charsPerLine; if (endIndex >= helpStrings.help.Length) { // rest of text fits on this line endIndex = helpStrings.help.Length; } else { endIndex = helpStrings.help.LastIndexOf(' ', endIndex - 1, Math.Min(endIndex - index, charsPerLine)); if (endIndex <= index) { // no spaces on this line, append full set of chars endIndex = index + charsPerLine; } } // add chars builder.Append(helpStrings.help, index, endIndex - index); index = endIndex; // do new line AddNewLine(newLine, builder, ref currentColumn); // don't start a new line with spaces while (index < helpStrings.help.Length && helpStrings.help[index] == ' ') index ++; } // add newline if there's no help text if (helpStrings.help.Length == 0) { builder.Append(newLine); } } return builder.ToString(); } private static void AddNewLine(string newLine, StringBuilder builder, ref int currentColumn) { builder.Append(newLine); currentColumn = 0; } private ArgumentHelpStrings[] GetAllHelpStrings() { ArgumentHelpStrings[] strings = new ArgumentHelpStrings[NumberOfParametersToDisplay()]; int index = 0; foreach (Argument arg in this.arguments) { strings[index] = GetHelpStrings(arg); index++; } strings[index++] = new ArgumentHelpStrings("@", "Read response file for more options"); if (this.defaultArgument != null) strings[index++] = GetHelpStrings(this.defaultArgument); return strings; } private static ArgumentHelpStrings GetHelpStrings(Argument arg) { return new ArgumentHelpStrings(arg.SyntaxHelp, arg.FullHelpText); } private int NumberOfParametersToDisplay() { int numberOfParameters = this.arguments.Count + 1; if (HasDefaultArgument) numberOfParameters += 1; return numberOfParameters; } /// /// Does this parser have a default argument. /// /// Does this parser have a default argument. public bool HasDefaultArgument { get { return this.defaultArgument != null; } } private bool LexFileArguments(string fileName, out string[] arguments) { string args = null; try { using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { args = (new StreamReader(file)).ReadToEnd(); } } catch (Exception e) { this.reporter(string.Format("Error: Can't open command line argument file '{0}' : '{1}'", fileName, e.Message)); arguments = null; return false; } bool hadError = false; ArrayList argArray = new ArrayList(); StringBuilder currentArg = new StringBuilder(); bool inQuotes = false; int index = 0; // while (index < args.Length) try { while (true) { // skip whitespace while (char.IsWhiteSpace(args[index])) { index += 1; } // # - comment to end of line if (args[index] == '#') { index += 1; while (args[index] != '\n') { index += 1; } continue; } // do one argument do { if (args[index] == '\\') { int cSlashes = 1; index += 1; while (index == args.Length && args[index] == '\\') { cSlashes += 1; } if (index == args.Length || args[index] != '"') { currentArg.Append('\\', cSlashes); } else { currentArg.Append('\\', (cSlashes >> 1)); if (0 != (cSlashes & 1)) { currentArg.Append('"'); } else { inQuotes = !inQuotes; } } } else if (args[index] == '"') { inQuotes = !inQuotes; index += 1; } else { currentArg.Append(args[index]); index += 1; } } while (!char.IsWhiteSpace(args[index]) || inQuotes); argArray.Add(currentArg.ToString()); currentArg.Length = 0; } } catch (System.IndexOutOfRangeException) { // got EOF if (inQuotes) { this.reporter(string.Format("Error: Unbalanced '\"' in command line argument file '{0}'", fileName)); hadError = true; } else if (currentArg.Length > 0) { // valid argument can be terminated by EOF argArray.Add(currentArg.ToString()); } } arguments = (string[]) argArray.ToArray(typeof (string)); return hadError; } private static string LongName(ArgumentAttribute attribute, FieldInfo field) { return (attribute == null || attribute.DefaultLongName) ? field.Name : attribute.LongName; } private static string ShortName(ArgumentAttribute attribute, FieldInfo field) { return !ExplicitShortName(attribute) ? LongName(attribute, field).Substring(0,1) : attribute.ShortName; } private static string HelpText(ArgumentAttribute attribute, FieldInfo field) { if (attribute == null) return null; else return attribute.HelpText; } private static bool HasHelpText(ArgumentAttribute attribute) { return (attribute != null && attribute.HasHelpText); } private static bool ExplicitShortName(ArgumentAttribute attribute) { return (attribute != null && !attribute.DefaultShortName); } private static object DefaultValue(ArgumentAttribute attribute, FieldInfo field) { return (attribute == null || !attribute.HasDefaultValue) ? null : attribute.DefaultValue; } private static Type ElementType(FieldInfo field) { if (IsCollectionType(field.FieldType)) return field.FieldType.GetElementType(); else return null; } private static ArgumentType Flags(ArgumentAttribute attribute, FieldInfo field) { if (attribute != null) return attribute.Type; else if (IsCollectionType(field.FieldType)) return ArgumentType.MultipleUnique; else return ArgumentType.AtMostOnce; } private static bool IsCollectionType(Type type) { return type.IsArray; } private static bool IsValidElementType(Type type) { return type != null && ( type == typeof(int) || type == typeof(uint) || type == typeof(string) || type == typeof(bool) || type.IsEnum); } private class Argument { public Argument(ArgumentAttribute attribute, FieldInfo field, ErrorReporter reporter) { this.longName = Parser.LongName(attribute, field); this.explicitShortName = Parser.ExplicitShortName(attribute); this.shortName = Parser.ShortName(attribute, field); this.hasHelpText = Parser.HasHelpText(attribute); this.helpText = Parser.HelpText(attribute, field); this.defaultValue = Parser.DefaultValue(attribute, field); this.elementType = ElementType(field); this.flags = Flags(attribute, field); this.field = field; this.seenValue = false; this.reporter = reporter; this.isDefault = attribute != null && attribute is DefaultArgumentAttribute; if (IsCollection) { this.collectionValues = new ArrayList(); } Debug.Assert(this.longName != null && this.longName.Length > 0); Debug.Assert(!IsCollection || AllowMultiple, "Collection arguments must have allow multiple"); Debug.Assert(!Unique || IsCollection, "Unique only applicable to collection arguments"); Debug.Assert(IsValidElementType(Type) || IsCollectionType(Type)); Debug.Assert((IsCollection && IsValidElementType(elementType)) || (!IsCollection && elementType == null)); Debug.Assert(!(this.IsRequired && this.HasDefaultValue), "Required arguments cannot have default value"); Debug.Assert(!this.HasDefaultValue || (this.defaultValue.GetType() == field.FieldType), "Type of default value must match field type"); } public bool Finish(object destination) { if (!this.SeenValue && this.HasDefaultValue) { this.field.SetValue(destination, this.DefaultValue); } if (this.IsCollection) { this.field.SetValue(destination, this.collectionValues.ToArray(this.elementType)); } return ReportMissingRequiredArgument(); } private bool ReportMissingRequiredArgument() { if (this.IsRequired && !this.SeenValue) { if (this.IsDefault) reporter(string.Format("Missing required argument '<{0}>'.", this.LongName)); else reporter(string.Format("Missing required argument '/{0}'.", this.LongName)); return true; } return false; } private void ReportDuplicateArgumentValue(string value) { this.reporter(string.Format("Duplicate '{0}' argument '{1}'", this.LongName, value)); } public bool SetValue(string value, object destination) { if (SeenValue && !AllowMultiple) { this.reporter(string.Format("Duplicate '{0}' argument", this.LongName)); return false; } this.seenValue = true; object newValue; if (!ParseValue(this.ValueType, value, out newValue)) return false; if (this.IsCollection) { if (this.Unique && this.collectionValues.Contains(newValue)) { ReportDuplicateArgumentValue(value); return false; } else { this.collectionValues.Add(newValue); } } else { this.field.SetValue(destination, newValue); } return true; } public Type ValueType { get { return this.IsCollection ? this.elementType : this.Type; } } private void ReportBadArgumentValue(string value) { this.reporter(string.Format("'{0}' is not a valid value for the '{1}' command line option", value, this.LongName)); } private bool ParseValue(Type type, string stringData, out object value) { // null is only valid for bool variables // empty string is never valid if ((stringData != null || type == typeof(bool)) && (stringData == null || stringData.Length > 0)) { try { if (type == typeof(string)) { value = stringData; return true; } else if (type == typeof(bool)) { if (stringData == null || stringData == "+") { value = true; return true; } else if (stringData == "-") { value = false; return true; } } else if (type == typeof(int)) { value = int.Parse(stringData); return true; } else if (type == typeof(uint)) { value = int.Parse(stringData); return true; } else { Debug.Assert(type.IsEnum); value = Enum.Parse(type, stringData, true); return true; } } catch { // catch parse errors } } ReportBadArgumentValue(stringData); value = null; return false; } private void AppendValue(StringBuilder builder, object value) { if (value is string || value is int || value is uint || value.GetType().IsEnum) { builder.Append(value.ToString()); } else if (value is bool) { builder.Append((bool) value ? "+" : "-"); } else { bool first = true; foreach (object o in (System.Array) value) { if (!first) { builder.Append(", "); } AppendValue(builder, o); first = false; } } } public string LongName { get { return this.longName; } } public bool ExplicitShortName { get { return this.explicitShortName; } } public string ShortName { get { return this.shortName; } } public bool HasShortName { get { return this.shortName != null; } } public void ClearShortName() { this.shortName = null; } public bool HasHelpText { get { return this.hasHelpText; } } public string HelpText { get { return this.helpText; } } public object DefaultValue { get { return this.defaultValue; } } public bool HasDefaultValue { get { return null != this.defaultValue; } } public string FullHelpText { get { StringBuilder builder = new StringBuilder(); if (this.HasHelpText) { builder.Append(this.HelpText); } if (this.HasDefaultValue) { if (builder.Length > 0) builder.Append(" "); builder.Append("Default value:'"); AppendValue(builder, this.DefaultValue); builder.Append('\''); } if (this.HasShortName) { if (builder.Length > 0) builder.Append(" "); builder.Append("(short form /"); builder.Append(this.ShortName); builder.Append(")"); } return builder.ToString(); } } public string SyntaxHelp { get { StringBuilder builder = new StringBuilder(); if (this.IsDefault) { builder.Append("<"); builder.Append(this.LongName); builder.Append(">"); } else { builder.Append("/"); builder.Append(this.LongName); Type valueType = this.ValueType; if (valueType == typeof(int)) { builder.Append(":"); } else if (valueType == typeof(uint)) { builder.Append(":"); } else if (valueType == typeof(bool)) { builder.Append("[+|-]"); } else if (valueType == typeof(string)) { builder.Append(":"); } else { Debug.Assert(valueType.IsEnum); builder.Append(":{"); bool first = true; foreach (FieldInfo field in valueType.GetFields()) { if (field.IsStatic) { if (first) first = false; else builder.Append('|'); builder.Append(field.Name); } } builder.Append('}'); } } return builder.ToString(); } } public bool IsRequired { get { return 0 != (this.flags & ArgumentType.Required); } } public bool SeenValue { get { return this.seenValue; } } public bool AllowMultiple { get { return 0 != (this.flags & ArgumentType.Multiple); } } public bool Unique { get { return 0 != (this.flags & ArgumentType.Unique); } } public Type Type { get { return field.FieldType; } } public bool IsCollection { get { return IsCollectionType(Type); } } public bool IsDefault { get { return this.isDefault; } } private string longName; private string shortName; private string helpText; private bool hasHelpText; private bool explicitShortName; private object defaultValue; private bool seenValue; private FieldInfo field; private Type elementType; private ArgumentType flags; private ArrayList collectionValues; private ErrorReporter reporter; private bool isDefault; } private ArrayList arguments; private Hashtable argumentMap; private Argument defaultArgument; private ErrorReporter reporter; } }