Files
libremetaverse/libsecondlife-cs/examples/TestClient/CommandLineArguments.cs
lonecoder a8acb61702 Renamed TestTool to TestClient
git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@625 52acb1d6-8a22-11de-b505-999d5b087335
2006-11-24 22:27:15 +00:00

1444 lines
44 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//////////////////////////////////////////////////////////////////////////////
// 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 its 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:<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.
//////////////////////////////////////////////////////////////////////////////
namespace CommandLine
{
using System;
using System.Diagnostics;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
/// <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 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
{
/// <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)
{
this.type = type;
}
/// <summary>
/// The error checking to be done on the argument.
/// </summary>
public ArgumentType Type
{
get { return this.type; }
}
/// <summary>
/// Returns true if the argument did not have an explicit short name specified.
/// </summary>
public bool DefaultShortName { get { return null == this.shortName; } }
/// <summary>
/// The short name of the argument.
/// </summary>
public string ShortName
{
get { return this.shortName; }
set { this.shortName = value; }
}
/// <summary>
/// Returns true if the argument did not have an explicit long name specified.
/// </summary>
public bool DefaultLongName { get { return null == this.longName; } }
/// <summary>
/// The long name of the argument.
/// </summary>
public string LongName
{
get { Debug.Assert(!this.DefaultLongName); return this.longName; }
set { this.longName = value; }
}
/// <summary>
/// The default value of the argument.
/// </summary>
public object DefaultValue
{
get { return this.defaultValue; }
set { this.defaultValue = value; }
}
/// <summary>
/// Returns true if the argument has a default value.
/// </summary>
public bool HasDefaultValue { get { return null != this.defaultValue; } }
/// <summary>
/// Returns true if the argument has help text specified.
/// </summary>
public bool HasHelpText { get { return null != this.helpText; } }
/// <summary>
/// The help text for the argument.
/// </summary>
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;
}
/// <summary>
/// Indicates that this argument is the default argument.
/// '/' or '-' prefix only the argument value is specified.
/// </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>
/// A delegate used in error reporting.
/// </summary>
public delegate void ErrorReporter(string message);
/// <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.
/// </summary>
public sealed class Parser
{
/// <summary>
/// The System Defined new line string.
/// </summary>
public const string NewLine = "\r\n";
/// <summary>
/// Don't ever call this.
/// </summary>
private Parser() { }
/// <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 (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;
}
/// <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 Parser.ParseArguments(arguments, destination, new ErrorReporter(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)
{
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;
}
/// <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)
{
Parser helpParser = new Parser(typeof(HelpArgument), new ErrorReporter(NullErrorReporter));
HelpArgument helpArgument = new HelpArgument();
helpParser.Parse(args, helpArgument);
return helpArgument.help;
}
/// <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 = Parser.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 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);
/// <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()
{
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;
}
/// <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;
}
private const int spaceBeforeParam = 2;
/// <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 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));
}
/// <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(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;
}
/// <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 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;
}
/// <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 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("@<file>", "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;
}
/// <summary>
/// Does this parser have a default argument.
/// </summary>
/// <value> Does this parser have a default argument. </value>
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(":<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 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;
}
}