Skip to content

Commit

Permalink
Fixed: bug with nested " marks inside " for command shell line
Browse files Browse the repository at this point in the history
  • Loading branch information
p3nGu1nZz committed Apr 7, 2024
1 parent c570743 commit b452f57
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Assets/CommandTerminal/CommandShell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void Run(string line)

while (_remaining != "")
{
var _arg = CommandUtils.EatArgument(ref _remaining);
var _arg = CommandUtils.ParseCommand(ref _remaining);

if (_arg.String != "")
{
Expand Down
44 changes: 39 additions & 5 deletions Assets/CommandTerminal/CommandUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,55 @@ public static Dictionary<string, MethodInfo> CacheCommandMethods()
return methodDictionary;
}

public static CommandArg EatArgument(ref string s)
public static CommandArg ParseCommand(ref string s)
{
var arg = new CommandArg();
int space_index = s.IndexOf(' ');
if (space_index >= 0)
s = s.Trim();

char[] quoteChars = { '\"', '\'' };

foreach (var quoteChar in quoteChars)
{
if (s.StartsWith(quoteChar.ToString()))
{
int quoteIndex = FindClosingQuote(s, quoteChar);
if (quoteIndex >= 0)
{
arg.String = UnescapedQuotes(s.Substring(1, quoteIndex - 1), quoteChar);
s = s.Substring(quoteIndex + 1).Trim();
return arg;
}
}
}

int spaceIndex = s.IndexOf(' ');
if (spaceIndex >= 0)
{
arg.String = s.Substring(0, space_index);
s = s.Substring(space_index + 1);
arg.String = s.Substring(0, spaceIndex);
s = s.Substring(spaceIndex + 1).Trim();
}
else
{
arg.String = s;
s = "";
}

return arg;
}

public static int FindClosingQuote(string s, char quoteChar)
{
int quoteIndex = s.IndexOf(quoteChar, 1);
while (quoteIndex > 0 && s[quoteIndex - 1] == '\\')
{
quoteIndex = s.IndexOf(quoteChar, quoteIndex + 1);
}
return quoteIndex;
}

public static string UnescapedQuotes(string s, char quoteChar)
{
return s.Replace("\\" + quoteChar, quoteChar.ToString());
}
}
}
145 changes: 145 additions & 0 deletions Tests/CommandArgTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using CommandTerminal;

