forked from Tyrrrz/CliFx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tyrrrz#13 - CommandLineSplitter utility, used to split input from pow…
…ershell
- Loading branch information
Showing
3 changed files
with
136 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using CliFx.Utils; | ||
using FluentAssertions; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace CliFx.Tests | ||
{ | ||
public class CommandLineSplitterSpecs | ||
{ | ||
[Theory] | ||
[InlineData("MyApp alpha beta", new string[] { "MyApp", "alpha", "beta" })] | ||
[InlineData("MyApp \"alpha with spaces\" \"beta with spaces\"", new string[] { "MyApp", "alpha with spaces", "beta with spaces" })] | ||
[InlineData("MyApp 'alpha with spaces' beta", new string[] { "MyApp", "'alpha", "with", "spaces'", "beta" })] | ||
[InlineData("MyApp \\\\\\alpha \\\\\\\\\"beta", new string[] { "MyApp", "\\\\\\alpha", "\\\\beta" })] | ||
[InlineData("MyApp \\\\\\\\\\\"alpha \\\"beta", new string[] { "MyApp", "\\\\\"alpha", "\"beta" })] | ||
public void Suggestion_service_can_emulate_GetCommandLineArgs(string input, string[] expected) | ||
{ | ||
var output = CommandLineSplitter.Split(input); | ||
output.Should().BeEquivalentTo(expected); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace CliFx.Utils | ||
{ | ||
internal static class CommandLineSplitter | ||
{ | ||
/// <summary> | ||
/// Reproduces Environment.GetCommandLineArgs() as per https://docs.microsoft.com/en-us/dotnet/api/system.environment.getcommandlineargs?view=net-5.0 | ||
/// | ||
/// Input at the command line Resulting command line arguments | ||
/// MyApp alpha beta MyApp, alpha, beta | ||
/// MyApp "alpha with spaces" "beta with spaces" MyApp, alpha with spaces, beta with spaces | ||
/// MyApp 'alpha with spaces' beta MyApp, 'alpha, with, spaces', beta | ||
/// MyApp \\\alpha \\\\"beta MyApp, \\\alpha, \\beta | ||
/// MyApp \\\\\"alpha \"beta MyApp, \\"alpha, "beta | ||
/// | ||
/// Used to parse autocomplete text as it is passed in as a single argument by Powershell | ||
/// | ||
/// </summary> | ||
public static string[] Split(string s) | ||
{ | ||
int escapeSequenceLength = 0; | ||
int escapeSequenceEnd = 0; | ||
bool ignoreSpaces = false; | ||
|
||
var tokens = new List<string>(); | ||
StringBuilder tokenBuilder = new StringBuilder(); | ||
|
||
for (int i = 0; i < s.Length; i++) | ||
{ | ||
// determine how long the escape character sequence is | ||
if (s[i] == '\\' && i > escapeSequenceEnd) | ||
{ | ||
for (int j = i; j < s.Length; j++) | ||
{ | ||
if (s[j] == '\\') | ||
{ | ||
continue; | ||
} | ||
else if (s[j] != '\"') | ||
{ | ||
// edge case: \\\alpha --> \\\alpha (no escape) | ||
escapeSequenceLength = 0; | ||
break; | ||
} | ||
|
||
escapeSequenceLength = j - i; | ||
|
||
// edge case: \\\\"beta -> \\beta | ||
// treat the " as an escape character so that we skip over it | ||
if (escapeSequenceLength == 4) | ||
{ | ||
escapeSequenceLength = 6; | ||
} | ||
else | ||
{ | ||
// capture the escaped character in our escape sequence | ||
escapeSequenceLength++; | ||
} | ||
|
||
escapeSequenceEnd = i + escapeSequenceLength; | ||
break; | ||
} | ||
} | ||
|
||
if (escapeSequenceLength > 0 && escapeSequenceLength % 2 == 0) | ||
{ | ||
// skip escape characters | ||
} | ||
else | ||
{ | ||
bool characterIsEscaped = escapeSequenceLength != 0; | ||
|
||
// edge case: '"' character is used to divide tokens eg: MyApp "alpha with spaces" "beta with spaces" | ||
// skip the '"' character | ||
if (!characterIsEscaped && s[i] == '"') | ||
{ | ||
ignoreSpaces = !ignoreSpaces; | ||
} | ||
// edge case: ' ' character is used to divide tokens | ||
else if (!characterIsEscaped && char.IsWhiteSpace(s[i]) && !ignoreSpaces) | ||
{ | ||
tokens.Add(tokenBuilder.ToString()); | ||
tokenBuilder.Clear(); | ||
} | ||
else | ||
{ | ||
tokenBuilder.Append(s[i]); | ||
} | ||
} | ||
|
||
if (escapeSequenceLength > 0) | ||
{ | ||
escapeSequenceLength--; | ||
} | ||
} | ||
|
||
var token = tokenBuilder.ToString(); | ||
if (!string.IsNullOrWhiteSpace(token)) | ||
{ | ||
tokens.Add(token); | ||
} | ||
return tokens.ToArray(); | ||
} | ||
} | ||
} |