diff --git a/Nuclex.Support (PC).csproj b/Nuclex.Support (PC).csproj index 4568858..462e7c5 100644 --- a/Nuclex.Support (PC).csproj +++ b/Nuclex.Support (PC).csproj @@ -179,6 +179,15 @@ SimpleRectanglePacker.Test SimpleRectanglePacker.cs + + false + CommandLineParser + + + false + CommandLineParser.Test + CommandLineParser.cs + false PathHelper @@ -308,7 +317,6 @@ - diff --git a/Source/Parsing/CommandLineParser.Test.cs b/Source/Parsing/CommandLineParser.Test.cs new file mode 100644 index 0000000..c5cb11d --- /dev/null +++ b/Source/Parsing/CommandLineParser.Test.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Text; + +#if UNITTEST + +using NUnit.Framework; + +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 TestPlainArguments() { + Assert.AreEqual( + true.ToString(), + new CommandLineParser(new string[] { "-hello" })["hello"], + "Argument with minus sign is recognized" + ); + Assert.AreEqual( + true.ToString(), + new CommandLineParser(new string[] { "--hello" })["hello"], + "Argument with double minus sign is recognized" + ); + Assert.AreEqual( + true.ToString(), + new CommandLineParser(new string[] { "/hello" })["hello"], + "Argument with slash is recognized" + ); + } + + /// Validates that argument assignments are working + [Test] + public void TestAssignments() { + 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" + ); + } + + } + +} // namespace Nuclex.Support.Parsing + +#endif // UNITTEST \ No newline at end of file diff --git a/Source/Parsing/CommandLineParser.cs b/Source/Parsing/CommandLineParser.cs new file mode 100644 index 0000000..5d788cf --- /dev/null +++ b/Source/Parsing/CommandLineParser.cs @@ -0,0 +1,157 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2007 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; + +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(); + + 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; + } + + // 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, true.ToString()); + + // 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, true.ToString()); + + 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, true.ToString()); + } + } + } + + /// 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]; } + } + + /// + /// 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; + + } + +} // namespace Nuclex.Support.Parsing