namespace DialogosEngine.Tests
{
[TestFixture]
public static class CommandArgTests
{
[Test]
public static void EatArgument_GivenString_ReturnsCorrectCommandArgAndRemainingString()
{
// Arrange
string input = "echo \"Hello World\"";
TestContext.WriteLine($"Testing with input string: '{input}'.");

// Act
CommandArg result = CommandUtils.ParseCommand(ref input);
TestContext.WriteLine($"Resulting CommandArg: '{result.String}' and remaining string: '{input}'");

// Assert
Assert.IsNotNull(result);
Assert.That(result.String, Is.EqualTo("echo"), "The CommandArg should contain the command 'echo'.");
Assert.That(input, Is.EqualTo("\"Hello World\""), "The remaining string should contain the quoted argument.");
TestContext.WriteLine($"Test passed: Input string '{input}' is correctly processed into CommandArg and remaining string.");
}

[Test]
public static void EatArgument_MultipleArgumentsWithAndWithoutQuotes_ReturnsCorrectCommandArgsAndRemainingString()
{
// Arrange
string input = "print \"Hello World\" \"Another \\\"quoted\\\" string\" unquoted";
TestContext.WriteLine($"Testing with input string: '{input}'.");

// Act
CommandArg firstArg = CommandUtils.ParseCommand(ref input);
CommandArg secondArg = CommandUtils.ParseCommand(ref input);
CommandArg thirdArg = CommandUtils.ParseCommand(ref input);
CommandArg fourthArg = CommandUtils.ParseCommand(ref input);

// Assert
Assert.IsNotNull(firstArg);
Assert.IsNotNull(secondArg);
Assert.IsNotNull(thirdArg);
Assert.IsNotNull(fourthArg);

Assert.That(firstArg.String, Is.EqualTo("print"), "The first CommandArg should contain the command 'print'.");
Assert.That(secondArg.String, Is.EqualTo("Hello World"), "The second CommandArg should contain the quoted string 'Hello World'.");
Assert.That(thirdArg.String, Is.EqualTo("Another \"quoted\" string"), "The third CommandArg should contain the quoted string with an escaped quote.");
Assert.That(fourthArg.String, Is.EqualTo("unquoted"), "The fourth CommandArg should contain the unquoted string 'unquoted'.");

Assert.That(input, Is.Empty, "The remaining string should be empty after processing all arguments.");

TestContext.WriteLine($"Test passed: Input string '{input}' is correctly processed into CommandArgs and remaining string.");
}

[Test]
public static void EatArgument_ComplexArgumentsWithNestedQuotesAndEscapedCharacters_ReturnsCorrectCommandArgsAndRemainingString()
{
// Arrange
string input = "complex \"Nested \\\"quotes\\\" and 'single quotes'\" 'Escaped \\'single\\' quotes' \"Mixed \\\"quotes\\\" 'and' single\" trailing";
TestContext.WriteLine($"Testing with input string: '{input}'.");

// Act
CommandArg firstArg = CommandUtils.ParseCommand(ref input);
CommandArg secondArg = CommandUtils.ParseCommand(ref input);
CommandArg thirdArg = CommandUtils.ParseCommand(ref input);
CommandArg fourthArg = CommandUtils.ParseCommand(ref input);
CommandArg fifthArg = CommandUtils.ParseCommand(ref input);

// Assert
Assert.IsNotNull(firstArg);
Assert.IsNotNull(secondArg);
Assert.IsNotNull(thirdArg);
Assert.IsNotNull(fourthArg);
Assert.IsNotNull(fifthArg);

Assert.That(firstArg.String, Is.EqualTo("complex"), "The first CommandArg should contain the command 'complex'.");
Assert.That(secondArg.String, Is.EqualTo("Nested \"quotes\" and 'single quotes'"), "The second CommandArg should contain the nested quoted string.");
Assert.That(thirdArg.String, Is.EqualTo("Escaped 'single' quotes"), "The third CommandArg should contain the escaped single quoted string.");
Assert.That(fourthArg.String, Is.EqualTo("Mixed \"quotes\" 'and' single"), "The fourth CommandArg should contain the mixed quoted string.");
Assert.That(fifthArg.String, Is.EqualTo("trailing"), "The fifth CommandArg should contain the unquoted string 'trailing'.");

Assert.That(input, Is.Empty, "The remaining string should be empty after processing all arguments.");

TestContext.WriteLine($"Test passed: Input string '{input}' is correctly processed into CommandArgs and remaining string.");
}

[Test]
public static void FindClosingQuote_WithEscapedAndUnescapedQuotes_ReturnsCorrectIndex()
{
// Arrange
string input = "\"This is a \\\"test\\\" string\"";
char quoteChar = '\"';
int expectedIndex = input.Length - 1;

// Act
int result = CommandUtils.FindClosingQuote(input, quoteChar);

// Assert
Assert.AreEqual(expectedIndex, result, "The index of the closing quote should be at the end of the string.");
}

[Test]
public static void FindClosingQuote_WithNoClosingQuote_ReturnsNegativeOne()
{
// Arrange
string input = "\"No closing quote here";
char quoteChar = '\"';

// Act
int result = CommandUtils.FindClosingQuote(input, quoteChar);

// Assert
Assert.That(result, Is.EqualTo(-1), "The result should be -1 indicating no closing quote was found.");
}

[Test]
public static void UnescapedQuotes_WithEscapedQuotes_ReturnsUnescapedString()
{
// Arrange
string input = "Escaped \\\"quotes\\\" here";
char quoteChar = '\"';
string expected = "Escaped \"quotes\" here";

// Act
string result = CommandUtils.UnescapedQuotes(input, quoteChar);

// Assert
Assert.That(result, Is.EqualTo(expected), "The escaped quotes should be unescaped in the result string.");
}

[Test]
public static void UnescapedQuotes_WithNoEscapedQuotes_ReturnsOriginalString()
{
// Arrange
string input = "No escaped quotes here";
char quoteChar = '\"';

// Act
string result = CommandUtils.UnescapedQuotes(input, quoteChar);

// Assert
Assert.That(result, Is.EqualTo(input), "The result should be the same as the input string when there are no escaped quotes.");
}
}
}

0 comments on commit b452f57

Please sign in to comment.