From 0b59ad239759ddc15a838c509de37f44d7d19e10 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Mon, 7 Mar 2022 09:24:59 -0800 Subject: [PATCH 1/6] wip --- src/System.CommandLine/Parsing/Parser.cs | 4 ++- .../Parsing/StringExtensions.cs | 31 +++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/System.CommandLine/Parsing/Parser.cs b/src/System.CommandLine/Parsing/Parser.cs index 74262e8e62..72f5e61d19 100644 --- a/src/System.CommandLine/Parsing/Parser.cs +++ b/src/System.CommandLine/Parsing/Parser.cs @@ -44,7 +44,9 @@ public ParseResult Parse( IReadOnlyList arguments, string? rawInput = null) { - var tokenizeResult = arguments.Tokenize(Configuration); + var tokenizeResult = arguments.Tokenize( + Configuration, + inferRootCommand: rawInput is not null); var operation = new ParseOperation( tokenizeResult, diff --git a/src/System.CommandLine/Parsing/StringExtensions.cs b/src/System.CommandLine/Parsing/StringExtensions.cs index 2874054b8f..fa4bd58713 100644 --- a/src/System.CommandLine/Parsing/StringExtensions.cs +++ b/src/System.CommandLine/Parsing/StringExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; namespace System.CommandLine.Parsing { @@ -66,14 +67,18 @@ internal static (string? Prefix, string Alias) SplitPrefix(this string rawAlias) internal static TokenizeResult Tokenize( this IReadOnlyList args, - CommandLineConfiguration configuration) + CommandLineConfiguration configuration, + bool inferRootCommand = true) { var errorList = new List(); Command currentCommand = configuration.RootCommand; var foundDoubleDash = false; var foundEndOfDirectives = !configuration.EnableDirectives; - var argList = NormalizeRootCommand(configuration, args); + + List argList; + argList = NormalizeRootCommand(args, configuration.RootCommand, inferRootCommand); + var tokenList = new List(argList.Count); var knownTokens = configuration.RootCommand.ValidTokens(); @@ -308,8 +313,9 @@ void ReadResponseFile(string filePath, int i) } private static List NormalizeRootCommand( - CommandLineConfiguration commandLineConfiguration, - IReadOnlyList? args) + IReadOnlyList? args, + Command rootCommand, + bool inferRootCommand = true) { if (args is null) { @@ -331,23 +337,30 @@ private static List NormalizeRootCommand( // possible exception for illegal characters in path on .NET Framework } - if (potentialRootCommand != null && - commandLineConfiguration.RootCommand.HasAlias(potentialRootCommand)) + if (potentialRootCommand is not null && + rootCommand.HasAlias(potentialRootCommand)) { list.AddRange(args); return list; } } - var commandName = commandLineConfiguration.RootCommand.Name; + var commandName = rootCommand.Name; list.Add(commandName); int startAt = 0; - if (FirstArgMatchesRootCommand()) + if (inferRootCommand) { - startAt = 1; + if (FirstArgMatchesRootCommand()) + { + // startAt = 1; + } + } + else + { + // FIX: (NormalizeRootCommand) } for (var i = startAt; i < args.Count; i++) From f96f85104f2cbfc1d5df5121fe80047f75acb72d Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Fri, 11 Mar 2022 19:10:30 -0800 Subject: [PATCH 2/6] clarify tests --- src/System.CommandLine.Tests/ParserTests.cs | 29 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/System.CommandLine.Tests/ParserTests.cs b/src/System.CommandLine.Tests/ParserTests.cs index 429e9f698e..c2f9d4a536 100644 --- a/src/System.CommandLine.Tests/ParserTests.cs +++ b/src/System.CommandLine.Tests/ParserTests.cs @@ -792,7 +792,7 @@ public void A_root_command_can_be_omitted_from_the_parsed_args() } [Fact] - public void A_root_command_can_match_a_full_path_to_an_executable() + public void When_parsing_an_unsplit_string_then_input_a_full_path_to_an_executable_is_matched_by_the_root_command() { var command = new RootCommand { @@ -802,15 +802,15 @@ public void A_root_command_can_match_a_full_path_to_an_executable() } }; - ParseResult result1 = command.Parse("inner -x hello"); + var result1 = command.Parse("inner -x hello"); - ParseResult result2 = command.Parse($"{RootCommand.ExecutablePath} inner -x hello"); + var result2 = command.Parse($"{RootCommand.ExecutablePath} inner -x hello"); result1.Diagram().Should().Be(result2.Diagram()); } [Fact] - public void A_renamed_RootCommand_can_be_omitted_from_the_parsed_args() + public void When_parsing_an_unsplit_string_then_a_renamed_RootCommand_can_be_omitted_from_the_parsed_args() { var rootCommand = new RootCommand { @@ -828,6 +828,27 @@ public void A_renamed_RootCommand_can_be_omitted_from_the_parsed_args() result2.RootCommandResult.Command.Should().BeSameAs(result1.RootCommandResult.Command); result3.RootCommandResult.Command.Should().BeSameAs(result1.RootCommandResult.Command); } + + [Fact] + public void When_parsing_a_string_array_input_then_a_full_path_to_an_executable_is_not_matched_by_the_root_command() + { + var command = new RootCommand + { + new Command("inner") + { + new Option("-x") { Arity = ArgumentArity.ExactlyOne } + } + }; + + command.Parse(Split("inner -x hello")).Errors.Should().BeEmpty(); + + command.Parse(Split($"{RootCommand.ExecutablePath} inner -x hello")) + .Errors + .Should() + .ContainSingle(e => e.Message == $"{LocalizationResources.Instance.UnrecognizedCommandOrArgument(RootCommand.ExecutablePath)}"); + + string[] Split(string value) => CommandLineStringSplitter.Instance.Split(value).ToArray(); + } [Fact] public void Absolute_unix_style_paths_are_lexed_correctly() From a339ac4e30d2e225b86dae6e67fe2f4ac8f2c1ad Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Mon, 4 Apr 2022 13:13:22 -0700 Subject: [PATCH 3/6] wip --- src/System.CommandLine.Tests/ParserTests.cs | 16 ++++++++------- .../Parsing/StringExtensions.cs | 20 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/System.CommandLine.Tests/ParserTests.cs b/src/System.CommandLine.Tests/ParserTests.cs index c2f9d4a536..fc27154daf 100644 --- a/src/System.CommandLine.Tests/ParserTests.cs +++ b/src/System.CommandLine.Tests/ParserTests.cs @@ -806,19 +806,21 @@ public void When_parsing_an_unsplit_string_then_input_a_full_path_to_an_executab var result2 = command.Parse($"{RootCommand.ExecutablePath} inner -x hello"); + result2.RootCommandResult.Token.Value.Should().Be(RootCommand.ExecutablePath); result1.Diagram().Should().Be(result2.Diagram()); + } [Fact] public void When_parsing_an_unsplit_string_then_a_renamed_RootCommand_can_be_omitted_from_the_parsed_args() { var rootCommand = new RootCommand - { - new Command("inner") - { - new Option("-x") - } - }; + { + new Command("inner") + { + new Option("-x") + } + }; rootCommand.Name = "outer"; var result1 = rootCommand.Parse("inner -x hello"); @@ -836,7 +838,7 @@ public void When_parsing_a_string_array_input_then_a_full_path_to_an_executable_ { new Command("inner") { - new Option("-x") { Arity = ArgumentArity.ExactlyOne } + new Option("-x") } }; diff --git a/src/System.CommandLine/Parsing/StringExtensions.cs b/src/System.CommandLine/Parsing/StringExtensions.cs index fa4bd58713..2becc3c1ee 100644 --- a/src/System.CommandLine/Parsing/StringExtensions.cs +++ b/src/System.CommandLine/Parsing/StringExtensions.cs @@ -337,11 +337,19 @@ private static List NormalizeRootCommand( // possible exception for illegal characters in path on .NET Framework } - if (potentialRootCommand is not null && - rootCommand.HasAlias(potentialRootCommand)) + if (potentialRootCommand is not null) { - list.AddRange(args); - return list; + if (rootCommand.HasAlias(potentialRootCommand)) + { + list.AddRange(args); + return list; + } + + if (args[0] == RootCommand.ExecutablePath) + { + list.AddRange(args); + return list; + } } } @@ -358,10 +366,6 @@ private static List NormalizeRootCommand( // startAt = 1; } } - else - { - // FIX: (NormalizeRootCommand) - } for (var i = startAt; i < args.Count; i++) { From 717e1a3aaae1ce0693e1204a5ea651561d847816 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Wed, 6 Apr 2022 07:49:41 -0700 Subject: [PATCH 4/6] wip --- src/System.CommandLine/Parsing/StringExtensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/System.CommandLine/Parsing/StringExtensions.cs b/src/System.CommandLine/Parsing/StringExtensions.cs index 2becc3c1ee..fc024cdd34 100644 --- a/src/System.CommandLine/Parsing/StringExtensions.cs +++ b/src/System.CommandLine/Parsing/StringExtensions.cs @@ -345,6 +345,7 @@ private static List NormalizeRootCommand( return list; } + // FIX: (NormalizeRootCommand) if (args[0] == RootCommand.ExecutablePath) { list.AddRange(args); @@ -363,7 +364,7 @@ private static List NormalizeRootCommand( { if (FirstArgMatchesRootCommand()) { - // startAt = 1; + startAt = 1; } } @@ -373,7 +374,7 @@ private static List NormalizeRootCommand( } return list; - + bool FirstArgMatchesRootCommand() { if (potentialRootCommand is null) From 2244dcc4a260504f67dd7fda95a7371e14934a2f Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Thu, 7 Apr 2022 08:08:24 -0700 Subject: [PATCH 5/6] small test reorg --- .../ParserTests.RootCommandAndArg0.cs | 106 ++++++++++++++++++ src/System.CommandLine.Tests/ParserTests.cs | 84 +------------- .../Parsing/StringExtensions.cs | 35 +++--- 3 files changed, 128 insertions(+), 97 deletions(-) create mode 100644 src/System.CommandLine.Tests/ParserTests.RootCommandAndArg0.cs diff --git a/src/System.CommandLine.Tests/ParserTests.RootCommandAndArg0.cs b/src/System.CommandLine.Tests/ParserTests.RootCommandAndArg0.cs new file mode 100644 index 0000000000..993b8c2d10 --- /dev/null +++ b/src/System.CommandLine.Tests/ParserTests.RootCommandAndArg0.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.CommandLine.Parsing; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace System.CommandLine.Tests; + +public partial class ParserTests +{ + public partial class RootCommandAndArg0 + { + [Fact] + public void When_parsing_a_string_array_a_root_command_can_be_omitted_from_the_parsed_args() + { + var command = new Command("outer") + { + new Command("inner") + { + new Option("-x") + } + }; + + var result1 = command.Parse(Split("inner -x hello")); + var result2 = command.Parse(Split("outer inner -x hello")); + + result1.Diagram().Should().Be(result2.Diagram()); + } + + [Fact] + public void When_parsing_a_string_array_input_then_a_full_path_to_an_executable_is_not_matched_by_the_root_command() + { + var command = new RootCommand + { + new Command("inner") + { + new Option("-x") + } + }; + + command.Parse(Split("inner -x hello")).Errors.Should().BeEmpty(); + + command.Parse(Split($"{RootCommand.ExecutablePath} inner -x hello")) + .Errors + .Should() + .ContainSingle(e => e.Message == $"{LocalizationResources.Instance.UnrecognizedCommandOrArgument(RootCommand.ExecutablePath)}"); + } + + [Fact] + public void When_parsing_an_unsplit_string_a_root_command_can_be_omitted_from_the_parsed_args() + { + var command = new Command("outer") + { + new Command("inner") + { + new Option("-x") + } + }; + + var result1 = command.Parse("inner -x hello"); + var result2 = command.Parse("outer inner -x hello"); + + result1.Diagram().Should().Be(result2.Diagram()); + } + + [Fact] + public void When_parsing_an_unsplit_string_then_input_a_full_path_to_an_executable_is_matched_by_the_root_command() + { + var command = new RootCommand + { + new Command("inner") + { + new Option("-x") + } + }; + + var result2 = command.Parse($"{RootCommand.ExecutablePath} inner -x hello"); + + result2.RootCommandResult.Token.Value.Should().Be(RootCommand.ExecutablePath); + } + + [Fact] + public void When_parsing_an_unsplit_string_then_a_renamed_RootCommand_can_be_omitted_from_the_parsed_args() + { + var rootCommand = new RootCommand + { + new Command("inner") + { + new Option("-x") + } + }; + rootCommand.Name = "outer"; + + var result1 = rootCommand.Parse("inner -x hello"); + var result2 = rootCommand.Parse("outer inner -x hello"); + var result3 = rootCommand.Parse($"{RootCommand.ExecutableName} inner -x hello"); + + result2.RootCommandResult.Command.Should().BeSameAs(result1.RootCommandResult.Command); + result3.RootCommandResult.Command.Should().BeSameAs(result1.RootCommandResult.Command); + } + + string[] Split(string value) => CommandLineStringSplitter.Instance.Split(value).ToArray(); + } +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParserTests.cs b/src/System.CommandLine.Tests/ParserTests.cs index fc27154daf..f3ff47ca5a 100644 --- a/src/System.CommandLine.Tests/ParserTests.cs +++ b/src/System.CommandLine.Tests/ParserTests.cs @@ -16,7 +16,9 @@ namespace System.CommandLine.Tests { public partial class ParserTests - { + {public partial class RootCommandAndArg0 + { + } private readonly ITestOutputHelper _output; public ParserTests(ITestOutputHelper output) @@ -774,84 +776,6 @@ public void Subsequent_occurrences_of_tokens_matching_command_names_are_parsed_a completeResult.Tokens.Select(t => t.Value).Should().BeEquivalentTo("the-command"); } - [Fact] - public void A_root_command_can_be_omitted_from_the_parsed_args() - { - var command = new Command("outer") - { - new Command("inner") - { - new Option("-x") - } - }; - - var result1 = command.Parse("inner -x hello"); - var result2 = command.Parse("outer inner -x hello"); - - result1.Diagram().Should().Be(result2.Diagram()); - } - - [Fact] - public void When_parsing_an_unsplit_string_then_input_a_full_path_to_an_executable_is_matched_by_the_root_command() - { - var command = new RootCommand - { - new Command("inner") - { - new Option("-x") - } - }; - - var result1 = command.Parse("inner -x hello"); - - var result2 = command.Parse($"{RootCommand.ExecutablePath} inner -x hello"); - - result2.RootCommandResult.Token.Value.Should().Be(RootCommand.ExecutablePath); - result1.Diagram().Should().Be(result2.Diagram()); - - } - - [Fact] - public void When_parsing_an_unsplit_string_then_a_renamed_RootCommand_can_be_omitted_from_the_parsed_args() - { - var rootCommand = new RootCommand - { - new Command("inner") - { - new Option("-x") - } - }; - rootCommand.Name = "outer"; - - var result1 = rootCommand.Parse("inner -x hello"); - var result2 = rootCommand.Parse("outer inner -x hello"); - var result3 = rootCommand.Parse($"{RootCommand.ExecutableName} inner -x hello"); - - result2.RootCommandResult.Command.Should().BeSameAs(result1.RootCommandResult.Command); - result3.RootCommandResult.Command.Should().BeSameAs(result1.RootCommandResult.Command); - } - - [Fact] - public void When_parsing_a_string_array_input_then_a_full_path_to_an_executable_is_not_matched_by_the_root_command() - { - var command = new RootCommand - { - new Command("inner") - { - new Option("-x") - } - }; - - command.Parse(Split("inner -x hello")).Errors.Should().BeEmpty(); - - command.Parse(Split($"{RootCommand.ExecutablePath} inner -x hello")) - .Errors - .Should() - .ContainSingle(e => e.Message == $"{LocalizationResources.Instance.UnrecognizedCommandOrArgument(RootCommand.ExecutablePath)}"); - - string[] Split(string value) => CommandLineStringSplitter.Instance.Split(value).ToArray(); - } - [Fact] public void Absolute_unix_style_paths_are_lexed_correctly() { @@ -1604,7 +1528,7 @@ public void Tokens_are_not_split_if_the_part_before_the_delimiter_is_not_an_opti } [Fact] - public void A_subcommand_wont_overflow_when_checking_maximum_argument_capcity() + public void A_subcommand_wont_overflow_when_checking_maximum_argument_capacity() { // Tests bug identified in https://github.com/dotnet/command-line-api/issues/997 diff --git a/src/System.CommandLine/Parsing/StringExtensions.cs b/src/System.CommandLine/Parsing/StringExtensions.cs index fc024cdd34..90fbc7c390 100644 --- a/src/System.CommandLine/Parsing/StringExtensions.cs +++ b/src/System.CommandLine/Parsing/StringExtensions.cs @@ -328,28 +328,27 @@ private static List NormalizeRootCommand( if (args.Count > 0) { - try + if (inferRootCommand && + args[0] == RootCommand.ExecutablePath) { - potentialRootCommand = Path.GetFileName(args[0]); + list.AddRange(args); + return list; } - catch (ArgumentException) + else { - // possible exception for illegal characters in path on .NET Framework - } - - if (potentialRootCommand is not null) - { - if (rootCommand.HasAlias(potentialRootCommand)) + try { - list.AddRange(args); - return list; - } + potentialRootCommand = Path.GetFileName(args[0]); - // FIX: (NormalizeRootCommand) - if (args[0] == RootCommand.ExecutablePath) + if (rootCommand.HasAlias(potentialRootCommand)) + { + list.AddRange(args); + return list; + } + } + catch (ArgumentException) { - list.AddRange(args); - return list; + // possible exception for illegal characters in path on .NET Framework } } } @@ -360,9 +359,11 @@ private static List NormalizeRootCommand( int startAt = 0; + var firstArgMatchesRootCommand = FirstArgMatchesRootCommand(); + if (inferRootCommand) { - if (FirstArgMatchesRootCommand()) + if (firstArgMatchesRootCommand) { startAt = 1; } From 8b2814a3ad8a21d6dad62f9beb972524c52b58b9 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Thu, 7 Apr 2022 11:57:50 -0700 Subject: [PATCH 6/6] clean up NormalizeRootCommand --- .../Parsing/StringExtensions.cs | 42 ++----------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/src/System.CommandLine/Parsing/StringExtensions.cs b/src/System.CommandLine/Parsing/StringExtensions.cs index 90fbc7c390..370c17c2fa 100644 --- a/src/System.CommandLine/Parsing/StringExtensions.cs +++ b/src/System.CommandLine/Parsing/StringExtensions.cs @@ -324,8 +324,6 @@ private static List NormalizeRootCommand( var list = new List(); - string? potentialRootCommand = null; - if (args.Count > 0) { if (inferRootCommand && @@ -338,7 +336,7 @@ private static List NormalizeRootCommand( { try { - potentialRootCommand = Path.GetFileName(args[0]); + var potentialRootCommand = Path.GetFileName(args[0]); if (rootCommand.HasAlias(potentialRootCommand)) { @@ -353,48 +351,14 @@ private static List NormalizeRootCommand( } } - var commandName = rootCommand.Name; - - list.Add(commandName); - - int startAt = 0; + list.Add(rootCommand.Name); - var firstArgMatchesRootCommand = FirstArgMatchesRootCommand(); - - if (inferRootCommand) - { - if (firstArgMatchesRootCommand) - { - startAt = 1; - } - } - - for (var i = startAt; i < args.Count; i++) + for (var i = 0; i < args.Count; i++) { list.Add(args[i]); } return list; - - bool FirstArgMatchesRootCommand() - { - if (potentialRootCommand is null) - { - return false; - } - - if (potentialRootCommand.Equals($"{commandName}.dll", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - if (potentialRootCommand.Equals($"{commandName}.exe", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - return false; - } } private static string? GetResponseFileReference(this string arg) =>