diff --git a/libsecondlife-cs/examples/TestTool/CommandLineArguments.cs b/libsecondlife-cs/examples/TestTool/CommandLineArguments.cs new file mode 100644 index 00000000..0dfa8d67 --- /dev/null +++ b/libsecondlife-cs/examples/TestTool/CommandLineArguments.cs @@ -0,0 +1,1444 @@ +////////////////////////////////////////////////////////////////////////////// +// 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; + } +} \ No newline at end of file diff --git a/libsecondlife-cs/examples/TestTool/Commands/FollowCommand.cs b/libsecondlife-cs/examples/TestTool/Commands/FollowCommand.cs index b212ccb4..2d82f0a8 100644 --- a/libsecondlife-cs/examples/TestTool/Commands/FollowCommand.cs +++ b/libsecondlife-cs/examples/TestTool/Commands/FollowCommand.cs @@ -29,16 +29,15 @@ namespace libsecondlife.TestTool const float DISTANCE_BUFFER = 3.0f; string followName; - int regionX; - int regionY; bool Follow(string name) { followName = name; - ulong regionHandle = Client.Network.CurrentSim.Region.Handle; - regionX = (int)(regionHandle >> 32); - regionY = (int)(regionHandle & 0xFFFFFFFF); + //ulong regionHandle = Client.Network.CurrentSim.Region.Handle; + //regionX = (int)(regionHandle >> 32); + //regionY = (int)(regionHandle & 0xFFFFFFFF); + foreach (Avatar av in TestTool.Avatars.Values) { @@ -46,9 +45,9 @@ namespace libsecondlife.TestTool else if (vecDist(av.Position, Client.Self.Position) > DISTANCE_BUFFER) { //move toward target - ulong x = (ulong)(av.Position.X + regionX); - ulong y = (ulong)(av.Position.Y + regionY); - Client.Self.AutoPilot(x, y, av.Position.Z); + ulong x = (ulong)(av.Position.X + (av.CurrentRegion.GridRegionData.X * 256)); + ulong y = (ulong)(av.Position.Y + (av.CurrentRegion.GridRegionData.Y * 256)); + Client.Self.AutoPilotLocal(Convert.ToInt32(av.Position.X), Convert.ToInt32(av.Position.Y), av.Position.Z); } else { diff --git a/libsecondlife-cs/examples/TestTool/Program.cs b/libsecondlife-cs/examples/TestTool/Program.cs index 2da6f821..3553118d 100644 --- a/libsecondlife-cs/examples/TestTool/Program.cs +++ b/libsecondlife-cs/examples/TestTool/Program.cs @@ -2,23 +2,31 @@ using System; using System.Collections.Generic; using System.Text; +using CommandLine; + namespace libsecondlife.TestTool { - class Program + public class Program { + [Argument(ArgumentType.Required, HelpText="First name of the SL account to log in with.")] + public string FirstName; + + [Argument(ArgumentType.Required, HelpText="Last name of the SL account to log in with.")] + public string LastName; + + [Argument(ArgumentType.Required, HelpText="Password of the SL account to log in with.")] + public string Password; + + [Argument(ArgumentType.AtMostOnce, HelpText="Full account name to recieve IM commands from.")] + public string MasterName; + static void Main(string[] args) { - if (args.Length < 3) - { - Console.WriteLine("Usage: TestTool.exe firstname lastname password [Master Name]"); - return; - } + Program program = new Program(); + CommandLine.Parser.ParseArgumentsWithUsage(args, program); - string masterName = String.Empty; - for (int ct = 3; ct < args.Length;ct++) - masterName = masterName + args[ct] + " "; - - TestTool testTool = new TestTool(args[0], args[1], args[2], masterName.TrimEnd()); + TestTool testTool = new TestTool(program.FirstName, program.LastName, program.Password); + testTool.Master = program.MasterName; testTool.Run(); } } diff --git a/libsecondlife-cs/examples/TestTool/TestTool.cs b/libsecondlife-cs/examples/TestTool/TestTool.cs index 1a6cadcb..52f95c7e 100644 --- a/libsecondlife-cs/examples/TestTool/TestTool.cs +++ b/libsecondlife-cs/examples/TestTool/TestTool.cs @@ -21,12 +21,10 @@ namespace libsecondlife.TestTool LLQuaternion bodyRotation; System.Timers.Timer updateTimer; - public TestTool(string first, string last, string password, string master) + public TestTool(string first, string last, string password) { Client = new SecondLife(); - Master = master; - GroupID = LLUUID.Zero; Client.Objects.RequestAllObjects = true; bodyRotation = LLQuaternion.Identity; diff --git a/libsecondlife-cs/examples/TestTool/TestTool.csproj b/libsecondlife-cs/examples/TestTool/TestTool.csproj index 28e0c4a9..3751b785 100644 --- a/libsecondlife-cs/examples/TestTool/TestTool.csproj +++ b/libsecondlife-cs/examples/TestTool/TestTool.csproj @@ -34,6 +34,7 @@ +