diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj index 18706a9..01d0ed6 100644 --- a/Nuclex.Support (Xbox 360).csproj +++ b/Nuclex.Support (Xbox 360).csproj @@ -157,10 +157,6 @@ CommandLine.cs - - - BrokenCommandLineParser.cs - CommandLine.cs diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj index d963aea..3661cbb 100644 --- a/Nuclex.Support.csproj +++ b/Nuclex.Support.csproj @@ -139,10 +139,6 @@ CommandLine.cs - - - BrokenCommandLineParser.cs - CommandLine.cs diff --git a/Source/Parsing/BrokenCommandLineParser.Test.cs b/Source/Parsing/BrokenCommandLineParser.Test.cs deleted file mode 100644 index 2e92c78..0000000 --- a/Source/Parsing/BrokenCommandLineParser.Test.cs +++ /dev/null @@ -1,147 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2009 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System; -using System.Collections.Generic; -using System.Text; - -#if UNITTEST - -using NUnit.Framework; - -#if ENABLE_BROKEN_COMMAND_LINE_PARSER // Too bugged. 100% test coverage not possible. - -namespace Nuclex.Support.Parsing { - - /// Ensures that the command line parser is working properly - [TestFixture] - public class CommandLineParserTest { - - /// Validates that normal arguments can be parsed - [Test] - public void TestArrayConstructorWithPlainArguments() { - Assert.IsTrue( - new CommandLineParser(new string[] { "-hello" }).HasArgument("hello"), - "Argument with minus sign is recognized" - ); - Assert.IsTrue( - new CommandLineParser(new string[] { "--hello" }).HasArgument("hello"), - "Argument with double minus sign is recognized" - ); - Assert.IsTrue( - new CommandLineParser(new string[] { "/hello" }).HasArgument("hello"), - "Argument with slash is recognized" - ); - } - - /// Validates that argument assignments are working - [Test] - public void TestArrayConstructorWithAssignments() { - Assert.AreEqual( - "world", - new CommandLineParser(new string[] { "-hello:world" })["hello"], - "Argument can be assigned with a double colon" - ); - Assert.AreEqual( - "world", - new CommandLineParser(new string[] { "-hello=world" })["hello"], - "Argument can be assigned with a equality sign" - ); - Assert.AreEqual( - "world", - new CommandLineParser(new string[] { "-hello", "world" })["hello"], - "Argument can be assigned with a space" - ); - } - - /// - /// Validates that loosely specified values are recognized by the parser - /// - [Test] - public void TestArrayConstructorWithLooseValues() { - Assert.IsTrue( - new CommandLineParser(new string[] { "hello" }).Values.Contains("hello"), - "Plain loose value is recognized" - ); - Assert.IsTrue( - new CommandLineParser(new string[] { "-hello:world", "foo" }).Values.Contains("foo"), - "Loose value following an assignment is recognized" - ); - } - - /// - /// Tests whether the parser can parse the processes current command line if - /// the default constructor is used - /// - [Test] - public void TestDefaultConstructor() { - new CommandLineParser(); - } - - /// - /// Tests whether the string constructor works for simple arguments being - /// specified on the command line - /// - [Test] - public void TestStringConstructorWithSimpleArguments() { - CommandLineParser parser = new CommandLineParser("argument1 argument2"); - Assert.AreEqual("argument1", parser.Values[0]); - Assert.AreEqual("argument2", parser.Values[1]); - } - - // TODO: This test fails!! -#if FAILED_TEST - /// - /// Bullshit - /// - [Test] - public void TestStringConstructorWithQuotedArguments() { - CommandLineParser parser = new CommandLineParser("\"this is a single argument\""); - Assert.AreEqual("this is a single argument", parser.Values[0]); - } -#endif - - /// - /// Tests whether the string constructor recognizes an unfinished argument - /// (that is, an argument that gets 'nothing' assigned) - /// - [Test] - public void TestStringConstructorWithUnfinishedAssignment() { - CommandLineParser parser = new CommandLineParser("--hello= --world="); - Assert.AreEqual(0, parser.Values.Count); - } - - /// - /// Tests whether the string constructor recognizes an argument with a space before - /// its assigned value - /// - [Test] - public void TestStringConstructorWithSpacedAssignment() { - CommandLineParser parser = new CommandLineParser("--hello= world"); - Assert.AreEqual(1, parser.Values.Count); - } - - } - -} // namespace Nuclex.Support.Parsing - -#endif // ENABLE_BROKEN_COMMAND_LINE_PARSER - -#endif // UNITTEST \ No newline at end of file diff --git a/Source/Parsing/BrokenCommandLineParser.cs b/Source/Parsing/BrokenCommandLineParser.cs deleted file mode 100644 index 5b37d55..0000000 --- a/Source/Parsing/BrokenCommandLineParser.cs +++ /dev/null @@ -1,185 +0,0 @@ -#region CPL License -/* -Nuclex Framework -Copyright (C) 2002-2009 Nuclex Development Labs - -This library is free software; you can redistribute it and/or -modify it under the terms of the IBM Common Public License as -published by the IBM Corporation; either version 1.0 of the -License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -IBM Common Public License for more details. - -You should have received a copy of the IBM Common Public -License along with this library -*/ -#endregion - -using System.Collections.Specialized; -using System.Text.RegularExpressions; - -#if ENABLE_BROKEN_COMMAND_LINE_PARSER // Too bugged. 100% test coverage not possible. - -namespace Nuclex.Support.Parsing { - - /// Parses an application's command line - /// - /// - /// Based on an article Richard Lopes published on "The Code Project" at - /// http://www.codeproject.com/csharp/command_line.asp - /// - /// - /// Valid forms for command line arguments: {-|/|--}param[{ |=|:}[{"|'}]value[{"|'}]] - /// - /// - /// - /// -param1 value1 - /// --param2 - /// /param3:"Test-:-work" - /// /param4=happy - /// -param5 '--=nice=--' - /// - /// - /// - public class CommandLineParser { - - /// - /// Initializes a new command line parser using the running program's command line - /// - public CommandLineParser() : this(System.Environment.CommandLine) { } - - /// Initializes a new command line parser - /// All supplied command line arguments as a single string - public CommandLineParser(string arguments) - : this(arguments.Split(new char[] { ' ', '\t' })) { } - - /// Initializes a new command line parser - /// Arguments that have been passed in the command line - public CommandLineParser(string[] arguments) { - this.arguments = new StringDictionary(); - this.values = new StringCollection(); - - string activeParameter = null; - - foreach(string argument in arguments) { - - // Look for arguments ('-', '/', '--') with their assignments ('=', ':') - string[] parts = splitter.Split(argument, 3); - switch(parts.Length) { - - // Value found without an argument being specified (eg. file name) - case 1: { - - if(activeParameter != null) { - if(!this.arguments.ContainsKey(activeParameter)) { - parts[0] = remover.Replace(parts[0], "$1"); - this.arguments.Add(activeParameter, parts[0]); - } - activeParameter = null; - } else { - this.values.Add(parts[0]); - } - - // Error: No argument is waiting for a value. Skip this argument. - break; - } - - // Found an argument with no value assignment - case 2: { - - // In case the previous argument is still waiting for a value we need to finish - // it up before switching to the argument we just found. - if(activeParameter != null) - if(!this.arguments.ContainsKey(activeParameter)) - this.arguments.Add(activeParameter, null); - - // Remember argument to allow for a later value assignment - activeParameter = parts[1]; - - break; - } - - // Found an argument with a proper assignment declaration - case 3: { - - // In case the previous argument is still waiting for a value we need to finish - // it up before switching to the argument we just found. - if(activeParameter != null) - if(!this.arguments.ContainsKey(activeParameter)) - this.arguments.Add(activeParameter, null); - - activeParameter = parts[1]; - - // Remove any quotes that might be enclosing this argument (",') - if(!this.arguments.ContainsKey(activeParameter)) { - parts[2] = remover.Replace(parts[2], "$1"); - this.arguments.Add(activeParameter, parts[2]); - } - - activeParameter = null; - - break; - } - } - } - - // In case the previous argument is still waiting for a value we need to finish - // it up before leaving the parsing method. - if(activeParameter != null) { - if(!this.arguments.ContainsKey(activeParameter)) { - this.arguments.Add(activeParameter, null); - } - } - } - - /// Returns the value of an argument by the argument's name - /// Name of the argument whose value will be returned - /// The value of the argument with the specified name - public string this[string argumentName] { - get { return this.arguments[argumentName]; } - } - - /// - /// Checks whether the specified argument was specified on the command line - /// - /// Name of the argument to check - /// True if the specified command was given on the command line - public bool HasArgument(string argumentName) { - return this.arguments.ContainsKey(argumentName); - } - - /// - /// Any values loosely specified on the command line without being assigned - /// to an argument. - /// - public StringCollection Values { - get { return this.values; } - } - - /// - /// Regular Expression used to split the arguments and their assigned values - /// - private static Regex splitter = - new Regex(@"^-{1,2}|^/|=|:", RegexOptions.IgnoreCase | RegexOptions.Compiled); - - /// - /// Regular Expression used to remove quotations around an argument's value - /// - private static Regex remover = - new Regex(@"^['""]?(.*?)['""]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); - - /// Stores the parsed arguments - private StringDictionary arguments; - /// - /// Stores any values passed on the command line without assigning an argument - /// - private StringCollection values; - - } - -} // namespace Nuclex.Support.Parsing - -#endif // ENABLE_BROKEN_COMMAND_LINE_PARSER diff --git a/Source/Parsing/CommandLine.Parser.cs b/Source/Parsing/CommandLine.Parser.cs index a889614..cfd6438 100644 --- a/Source/Parsing/CommandLine.Parser.cs +++ b/Source/Parsing/CommandLine.Parser.cs @@ -34,20 +34,35 @@ namespace Nuclex.Support.Parsing { /// Whether the / character initiates an argument private Parser(bool windowsMode) { this.windowsMode = windowsMode; - this.commandLine = new CommandLine(); + this.arguments = new List(); } /// Parses a string containing command line arguments /// String that will be parsed /// Whether the / character initiates an argument /// The parsed command line arguments from the string - public static CommandLine Parse(string commandLineString, bool windowsMode) { - Console.WriteLine("Parsing '" + commandLineString + "'"); + public static List Parse( + string commandLineString, bool windowsMode + ) { Parser theParser = new Parser(windowsMode); theParser.parseFullCommandLine(commandLineString); - return theParser.commandLine; + return theParser.arguments; } +#if ENABLE_TOKENIZED_COMMAND_LINE_PARSING // don't enable, it's broken! + /// Parses a string containing command line arguments + /// Command line tokens that will be parsed + /// Whether the / character initiates an argument + /// The parsed command line arguments from the string + public static List Parse( + string[] commandLineArguments, bool windowsMode + ) { + Parser theParser = new Parser(windowsMode); + theParser.parseSplitCommandLine(commandLineArguments); + return theParser.arguments; + } +#endif // ENABLE_TOKENIZED_COMMAND_LINE_PARSING + /// /// Parses the provided string and adds the parameters found to /// the command line representation @@ -79,6 +94,31 @@ namespace Nuclex.Support.Parsing { } } +#if ENABLE_TOKENIZED_COMMAND_LINE_PARSING // don't enable, it's broken! + /// + /// Parses the command line from a series pre-split argument tokens + /// + /// Split argument tokens that will be parsed + private void parseSplitCommandLine(string[] commandLineParts) { + if(commandLineParts == null) { + return; + } + + // Walk through the command line character by character and gather + // the parameters and values to build the command line representation from + for(int index = 0; index < commandLineParts.Length; ++index) { + + if(commandLineParts[index] != null) { + int characterIndex = 0; + parseChunk(commandLineParts[index], ref characterIndex); + + Debug.Assert(characterIndex == commandLineParts[index].Length); + } + + } + } +#endif // ENABLE_TOKENIZED_COMMAND_LINE_PARSING + /// /// Parses a chunk of characters and adds it as an option or a loose value to /// the command line representation we're building @@ -100,7 +140,7 @@ namespace Nuclex.Support.Parsing { // Does the string end here? Stop parsing. if(index >= commandLineString.Length) { - this.commandLine.addValue(new StringSegment(commandLineString, startIndex, 1)); + addValue(new StringSegment(commandLineString, startIndex, 1)); break; } @@ -153,12 +193,12 @@ namespace Nuclex.Support.Parsing { /// /// The number of characters consumed private void parsePotentialOption( - string commandLineString, int initiatorStartIndex, ref int index + string commandLineString, int initiatorStartIndex, ref int index ) { // If the string ends here this can only be considered as a loose value if(index == commandLineString.Length) { - this.commandLine.addValue( + addValue( new StringSegment( commandLineString, initiatorStartIndex, @@ -187,7 +227,7 @@ namespace Nuclex.Support.Parsing { index = commandLineString.Length; } - commandLine.addValue( + addValue( new StringSegment( commandLineString, initiatorStartIndex, index - initiatorStartIndex ) @@ -211,7 +251,7 @@ namespace Nuclex.Support.Parsing { /// /// Index at which the option name ended private void parsePotentialOptionAssignment( - string commandLineString, int initiatorStartIndex, int nameStartIndex, ref int index + string commandLineString, int initiatorStartIndex, int nameStartIndex, ref int index ) { int nameEndIndex = index; int valueStartIndex; @@ -245,7 +285,7 @@ namespace Nuclex.Support.Parsing { } int argumentLength = index - initiatorStartIndex; - this.commandLine.addArgument( + this.arguments.Add( new Argument( new StringSegment(commandLineString, initiatorStartIndex, argumentLength), nameStartIndex, nameEndIndex - nameStartIndex, @@ -264,7 +304,7 @@ namespace Nuclex.Support.Parsing { /// /// Index at which the option name ended private void parseOptionValue( - string commandLineString, int initiatorStartIndex, int nameStartIndex, ref int index + string commandLineString, int initiatorStartIndex, int nameStartIndex, ref int index ) { int nameEndIndex = index - 1; int valueStartIndex, valueEndIndex; @@ -308,7 +348,7 @@ namespace Nuclex.Support.Parsing { } int argumentLength = index - initiatorStartIndex; - this.commandLine.addArgument( + this.arguments.Add( new Argument( new StringSegment(commandLineString, initiatorStartIndex, argumentLength), nameStartIndex, nameEndIndex - nameStartIndex, @@ -329,14 +369,14 @@ namespace Nuclex.Support.Parsing { index = commandLineString.IndexOf(quoteCharacter, valueIndex); if(index == -1) { index = commandLineString.Length; // value ends at string end - commandLine.addArgument( + this.arguments.Add( Argument.ValueOnly( new StringSegment(commandLineString, startIndex, index - startIndex), valueIndex, index - valueIndex ) ); } else { // A closing quote was found - commandLine.addArgument( + this.arguments.Add( Argument.ValueOnly( new StringSegment(commandLineString, startIndex, index - startIndex + 1), valueIndex, index - valueIndex @@ -357,8 +397,14 @@ namespace Nuclex.Support.Parsing { index = commandLineString.Length; } - commandLine.addValue( - new StringSegment(commandLineString, startIndex, index - startIndex) + addValue(new StringSegment(commandLineString, startIndex, index - startIndex)); + } + + /// Adds a loose value to the command line + /// Value taht will be added + private void addValue(StringSegment value) { + this.arguments.Add( + Argument.ValueOnly(value, value.Offset, value.Count) ); } @@ -383,8 +429,8 @@ namespace Nuclex.Support.Parsing { /// Characters the parser considers to be whitespace private static readonly char[] WhitespaceCharacters = new char[] { ' ', '\t' }; - /// Command line currently being built by the parser - private CommandLine commandLine; + /// Argument list being filled by the parser + private List arguments; /// Whether the '/' character initiates an argument private bool windowsMode; diff --git a/Source/Parsing/CommandLine.Test.cs b/Source/Parsing/CommandLine.Test.cs index dac28bb..c53a38e 100644 --- a/Source/Parsing/CommandLine.Test.cs +++ b/Source/Parsing/CommandLine.Test.cs @@ -194,6 +194,14 @@ namespace Nuclex.Support.Parsing { #endregion // class ArgumentTest + /// Verifies that the default constructor is working + [Test] + public void TestDefaultConstructor() { + CommandLine commandLine = new CommandLine(); + + Assert.AreEqual(0, commandLine.Arguments.Count); + } + /// /// Validates that the parser can handle an argument initiator with an /// assignment that is missing a name @@ -302,7 +310,7 @@ namespace Nuclex.Support.Parsing { /// Validates that null can be parsed [Test] public void TestParseNull() { - CommandLine commandLine = CommandLine.Parse(null); + CommandLine commandLine = CommandLine.Parse((string)null); Assert.AreEqual(0, commandLine.Arguments.Count); } @@ -558,6 +566,19 @@ namespace Nuclex.Support.Parsing { Assert.AreEqual("//world", commandLine.Arguments[1].Value); } + /// + /// Tests whether the existence of named arguments can be checked + /// + [Test] + public void TestHasArgument() { + CommandLine test = CommandLine.Parse("/first:x /second:y /second:z third"); + + Assert.IsTrue(test.HasArgument("first")); + Assert.IsTrue(test.HasArgument("second")); + Assert.IsFalse(test.HasArgument("third")); + Assert.IsFalse(test.HasArgument("fourth")); + } + } } // namespace Nuclex.Support.Parsing diff --git a/Source/Parsing/CommandLine.cs b/Source/Parsing/CommandLine.cs index 651ec0d..54deb7e 100644 --- a/Source/Parsing/CommandLine.cs +++ b/Source/Parsing/CommandLine.cs @@ -21,6 +21,7 @@ License along with this library using System; using System.Collections.Generic; using System.IO; +using System.Text; using Nuclex.Support.Collections; @@ -82,55 +83,120 @@ namespace Nuclex.Support.Parsing { public partial class CommandLine { /// Initializes a new command line - public CommandLine() { - this.arguments = new List(); + public CommandLine() : this(new List()) { } + + /// Initializes a new command line + /// List containing the parsed arguments + private CommandLine(List argumentList) { + this.arguments = argumentList; } +#if ENABLE_TOKENIZED_COMMAND_LINE_PARSING // don't enable, it's broken! + /// Parses the command line arguments from the provided string + /// Command line tokens that will be parsed + /// The parsed command line + public static CommandLine Parse(string[] commandLineArguments) { + bool windowsMode = (Path.DirectorySeparatorChar != '/'); + return Parse(commandLineArguments, windowsMode); + } + + /// Parses the command line arguments from the provided string + /// Command line tokens that will be parsed + /// Whether the / character initiates an argument + /// The parsed command line + public static CommandLine Parse(string[] commandLineArguments, bool windowsMode) { + return new CommandLine( + Parser.Parse(commandLineArguments, windowsMode) + ); + } +#endif // ENABLE_TOKENIZED_COMMAND_LINE_PARSING + /// Parses the command line arguments from the provided string /// String containing the command line arguments /// The parsed command line + /// + /// You should always pass Environment.CommandLine to this methods to avoid + /// some problems with the build-in command line tokenizer in .NET + /// (which splits '--test"hello world"/v' into '--testhello world/v') + /// public static CommandLine Parse(string commandLineString) { bool windowsMode = (Path.DirectorySeparatorChar != '/'); - return Parser.Parse(commandLineString, windowsMode); + return Parse(commandLineString, windowsMode); } /// Parses the command line arguments from the provided string /// String containing the command line arguments /// Whether the / character initiates an argument /// The parsed command line + /// + /// You should always pass Environment.CommandLine to this methods to avoid + /// some problems with the build-in command line tokenizer in .NET + /// (which splits '--test"hello world"/v' into '--testhello world/v') + /// public static CommandLine Parse(string commandLineString, bool windowsMode) { - return Parser.Parse(commandLineString, windowsMode); - } - - #region To Be Removed - - /// Adds a loose value to the command line - /// Value taht will be added - internal void addValue(StringSegment value) { - /* - Console.WriteLine("Discovered loose value: '" + value.ToString() + "'"); - */ - - this.arguments.Add( - Argument.ValueOnly(value, value.Offset, value.Count) + return new CommandLine( + Parser.Parse(commandLineString, windowsMode) ); } - /// Adds an argument to the command line - /// Argument that will be added - internal void addArgument(Argument argument) { - /* - Console.WriteLine("Discovered option: '" + argument.Raw.ToString() + "'"); - Console.WriteLine(" Name: '" + argument.Name + "'"); - if(argument.Value != null) { - Console.WriteLine(" Value: '" + argument.Value + "'"); - } - */ - - this.arguments.Add(argument); + /// Returns whether an argument with the specified name exists + /// Name of the argument whose existence will be checked + /// True if an argument with the specified name exists + public bool HasArgument(string name) { + return (indexOfArgument(name) != -1); + } + +#if false + /// Retrieves the value of the specified argument + /// Name of the argument whose value will be retrieved + /// The value of the specified argument + public string GetValue(string name) { + int index = indexOfArgument(name); + if(index == -1) { + return null; + } + + // Does this argument have a value? + Argument argument = this.arguments[index]; + if(argument.Value != null) { + return argument.Value; + } else { // No, it might be a spaced argument + + // See if anything more follows this argument + ++index; + if(index < this.arguments.Count) { + + // If something follows the argument, and it is not an option of its own, + // use its value as the value for the preceding argument + argument = this.arguments[index]; + if(argument.Name == null) { + return argument.Value; + } + + } + + } + + // No argument found + return null; + } +#endif + + /// Retrieves the index of the argument with the specified name + /// Name of the argument whose index will be returned + /// + /// The index of the indicated argument of -1 if no argument with that name exists + /// + private int indexOfArgument(string name) { + for(int index = 0; index < this.arguments.Count; ++index) { + if(this.arguments[index].Name == name) { + return index; + } + } + + return -1; } - #endregion // To Be Removed /// Options that were specified on the command line public IList Arguments {