From 20addd36f2d25abf6bd075a016c6696cfd8ac92a Mon Sep 17 00:00:00 2001 From: Kumar Sahab Date: Fri, 27 Aug 2021 19:20:32 +0530 Subject: [PATCH 1/2] Fix #1385 Fix for issue #1385 Ignore whitespace inside the quote Ignore quotes if seeking == Boundary.WordEnd Handle edge cases --- .../Parsing/CommandLineStringSplitterTests.cs | 20 ++++ .../Parsing/CommandLineStringSplitter.cs | 113 +++++++++--------- 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs b/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs index 3f0c5d2616..59c3b71d65 100644 --- a/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs +++ b/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs @@ -75,5 +75,25 @@ public void It_handles_multiple_options_with_quoted_arguments() destination, "--verbose"); } + + [Fact] + public void Internal_quotes_do_not_cause_string_to_be_split() + { + var commandLine = @"POST --raw='{""Id"":1,""Name"":""Alice""}'"; + + _splitter.Split(commandLine) + .Should() + .BeEquivalentTo("POST", "--raw='{Id:1,Name:Alice}'"); + } + + [Fact] + public void Internal_whitespaces_are_preserved_and_do_not_cause_string_to_be_split() + { + var commandLine = @"command --raw='{""Id"":1,""Movie Name"":""The Three Musketeers""}'"; + + _splitter.Split(commandLine) + .Should() + .BeEquivalentTo("command", "--raw='{Id:1,Movie Name:The Three Musketeers}'"); + } } } diff --git a/src/System.CommandLine/Parsing/CommandLineStringSplitter.cs b/src/System.CommandLine/Parsing/CommandLineStringSplitter.cs index ccb91230f5..6febe7f199 100644 --- a/src/System.CommandLine/Parsing/CommandLineStringSplitter.cs +++ b/src/System.CommandLine/Parsing/CommandLineStringSplitter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using System.Text; namespace System.CommandLine.Parsing { @@ -13,6 +14,7 @@ private enum Boundary { TokenStart, WordEnd, + QuoteStart, QuoteEnd } @@ -25,7 +27,8 @@ public IEnumerable Split(string commandLine) var pos = 0; var seeking = Boundary.TokenStart; - int? skipQuoteAtIndex = null; + var seekingQuote = Boundary.QuoteStart; + StringBuilder sb = new StringBuilder(); while (pos < memory.Length) { @@ -33,44 +36,57 @@ public IEnumerable Split(string commandLine) if (char.IsWhiteSpace(c)) { - switch (seeking) + if (seekingQuote == Boundary.QuoteStart) { - case Boundary.WordEnd: - yield return CurrentToken(); - startTokenIndex = pos; - seeking = Boundary.TokenStart; - break; - - case Boundary.TokenStart: - startTokenIndex = pos; - break; - - case Boundary.QuoteEnd: - break; + switch (seeking) + { + case Boundary.WordEnd: + yield return CurrentToken(); + startTokenIndex = pos; + seeking = Boundary.TokenStart; + break; + + case Boundary.TokenStart: + startTokenIndex = pos; + break; + } } } else if (c == '\"') { - switch (seeking) + if (seeking == Boundary.TokenStart) { - case Boundary.QuoteEnd: - yield return CurrentToken(); - startTokenIndex = pos; - seeking = Boundary.TokenStart; - break; - - case Boundary.TokenStart: - startTokenIndex = pos + 1; - seeking = Boundary.QuoteEnd; - break; - - case Boundary.WordEnd: - seeking = Boundary.QuoteEnd; - skipQuoteAtIndex = pos; - break; + switch (seekingQuote) + { + case Boundary.QuoteEnd: + yield return CurrentToken(); + startTokenIndex = pos; + seekingQuote = Boundary.QuoteStart; + break; + + case Boundary.QuoteStart: + startTokenIndex = pos + 1; + seekingQuote = Boundary.QuoteEnd; + break; + } + } + else + { + switch (seekingQuote) + { + case Boundary.QuoteEnd: + seekingQuote = Boundary.QuoteStart; + AppendToken(); + break; + + case Boundary.QuoteStart: + seekingQuote = Boundary.QuoteEnd; + AppendToken(); + break; + } } } - else if (seeking == Boundary.TokenStart) + else if (seeking == Boundary.TokenStart && seekingQuote == Boundary.QuoteStart) { seeking = Boundary.WordEnd; startTokenIndex = pos; @@ -93,33 +109,22 @@ public IEnumerable Split(string commandLine) void Advance() => pos++; - string CurrentToken() + void AppendToken() { - if (skipQuoteAtIndex is null) - { - return memory.Slice( - startTokenIndex, - IndexOfEndOfToken()) - .ToString(); - } - else - { - var beforeQuote = memory.Slice( - startTokenIndex, - skipQuoteAtIndex.Value - startTokenIndex); - - var indexOfCharAfterQuote = skipQuoteAtIndex.Value + 1; - - var afterQuote = memory.Slice( - indexOfCharAfterQuote, - pos - skipQuoteAtIndex.Value - 1); - - skipQuoteAtIndex = null; + sb.Append(GetToken()); + startTokenIndex = pos + 1; + } - return $"{beforeQuote}{afterQuote}"; - } + string CurrentToken() + { + sb.Append(GetToken()); + var result = sb.ToString(); + sb.Clear(); + return result; } + string GetToken() => memory.Slice(startTokenIndex, IndexOfEndOfToken()).ToString(); + int IndexOfEndOfToken() => pos - startTokenIndex; bool IsAtEndOfInput() => pos == memory.Length; From f8fa3908d8a9d7998f25e0e53cc4a8da4081de90 Mon Sep 17 00:00:00 2001 From: Kumar Sahab Date: Sat, 28 Aug 2021 11:20:35 +0530 Subject: [PATCH 2/2] Update CommandLineStringSplitter.cs Optimizing string operations --- .../Parsing/CommandLineStringSplitter.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/System.CommandLine/Parsing/CommandLineStringSplitter.cs b/src/System.CommandLine/Parsing/CommandLineStringSplitter.cs index 6febe7f199..55f25e97a9 100644 --- a/src/System.CommandLine/Parsing/CommandLineStringSplitter.cs +++ b/src/System.CommandLine/Parsing/CommandLineStringSplitter.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using System.Text; namespace System.CommandLine.Parsing { @@ -28,7 +27,6 @@ public IEnumerable Split(string commandLine) var seeking = Boundary.TokenStart; var seekingQuote = Boundary.QuoteStart; - StringBuilder sb = new StringBuilder(); while (pos < memory.Length) { @@ -76,12 +74,10 @@ public IEnumerable Split(string commandLine) { case Boundary.QuoteEnd: seekingQuote = Boundary.QuoteStart; - AppendToken(); break; case Boundary.QuoteStart: seekingQuote = Boundary.QuoteEnd; - AppendToken(); break; } } @@ -109,22 +105,11 @@ public IEnumerable Split(string commandLine) void Advance() => pos++; - void AppendToken() - { - sb.Append(GetToken()); - startTokenIndex = pos + 1; - } - string CurrentToken() { - sb.Append(GetToken()); - var result = sb.ToString(); - sb.Clear(); - return result; + return memory.Slice(startTokenIndex, IndexOfEndOfToken()).ToString().Replace("\"", ""); } - string GetToken() => memory.Slice(startTokenIndex, IndexOfEndOfToken()).ToString(); - int IndexOfEndOfToken() => pos - startTokenIndex; bool IsAtEndOfInput() => pos == memory.Length;