The CommandLine class didn't construct command lines with empty arguments correctly (these need to be passed as empty quotes to be recognizable as an argument), fixed; added unit tests that exposes this bug; some minor cosmetic fixes

git-svn-id: file:///srv/devel/repo-conversion/nusu@154 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2009-06-24 19:20:26 +00:00
parent ce9a6bc932
commit 839d46ecf1
6 changed files with 100 additions and 28 deletions

View File

@ -584,7 +584,7 @@ namespace Nuclex.Support.Parsing {
/// </summary> /// </summary>
[Test] [Test]
public void TestCommandLineFormatting() { public void TestCommandLineFormatting() {
CommandLine commandLine = new CommandLine(); CommandLine commandLine = new CommandLine(true);
commandLine.AddValue("single"); commandLine.AddValue("single");
commandLine.AddValue("with space"); commandLine.AddValue("with space");
@ -623,6 +623,23 @@ namespace Nuclex.Support.Parsing {
} }
/// <summary>
/// Tests whether a command line can be built that contains empty arguments
/// </summary>
[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 } // namespace Nuclex.Support.Parsing

View File

@ -82,21 +82,40 @@ namespace Nuclex.Support.Parsing {
/// </remarks> /// </remarks>
public partial class CommandLine { public partial class CommandLine {
/// <summary>
/// Whether the command line should use Windows mode by default
/// </summary>
public static readonly bool WindowsModeDefault =
(Path.DirectorySeparatorChar == '\\');
/// <summary>Initializes a new command line</summary> /// <summary>Initializes a new command line</summary>
public CommandLine() : this(new List<Argument>()) { } public CommandLine() :
this(new List<Argument>(), WindowsModeDefault) { }
/// <summary>Initializes a new command line</summary>
/// <param name="windowsMode">Whether the / character initiates an argument</param>
public CommandLine(bool windowsMode) :
this(new List<Argument>(), windowsMode) { }
/// <summary>Initializes a new command line</summary> /// <summary>Initializes a new command line</summary>
/// <param name="argumentList">List containing the parsed arguments</param> /// <param name="argumentList">List containing the parsed arguments</param>
private CommandLine(List<Argument> argumentList) { private CommandLine(List<Argument> argumentList) :
this(argumentList, WindowsModeDefault) { }
/// <summary>Initializes a new command line</summary>
/// <param name="argumentList">List containing the parsed arguments</param>
/// <param name="windowsMode">Whether the / character initiates an argument</param>
private CommandLine(List<Argument> argumentList, bool windowsMode) {
this.arguments = argumentList; this.arguments = argumentList;
this.windowsMode = windowsMode;
} }
/// <summary>Parses the command line arguments from the provided string</summary> /// <summary>Parses the command line arguments from the provided string</summary>
/// <param name="commandLineString">String containing the command line arguments</param> /// <param name="commandLineString">String containing the command line arguments</param>
/// <returns>The parsed command line</returns> /// <returns>The parsed command line</returns>
/// <remarks> /// <remarks>
/// You should always pass Environment.CommandLine to this methods to avoid /// You should always pass Environment.CommandLine to this method 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') /// (which splits '--test"hello world"/v' into '--testhello world/v')
/// </remarks> /// </remarks>
public static CommandLine Parse(string commandLineString) { public static CommandLine Parse(string commandLineString) {
@ -110,7 +129,7 @@ namespace Nuclex.Support.Parsing {
/// <returns>The parsed command line</returns> /// <returns>The parsed command line</returns>
/// <remarks> /// <remarks>
/// You should always pass Environment.CommandLine to this methods to avoid /// 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') /// (which splits '--test"hello world"/v' into '--testhello world/v')
/// </remarks> /// </remarks>
public static CommandLine Parse(string commandLineString, bool windowsMode) { public static CommandLine Parse(string commandLineString, bool windowsMode) {
@ -129,24 +148,24 @@ namespace Nuclex.Support.Parsing {
/// <summary>Adds a value to the command line</summary> /// <summary>Adds a value to the command line</summary>
/// <param name="value">Value that will be added</param> /// <param name="value">Value that will be added</param>
public void AddValue(string value) { public void AddValue(string value) {
bool valueContainsSpaces = (value.IndexOfAny(new char[] { ' ', '\t' }) != -1); int valueLength = (value != null) ? value.Length : 0;
if(valueContainsSpaces) { if(requiresQuotes(value)) {
StringBuilder builder = new StringBuilder(value.Length + 2); StringBuilder builder = new StringBuilder(valueLength + 2);
builder.Append('"'); builder.Append('"');
builder.Append(value); builder.Append(value);
builder.Append('"'); builder.Append('"');
this.arguments.Add( this.arguments.Add(
Argument.ValueOnly( Argument.ValueOnly(
new StringSegment(builder.ToString(), 0, value.Length + 2), new StringSegment(builder.ToString(), 0, valueLength + 2),
1, 1,
value.Length valueLength
) )
); );
} else { } else {
this.arguments.Add( 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 {
/// <param name="initiator">Initiator that will be used to start the option</param> /// <param name="initiator">Initiator that will be used to start the option</param>
/// <param name="name">Name of the option that will be added</param> /// <param name="name">Name of the option that will be added</param>
public void AddOption(string initiator, string name) { public void AddOption(string initiator, string name) {
StringBuilder builder = new StringBuilder( StringBuilder builder = new StringBuilder(initiator.Length + name.Length);
initiator.Length + name.Length
);
builder.Append(initiator); builder.Append(initiator);
builder.Append(name); builder.Append(name);
@ -188,10 +205,9 @@ namespace Nuclex.Support.Parsing {
/// <param name="name">Name of the option that will be added</param> /// <param name="name">Name of the option that will be added</param>
/// <param name="value">Value that will be assigned to the option</param> /// <param name="value">Value that will be assigned to the option</param>
public void AddAssignment(string initiator, string name, string value) { 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( StringBuilder builder = new StringBuilder(
initiator.Length + name.Length + 1 + value.Length + initiator.Length + name.Length + 1 + value.Length + (valueContainsSpaces ? 2 : 0)
(valueContainsSpaces ? 2 : 0)
); );
builder.Append(initiator); builder.Append(initiator);
builder.Append(name); builder.Append(name);
@ -209,8 +225,7 @@ namespace Nuclex.Support.Parsing {
new StringSegment(builder.ToString()), new StringSegment(builder.ToString()),
initiator.Length, initiator.Length,
name.Length, name.Length,
initiator.Length + name.Length + 1 + initiator.Length + name.Length + 1 + (valueContainsSpaces ? 1 : 0),
(valueContainsSpaces ? 1 : 0),
value.Length value.Length
) )
); );
@ -242,8 +257,50 @@ namespace Nuclex.Support.Parsing {
get { return this.arguments; } get { return this.arguments; }
} }
/// <summary>
/// Determines whether the string requires quotes to survive the command line
/// </summary>
/// <param name="value">Value that will be checked for requiring quotes</param>
/// <returns>True if the value requires quotes to survive the command line</returns>
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;
}
/// <summary>
/// Determines whether the string contains any whitespace characters
/// </summary>
/// <param name="value">String that will be scanned for whitespace characters</param>
/// <returns>True if the provided string contains whitespace characters</returns>
private static bool containsWhitespace(string value) {
return
(value.IndexOf(' ') != -1) ||
(value.IndexOf('\t') != -1);
}
/// <summary>Options that were specified on the command line</summary> /// <summary>Options that were specified on the command line</summary>
private List<Argument> arguments; private List<Argument> arguments;
/// <summary>Whether the / character initiates an argument</summary>
private bool windowsMode;
} }

View File

@ -101,6 +101,7 @@ namespace Nuclex.Support.Plugins {
Trace.WriteLine("Could not employ " + type.ToString() + ": " + exception.Message); Trace.WriteLine("Could not employ " + type.ToString() + ": " + exception.Message);
} }
} }
} }
/// <summary> /// <summary>

View File

@ -43,18 +43,14 @@ namespace Nuclex.Support.Scheduling {
/// <param name="inner">Preceding exception that has caused this exception</param> /// <param name="inner">Preceding exception that has caused this exception</param>
public AbortedException(string message, Exception inner) : base(message, inner) { } public AbortedException(string message, Exception inner) : base(message, inner) { }
#if !COMPACTFRAMEWORK
/// <summary>Initializes the exception from its serialized state</summary> /// <summary>Initializes the exception from its serialized state</summary>
/// <param name="info">Contains the serialized fields of the exception</param> /// <param name="info">Contains the serialized fields of the exception</param>
/// <param name="context">Additional environmental informations</param> /// <param name="context">Additional environmental informations</param>
protected AbortedException( protected AbortedException(
System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context System.Runtime.Serialization.StreamingContext context
) ) :
: base(info, context) { } base(info, context) { }
#endif // !COMPACTFRAMEWORK
} }

View File

@ -77,6 +77,7 @@ namespace Nuclex.Support.Scheduling {
public GenericTimeSource(bool useStopwatch) { public GenericTimeSource(bool useStopwatch) {
this.useStopwatch = useStopwatch; this.useStopwatch = useStopwatch;
// Update the lastCheckedTime and lastCheckedTicks fields
checkForTimeAdjustment(); checkForTimeAdjustment();
} }
@ -96,7 +97,7 @@ namespace Nuclex.Support.Scheduling {
// See whether the system date/time have been adjusted while we were asleep. // See whether the system date/time have been adjusted while we were asleep.
checkForTimeAdjustment(); checkForTimeAdjustment();
// Now tell the caller whether his even was signalled // Now tell the caller whether his event was signalled
return signalled; return signalled;
} }

View File

@ -44,7 +44,7 @@ namespace Nuclex.Support.Scheduling {
/// </returns> /// </returns>
/// <remarks> /// <remarks>
/// Depending on whether the system will provide notifications when date/time /// 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 /// less than the indicated time before returning a timeout in order to give
/// the caller a chance to recheck the system time. /// the caller a chance to recheck the system time.
/// </remarks> /// </remarks>