diff --git a/Source/Parsing/CommandLine.Test.cs b/Source/Parsing/CommandLine.Test.cs index 3e4e440..375747a 100644 --- a/Source/Parsing/CommandLine.Test.cs +++ b/Source/Parsing/CommandLine.Test.cs @@ -584,7 +584,7 @@ namespace Nuclex.Support.Parsing { /// [Test] public void TestCommandLineFormatting() { - CommandLine commandLine = new CommandLine(); + CommandLine commandLine = new CommandLine(true); commandLine.AddValue("single"); commandLine.AddValue("with space"); @@ -623,6 +623,23 @@ namespace Nuclex.Support.Parsing { } + /// + /// Tests whether a command line can be built that contains empty arguments + /// + [Test] + public void TestNullArgumentFormatting() { + CommandLine commandLine = new CommandLine(false); + + commandLine.AddValue(string.Empty); + commandLine.AddValue("hello"); + commandLine.AddValue(null); + commandLine.AddValue("-test"); + + Assert.AreEqual(4, commandLine.Arguments.Count); + string commandLineString = commandLine.ToString(); + Assert.AreEqual("\"\" hello \"\" \"-test\"", commandLineString); + } + } } // namespace Nuclex.Support.Parsing diff --git a/Source/Parsing/CommandLine.cs b/Source/Parsing/CommandLine.cs index 0e9d45a..65fc48f 100644 --- a/Source/Parsing/CommandLine.cs +++ b/Source/Parsing/CommandLine.cs @@ -82,21 +82,40 @@ namespace Nuclex.Support.Parsing { /// public partial class CommandLine { + /// + /// Whether the command line should use Windows mode by default + /// + public static readonly bool WindowsModeDefault = + (Path.DirectorySeparatorChar == '\\'); + /// Initializes a new command line - public CommandLine() : this(new List()) { } + public CommandLine() : + this(new List(), WindowsModeDefault) { } + + /// Initializes a new command line + /// Whether the / character initiates an argument + public CommandLine(bool windowsMode) : + this(new List(), windowsMode) { } /// Initializes a new command line /// List containing the parsed arguments - private CommandLine(List argumentList) { + private CommandLine(List argumentList) : + this(argumentList, WindowsModeDefault) { } + + /// Initializes a new command line + /// List containing the parsed arguments + /// Whether the / character initiates an argument + private CommandLine(List argumentList, bool windowsMode) { this.arguments = argumentList; + this.windowsMode = windowsMode; } /// 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 + /// You should always pass Environment.CommandLine to this method to avoid + /// some problems with the built-in command line tokenizer in .NET /// (which splits '--test"hello world"/v' into '--testhello world/v') /// public static CommandLine Parse(string commandLineString) { @@ -110,7 +129,7 @@ namespace Nuclex.Support.Parsing { /// 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 + /// some problems with the built-in command line tokenizer in .NET /// (which splits '--test"hello world"/v' into '--testhello world/v') /// public static CommandLine Parse(string commandLineString, bool windowsMode) { @@ -129,24 +148,24 @@ namespace Nuclex.Support.Parsing { /// Adds a value to the command line /// Value that will be added public void AddValue(string value) { - bool valueContainsSpaces = (value.IndexOfAny(new char[] { ' ', '\t' }) != -1); + int valueLength = (value != null) ? value.Length : 0; - if(valueContainsSpaces) { - StringBuilder builder = new StringBuilder(value.Length + 2); + if(requiresQuotes(value)) { + StringBuilder builder = new StringBuilder(valueLength + 2); builder.Append('"'); builder.Append(value); builder.Append('"'); this.arguments.Add( Argument.ValueOnly( - new StringSegment(builder.ToString(), 0, value.Length + 2), + new StringSegment(builder.ToString(), 0, valueLength + 2), 1, - value.Length + valueLength ) ); } else { this.arguments.Add( - Argument.ValueOnly(new StringSegment(value), 0, value.Length) + Argument.ValueOnly(new StringSegment(value), 0, valueLength) ); } } @@ -161,9 +180,7 @@ namespace Nuclex.Support.Parsing { /// Initiator that will be used to start the option /// Name of the option that will be added public void AddOption(string initiator, string name) { - StringBuilder builder = new StringBuilder( - initiator.Length + name.Length - ); + StringBuilder builder = new StringBuilder(initiator.Length + name.Length); builder.Append(initiator); builder.Append(name); @@ -188,10 +205,9 @@ namespace Nuclex.Support.Parsing { /// Name of the option that will be added /// Value that will be assigned to the option public void AddAssignment(string initiator, string name, string value) { - bool valueContainsSpaces = (value.IndexOfAny(new char[] { ' ', '\t' }) != -1); + bool valueContainsSpaces = containsWhitespace(value); StringBuilder builder = new StringBuilder( - initiator.Length + name.Length + 1 + value.Length + - (valueContainsSpaces ? 2 : 0) + initiator.Length + name.Length + 1 + value.Length + (valueContainsSpaces ? 2 : 0) ); builder.Append(initiator); builder.Append(name); @@ -209,8 +225,7 @@ namespace Nuclex.Support.Parsing { new StringSegment(builder.ToString()), initiator.Length, name.Length, - initiator.Length + name.Length + 1 + - (valueContainsSpaces ? 1 : 0), + initiator.Length + name.Length + 1 + (valueContainsSpaces ? 1 : 0), value.Length ) ); @@ -242,8 +257,50 @@ namespace Nuclex.Support.Parsing { get { return this.arguments; } } + /// + /// Determines whether the string requires quotes to survive the command line + /// + /// Value that will be checked for requiring quotes + /// True if the value requires quotes to survive the command line + private bool requiresQuotes(string value) { + + // If the value is empty, it needs quotes to become visible as an argument + // (versus being intepreted as spacing between other arguments) + if(string.IsNullOrEmpty(value)) { + return true; + } + + // Any whitespace characters force us to use quotes, so does a minus sign + // at the beginning of the value (otherwise, it would become an option argument) + bool requiresQuotes = + containsWhitespace(value) || + (value[0] == '-'); + + // On windows, option arguments can also be starten with the forward slash + // character, so we require quotes as well if the value starts with one + if(this.windowsMode) { + requiresQuotes |= (value[0] == '/'); + } + + return requiresQuotes; + + } + + /// + /// Determines whether the string contains any whitespace characters + /// + /// String that will be scanned for whitespace characters + /// True if the provided string contains whitespace characters + private static bool containsWhitespace(string value) { + return + (value.IndexOf(' ') != -1) || + (value.IndexOf('\t') != -1); + } + /// Options that were specified on the command line private List arguments; + /// Whether the / character initiates an argument + private bool windowsMode; } diff --git a/Source/Plugins/PluginHost.cs b/Source/Plugins/PluginHost.cs index 7e2cda9..395d4e9 100644 --- a/Source/Plugins/PluginHost.cs +++ b/Source/Plugins/PluginHost.cs @@ -101,6 +101,7 @@ namespace Nuclex.Support.Plugins { Trace.WriteLine("Could not employ " + type.ToString() + ": " + exception.Message); } } + } /// diff --git a/Source/Scheduling/AbortedException.cs b/Source/Scheduling/AbortedException.cs index 30f8dda..993f334 100644 --- a/Source/Scheduling/AbortedException.cs +++ b/Source/Scheduling/AbortedException.cs @@ -43,18 +43,14 @@ namespace Nuclex.Support.Scheduling { /// Preceding exception that has caused this exception public AbortedException(string message, Exception inner) : base(message, inner) { } -#if !COMPACTFRAMEWORK - /// Initializes the exception from its serialized state /// Contains the serialized fields of the exception /// Additional environmental informations protected AbortedException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context - ) - : base(info, context) { } - -#endif // !COMPACTFRAMEWORK + ) : + base(info, context) { } } diff --git a/Source/Scheduling/GenericTimeSource.cs b/Source/Scheduling/GenericTimeSource.cs index 47acab5..db27ec8 100644 --- a/Source/Scheduling/GenericTimeSource.cs +++ b/Source/Scheduling/GenericTimeSource.cs @@ -77,6 +77,7 @@ namespace Nuclex.Support.Scheduling { public GenericTimeSource(bool useStopwatch) { this.useStopwatch = useStopwatch; + // Update the lastCheckedTime and lastCheckedTicks fields checkForTimeAdjustment(); } @@ -96,7 +97,7 @@ namespace Nuclex.Support.Scheduling { // See whether the system date/time have been adjusted while we were asleep. checkForTimeAdjustment(); - // Now tell the caller whether his even was signalled + // Now tell the caller whether his event was signalled return signalled; } diff --git a/Source/Scheduling/ITimeSource.cs b/Source/Scheduling/ITimeSource.cs index beee0b7..d674b23 100644 --- a/Source/Scheduling/ITimeSource.cs +++ b/Source/Scheduling/ITimeSource.cs @@ -44,7 +44,7 @@ namespace Nuclex.Support.Scheduling { /// /// /// Depending on whether the system will provide notifications when date/time - /// is adjusted, the time source will be forced to let thid method block for + /// is adjusted, the time source will be forced to let this method block for /// less than the indicated time before returning a timeout in order to give /// the caller a chance to recheck the system time. ///