1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585 |
- // **********************************************************************************
- // CassiniDev - http://cassinidev.codeplex.com
- //
- // Copyright (c) 2010 Sky Sanders. All rights reserved.
- // Copyright (c) Microsoft Corporation. All rights reserved.
- //
- // This source code is subject to terms and conditions of the Microsoft Public
- // License (Ms-PL). A copy of the license can be found in the license.txt file
- // included in this distribution.
- //
- // You must not remove this notice, or any other, from this software.
- //
- // **********************************************************************************
- #region
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.Reflection;
- using System.Text;
- using System.Text.RegularExpressions;
- #endregion
- namespace CassiniDev
- {
- /// <summary>
- /// 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.
- ///
- ///
- /// 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:<string> short form /f
- /// @<file> 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)
- /// @<file> Read response file for more options
- /// <files> 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.
- /// 02/23/2005 Fix bug with short name and default arguments.
- ///
- /// 12/24/2009 sky: Added ushort as valid argument type.
- /// 12/27/2009 sky: todo: expose out and err to enable use in forms app.
- /// 12/29/2009 sky: added ArgumentsAttribute and GetGenericUsageString to allow attaching generic help text
- /// 01/01/2010 sky: split classes into seperate files
- /// 01/01/2010 sky: cleaned up Parser.cs
- /// 05/22/2010 sky: major cleanup - more to come.
- /// </summary>
- public sealed class CommandLineParser
- {
- /// <summary>
- /// The System Defined new line string.
- /// </summary>
- public const string NewLine = "\r\n";
- private const int SpaceBeforeParam = 2;
- private const int StdOutputHandle = -11;
- private readonly Hashtable _argumentMap;
- private readonly ArrayList _arguments;
- private readonly Type _argumentSpecification;
- private readonly Argument _defaultArgument;
- private readonly ErrorReporter _reporter;
- /// <summary>
- /// Don't ever call this.
- /// </summary>
- // ReSharper disable UnusedMember.Local
- private CommandLineParser()
- // ReSharper restore UnusedMember.Local
- {
- //noop
- }
- /// <summary>
- /// Creates a new command line argument parser.
- /// </summary>
- /// <param name="argumentSpecification"> The type of object to parse. </param>
- /// <param name="reporter"> The destination for parse errors. </param>
- public CommandLineParser(Type argumentSpecification, ErrorReporter reporter)
- {
- _argumentSpecification = argumentSpecification;
- _reporter = reporter;
- _arguments = new ArrayList();
- _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(_defaultArgument == null);
- _defaultArgument = new Argument(attribute, field, reporter);
- }
- else
- {
- _arguments.Add(new Argument(attribute, field, reporter));
- }
- }
- }
- // add explicit names to map
- foreach (Argument argument in _arguments)
- {
- Debug.Assert(!_argumentMap.ContainsKey(argument.LongName));
- _argumentMap[argument.LongName] = argument;
- if (argument.ExplicitShortName)
- {
- if (!string.IsNullOrEmpty(argument.ShortName))
- {
- Debug.Assert(!_argumentMap.ContainsKey(argument.ShortName));
- _argumentMap[argument.ShortName] = argument;
- }
- else
- {
- argument.ClearShortName();
- }
- }
- }
- // add implicit names which don't collide to map
- foreach (Argument argument in _arguments)
- {
- if (!argument.ExplicitShortName)
- {
- if (!string.IsNullOrEmpty(argument.ShortName) && !_argumentMap.ContainsKey(argument.ShortName))
- _argumentMap[argument.ShortName] = argument;
- else
- argument.ClearShortName();
- }
- }
- }
- /// <summary>
- /// Does this parser have a default argument.
- /// </summary>
- /// <value> Does this parser have a default argument. </value>
- public bool HasDefaultArgument
- {
- get { return _defaultArgument != null; }
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="argumentType"> The type of the arguments to display usage for. </param>
- /// <returns> Printable string containing a user friendly description of command line arguments. </returns>
- public static string ArgumentsUsage(Type argumentType)
- {
- int screenWidth = GetConsoleWindowWidth();
- if (screenWidth == 0)
- screenWidth = 80;
- return ArgumentsUsage(argumentType, screenWidth);
- }
- /// <summary>
- /// Returns a Usage string for command line argument parsing.
- /// Use ArgumentAttributes to control parsing behaviour.
- /// </summary>
- /// <param name="argumentType"> The type of the arguments to display usage for. </param>
- /// <param name="columns"> The number of columns to format the output to. </param>
- /// <returns> Printable string containing a user friendly description of command line arguments. </returns>
- public static string ArgumentsUsage(Type argumentType, int columns)
- {
- return (new CommandLineParser(argumentType, null)).GetUsageString(columns);
- }
- /// <summary>
- /// Returns the number of columns in the current console window
- /// </summary>
- /// <returns>Returns the number of columns in the current console window</returns>
- public static int GetConsoleWindowWidth()
- {
- Interop.CONSOLE_SCREEN_BUFFER_INFO csbi = new Interop.CONSOLE_SCREEN_BUFFER_INFO();
- #pragma warning disable 168
- int rc = Interop.GetConsoleScreenBufferInfo(Interop.GetStdHandle(StdOutputHandle), ref csbi);
- #pragma warning restore 168
- int screenWidth = csbi.dwSize.x;
- return screenWidth;
- }
- /// <summary>
- /// A user firendly usage string describing the command line argument syntax.
- /// </summary>
- 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 idealMinimumHelpTextColumn = maxParamLen + SpaceBeforeParam;
- screenWidth = Math.Max(screenWidth, minimumScreenWidth);
- int helpTextColumn = screenWidth < (idealMinimumHelpTextColumn + minimumNumberOfCharsForHelpText)
- ? minimumHelpTextColumn
- : idealMinimumHelpTextColumn;
- const string newLine = "\n";
- StringBuilder builder = new StringBuilder();
- // 01/01/2010 sky
- string genericUsage = GetGenericUsageString(_argumentSpecification, screenWidth);
- if (!string.IsNullOrEmpty(genericUsage))
- {
- builder.AppendLine(genericUsage);
- }
- 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();
- }
- /// <summary>
- /// Searches a StringBuilder for a character
- /// </summary>
- /// <param name="text"> The text to search. </param>
- /// <param name="value"> The character value to search for. </param>
- /// <param name="startIndex"> The index to stat searching at. </param>
- /// <returns> The index of the first occurence of value or -1 if it is not found. </returns>
- 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;
- }
- /// <summary>
- /// Searches a StringBuilder for a character in reverse
- /// </summary>
- /// <param name="text"> The text to search. </param>
- /// <param name="value"> The character to search for. </param>
- /// <param name="startIndex"> The index to start the search at. </param>
- /// <returns>The index of the last occurence of value in text or -1 if it is not found. </returns>
- 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;
- }
- /// <summary>
- /// Parses an argument list.
- /// </summary>
- /// <param name="args"> The arguments to parse. </param>
- /// <param name="destination"> The destination of the parsed arguments. </param>
- /// <returns> true if no parse errors were encountered. </returns>
- public bool Parse(string[] args, object destination)
- {
- bool hadError = ParseArgumentList(args, destination);
- // check for missing required arguments
- foreach (Argument arg in _arguments)
- {
- hadError |= arg.Finish(destination);
- }
- if (_defaultArgument != null)
- {
- hadError |= _defaultArgument.Finish(destination);
- }
- return !hadError;
- }
- /// <summary>
- /// Parses Command Line Arguments.
- /// Errors are output on Console.Error.
- /// Use ArgumentAttributes to control parsing behaviour.
- /// </summary>
- /// <param name="arguments"> The actual arguments. </param>
- /// <param name="destination"> The resulting parsed arguments. </param>
- /// <returns> true if no errors were detected. </returns>
- public static bool ParseArguments(string[] arguments, object destination)
- {
- return ParseArguments(arguments, destination, Console.Error.WriteLine);
- }
- /// <summary>
- /// Parses Command Line Arguments.
- /// Use ArgumentAttributes to control parsing behaviour.
- /// </summary>
- /// <param name="arguments"> The actual arguments. </param>
- /// <param name="destination"> The resulting parsed arguments. </param>
- /// <param name="reporter"> The destination for parse errors. </param>
- /// <returns> true if no errors were detected. </returns>
- public static bool ParseArguments(string[] arguments, object destination, ErrorReporter reporter)
- {
- CommandLineParser parser = new CommandLineParser(destination.GetType(), reporter);
- return parser.Parse(arguments, destination);
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="arguments"> The actual arguments. </param>
- /// <param name="destination"> The resulting parsed arguments. </param>
- /// <returns> true if no errors were detected. </returns>
- public static bool ParseArgumentsWithUsage(string[] arguments, object destination)
- {
- if (ParseHelp(arguments) || !ParseArguments(arguments, destination))
- {
- // error encountered in arguments. Display usage message
- Console.Write(ArgumentsUsage(destination.GetType()));
- return false;
- }
- return true;
- }
- /// <summary>
- /// Checks if a set of arguments asks for help.
- /// </summary>
- /// <param name="args"> Args to check for help. </param>
- /// <returns> Returns true if args contains /? or /help. </returns>
- public static bool ParseHelp(string[] args)
- {
- CommandLineParser helpParser = new CommandLineParser(typeof (HelpArgument), NullErrorReporter);
- HelpArgument helpArgument = new HelpArgument();
- helpParser.Parse(args, helpArgument);
- return helpArgument.help;
- }
- private static void AddNewLine(string newLine, StringBuilder builder, ref int currentColumn)
- {
- builder.Append(newLine);
- currentColumn = 0;
- }
- private static object DefaultValue(ArgumentAttribute attribute)
- {
- return (attribute == null || !attribute.HasDefaultValue) ? null : attribute.DefaultValue;
- }
- private static Type ElementType(FieldInfo field)
- {
- return IsCollectionType(field.FieldType) ? field.FieldType.GetElementType() : null;
- }
- private static bool ExplicitShortName(ArgumentAttribute attribute)
- {
- return (attribute != null && !attribute.DefaultShortName);
- }
- private static ArgumentType Flags(ArgumentAttribute attribute, FieldInfo field)
- {
- if (attribute != null)
- {
- return attribute.Type;
- }
- return IsCollectionType(field.FieldType) ? ArgumentType.MultipleUnique : ArgumentType.AtMostOnce;
- }
- private ArgumentHelpStrings[] GetAllHelpStrings()
- {
- ArgumentHelpStrings[] strings = new ArgumentHelpStrings[NumberOfParametersToDisplay()];
- int index = 0;
- foreach (Argument arg in _arguments)
- {
- strings[index] = GetHelpStrings(arg);
- index++;
- }
- strings[index++] = new ArgumentHelpStrings("@<file>", "Read response file for more options");
- if (_defaultArgument != null)
- {
- strings[index] = GetHelpStrings(_defaultArgument);
- }
- return strings;
- }
- private static ArgumentAttribute GetAttribute(ICustomAttributeProvider field)
- {
- object[] attributes = field.GetCustomAttributes(typeof (ArgumentAttribute), false);
- if (attributes.Length == 1)
- return (ArgumentAttribute) attributes[0];
- Debug.Assert(attributes.Length == 0);
- return null;
- }
- /// <summary>
- /// 01/01/2010 sky
- /// </summary>
- private static string GetGenericUsageString(ICustomAttributeProvider type, int cols)
- {
- object[] attributes = type.GetCustomAttributes(typeof (ArgumentsAttribute), true);
- if (attributes.Length == 0 || !((ArgumentsAttribute) attributes[0]).HasHelpText)
- {
- return string.Empty;
- }
- StringBuilder sb = new StringBuilder();
- string usage = ((ArgumentsAttribute) attributes[0]).HelpText;
- // simple width formatter
- string[] lines = Regex.Split(usage, Environment.NewLine);
- foreach (string line in lines)
- {
- string[] words = Regex.Split(line, " ");
- string currentLine = string.Empty;
- foreach (string word in words)
- {
- if (currentLine.Length + word.Length + 1 > cols)
- {
- // start new line
- sb.AppendLine(currentLine);
- currentLine = word + " ";
- }
- else
- {
- currentLine += (word + " ");
- }
- }
- sb.AppendLine(currentLine);
- sb.Append(Environment.NewLine);
- }
- return sb.ToString();
- }
- private static ArgumentHelpStrings GetHelpStrings(Argument arg)
- {
- return new ArgumentHelpStrings(arg.SyntaxHelp, arg.FullHelpText);
- }
- private static bool HasHelpText(ArgumentAttribute attribute)
- {
- return (attribute != null && attribute.HasHelpText);
- }
- private static string HelpText(ArgumentAttribute attribute)
- {
- return attribute == null ? null : attribute.HelpText;
- }
- private static bool IsCollectionType(Type type)
- {
- return type.IsArray;
- }
- private static bool IsValidElementType(Type type)
- {
- //SKY:12/25/09 - added ushort
- return type != null && (
- type == typeof (int) ||
- type == typeof (uint) ||
- type == typeof (ushort) ||
- type == typeof (string) ||
- type == typeof (bool) ||
- type.IsEnum);
- }
- private bool LexFileArguments(string fileName, out string[] argumentsOut)
- {
- string args;
- try
- {
- using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read))
- {
- args = (new StreamReader(file)).ReadToEnd();
- }
- }
- catch (Exception e)
- {
- _reporter(string.Format("Error: Can't open command line argument file '{0}' : '{1}'", fileName,
- e.Message));
- argumentsOut = 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 (IndexOutOfRangeException)
- {
- // got EOF
- if (inQuotes)
- {
- _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());
- }
- }
- argumentsOut = (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 void NullErrorReporter(string message)
- {
- }
- private int NumberOfParametersToDisplay()
- {
- int numberOfParameters = _arguments.Count + 1;
- if (HasDefaultArgument)
- {
- numberOfParameters += 1;
- }
- return numberOfParameters;
- }
- /// <summary>
- /// Parses an argument list into an object
- /// </summary>
- /// <param name="args"></param>
- /// <param name="destination"></param>
- /// <returns> true if an error occurred </returns>
- private bool ParseArgumentList(IEnumerable<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[] {':', '+', '-'}, 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) _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 (_defaultArgument != null)
- {
- hadError |= !_defaultArgument.SetValue(argument, destination);
- }
- else
- {
- ReportUnrecognizedArgument(argument);
- hadError = true;
- }
- break;
- }
- }
- }
- }
- return hadError;
- }
- private void ReportUnrecognizedArgument(string argument)
- {
- _reporter(string.Format("Unrecognized command line argument '{0}'", argument));
- }
- private static string ShortName(ArgumentAttribute attribute, FieldInfo field)
- {
- if (attribute is DefaultArgumentAttribute)
- return null;
- if (!ExplicitShortName(attribute))
- return LongName(attribute, field).Substring(0, 1);
- return attribute.ShortName;
- }
- #region Nested type: Argument
- [DebuggerDisplay("Name = {LongName}")]
- private class Argument
- {
- private readonly ArrayList _collectionValues;
- private readonly object _defaultValue;
- private readonly Type _elementType;
- private readonly bool _explicitShortName;
- private readonly FieldInfo _field;
- private readonly ArgumentType _flags;
- private readonly bool _hasHelpText;
- private readonly string _helpText;
- private readonly bool _isDefault;
- private readonly string _longName;
- private readonly ErrorReporter _reporter;
- private bool _seenValue;
- private string _shortName;
- public Argument(ArgumentAttribute attribute, FieldInfo field, ErrorReporter reporter)
- {
- _longName = CommandLineParser.LongName(attribute, field);
- _explicitShortName = CommandLineParser.ExplicitShortName(attribute);
- _shortName = CommandLineParser.ShortName(attribute, field);
- _hasHelpText = CommandLineParser.HasHelpText(attribute);
- _helpText = CommandLineParser.HelpText(attribute);
- _defaultValue = CommandLineParser.DefaultValue(attribute);
- _elementType = ElementType(field);
- _flags = Flags(attribute, field);
- _field = field;
- _seenValue = false;
- _reporter = reporter;
- _isDefault = attribute != null && attribute is DefaultArgumentAttribute;
- if (IsCollection)
- {
- _collectionValues = new ArrayList();
- }
- Debug.Assert(!string.IsNullOrEmpty(_longName));
- Debug.Assert(!_isDefault || !ExplicitShortName);
- 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(!(IsRequired && HasDefaultValue), "Required arguments cannot have default value");
- Debug.Assert(!HasDefaultValue || (_defaultValue.GetType() == field.FieldType),
- "Type of default value must match field type");
- }
- private Type ValueType
- {
- get { return IsCollection ? _elementType : Type; }
- }
- public string LongName
- {
- get { return _longName; }
- }
- public bool ExplicitShortName
- {
- get { return _explicitShortName; }
- }
- public string ShortName
- {
- get { return _shortName; }
- }
- private bool HasShortName
- {
- get { return _shortName != null; }
- }
- private bool HasHelpText
- {
- get { return _hasHelpText; }
- }
- private string HelpText
- {
- get { return _helpText; }
- }
- private object DefaultValue
- {
- get { return _defaultValue; }
- }
- private bool HasDefaultValue
- {
- get { return null != _defaultValue; }
- }
- public string FullHelpText
- {
- get
- {
- StringBuilder builder = new StringBuilder();
- if (HasHelpText)
- {
- builder.Append(HelpText);
- }
- if (HasDefaultValue)
- {
- if (builder.Length > 0)
- builder.Append(" ");
- builder.Append("Default value:'");
- AppendValue(builder, DefaultValue);
- builder.Append('\'');
- }
- if (HasShortName)
- {
- if (builder.Length > 0)
- builder.Append(" ");
- builder.Append("(short form /");
- builder.Append(ShortName);
- builder.Append(")");
- }
- return builder.ToString();
- }
- }
- public string SyntaxHelp
- {
- get
- {
- StringBuilder builder = new StringBuilder();
- if (IsDefault)
- {
- builder.Append("<");
- builder.Append(LongName);
- builder.Append(">");
- }
- else
- {
- builder.Append("/");
- builder.Append(LongName);
- Type valueType = ValueType;
- if (valueType == typeof (int))
- {
- builder.Append(":<int>");
- }
- else if (valueType == typeof (uint))
- {
- builder.Append(":<uint>");
- }
- else if (valueType == typeof (bool))
- {
- builder.Append("[+|-]");
- }
- else if (valueType == typeof (string))
- {
- builder.Append(":<string>");
- }
- else
- {
- Debug.Assert(valueType.IsEnum);
- builder.Append(":{");
- bool first = true;
- foreach (FieldInfo info in valueType.GetFields())
- {
- if (info.IsStatic)
- {
- if (first)
- first = false;
- else
- builder.Append('|');
- builder.Append(info.Name);
- }
- }
- builder.Append('}');
- }
- }
- return builder.ToString();
- }
- }
- private bool IsRequired
- {
- get { return 0 != (_flags & ArgumentType.Required); }
- }
- private bool SeenValue
- {
- get { return _seenValue; }
- }
- private bool AllowMultiple
- {
- get { return 0 != (_flags & ArgumentType.Multiple); }
- }
- private bool Unique
- {
- get { return 0 != (_flags & ArgumentType.Unique); }
- }
- private Type Type
- {
- get { return _field.FieldType; }
- }
- private bool IsCollection
- {
- get { return IsCollectionType(Type); }
- }
- private bool IsDefault
- {
- get { return _isDefault; }
- }
- public bool Finish(object destination)
- {
- if (SeenValue)
- {
- if (IsCollection)
- {
- _field.SetValue(destination, _collectionValues.ToArray(_elementType));
- }
- }
- else
- {
- if (HasDefaultValue)
- {
- _field.SetValue(destination, DefaultValue);
- }
- }
- return ReportMissingRequiredArgument();
- }
- private bool ReportMissingRequiredArgument()
- {
- if (IsRequired && !SeenValue)
- {
- if (IsDefault)
- _reporter(string.Format("Missing required argument '<{0}>'.", LongName));
- else
- _reporter(string.Format("Missing required argument '/{0}'.", LongName));
- return true;
- }
- return false;
- }
- private void ReportDuplicateArgumentValue(string value)
- {
- _reporter(string.Format("Duplicate '{0}' argument '{1}'", LongName, value));
- }
- public bool SetValue(string value, object destination)
- {
- if (SeenValue && !AllowMultiple)
- {
- _reporter(string.Format("Duplicate '{0}' argument", LongName));
- return false;
- }
- _seenValue = true;
- object newValue;
- if (!ParseValue(ValueType, value, out newValue))
- {
- return false;
- }
- if (IsCollection)
- {
- if (Unique && _collectionValues.Contains(newValue))
- {
- ReportDuplicateArgumentValue(value);
- return false;
- }
- _collectionValues.Add(newValue);
- }
- else
- {
- _field.SetValue(destination, newValue);
- }
- return true;
- }
- private void ReportBadArgumentValue(string value)
- {
- _reporter(string.Format("'{0}' is not a valid value for the '{1}' command line option", value, LongName));
- }
- private bool ParseValue(Type type, string stringData, out object value)
- {
- // null is only valid for bool variables
- // empty string is never valid
- if (!string.IsNullOrEmpty(stringData) || type == typeof (bool))
- {
- try
- {
- do //omed loop
- {
- if (type == typeof (string))
- {
- value = stringData;
- return true;
- }
- if (type == typeof (bool))
- {
- if (stringData == null || stringData == "+")
- {
- value = true;
- return true;
- }
- if (stringData == "-")
- {
- value = false;
- return true;
- }
- break;
- }
- // from this point on, a null or empty string is invalid
- if (string.IsNullOrEmpty(stringData))
- {
- break;
- }
- if (type == typeof (int))
- {
- value = int.Parse(stringData);
- return true;
- }
- if (type == typeof (uint))
- {
- value = int.Parse(stringData);
- return true;
- }
- //SKY:12/25/09 - added ushort
- if (type == typeof (ushort))
- {
- value = ushort.Parse(stringData);
- return true;
- }
- Debug.Assert(type.IsEnum);
- bool valid = false;
- foreach (string name in Enum.GetNames(type))
- {
- if (string.Compare(name, stringData, StringComparison.InvariantCultureIgnoreCase) == 0)
- {
- valid = true;
- break;
- }
- }
- if (valid)
- {
- value = Enum.Parse(type, stringData, true);
- return true;
- }
- } while (false);
- }
- // ReSharper disable EmptyGeneralCatchClause
- catch
- // ReSharper restore EmptyGeneralCatchClause
- {
- // catch parse errors
- }
- }
- ReportBadArgumentValue(stringData);
- value = null;
- return false;
- }
- private static void AppendValue(StringBuilder builder, object value)
- {
- //SKY:12/25/09 - add ushort
- if (value is string || value is int || value is uint || value is ushort || value.GetType().IsEnum)
- {
- builder.Append(value.ToString());
- }
- else if (value is bool)
- {
- builder.Append((bool) value ? "+" : "-");
- }
- else
- {
- bool first = true;
- foreach (object o in (Array) value)
- {
- if (!first)
- {
- builder.Append(", ");
- }
- AppendValue(builder, o);
- first = false;
- }
- }
- }
- public void ClearShortName()
- {
- _shortName = null;
- }
- }
- #endregion
- #region Nested type: ArgumentHelpStrings
- private struct ArgumentHelpStrings
- {
- public readonly string help;
- public readonly string syntax;
- public ArgumentHelpStrings(string syntax, string help)
- {
- this.syntax = syntax;
- this.help = help;
- }
- }
- #endregion
- #region Nested type: HelpArgument
- private class HelpArgument
- {
- [Argument(ArgumentType.AtMostOnce, ShortName = "?")] public bool help;
- }
- #endregion
- }
- /// <summary>
- /// A delegate used in error reporting.
- /// </summary>
- public delegate void ErrorReporter(string message);
- /// <summary>
- /// Indicates that this argument is the default argument.
- /// '/' or '-' prefix only the argument value is specified.
- /// The ShortName property should not be set for DefaultArgumentAttribute
- /// instances. The LongName property is used for usage text only and
- /// does not affect the usage of the argument.
- /// </summary>
- [AttributeUsage(AttributeTargets.Field)]
- public class DefaultArgumentAttribute : ArgumentAttribute
- {
- /// <summary>
- /// Indicates that this argument is the default argument.
- /// </summary>
- /// <param name="type"> Specifies the error checking to be done on the argument. </param>
- public DefaultArgumentAttribute(ArgumentType type)
- : base(type)
- {
- }
- }
- /// <summary>
- /// Used to control parsing of command line arguments.
- /// </summary>
- [Flags]
- public enum ArgumentType
- {
- /// <summary>
- /// Indicates that this field is required. An error will be displayed
- /// if it is not present when parsing arguments.
- /// </summary>
- Required = 0x01,
- /// <summary>
- /// Only valid in conjunction with Multiple.
- /// Duplicate values will result in an error.
- /// </summary>
- Unique = 0x02,
- /// <summary>
- /// Inidicates that the argument may be specified more than once.
- /// Only valid if the argument is a collection
- /// </summary>
- Multiple = 0x04,
- /// <summary>
- /// 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.
- /// </summary>
- AtMostOnce = 0x00,
- /// <summary>
- /// 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.
- /// </summary>
- LastOccurenceWins = Multiple,
- /// <summary>
- /// The default type for collection arguments.
- /// The argument is permitted to occur multiple times, but duplicate
- /// values will cause an error to be reported.
- /// </summary>
- MultipleUnique = Multiple | Unique,
- }
- /// <summary>
- /// Allows attaching generic help text to arguments class
- ///
- /// 12/29/09 sky: added
- /// </summary>
- [AttributeUsage(AttributeTargets.Class)]
- public class ArgumentsAttribute : Attribute
- {
- private string _helpText;
- /// <summary>
- /// Returns true if the argument has help text specified.
- /// </summary>
- public bool HasHelpText
- {
- get { return null != _helpText; }
- }
- /// <summary>
- /// The help text for the argument.
- /// </summary>
- public string HelpText
- {
- get { return _helpText; }
- set { _helpText = value; }
- }
- }
- /// <summary>
- /// Allows control of command line parsing.
- /// Attach this attribute to instance fields of types used
- /// as the destination of command line argument parsing.
- /// </summary>
- [AttributeUsage(AttributeTargets.Field)]
- public class ArgumentAttribute : Attribute
- {
- #region Fields
- private readonly ArgumentType _type;
- private object _defaultValue;
- private string _helpText;
- private string _longName;
- private string _shortName;
- #endregion
- #region Constructors
- /// <summary>
- /// Allows control of command line parsing.
- /// </summary>
- /// <param name="type"> Specifies the error checking to be done on the argument. </param>
- public ArgumentAttribute(ArgumentType type)
- {
- _type = type;
- }
- #endregion
- #region Properties
- /// <summary>
- /// Returns true if the argument did not have an explicit long name specified.
- /// </summary>
- public bool DefaultLongName
- {
- get { return null == _longName; }
- }
- /// <summary>
- /// Returns true if the argument did not have an explicit short name specified.
- /// </summary>
- public bool DefaultShortName
- {
- get { return null == _shortName; }
- }
- /// <summary>
- /// The default value of the argument.
- /// </summary>
- public object DefaultValue
- {
- get { return _defaultValue; }
- set { _defaultValue = value; }
- }
- /// <summary>
- /// Returns true if the argument has a default value.
- /// </summary>
- public bool HasDefaultValue
- {
- get { return null != _defaultValue; }
- }
- /// <summary>
- /// Returns true if the argument has help text specified.
- /// </summary>
- public bool HasHelpText
- {
- get { return null != _helpText; }
- }
- /// <summary>
- /// The help text for the argument.
- /// </summary>
- public string HelpText
- {
- get { return _helpText; }
- set { _helpText = value; }
- }
- /// <summary>
- /// The long name of the argument.
- /// Set to null means use the default long name.
- /// The long name for every argument must be unique.
- /// It is an error to specify a long name of String.Empty.
- /// </summary>
- public string LongName
- {
- get
- {
- Debug.Assert(!DefaultLongName);
- return _longName;
- }
- set
- {
- Debug.Assert(value != "");
- _longName = value;
- }
- }
- /// <summary>
- /// The short name of the argument.
- /// Set to null means use the default short name if it does not
- /// conflict with any other parameter name.
- /// Set to String.Empty for no short name.
- /// This property should not be set for DefaultArgumentAttributes.
- /// </summary>
- public string ShortName
- {
- get { return _shortName; }
- set
- {
- Debug.Assert(value == null || !(this is DefaultArgumentAttribute));
- _shortName = value;
- }
- }
- /// <summary>
- /// The error checking to be done on the argument.
- /// </summary>
- public ArgumentType Type
- {
- get { return _type; }
- }
- #endregion
- }
- }
|