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 {