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:
parent
ce9a6bc932
commit
839d46ecf1
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user