From beb6cebb4b376a11bfd6794caa8ce0c3fc735fcb Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 6 Jul 2025 10:16:44 +1000 Subject: [PATCH] move to file scoped namespaces --- .../LocalizationTests.cs | 45 +- .../DotnetSuggestEndToEndTests.cs | 253 +- .../FileEnumeratorTests.cs | 67 +- .../FileSuggestionProviderTests.cs | 29 +- .../GlobalToolsSuggestionRegistrationTests.cs | 97 +- .../SuggestionDispatcherTests.cs | 349 ++- .../SuggestionRegistrationTest.cs | 147 +- .../SuggestionShellScriptHandlerTest.cs | 85 +- .../TestSuggestionRegistration.cs | 35 +- .../TestSuggestionRegistrationTests.cs | 11 +- .../CombineSuggestionRegistration.cs | 51 +- .../DotnetProfileDirectory.cs | 37 +- .../FileEnumerator.cs | 21 +- .../FileSuggestionRegistration.cs | 125 +- .../GlobalToolsSuggestionRegistration.cs | 87 +- .../ISuggestionRegistration.cs | 15 +- .../ISuggestionStore.cs | 12 +- .../PathExtensions.cs | 19 +- src/System.CommandLine.Suggest/Program.cs | 47 +- .../RegistrationPair.cs | 17 +- src/System.CommandLine.Suggest/ShellType.cs | 15 +- .../StringExtensions.cs | 11 +- .../SuggestionDispatcher.cs | 391 ++- .../SuggestionShellScriptException.cs | 23 +- .../SuggestionShellScriptHandler.cs | 65 +- .../SuggestionStore.cs | 96 +- .../Binding/TestModels.cs | 187 +- .../Binding/TypeConversionTests.cs | 1277 ++++---- src/System.CommandLine.Tests/CommandTests.cs | 515 ++-- .../CompletionContextTests.cs | 341 ++- .../CompletionTests.cs | 1671 ++++++----- .../DirectiveTests.cs | 339 ++- .../EnvironmentVariableDirectiveTests.cs | 239 +- .../GlobalOptionTests.cs | 179 +- .../Help/CustomHelpAction.cs | 45 +- .../Help/HelpBuilderExtensions.cs | 17 +- .../Help/HelpBuilderTests.Approval.cs | 133 +- .../Help/HelpBuilderTests.cs | 2469 ++++++++------- .../CancelOnProcessTerminationTests.cs | 155 +- .../Invocation/InvocationTests.cs | 539 ++-- .../Invocation/TypoCorrectionTests.cs | 311 +- .../OptionTests.MultipleArgumentsPerToken.cs | 329 +- src/System.CommandLine.Tests/OptionTests.cs | 829 +++--- .../ParseDiagramTests.cs | 169 +- .../ParseDirectiveTests.cs | 231 +- .../ParseResultTests.cs | 219 +- .../ParserTests.DoubleDash.cs | 105 +- .../ParserTests.MultipleArguments.cs | 499 ++-- .../ParserTests.MultiplePositions.cs | 283 +- src/System.CommandLine.Tests/ParserTests.cs | 2643 ++++++++--------- .../Parsing/CommandLineStringSplitterTests.cs | 169 +- .../ParsingValidationTests.cs | 2029 +++++++------ .../ResponseFileTests.cs | 601 ++-- .../RootCommandTests.cs | 17 +- .../SplitCommandLineTests.cs | 117 +- .../SuggestDirectiveTests.cs | 471 ++- .../UseExceptionHandlerTests.cs | 137 +- .../Utility/LinuxOnlyTheory.cs | 13 +- .../Utility/NonWindowsOnlyFactAttribute.cs | 13 +- .../Utility/ParseResultExtensions.cs | 35 +- .../Utility/ReleaseBuildOnlyFactAttribute.cs | 13 +- .../Utility/RemoteExecution.cs | 105 +- .../Utility/RemoteExecutor.cs | 307 +- .../Utility/WindowsOnlyFactAttribute.cs | 15 +- .../VersionOptionTests.cs | 297 +- src/System.CommandLine/AliasSet.cs | 55 +- src/System.CommandLine/Argument.cs | 209 +- src/System.CommandLine/ArgumentArity.cs | 243 +- src/System.CommandLine/ArgumentValidation.cs | 267 +- src/System.CommandLine/Argument{T}.cs | 189 +- .../Binding/ArgumentConversionResult.cs | 117 +- .../Binding/ArgumentConversionResultType.cs | 21 +- .../Binding/ArgumentConverter.cs | 327 +- .../Binding/TryConvertArgument.cs | 11 +- .../Binding/TypeExtensions.cs | 81 +- src/System.CommandLine/ChildSymbolList{T}.cs | 83 +- src/System.CommandLine/Command.cs | 515 ++-- .../CommandLineConfiguration.cs | 363 ++- .../CompletionSourceExtensions.cs | 79 +- .../Completions/CompletionContext.cs | 139 +- .../Completions/CompletionItem.cs | 141 +- src/System.CommandLine/ConsoleHelpers.cs | 33 +- src/System.CommandLine/Directive.cs | 53 +- .../EnumerableExtensions.cs | 43 +- .../EnvironmentVariablesDirective.cs | 81 +- src/System.CommandLine/Help/HelpAction.cs | 45 +- src/System.CommandLine/Help/HelpOption.cs | 79 +- .../Invocation/InvocationPipeline.cs | 215 +- src/System.CommandLine/Option.cs | 243 +- src/System.CommandLine/OptionValidation.cs | 87 +- src/System.CommandLine/Option{T}.cs | 123 +- .../ParseDiagramDirective.cs | 45 +- src/System.CommandLine/ParseResult.cs | 631 ++-- .../Parsing/ArgumentResult.cs | 321 +- .../Parsing/CommandLineParser.cs | 257 +- .../Parsing/CommandResult.cs | 257 +- .../Parsing/DirectiveResult.cs | 57 +- .../Parsing/OptionResult.cs | 103 +- .../Parsing/ParseDiagramAction.cs | 245 +- src/System.CommandLine/Parsing/ParseError.cs | 53 +- .../Parsing/ParseOperation.cs | 555 ++-- .../Parsing/StringExtensions.cs | 671 +++-- .../Parsing/SymbolResult.cs | 299 +- .../Parsing/SymbolResultExtensions.cs | 23 +- .../Parsing/SymbolResultTree.cs | 205 +- src/System.CommandLine/Parsing/Token.cs | 123 +- src/System.CommandLine/Parsing/TokenType.cs | 59 +- src/System.CommandLine/RootCommand.cs | 113 +- src/System.CommandLine/Symbol.cs | 135 +- src/System.CommandLine/SymbolNode.cs | 21 +- src/System.CommandLine/VersionOption.cs | 95 +- 111 files changed, 14105 insertions(+), 14218 deletions(-) diff --git a/src/System.CommandLine.ApiCompatibility.Tests/LocalizationTests.cs b/src/System.CommandLine.ApiCompatibility.Tests/LocalizationTests.cs index 3d730759b1..4c4c9e192d 100644 --- a/src/System.CommandLine.ApiCompatibility.Tests/LocalizationTests.cs +++ b/src/System.CommandLine.ApiCompatibility.Tests/LocalizationTests.cs @@ -2,36 +2,35 @@ using System.Linq; using Xunit; -namespace System.CommandLine.ApiCompatibility.Tests +namespace System.CommandLine.ApiCompatibility.Tests; + +public class LocalizationTests { - public class LocalizationTests + private const string CommandName = "the-command"; + + [Theory] + [InlineData("es", $"Falta el argumento requerido para el comando: '{CommandName}'.")] + [InlineData("en-US", $"Required argument missing for command: '{CommandName}'.")] + public void ErrorMessages_AreLocalized(string cultureName, string expectedMessage) { - private const string CommandName = "the-command"; + CultureInfo uiCultureBefore = CultureInfo.CurrentUICulture; - [Theory] - [InlineData("es", $"Falta el argumento requerido para el comando: '{CommandName}'.")] - [InlineData("en-US", $"Required argument missing for command: '{CommandName}'.")] - public void ErrorMessages_AreLocalized(string cultureName, string expectedMessage) + try { - CultureInfo uiCultureBefore = CultureInfo.CurrentUICulture; + CultureInfo.CurrentUICulture = new CultureInfo(cultureName); - try + Command command = new(CommandName) { - CultureInfo.CurrentUICulture = new CultureInfo(cultureName); - - Command command = new(CommandName) - { - new Argument("arg") - }; + new Argument("arg") + }; - ParseResult parseResult = command.Parse(CommandName); + ParseResult parseResult = command.Parse(CommandName); - Assert.Equal(expectedMessage, parseResult.Errors.Single().Message); - } - finally - { - CultureInfo.CurrentUICulture = uiCultureBefore; - } + Assert.Equal(expectedMessage, parseResult.Errors.Single().Message); + } + finally + { + CultureInfo.CurrentUICulture = uiCultureBefore; } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/DotnetSuggestEndToEndTests.cs b/src/System.CommandLine.Suggest.Tests/DotnetSuggestEndToEndTests.cs index 7e08fc8d65..cefdf39041 100644 --- a/src/System.CommandLine.Suggest.Tests/DotnetSuggestEndToEndTests.cs +++ b/src/System.CommandLine.Suggest.Tests/DotnetSuggestEndToEndTests.cs @@ -10,151 +10,150 @@ using static System.Environment; using Process = System.CommandLine.Tests.Utility.Process; -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +public class DotnetSuggestEndToEndTests : IDisposable { - public class DotnetSuggestEndToEndTests : IDisposable + private readonly ITestOutputHelper _output; + private readonly FileInfo _endToEndTestApp; + private readonly FileInfo _dotnetSuggest; + private readonly (string, string)[] _environmentVariables; + private readonly DirectoryInfo _dotnetHostDir = DotnetMuxer.Path.Directory; + private static string _testRoot; + + public DotnetSuggestEndToEndTests(ITestOutputHelper output) { - private readonly ITestOutputHelper _output; - private readonly FileInfo _endToEndTestApp; - private readonly FileInfo _dotnetSuggest; - private readonly (string, string)[] _environmentVariables; - private readonly DirectoryInfo _dotnetHostDir = DotnetMuxer.Path.Directory; - private static string _testRoot; - - public DotnetSuggestEndToEndTests(ITestOutputHelper output) - { - _output = output; + _output = output; - // delete sentinel files for EndToEndTestApp in order to trigger registration when it's run - var sentinelsDir = new DirectoryInfo(Path.Combine(Path.GetTempPath(), "system-commandline-sentinel-files")); + // delete sentinel files for EndToEndTestApp in order to trigger registration when it's run + var sentinelsDir = new DirectoryInfo(Path.Combine(Path.GetTempPath(), "system-commandline-sentinel-files")); - if (sentinelsDir.Exists) - { - var sentinels = sentinelsDir.GetFiles("*EndToEndTestApp*"); + if (sentinelsDir.Exists) + { + var sentinels = sentinelsDir.GetFiles("*EndToEndTestApp*"); - foreach (var sentinel in sentinels) - { - sentinel.Delete(); - } + foreach (var sentinel in sentinels) + { + sentinel.Delete(); } + } - var currentDirectory = Path.Combine( - Directory.GetCurrentDirectory(), - "TestAssets"); + var currentDirectory = Path.Combine( + Directory.GetCurrentDirectory(), + "TestAssets"); - _endToEndTestApp = new DirectoryInfo(currentDirectory) - .GetFiles("EndToEndTestApp".ExecutableName()) - .SingleOrDefault(); + _endToEndTestApp = new DirectoryInfo(currentDirectory) + .GetFiles("EndToEndTestApp".ExecutableName()) + .SingleOrDefault(); - _dotnetSuggest = new DirectoryInfo(currentDirectory) - .GetFiles("dotnet-suggest".ExecutableName()) - .SingleOrDefault(); + _dotnetSuggest = new DirectoryInfo(currentDirectory) + .GetFiles("dotnet-suggest".ExecutableName()) + .SingleOrDefault(); - PrepareTestHomeDirectoryToAvoidPolluteBuildMachineHome(); + PrepareTestHomeDirectoryToAvoidPolluteBuildMachineHome(); - _environmentVariables = new[] { - ("DOTNET_ROOT", _dotnetHostDir.FullName), - ("INTERNAL_TEST_DOTNET_SUGGEST_HOME", _testRoot)}; - } + _environmentVariables = new[] { + ("DOTNET_ROOT", _dotnetHostDir.FullName), + ("INTERNAL_TEST_DOTNET_SUGGEST_HOME", _testRoot)}; + } - public void Dispose() + public void Dispose() + { + if (_testRoot != null && Directory.Exists(_testRoot)) { - if (_testRoot != null && Directory.Exists(_testRoot)) - { - Directory.Delete(_testRoot, recursive: true); - } + Directory.Delete(_testRoot, recursive: true); } + } - private static void PrepareTestHomeDirectoryToAvoidPolluteBuildMachineHome() - { - _testRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(_testRoot); - } + private static void PrepareTestHomeDirectoryToAvoidPolluteBuildMachineHome() + { + _testRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(_testRoot); + } - [ReleaseBuildOnlyFact] - public void Test_app_supplies_suggestions() - { - var stdOut = new StringBuilder(); + [ReleaseBuildOnlyFact] + public void Test_app_supplies_suggestions() + { + var stdOut = new StringBuilder(); - Process.RunToCompletion( - _endToEndTestApp.FullName, - "[suggest:1] \"a\"", - stdOut: value => stdOut.AppendLine(value), - environmentVariables: _environmentVariables); + Process.RunToCompletion( + _endToEndTestApp.FullName, + "[suggest:1] \"a\"", + stdOut: value => stdOut.AppendLine(value), + environmentVariables: _environmentVariables); - stdOut.ToString() - .Should() - .Be($"--apple{NewLine}--banana{NewLine}--durian{NewLine}"); - } + stdOut.ToString() + .Should() + .Be($"--apple{NewLine}--banana{NewLine}--durian{NewLine}"); + } - [ReleaseBuildOnlyFact] - public void Dotnet_suggest_provides_suggestions_for_app() - { - // run "dotnet-suggest register" in explicit way - Process.RunToCompletion( - _dotnetSuggest.FullName, - $"register --command-path \"{_endToEndTestApp.FullName}\"", - stdOut: s => _output.WriteLine(s), - stdErr: s => _output.WriteLine(s), - environmentVariables: _environmentVariables).Should().Be(0); - - var stdOut = new StringBuilder(); - var stdErr = new StringBuilder(); - - var commandLineToComplete = "a"; - - Process.RunToCompletion( - _dotnetSuggest.FullName, - $"get -e \"{_endToEndTestApp.FullName}\" --position {commandLineToComplete.Length} -- \"{commandLineToComplete}\"", - stdOut: value => stdOut.AppendLine(value), - stdErr: value => stdErr.AppendLine(value), - environmentVariables: _environmentVariables); - - _output.WriteLine($"stdOut:{NewLine}{stdOut}{NewLine}"); - _output.WriteLine($"stdErr:{NewLine}{stdErr}{NewLine}"); - - stdErr.ToString() - .Should() - .BeEmpty(); - - stdOut.ToString() - .Should() - .Be($"--apple{NewLine}--banana{NewLine}--durian{NewLine}"); - } + [ReleaseBuildOnlyFact] + public void Dotnet_suggest_provides_suggestions_for_app() + { + // run "dotnet-suggest register" in explicit way + Process.RunToCompletion( + _dotnetSuggest.FullName, + $"register --command-path \"{_endToEndTestApp.FullName}\"", + stdOut: s => _output.WriteLine(s), + stdErr: s => _output.WriteLine(s), + environmentVariables: _environmentVariables).Should().Be(0); + + var stdOut = new StringBuilder(); + var stdErr = new StringBuilder(); + + var commandLineToComplete = "a"; + + Process.RunToCompletion( + _dotnetSuggest.FullName, + $"get -e \"{_endToEndTestApp.FullName}\" --position {commandLineToComplete.Length} -- \"{commandLineToComplete}\"", + stdOut: value => stdOut.AppendLine(value), + stdErr: value => stdErr.AppendLine(value), + environmentVariables: _environmentVariables); + + _output.WriteLine($"stdOut:{NewLine}{stdOut}{NewLine}"); + _output.WriteLine($"stdErr:{NewLine}{stdErr}{NewLine}"); + + stdErr.ToString() + .Should() + .BeEmpty(); + + stdOut.ToString() + .Should() + .Be($"--apple{NewLine}--banana{NewLine}--durian{NewLine}"); + } - [ReleaseBuildOnlyFact] - public void Dotnet_suggest_provides_suggestions_for_app_with_only_commandname() - { - // run "dotnet-suggest register" in explicit way - Process.RunToCompletion( - _dotnetSuggest.FullName, - $"register --command-path \"{_endToEndTestApp.FullName}\"", - stdOut: s => _output.WriteLine(s), - stdErr: s => _output.WriteLine(s), - environmentVariables: _environmentVariables).Should().Be(0); - - var stdOut = new StringBuilder(); - var stdErr = new StringBuilder(); - - var commandLineToComplete = "a "; - - Process.RunToCompletion( - _dotnetSuggest.FullName, - $"get -e \"{_endToEndTestApp.FullName}\" --position {commandLineToComplete.Length} -- \"{commandLineToComplete}\"", - stdOut: value => stdOut.AppendLine(value), - stdErr: value => stdErr.AppendLine(value), - environmentVariables: _environmentVariables); - - _output.WriteLine($"stdOut:{NewLine}{stdOut}{NewLine}"); - _output.WriteLine($"stdErr:{NewLine}{stdErr}{NewLine}"); - - stdErr.ToString() - .Should() - .BeEmpty(); - - stdOut.ToString() - .Should() - .Be($"--apple{NewLine}--banana{NewLine}--cherry{NewLine}--durian{NewLine}--help{NewLine}--version{NewLine}-?{NewLine}-h{NewLine}/?{NewLine}/h{NewLine}"); - } + [ReleaseBuildOnlyFact] + public void Dotnet_suggest_provides_suggestions_for_app_with_only_commandname() + { + // run "dotnet-suggest register" in explicit way + Process.RunToCompletion( + _dotnetSuggest.FullName, + $"register --command-path \"{_endToEndTestApp.FullName}\"", + stdOut: s => _output.WriteLine(s), + stdErr: s => _output.WriteLine(s), + environmentVariables: _environmentVariables).Should().Be(0); + + var stdOut = new StringBuilder(); + var stdErr = new StringBuilder(); + + var commandLineToComplete = "a "; + + Process.RunToCompletion( + _dotnetSuggest.FullName, + $"get -e \"{_endToEndTestApp.FullName}\" --position {commandLineToComplete.Length} -- \"{commandLineToComplete}\"", + stdOut: value => stdOut.AppendLine(value), + stdErr: value => stdErr.AppendLine(value), + environmentVariables: _environmentVariables); + + _output.WriteLine($"stdOut:{NewLine}{stdOut}{NewLine}"); + _output.WriteLine($"stdErr:{NewLine}{stdErr}{NewLine}"); + + stdErr.ToString() + .Should() + .BeEmpty(); + + stdOut.ToString() + .Should() + .Be($"--apple{NewLine}--banana{NewLine}--cherry{NewLine}--durian{NewLine}--help{NewLine}--version{NewLine}-?{NewLine}-h{NewLine}/?{NewLine}/h{NewLine}"); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/FileEnumeratorTests.cs b/src/System.CommandLine.Suggest.Tests/FileEnumeratorTests.cs index 6c6902dcf9..a1e1d60eef 100644 --- a/src/System.CommandLine.Suggest.Tests/FileEnumeratorTests.cs +++ b/src/System.CommandLine.Suggest.Tests/FileEnumeratorTests.cs @@ -5,46 +5,45 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +public class FileEnumeratorTests { - public class FileEnumeratorTests + [Fact] + public void EnumerateFilesWithoutExtension_returns_empty_when_pass_in_null() { - [Fact] - public void EnumerateFilesWithoutExtension_returns_empty_when_pass_in_null() - { - FileEnumerator.EnumerateFilesWithoutExtension(null).Should().BeEmpty(); - } + FileEnumerator.EnumerateFilesWithoutExtension(null).Should().BeEmpty(); + } - [Fact] - public void EnumerateFilesWithoutExtension_returns_empty_when_directory_does_not_exist() - { - var path = Path.GetTempPath(); - FileEnumerator.EnumerateFilesWithoutExtension( + [Fact] + public void EnumerateFilesWithoutExtension_returns_empty_when_directory_does_not_exist() + { + var path = Path.GetTempPath(); + FileEnumerator.EnumerateFilesWithoutExtension( new DirectoryInfo(Path.Combine(path, - Path.GetRandomFileName(), - "notexist"))) - .Should().BeEmpty(); - } + Path.GetRandomFileName(), + "notexist"))) + .Should().BeEmpty(); + } - [Fact] - public void EnumerateFilesWithoutExtension_returns_files_without_extension() + [Fact] + public void EnumerateFilesWithoutExtension_returns_files_without_extension() + { + var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try { - var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - try - { - Directory.CreateDirectory(path); - File.WriteAllText(Path.Combine(path, "dotnet-suggest"), ""); - File.WriteAllText(Path.Combine(path, "t-rex"), ""); - FileEnumerator.EnumerateFilesWithoutExtension(new DirectoryInfo(path)) - .Should() - .BeEquivalentTo( - GlobalToolsSuggestionRegistrationTests + Directory.CreateDirectory(path); + File.WriteAllText(Path.Combine(path, "dotnet-suggest"), ""); + File.WriteAllText(Path.Combine(path, "t-rex"), ""); + FileEnumerator.EnumerateFilesWithoutExtension(new DirectoryInfo(path)) + .Should() + .BeEquivalentTo( + GlobalToolsSuggestionRegistrationTests .FilesNameWithoutExtensionUnderDotnetProfileToolsExample); - } - finally - { - Directory.Delete(path, true); - } + } + finally + { + Directory.Delete(path, true); } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/FileSuggestionProviderTests.cs b/src/System.CommandLine.Suggest.Tests/FileSuggestionProviderTests.cs index 275e0de17f..7d1ad811fe 100644 --- a/src/System.CommandLine.Suggest.Tests/FileSuggestionProviderTests.cs +++ b/src/System.CommandLine.Suggest.Tests/FileSuggestionProviderTests.cs @@ -1,24 +1,23 @@ using System.IO; -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +public class FileSuggestionProviderTests : SuggestionRegistrationTest, IDisposable { - public class FileSuggestionProviderTests : SuggestionRegistrationTest, IDisposable - { - protected override ISuggestionRegistration GetSuggestionRegistration() => new FileSuggestionRegistration(_filePath); + protected override ISuggestionRegistration GetSuggestionRegistration() => new FileSuggestionRegistration(_filePath); - private readonly string _filePath; + private readonly string _filePath; - public FileSuggestionProviderTests() - { - _filePath = Path.GetFullPath(Path.GetRandomFileName()); - } + public FileSuggestionProviderTests() + { + _filePath = Path.GetFullPath(Path.GetRandomFileName()); + } - public void Dispose() + public void Dispose() + { + if (File.Exists(_filePath)) { - if (File.Exists(_filePath)) - { - File.Delete(_filePath); - } + File.Delete(_filePath); } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/GlobalToolsSuggestionRegistrationTests.cs b/src/System.CommandLine.Suggest.Tests/GlobalToolsSuggestionRegistrationTests.cs index d3ef83b775..84e3288b19 100644 --- a/src/System.CommandLine.Suggest.Tests/GlobalToolsSuggestionRegistrationTests.cs +++ b/src/System.CommandLine.Suggest.Tests/GlobalToolsSuggestionRegistrationTests.cs @@ -6,55 +6,54 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +public class GlobalToolsSuggestionRegistrationTests { - public class GlobalToolsSuggestionRegistrationTests + public static IEnumerable FilesNameWithoutExtensionUnderDotnetProfileToolsExample = new[] { "dotnet-suggest", "t-rex" }; + [Fact] + public void Path_is_in_global_tools() + { + var dotnetProfileDirectory = Path.GetTempPath(); + var validToolsPath = Path.Combine(dotnetProfileDirectory, "tools", "play"); + var fileInfo = new FileInfo(validToolsPath); + var suggestionRegistration = new GlobalToolsSuggestionRegistration(dotnetProfileDirectory, + FilesNameWithoutExtensionUnderDotnetProfileToolsExample); + + var pair = suggestionRegistration.FindRegistration(fileInfo); + + pair.ExecutablePath.Should().Be(validToolsPath); + } + + [Fact] + public void Invalid_global_tools_returns_null() + { + var dotnetProfileDirectory = Path.GetTempPath(); + var invalidToolsPath = Path.Combine(dotnetProfileDirectory, "not-valid"); + var fileInfo = new FileInfo(invalidToolsPath); + var suggestionRegistration = new GlobalToolsSuggestionRegistration(dotnetProfileDirectory, + FilesNameWithoutExtensionUnderDotnetProfileToolsExample); + + var pair = suggestionRegistration.FindRegistration(fileInfo); + + pair.Should().BeNull(); + } + + [Fact] + public void Global_tools_can_be_found() { - public static IEnumerable FilesNameWithoutExtensionUnderDotnetProfileToolsExample = new[] { "dotnet-suggest", "t-rex" }; - [Fact] - public void Path_is_in_global_tools() - { - var dotnetProfileDirectory = Path.GetTempPath(); - var validToolsPath = Path.Combine(dotnetProfileDirectory, "tools", "play"); - var fileInfo = new FileInfo(validToolsPath); - var suggestionRegistration = new GlobalToolsSuggestionRegistration(dotnetProfileDirectory, - FilesNameWithoutExtensionUnderDotnetProfileToolsExample); - - var pair = suggestionRegistration.FindRegistration(fileInfo); - - pair.ExecutablePath.Should().Be(validToolsPath); - } - - [Fact] - public void Invalid_global_tools_returns_null() - { - var dotnetProfileDirectory = Path.GetTempPath(); - var invalidToolsPath = Path.Combine(dotnetProfileDirectory, "not-valid"); - var fileInfo = new FileInfo(invalidToolsPath); - var suggestionRegistration = new GlobalToolsSuggestionRegistration(dotnetProfileDirectory, - FilesNameWithoutExtensionUnderDotnetProfileToolsExample); - - var pair = suggestionRegistration.FindRegistration(fileInfo); - - pair.Should().BeNull(); - } - - [Fact] - public void Global_tools_can_be_found() - { - var dotnetProfileDirectory = Path.GetTempPath(); - var suggestionRegistration = new GlobalToolsSuggestionRegistration(dotnetProfileDirectory, - FilesNameWithoutExtensionUnderDotnetProfileToolsExample); - - var registrationPairs = suggestionRegistration.FindAllRegistrations(); - - registrationPairs - .Should() - .BeEquivalentTo( new [] { - new Registration( - Path.Combine(dotnetProfileDirectory, "tools", "dotnet-suggest")), - new Registration( - Path.Combine(dotnetProfileDirectory, "tools", "t-rex"))}); - } + var dotnetProfileDirectory = Path.GetTempPath(); + var suggestionRegistration = new GlobalToolsSuggestionRegistration(dotnetProfileDirectory, + FilesNameWithoutExtensionUnderDotnetProfileToolsExample); + + var registrationPairs = suggestionRegistration.FindAllRegistrations(); + + registrationPairs + .Should() + .BeEquivalentTo( new [] { + new Registration( + Path.Combine(dotnetProfileDirectory, "tools", "dotnet-suggest")), + new Registration( + Path.Combine(dotnetProfileDirectory, "tools", "t-rex"))}); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/SuggestionDispatcherTests.cs b/src/System.CommandLine.Suggest.Tests/SuggestionDispatcherTests.cs index fa7c4841d2..dbc41dca92 100644 --- a/src/System.CommandLine.Suggest.Tests/SuggestionDispatcherTests.cs +++ b/src/System.CommandLine.Suggest.Tests/SuggestionDispatcherTests.cs @@ -10,232 +10,231 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +public class SuggestionDispatcherTests { - public class SuggestionDispatcherTests - { - private static readonly string _currentExeName = RootCommand.ExecutableName; + private static readonly string _currentExeName = RootCommand.ExecutableName; - private static readonly string _dotnetExeFullPath = - DotnetMuxer.Path.FullName; + private static readonly string _dotnetExeFullPath = + DotnetMuxer.Path.FullName; - private static readonly string _dotnetFormatExeFullPath = - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? @"C:\Program Files\dotnet-format.exe" - : "/bin/dotnet-format"; + private static readonly string _dotnetFormatExeFullPath = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? @"C:\Program Files\dotnet-format.exe" + : "/bin/dotnet-format"; - private static readonly string _netExeFullPath = - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? @"C:\Windows\System32\net.exe" - : "/bin/net"; + private static readonly string _netExeFullPath = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? @"C:\Windows\System32\net.exe" + : "/bin/net"; - private static Registration CurrentExeRegistrationPair() => new(CurrentExeFullPath()); + private static Registration CurrentExeRegistrationPair() => new(CurrentExeFullPath()); - private static string CurrentExeFullPath() => Path.GetFullPath(_currentExeName); + private static string CurrentExeFullPath() => Path.GetFullPath(_currentExeName); - [Fact] - public async Task InvokeAsync_executes_registered_executable() - { - string receivedTargetExeName = null; + [Fact] + public async Task InvokeAsync_executes_registered_executable() + { + string receivedTargetExeName = null; - string[] args = CommandLineParser.SplitCommandLine($@"get -p 12 -e ""{CurrentExeFullPath()}"" -- ""{_currentExeName} add""").ToArray(); + string[] args = CommandLineParser.SplitCommandLine($@"get -p 12 -e ""{CurrentExeFullPath()}"" -- ""{_currentExeName} add""").ToArray(); - await InvokeAsync( - args, - new TestSuggestionRegistration(CurrentExeRegistrationPair()), - new AnonymousSuggestionStore( - (targetExeName, targetExeArgs, _) => - { - receivedTargetExeName = targetExeName; + await InvokeAsync( + args, + new TestSuggestionRegistration(CurrentExeRegistrationPair()), + new AnonymousSuggestionStore( + (targetExeName, targetExeArgs, _) => + { + receivedTargetExeName = targetExeName; - return ""; - })); + return ""; + })); - receivedTargetExeName.Should().Be(CurrentExeFullPath()); - } + receivedTargetExeName.Should().Be(CurrentExeFullPath()); + } - [Fact] - public async Task InvokeAsync_executes_suggestion_command_for_executable() - { - string receivedTargetExeArgs = null; + [Fact] + public async Task InvokeAsync_executes_suggestion_command_for_executable() + { + string receivedTargetExeArgs = null; - var args = PrepareArgs($@"get -p 58 -e ""{CurrentExeFullPath()}"" -- ""{_currentExeName} add"""); + var args = PrepareArgs($@"get -p 58 -e ""{CurrentExeFullPath()}"" -- ""{_currentExeName} add"""); - await InvokeAsync( - args, - new TestSuggestionRegistration(CurrentExeRegistrationPair()), - new AnonymousSuggestionStore( - (targetExeName, targetExeArgs, _) => - { - receivedTargetExeArgs = targetExeArgs; + await InvokeAsync( + args, + new TestSuggestionRegistration(CurrentExeRegistrationPair()), + new AnonymousSuggestionStore( + (targetExeName, targetExeArgs, _) => + { + receivedTargetExeArgs = targetExeArgs; - return ""; - })); + return ""; + })); - var expectedPosition = 57 - _currentExeName.Length; + var expectedPosition = 57 - _currentExeName.Length; - receivedTargetExeArgs.Should() - .Be($"[suggest:{expectedPosition}] \"add\""); - } + receivedTargetExeArgs.Should() + .Be($"[suggest:{expectedPosition}] \"add\""); + } - [Theory] - [InlineData("dotnet-abcdef.exe --dry", 23, "[suggest:5] \"--dry\"")] - [InlineData("dotnet abcdef --dry", 19, "[suggest:5] \"--dry\"")] - [InlineData("dotnet abcdef --dry", 23, "[suggest:5] \"--dry\"")] - [InlineData("dotnet abcdef", 18, "[suggest:0] \"\"")] - [InlineData("dotnet", 7, "[suggest:0] \"\"")] - public async Task InvokeAsync_executes_suggestion_command_for_executable_called_via_dotnet_muxer( - string scriptSendsCommand, - int scriptSendsPosition, - string expectToReceive) - { - string receivedTargetExeArgs = null; + [Theory] + [InlineData("dotnet-abcdef.exe --dry", 23, "[suggest:5] \"--dry\"")] + [InlineData("dotnet abcdef --dry", 19, "[suggest:5] \"--dry\"")] + [InlineData("dotnet abcdef --dry", 23, "[suggest:5] \"--dry\"")] + [InlineData("dotnet abcdef", 18, "[suggest:0] \"\"")] + [InlineData("dotnet", 7, "[suggest:0] \"\"")] + public async Task InvokeAsync_executes_suggestion_command_for_executable_called_via_dotnet_muxer( + string scriptSendsCommand, + int scriptSendsPosition, + string expectToReceive) + { + string receivedTargetExeArgs = null; - var args = PrepareArgs($@"get -p {scriptSendsPosition} -e ""{_dotnetExeFullPath}"" -- ""{scriptSendsCommand}"""); + var args = PrepareArgs($@"get -p {scriptSendsPosition} -e ""{_dotnetExeFullPath}"" -- ""{scriptSendsCommand}"""); - await InvokeAsync( - args, - new TestSuggestionRegistration(CurrentExeRegistrationPair()), - new AnonymousSuggestionStore( - (targetExeName, targetExeArgs, _) => - { - receivedTargetExeArgs = targetExeArgs; + await InvokeAsync( + args, + new TestSuggestionRegistration(CurrentExeRegistrationPair()), + new AnonymousSuggestionStore( + (targetExeName, targetExeArgs, _) => + { + receivedTargetExeArgs = targetExeArgs; - return ""; - })); + return ""; + })); - receivedTargetExeArgs.Should() - .Be(expectToReceive); - } + receivedTargetExeArgs.Should() + .Be(expectToReceive); + } - private static string[] PrepareArgs(string args) - { - var formattableString = args.Replace("$", ""); - return CommandLineParser.SplitCommandLine(formattableString).ToArray(); - } + private static string[] PrepareArgs(string args) + { + var formattableString = args.Replace("$", ""); + return CommandLineParser.SplitCommandLine(formattableString).ToArray(); + } - [Fact] - public async Task InvokeAsync_with_unknown_suggestion_provider_returns_empty_string() - { - string[] args = Enumerable.ToArray(CommandLineParser.SplitCommandLine(@"get -p 10 -e ""testcli.exe"" -- command op")); - (await InvokeAsync(args, new TestSuggestionRegistration())) - .Should() - .BeEmpty(); - } + [Fact] + public async Task InvokeAsync_with_unknown_suggestion_provider_returns_empty_string() + { + string[] args = Enumerable.ToArray(CommandLineParser.SplitCommandLine(@"get -p 10 -e ""testcli.exe"" -- command op")); + (await InvokeAsync(args, new TestSuggestionRegistration())) + .Should() + .BeEmpty(); + } - [Fact] - public async Task When_command_suggestions_use_process_that_remains_open_it_returns_empty_string() - { - var provider = new TestSuggestionRegistration(new Registration(CurrentExeFullPath())); - var dispatcher = new SuggestionDispatcher(provider, new TestSuggestionStore()); - dispatcher.Timeout = TimeSpan.FromMilliseconds(1); - dispatcher.Configuration.Output = new StringWriter(); + [Fact] + public async Task When_command_suggestions_use_process_that_remains_open_it_returns_empty_string() + { + var provider = new TestSuggestionRegistration(new Registration(CurrentExeFullPath())); + var dispatcher = new SuggestionDispatcher(provider, new TestSuggestionStore()); + dispatcher.Timeout = TimeSpan.FromMilliseconds(1); + dispatcher.Configuration.Output = new StringWriter(); - var args = CommandLineParser.SplitCommandLine($@"get -p 0 -e ""{_currentExeName}"" -- {_currentExeName} add").ToArray(); + var args = CommandLineParser.SplitCommandLine($@"get -p 0 -e ""{_currentExeName}"" -- {_currentExeName} add").ToArray(); - await dispatcher.InvokeAsync(args); + await dispatcher.InvokeAsync(args); - dispatcher.Configuration.Output.ToString().Should().BeEmpty(); - } + dispatcher.Configuration.Output.ToString().Should().BeEmpty(); + } - [Fact] - public async Task List_command_gets_all_executable_names() - { - string _kiwiFruitExeFullPath = - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? @"C:\Program Files\kiwi-fruit.exe" - : "/bin/kiwi-fruit"; + [Fact] + public async Task List_command_gets_all_executable_names() + { + string _kiwiFruitExeFullPath = + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? @"C:\Program Files\kiwi-fruit.exe" + : "/bin/kiwi-fruit"; - var testSuggestionProvider = new TestSuggestionRegistration( - new Registration(_dotnetFormatExeFullPath), - new Registration(_kiwiFruitExeFullPath)); + var testSuggestionProvider = new TestSuggestionRegistration( + new Registration(_dotnetFormatExeFullPath), + new Registration(_kiwiFruitExeFullPath)); - var dispatcher = new SuggestionDispatcher(testSuggestionProvider); - dispatcher.Configuration.Output = new StringWriter(); + var dispatcher = new SuggestionDispatcher(testSuggestionProvider); + dispatcher.Configuration.Output = new StringWriter(); - await dispatcher.InvokeAsync(new[] { "list" }); + await dispatcher.InvokeAsync(new[] { "list" }); - dispatcher.Configuration.Output - .ToString() - .Should() - .Be($"dotnet-format{Environment.NewLine}dotnet format{Environment.NewLine}kiwi-fruit{Environment.NewLine}"); - } + dispatcher.Configuration.Output + .ToString() + .Should() + .Be($"dotnet-format{Environment.NewLine}dotnet format{Environment.NewLine}kiwi-fruit{Environment.NewLine}"); + } - [Fact] - public async Task Register_command_adds_new_suggestion_entry() - { - var provider = new TestSuggestionRegistration(); - var dispatcher = new SuggestionDispatcher(provider); + [Fact] + public async Task Register_command_adds_new_suggestion_entry() + { + var provider = new TestSuggestionRegistration(); + var dispatcher = new SuggestionDispatcher(provider); - var args = CommandLineParser.SplitCommandLine($"register --command-path \"{_netExeFullPath}\"").ToArray(); + var args = CommandLineParser.SplitCommandLine($"register --command-path \"{_netExeFullPath}\"").ToArray(); - await dispatcher.InvokeAsync(args); + await dispatcher.InvokeAsync(args); - Registration addedRegistration = provider.FindAllRegistrations().Single(); - addedRegistration.ExecutablePath.Should().Be(_netExeFullPath); - } + Registration addedRegistration = provider.FindAllRegistrations().Single(); + addedRegistration.ExecutablePath.Should().Be(_netExeFullPath); + } - [Fact] - public async Task Register_command_will_not_add_duplicate_entry() - { - var provider = new TestSuggestionRegistration(); - var dispatcher = new SuggestionDispatcher(provider); + [Fact] + public async Task Register_command_will_not_add_duplicate_entry() + { + var provider = new TestSuggestionRegistration(); + var dispatcher = new SuggestionDispatcher(provider); - var args = CommandLineParser.SplitCommandLine($"register --command-path \"{_netExeFullPath}\"").ToArray(); + var args = CommandLineParser.SplitCommandLine($"register --command-path \"{_netExeFullPath}\"").ToArray(); - await dispatcher.InvokeAsync(args); - await dispatcher.InvokeAsync(args); + await dispatcher.InvokeAsync(args); + await dispatcher.InvokeAsync(args); - provider.FindAllRegistrations().Should().HaveCount(1); - } + provider.FindAllRegistrations().Should().HaveCount(1); + } - private static async Task InvokeAsync( - string[] args, - ISuggestionRegistration suggestionProvider, - ISuggestionStore suggestionStore = null) - { - var dispatcher = new SuggestionDispatcher(suggestionProvider, suggestionStore ?? new TestSuggestionStore()); - dispatcher.Configuration.Output = new StringWriter(); - await dispatcher.InvokeAsync(args); - return dispatcher.Configuration.Output.ToString(); - } + private static async Task InvokeAsync( + string[] args, + ISuggestionRegistration suggestionProvider, + ISuggestionStore suggestionStore = null) + { + var dispatcher = new SuggestionDispatcher(suggestionProvider, suggestionStore ?? new TestSuggestionStore()); + dispatcher.Configuration.Output = new StringWriter(); + await dispatcher.InvokeAsync(args); + return dispatcher.Configuration.Output.ToString(); + } - private class TestSuggestionStore : ISuggestionStore + private class TestSuggestionStore : ISuggestionStore + { + public string GetCompletions(string exeFileName, string suggestionTargetArguments, TimeSpan timeout) { - public string GetCompletions(string exeFileName, string suggestionTargetArguments, TimeSpan timeout) + if (timeout <= TimeSpan.FromMilliseconds(100)) { - if (timeout <= TimeSpan.FromMilliseconds(100)) - { - return ""; - } - - if (exeFileName != CurrentExeFullPath()) - { - return $"unexpected value for {nameof(exeFileName)}: {exeFileName}"; - } - - if (!Regex.IsMatch(suggestionTargetArguments, @"\[suggest:\d+\] add")) - { - return $"unexpected value for {nameof(suggestionTargetArguments)}: {suggestionTargetArguments}"; - } - - return $"package{Environment.NewLine}reference{Environment.NewLine}"; + return ""; } - } - - private class AnonymousSuggestionStore : ISuggestionStore - { - private readonly Func _getSuggestions; - public AnonymousSuggestionStore(Func getSuggestions) + if (exeFileName != CurrentExeFullPath()) { - _getSuggestions = getSuggestions; + return $"unexpected value for {nameof(exeFileName)}: {exeFileName}"; } - public string GetCompletions(string exeFileName, string suggestionTargetArguments, TimeSpan timeout) + if (!Regex.IsMatch(suggestionTargetArguments, @"\[suggest:\d+\] add")) { - return _getSuggestions(exeFileName, suggestionTargetArguments, timeout); + return $"unexpected value for {nameof(suggestionTargetArguments)}: {suggestionTargetArguments}"; } + + return $"package{Environment.NewLine}reference{Environment.NewLine}"; + } + } + + private class AnonymousSuggestionStore : ISuggestionStore + { + private readonly Func _getSuggestions; + + public AnonymousSuggestionStore(Func getSuggestions) + { + _getSuggestions = getSuggestions; + } + + public string GetCompletions(string exeFileName, string suggestionTargetArguments, TimeSpan timeout) + { + return _getSuggestions(exeFileName, suggestionTargetArguments, timeout); } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/SuggestionRegistrationTest.cs b/src/System.CommandLine.Suggest.Tests/SuggestionRegistrationTest.cs index 7e4aefa367..34717c647d 100644 --- a/src/System.CommandLine.Suggest.Tests/SuggestionRegistrationTest.cs +++ b/src/System.CommandLine.Suggest.Tests/SuggestionRegistrationTest.cs @@ -5,95 +5,94 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +public abstract class SuggestionRegistrationTest { - public abstract class SuggestionRegistrationTest - { - protected abstract ISuggestionRegistration GetSuggestionRegistration(); + protected abstract ISuggestionRegistration GetSuggestionRegistration(); - [Fact] - public void Added_suggestions_can_be_retrieved() - { - ISuggestionRegistration suggestionProvider = GetSuggestionRegistration(); + [Fact] + public void Added_suggestions_can_be_retrieved() + { + ISuggestionRegistration suggestionProvider = GetSuggestionRegistration(); - var suggestion1 = new Registration("commandPath1"); - var suggestion2 = new Registration("commandPath2"); + var suggestion1 = new Registration("commandPath1"); + var suggestion2 = new Registration("commandPath2"); - suggestionProvider.AddSuggestionRegistration(suggestion1); - suggestionProvider.AddSuggestionRegistration(suggestion2); + suggestionProvider.AddSuggestionRegistration(suggestion1); + suggestionProvider.AddSuggestionRegistration(suggestion2); - var allRegistrations = suggestionProvider.FindAllRegistrations(); - allRegistrations - .Should() - .HaveCount(2) - .And - .Contain(x => - x.ExecutablePath == suggestion1.ExecutablePath) - .And - .Contain(x => - x.ExecutablePath == suggestion2.ExecutablePath); - } + var allRegistrations = suggestionProvider.FindAllRegistrations(); + allRegistrations + .Should() + .HaveCount(2) + .And + .Contain(x => + x.ExecutablePath == suggestion1.ExecutablePath) + .And + .Contain(x => + x.ExecutablePath == suggestion2.ExecutablePath); + } - [Fact] - public void Missing_suggestion_can_not_be_found() - { - ISuggestionRegistration suggestionProvider = GetSuggestionRegistration(); + [Fact] + public void Missing_suggestion_can_not_be_found() + { + ISuggestionRegistration suggestionProvider = GetSuggestionRegistration(); - var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - try - { - Directory.CreateDirectory(path); - var unregisteredFile = Path.Combine(path, "im-not-registered"); - File.WriteAllText(unregisteredFile, ""); + var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try + { + Directory.CreateDirectory(path); + var unregisteredFile = Path.Combine(path, "im-not-registered"); + File.WriteAllText(unregisteredFile, ""); - var foundRegistration = suggestionProvider.FindRegistration(new FileInfo(unregisteredFile)); + var foundRegistration = suggestionProvider.FindRegistration(new FileInfo(unregisteredFile)); - foundRegistration - .Should() - .BeNull(); - } - finally - { - Directory.Delete(path, true); - } - } - - [Fact] - public void Added_suggestion_can_be_found() + foundRegistration + .Should() + .BeNull(); + } + finally { - ISuggestionRegistration suggestionProvider = GetSuggestionRegistration(); + Directory.Delete(path, true); + } + } + + [Fact] + public void Added_suggestion_can_be_found() + { + ISuggestionRegistration suggestionProvider = GetSuggestionRegistration(); - var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - try - { - Directory.CreateDirectory(path); - var registeredFile = Path.Combine(path, "im-registered"); - File.WriteAllText(registeredFile, ""); + var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try + { + Directory.CreateDirectory(path); + var registeredFile = Path.Combine(path, "im-registered"); + File.WriteAllText(registeredFile, ""); - suggestionProvider.AddSuggestionRegistration(new Registration(registeredFile)); - var foundRegistration = suggestionProvider.FindRegistration(new FileInfo(registeredFile)); - - foundRegistration - .Should() - .NotBeNull(); - } - finally - { - Directory.Delete(path, true); - } - } + suggestionProvider.AddSuggestionRegistration(new Registration(registeredFile)); + var foundRegistration = suggestionProvider.FindRegistration(new FileInfo(registeredFile)); - [Fact] - public void Suggestion_command_path_is_not_case_sensitive() + foundRegistration + .Should() + .NotBeNull(); + } + finally { - ISuggestionRegistration suggestionProvider = GetSuggestionRegistration(); + Directory.Delete(path, true); + } + } - suggestionProvider.AddSuggestionRegistration( - new Registration(Path.GetFullPath("commandPath"))); + [Fact] + public void Suggestion_command_path_is_not_case_sensitive() + { + ISuggestionRegistration suggestionProvider = GetSuggestionRegistration(); - Registration registration = suggestionProvider.FindRegistration(new FileInfo("COMMANDPATH")); + suggestionProvider.AddSuggestionRegistration( + new Registration(Path.GetFullPath("commandPath"))); - registration.ExecutablePath.Should().Be(Path.GetFullPath("commandPath")); - } + Registration registration = suggestionProvider.FindRegistration(new FileInfo("COMMANDPATH")); + + registration.ExecutablePath.Should().Be(Path.GetFullPath("commandPath")); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/SuggestionShellScriptHandlerTest.cs b/src/System.CommandLine.Suggest.Tests/SuggestionShellScriptHandlerTest.cs index 0ca6f6805e..0a4be1357f 100644 --- a/src/System.CommandLine.Suggest.Tests/SuggestionShellScriptHandlerTest.cs +++ b/src/System.CommandLine.Suggest.Tests/SuggestionShellScriptHandlerTest.cs @@ -6,61 +6,60 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +public class SuggestionShellScriptHandlerTest { - public class SuggestionShellScriptHandlerTest - { - private readonly CommandLineConfiguration _configuration; + private readonly CommandLineConfiguration _configuration; - public SuggestionShellScriptHandlerTest() - { - _configuration = new SuggestionDispatcher(new TestSuggestionRegistration()).Configuration; - } + public SuggestionShellScriptHandlerTest() + { + _configuration = new SuggestionDispatcher(new TestSuggestionRegistration()).Configuration; + } - [Fact] - public async Task When_shell_type_is_not_supported_it_throws() - { - _configuration.Error = new StringWriter(); + [Fact] + public async Task When_shell_type_is_not_supported_it_throws() + { + _configuration.Error = new StringWriter(); - await _configuration.InvokeAsync("script 123"); + await _configuration.InvokeAsync("script 123"); - _configuration.Error - .ToString() - .Should() - .Contain("Shell '123' is not supported."); - } + _configuration.Error + .ToString() + .Should() + .Contain("Shell '123' is not supported."); + } - [Fact] - public async Task It_should_print_bash_shell_script() - { - _configuration.Output = new StringWriter(); + [Fact] + public async Task It_should_print_bash_shell_script() + { + _configuration.Output = new StringWriter(); - await _configuration.InvokeAsync("script bash"); + await _configuration.InvokeAsync("script bash"); - _configuration.Output.ToString().Should().Contain("_dotnet_bash_complete()"); - _configuration.Output.ToString().Should().NotContain("\r\n"); - } + _configuration.Output.ToString().Should().Contain("_dotnet_bash_complete()"); + _configuration.Output.ToString().Should().NotContain("\r\n"); + } - [Fact] - public async Task It_should_print_powershell_shell_script() - { - _configuration.Output = new StringWriter(); + [Fact] + public async Task It_should_print_powershell_shell_script() + { + _configuration.Output = new StringWriter(); - await _configuration.InvokeAsync("script powershell"); + await _configuration.InvokeAsync("script powershell"); - _configuration.Output.ToString().Should().Contain("Register-ArgumentCompleter"); - _configuration.Output.ToString().Should().Contain("\r\n"); - } + _configuration.Output.ToString().Should().Contain("Register-ArgumentCompleter"); + _configuration.Output.ToString().Should().Contain("\r\n"); + } - [Fact] - public async Task It_should_print_zsh_shell_script() - { - _configuration.Output = new StringWriter(); + [Fact] + public async Task It_should_print_zsh_shell_script() + { + _configuration.Output = new StringWriter(); - await _configuration.InvokeAsync("script zsh"); + await _configuration.InvokeAsync("script zsh"); - _configuration.Output.ToString().Should().Contain("_dotnet_zsh_complete()"); - _configuration.Output.ToString().Should().NotContain("\r\n"); - } + _configuration.Output.ToString().Should().Contain("_dotnet_zsh_complete()"); + _configuration.Output.ToString().Should().NotContain("\r\n"); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/TestSuggestionRegistration.cs b/src/System.CommandLine.Suggest.Tests/TestSuggestionRegistration.cs index 2d0760becc..2c8a0fc167 100644 --- a/src/System.CommandLine.Suggest.Tests/TestSuggestionRegistration.cs +++ b/src/System.CommandLine.Suggest.Tests/TestSuggestionRegistration.cs @@ -5,29 +5,28 @@ using System.IO; using System.Linq; -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +internal class TestSuggestionRegistration : ISuggestionRegistration { - internal class TestSuggestionRegistration : ISuggestionRegistration - { - private readonly List _suggestionRegistrations = new(); + private readonly List _suggestionRegistrations = new(); - public TestSuggestionRegistration(params Registration[] suggestionRegistrations) + public TestSuggestionRegistration(params Registration[] suggestionRegistrations) + { + foreach (Registration suggestionRegistration in suggestionRegistrations) { - foreach (Registration suggestionRegistration in suggestionRegistrations) - { - AddSuggestionRegistration(suggestionRegistration); - } + AddSuggestionRegistration(suggestionRegistration); } + } - public Registration FindRegistration(FileInfo soughtExecutable) - => _suggestionRegistrations.FirstOrDefault(x => x.ExecutablePath.StartsWith(soughtExecutable.FullName, StringComparison.OrdinalIgnoreCase)); + public Registration FindRegistration(FileInfo soughtExecutable) + => _suggestionRegistrations.FirstOrDefault(x => x.ExecutablePath.StartsWith(soughtExecutable.FullName, StringComparison.OrdinalIgnoreCase)); - public IEnumerable FindAllRegistrations() - => _suggestionRegistrations; + public IEnumerable FindAllRegistrations() + => _suggestionRegistrations; - public void AddSuggestionRegistration(Registration registration) - { - _suggestionRegistrations.Add(registration); - } + public void AddSuggestionRegistration(Registration registration) + { + _suggestionRegistrations.Add(registration); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest.Tests/TestSuggestionRegistrationTests.cs b/src/System.CommandLine.Suggest.Tests/TestSuggestionRegistrationTests.cs index 93abe7b6ab..76a294e988 100644 --- a/src/System.CommandLine.Suggest.Tests/TestSuggestionRegistrationTests.cs +++ b/src/System.CommandLine.Suggest.Tests/TestSuggestionRegistrationTests.cs @@ -1,7 +1,6 @@ -namespace System.CommandLine.Suggest.Tests +namespace System.CommandLine.Suggest.Tests; + +public class TestSuggestionRegistrationTests : SuggestionRegistrationTest { - public class TestSuggestionRegistrationTests : SuggestionRegistrationTest - { - protected override ISuggestionRegistration GetSuggestionRegistration() => new TestSuggestionRegistration(); - } -} + protected override ISuggestionRegistration GetSuggestionRegistration() => new TestSuggestionRegistration(); +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/CombineSuggestionRegistration.cs b/src/System.CommandLine.Suggest/CombineSuggestionRegistration.cs index 28def8f4f9..bfbdc9a4fb 100644 --- a/src/System.CommandLine.Suggest/CombineSuggestionRegistration.cs +++ b/src/System.CommandLine.Suggest/CombineSuggestionRegistration.cs @@ -5,37 +5,36 @@ using System.IO; using System.Linq; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public class CombineSuggestionRegistration : ISuggestionRegistration { - public class CombineSuggestionRegistration : ISuggestionRegistration - { - private readonly ISuggestionRegistration[] _suggestionRegistrations; + private readonly ISuggestionRegistration[] _suggestionRegistrations; - public CombineSuggestionRegistration(params ISuggestionRegistration[] suggestionRegistration) - { - _suggestionRegistrations = - suggestionRegistration ?? throw new ArgumentNullException(nameof(suggestionRegistration)); - } + public CombineSuggestionRegistration(params ISuggestionRegistration[] suggestionRegistration) + { + _suggestionRegistrations = + suggestionRegistration ?? throw new ArgumentNullException(nameof(suggestionRegistration)); + } - public void AddSuggestionRegistration(Registration registration) + public void AddSuggestionRegistration(Registration registration) + { + foreach (var suggestionRegistration in _suggestionRegistrations) { - foreach (var suggestionRegistration in _suggestionRegistrations) - { - suggestionRegistration.AddSuggestionRegistration(registration); - } + suggestionRegistration.AddSuggestionRegistration(registration); } + } - public Registration FindRegistration(FileInfo soughtExecutable) - { - return _suggestionRegistrations - .Select(s => s.FindRegistration(soughtExecutable)) - .FirstOrDefault(s => s != null); - } + public Registration FindRegistration(FileInfo soughtExecutable) + { + return _suggestionRegistrations + .Select(s => s.FindRegistration(soughtExecutable)) + .FirstOrDefault(s => s != null); + } - public IEnumerable FindAllRegistrations() - { - return _suggestionRegistrations - .SelectMany(s => s.FindAllRegistrations()); - } + public IEnumerable FindAllRegistrations() + { + return _suggestionRegistrations + .SelectMany(s => s.FindAllRegistrations()); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/DotnetProfileDirectory.cs b/src/System.CommandLine.Suggest/DotnetProfileDirectory.cs index 98f7a2d396..39a7fc40ab 100644 --- a/src/System.CommandLine.Suggest/DotnetProfileDirectory.cs +++ b/src/System.CommandLine.Suggest/DotnetProfileDirectory.cs @@ -4,31 +4,30 @@ using System.IO; using System.Runtime.InteropServices; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public static class DotnetProfileDirectory { - public static class DotnetProfileDirectory - { - private const string DotnetHomeVariableName = "DOTNET_CLI_HOME"; - private const string DotnetProfileDirectoryName = ".dotnet"; + private const string DotnetHomeVariableName = "DOTNET_CLI_HOME"; + private const string DotnetProfileDirectoryName = ".dotnet"; - private static string PlatformHomeVariableName => - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "USERPROFILE" : "HOME"; + private static string PlatformHomeVariableName => + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "USERPROFILE" : "HOME"; - public static bool TryGet(out string dotnetProfileDirectory) + public static bool TryGet(out string dotnetProfileDirectory) + { + dotnetProfileDirectory = null; + var home = Environment.GetEnvironmentVariable(DotnetHomeVariableName); + if (string.IsNullOrEmpty(home)) { - dotnetProfileDirectory = null; - var home = Environment.GetEnvironmentVariable(DotnetHomeVariableName); + home = Environment.GetEnvironmentVariable(PlatformHomeVariableName); if (string.IsNullOrEmpty(home)) { - home = Environment.GetEnvironmentVariable(PlatformHomeVariableName); - if (string.IsNullOrEmpty(home)) - { - return false; - } + return false; } - - dotnetProfileDirectory = Path.Combine(home, DotnetProfileDirectoryName); - return true; } + + dotnetProfileDirectory = Path.Combine(home, DotnetProfileDirectoryName); + return true; } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/FileEnumerator.cs b/src/System.CommandLine.Suggest/FileEnumerator.cs index 2fea3e1993..d3bd32185d 100644 --- a/src/System.CommandLine.Suggest/FileEnumerator.cs +++ b/src/System.CommandLine.Suggest/FileEnumerator.cs @@ -5,19 +5,18 @@ using System.IO; using System.Linq; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public static class FileEnumerator { - public static class FileEnumerator + public static IEnumerable EnumerateFilesWithoutExtension(DirectoryInfo path) { - public static IEnumerable EnumerateFilesWithoutExtension(DirectoryInfo path) + if (path == null || !path.Exists) { - if (path == null || !path.Exists) - { - return Array.Empty(); - } - - return path.EnumerateFiles() - .Select(p => Path.GetFileNameWithoutExtension(p.FullName)); + return Array.Empty(); } + + return path.EnumerateFiles() + .Select(p => Path.GetFileNameWithoutExtension(p.FullName)); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/FileSuggestionRegistration.cs b/src/System.CommandLine.Suggest/FileSuggestionRegistration.cs index 1b832a26a9..3d3f51c1df 100644 --- a/src/System.CommandLine.Suggest/FileSuggestionRegistration.cs +++ b/src/System.CommandLine.Suggest/FileSuggestionRegistration.cs @@ -6,96 +6,95 @@ using System.Text; using static System.Environment; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public class FileSuggestionRegistration : ISuggestionRegistration { - public class FileSuggestionRegistration : ISuggestionRegistration + private const string RegistrationFileName = ".dotnet-suggest-registration.txt"; + private const string TestDirectoryOverride = "INTERNAL_TEST_DOTNET_SUGGEST_HOME"; + private readonly string _registrationConfigurationFilePath; + + public FileSuggestionRegistration(string registrationsConfigurationFilePath = null) { - private const string RegistrationFileName = ".dotnet-suggest-registration.txt"; - private const string TestDirectoryOverride = "INTERNAL_TEST_DOTNET_SUGGEST_HOME"; - private readonly string _registrationConfigurationFilePath; + if (!string.IsNullOrWhiteSpace(registrationsConfigurationFilePath)) + { + _registrationConfigurationFilePath = registrationsConfigurationFilePath; + return; + } - public FileSuggestionRegistration(string registrationsConfigurationFilePath = null) + var testDirectoryOverride = GetEnvironmentVariable(TestDirectoryOverride); + if (!string.IsNullOrWhiteSpace(testDirectoryOverride)) { - if (!string.IsNullOrWhiteSpace(registrationsConfigurationFilePath)) - { - _registrationConfigurationFilePath = registrationsConfigurationFilePath; - return; - } + _registrationConfigurationFilePath = Path.Combine(testDirectoryOverride, RegistrationFileName); + return; + } - var testDirectoryOverride = GetEnvironmentVariable(TestDirectoryOverride); - if (!string.IsNullOrWhiteSpace(testDirectoryOverride)) - { - _registrationConfigurationFilePath = Path.Combine(testDirectoryOverride, RegistrationFileName); - return; - } + var userProfile = GetFolderPath(SpecialFolder.UserProfile); - var userProfile = GetFolderPath(SpecialFolder.UserProfile); + _registrationConfigurationFilePath = Path.Combine(userProfile, RegistrationFileName); + } - _registrationConfigurationFilePath = Path.Combine(userProfile, RegistrationFileName); + public Registration FindRegistration(FileInfo soughtExecutable) + { + if (soughtExecutable == null) + { + return null; } - public Registration FindRegistration(FileInfo soughtExecutable) + if (_registrationConfigurationFilePath == null + || !File.Exists(_registrationConfigurationFilePath)) { - if (soughtExecutable == null) - { - return null; - } - - if (_registrationConfigurationFilePath == null - || !File.Exists(_registrationConfigurationFilePath)) - { - return null; - } + return null; + } - string completionTarget = null; - using (var sr = new StreamReader(_registrationConfigurationFilePath, Encoding.UTF8)) + string completionTarget = null; + using (var sr = new StreamReader(_registrationConfigurationFilePath, Encoding.UTF8)) + { + while (sr.ReadLine() is string line) { - while (sr.ReadLine() is string line) + if (line.StartsWith(soughtExecutable.FullName, StringComparison.OrdinalIgnoreCase)) { - if (line.StartsWith(soughtExecutable.FullName, StringComparison.OrdinalIgnoreCase)) - { - completionTarget = line; - } + completionTarget = line; } } - - if (completionTarget is null) - { - // Completion provider not found! - return null; - } - - return new Registration(completionTarget); } - public IEnumerable FindAllRegistrations() + if (completionTarget is null) { - var allRegistration = new List(); + // Completion provider not found! + return null; + } - if (_registrationConfigurationFilePath != null && File.Exists(_registrationConfigurationFilePath)) + return new Registration(completionTarget); + } + + public IEnumerable FindAllRegistrations() + { + var allRegistration = new List(); + + if (_registrationConfigurationFilePath != null && File.Exists(_registrationConfigurationFilePath)) + { + using (var sr = new StreamReader(_registrationConfigurationFilePath, Encoding.UTF8)) { - using (var sr = new StreamReader(_registrationConfigurationFilePath, Encoding.UTF8)) + string line; + while ((line = sr.ReadLine()) != null) { - string line; - while ((line = sr.ReadLine()) != null) + if (!string.IsNullOrWhiteSpace(line)) { - if (!string.IsNullOrWhiteSpace(line)) - { - allRegistration.Add(new Registration(line.Trim())); - } + allRegistration.Add(new Registration(line.Trim())); } } } - - return allRegistration; } - public void AddSuggestionRegistration(Registration registration) + return allRegistration; + } + + public void AddSuggestionRegistration(Registration registration) + { + using (var writer = new StreamWriter(_registrationConfigurationFilePath, true)) { - using (var writer = new StreamWriter(_registrationConfigurationFilePath, true)) - { - writer.WriteLine(registration.ExecutablePath); - } + writer.WriteLine(registration.ExecutablePath); } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/GlobalToolsSuggestionRegistration.cs b/src/System.CommandLine.Suggest/GlobalToolsSuggestionRegistration.cs index a04481f0fd..dd6f17d32c 100644 --- a/src/System.CommandLine.Suggest/GlobalToolsSuggestionRegistration.cs +++ b/src/System.CommandLine.Suggest/GlobalToolsSuggestionRegistration.cs @@ -5,63 +5,62 @@ using System.IO; using System.Linq; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public class GlobalToolsSuggestionRegistration : ISuggestionRegistration { - public class GlobalToolsSuggestionRegistration : ISuggestionRegistration - { - private readonly string _nullableToolsShimPath; - private readonly IEnumerable _filesNameWithoutExtensionUnderDotnetProfileTools; + private readonly string _nullableToolsShimPath; + private readonly IEnumerable _filesNameWithoutExtensionUnderDotnetProfileTools; - public GlobalToolsSuggestionRegistration(string dotnetProfileDirectory = null, - IEnumerable filesNameWithoutExtensionUnderDotnetProfileTools = null) + public GlobalToolsSuggestionRegistration(string dotnetProfileDirectory = null, + IEnumerable filesNameWithoutExtensionUnderDotnetProfileTools = null) + { + var directory = dotnetProfileDirectory; + if (directory == null) { - var directory = dotnetProfileDirectory; - if (directory == null) - { - DotnetProfileDirectory.TryGet(out directory); - } + DotnetProfileDirectory.TryGet(out directory); + } - _nullableToolsShimPath = directory != null - ? Path.Combine(directory, "tools") - : null; + _nullableToolsShimPath = directory != null + ? Path.Combine(directory, "tools") + : null; - _filesNameWithoutExtensionUnderDotnetProfileTools - = filesNameWithoutExtensionUnderDotnetProfileTools ?? FileEnumerator.EnumerateFilesWithoutExtension(new DirectoryInfo(_nullableToolsShimPath)); - } + _filesNameWithoutExtensionUnderDotnetProfileTools + = filesNameWithoutExtensionUnderDotnetProfileTools ?? FileEnumerator.EnumerateFilesWithoutExtension(new DirectoryInfo(_nullableToolsShimPath)); + } - public void AddSuggestionRegistration(Registration registration) + public void AddSuggestionRegistration(Registration registration) + { + } + + public IEnumerable FindAllRegistrations() + { + if (_nullableToolsShimPath == null) { + return Array.Empty(); } - public IEnumerable FindAllRegistrations() - { - if (_nullableToolsShimPath == null) - { - return Array.Empty(); - } + return _filesNameWithoutExtensionUnderDotnetProfileTools.Select(p => + new Registration(Path.Combine(_nullableToolsShimPath, p))); + } - return _filesNameWithoutExtensionUnderDotnetProfileTools.Select(p => - new Registration(Path.Combine(_nullableToolsShimPath, p))); + public Registration FindRegistration(FileInfo soughtExecutable) + { + if (soughtExecutable == null) + { + throw new ArgumentNullException(nameof(soughtExecutable)); } - public Registration FindRegistration(FileInfo soughtExecutable) + if (_nullableToolsShimPath == null) { - if (soughtExecutable == null) - { - throw new ArgumentNullException(nameof(soughtExecutable)); - } - - if (_nullableToolsShimPath == null) - { - return null; - } - - if (!soughtExecutable.FullName.StartsWith(_nullableToolsShimPath)) - { - return null; - } + return null; + } - return new Registration(soughtExecutable.FullName); + if (!soughtExecutable.FullName.StartsWith(_nullableToolsShimPath)) + { + return null; } + + return new Registration(soughtExecutable.FullName); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/ISuggestionRegistration.cs b/src/System.CommandLine.Suggest/ISuggestionRegistration.cs index bd0e84e7c1..5da793033f 100644 --- a/src/System.CommandLine.Suggest/ISuggestionRegistration.cs +++ b/src/System.CommandLine.Suggest/ISuggestionRegistration.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; using System.IO; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public interface ISuggestionRegistration { - public interface ISuggestionRegistration - { - void AddSuggestionRegistration(Registration registration); - Registration FindRegistration(FileInfo soughtExecutable); - IEnumerable FindAllRegistrations(); - } -} + void AddSuggestionRegistration(Registration registration); + Registration FindRegistration(FileInfo soughtExecutable); + IEnumerable FindAllRegistrations(); +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/ISuggestionStore.cs b/src/System.CommandLine.Suggest/ISuggestionStore.cs index 9398a05db7..937e05e851 100644 --- a/src/System.CommandLine.Suggest/ISuggestionStore.cs +++ b/src/System.CommandLine.Suggest/ISuggestionStore.cs @@ -2,11 +2,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace System.CommandLine.Suggest -{ - public interface ISuggestionStore - { - string GetCompletions(string exeFileName, string suggestionTargetArguments, TimeSpan timeout); - } -} +namespace System.CommandLine.Suggest; +public interface ISuggestionStore +{ + string GetCompletions(string exeFileName, string suggestionTargetArguments, TimeSpan timeout); +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/PathExtensions.cs b/src/System.CommandLine.Suggest/PathExtensions.cs index 36d86c9c57..d5513b57cb 100644 --- a/src/System.CommandLine.Suggest/PathExtensions.cs +++ b/src/System.CommandLine.Suggest/PathExtensions.cs @@ -1,18 +1,17 @@ // 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. -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +internal static class PathExtensions { - internal static class PathExtensions + public static string RemoveExeExtension(this string path) { - public static string RemoveExeExtension(this string path) + if (path.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { - if (path.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) - { - path = path.Remove(path.LastIndexOf(".exe", StringComparison.OrdinalIgnoreCase)); - } - - return path; + path = path.Remove(path.LastIndexOf(".exe", StringComparison.OrdinalIgnoreCase)); } + + return path; } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/Program.cs b/src/System.CommandLine.Suggest/Program.cs index 42182a849c..4a7dc561ad 100644 --- a/src/System.CommandLine.Suggest/Program.cs +++ b/src/System.CommandLine.Suggest/Program.cs @@ -5,39 +5,38 @@ using System.Linq; using System.Threading.Tasks; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public class Program { - public class Program - { - internal static string DOTNET_SUGGEST_LOGGING = nameof(DOTNET_SUGGEST_LOGGING); + internal static string DOTNET_SUGGEST_LOGGING = nameof(DOTNET_SUGGEST_LOGGING); - public static async Task Main(string[] args) - { + public static async Task Main(string[] args) + { #if DEBUG - LogDebug(new[] { "dotnet-suggest received: " }.Concat(args).ToArray()); + LogDebug(new[] { "dotnet-suggest received: " }.Concat(args).ToArray()); #endif - var provider = new CombineSuggestionRegistration( - new GlobalToolsSuggestionRegistration(), - new FileSuggestionRegistration()); - var dispatcher = new SuggestionDispatcher(provider); - return await dispatcher.InvokeAsync(args); - } + var provider = new CombineSuggestionRegistration( + new GlobalToolsSuggestionRegistration(), + new FileSuggestionRegistration()); + var dispatcher = new SuggestionDispatcher(provider); + return await dispatcher.InvokeAsync(args); + } #if DEBUG - internal static void LogDebug(params string[] args) + internal static void LogDebug(params string[] args) + { + if (Environment.GetEnvironmentVariable(DOTNET_SUGGEST_LOGGING) == "1") { - if (Environment.GetEnvironmentVariable(DOTNET_SUGGEST_LOGGING) == "1") - { - var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - var dotnetSuggestFolder = Path.Combine(appData, "dotnet-suggest"); + var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var dotnetSuggestFolder = Path.Combine(appData, "dotnet-suggest"); - Directory.CreateDirectory(dotnetSuggestFolder); + Directory.CreateDirectory(dotnetSuggestFolder); - var logFile = Path.Combine(dotnetSuggestFolder, "debug.log"); - File.AppendAllText(logFile, string.Join("|", args) + Environment.NewLine); - } + var logFile = Path.Combine(dotnetSuggestFolder, "debug.log"); + File.AppendAllText(logFile, string.Join("|", args) + Environment.NewLine); } -#endif } -} +#endif +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/RegistrationPair.cs b/src/System.CommandLine.Suggest/RegistrationPair.cs index 7833eb26e4..c1a90e3d64 100644 --- a/src/System.CommandLine.Suggest/RegistrationPair.cs +++ b/src/System.CommandLine.Suggest/RegistrationPair.cs @@ -1,12 +1,11 @@ -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public class Registration { - public class Registration + public Registration(string executablePath) { - public Registration(string executablePath) - { - ExecutablePath = executablePath ?? throw new ArgumentNullException(nameof(executablePath)); - } - - public string ExecutablePath { get; } + ExecutablePath = executablePath ?? throw new ArgumentNullException(nameof(executablePath)); } -} + + public string ExecutablePath { get; } +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/ShellType.cs b/src/System.CommandLine.Suggest/ShellType.cs index 6f6fec0f20..94a503df41 100644 --- a/src/System.CommandLine.Suggest/ShellType.cs +++ b/src/System.CommandLine.Suggest/ShellType.cs @@ -1,12 +1,11 @@ // 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. -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public enum ShellType { - public enum ShellType - { - Bash, - PowerShell, - Zsh - } -} + Bash, + PowerShell, + Zsh +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/StringExtensions.cs b/src/System.CommandLine.Suggest/StringExtensions.cs index 24cc5a6727..92ecdcdeac 100644 --- a/src/System.CommandLine.Suggest/StringExtensions.cs +++ b/src/System.CommandLine.Suggest/StringExtensions.cs @@ -1,10 +1,9 @@ // 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. -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +internal static class StringExtensions { - internal static class StringExtensions - { - public static string Escape(this string commandLine) => commandLine.Replace("\"", "\\\""); - } -} + public static string Escape(this string commandLine) => commandLine.Replace("\"", "\\\""); +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/SuggestionDispatcher.cs b/src/System.CommandLine.Suggest/SuggestionDispatcher.cs index 512712a41f..2077e16837 100644 --- a/src/System.CommandLine.Suggest/SuggestionDispatcher.cs +++ b/src/System.CommandLine.Suggest/SuggestionDispatcher.cs @@ -8,286 +8,285 @@ using System.Threading.Tasks; using System.CommandLine.Completions; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public class SuggestionDispatcher { - public class SuggestionDispatcher + private readonly ISuggestionRegistration _suggestionRegistration; + private readonly ISuggestionStore _suggestionStore; + + public SuggestionDispatcher(ISuggestionRegistration suggestionRegistration, ISuggestionStore suggestionStore = null) { - private readonly ISuggestionRegistration _suggestionRegistration; - private readonly ISuggestionStore _suggestionStore; + _suggestionRegistration = suggestionRegistration ?? throw new ArgumentNullException(nameof(suggestionRegistration)); - public SuggestionDispatcher(ISuggestionRegistration suggestionRegistration, ISuggestionStore suggestionStore = null) - { - _suggestionRegistration = suggestionRegistration ?? throw new ArgumentNullException(nameof(suggestionRegistration)); + _suggestionStore = suggestionStore ?? new SuggestionStore(); - _suggestionStore = suggestionStore ?? new SuggestionStore(); + var shellTypeArgument = new Argument(nameof(ShellType)); - var shellTypeArgument = new Argument(nameof(ShellType)); + CompleteScriptCommand = new Command("script", "Print complete script for specific shell") + { + shellTypeArgument + }; + CompleteScriptCommand.SetAction(context => + { + SuggestionShellScriptHandler.Handle(context.Configuration.Output, context.GetValue(shellTypeArgument)); + }); - CompleteScriptCommand = new Command("script", "Print complete script for specific shell") - { - shellTypeArgument - }; - CompleteScriptCommand.SetAction(context => - { - SuggestionShellScriptHandler.Handle(context.Configuration.Output, context.GetValue(shellTypeArgument)); - }); + ListCommand = new Command("list") + { + Description = "Lists apps registered for suggestions", + }; + ListCommand.SetAction((ctx, cancellationToken) => + { + ctx.Configuration.Output.WriteLine(ShellPrefixesToMatch(_suggestionRegistration)); + return Task.CompletedTask; + }); - ListCommand = new Command("list") - { - Description = "Lists apps registered for suggestions", - }; - ListCommand.SetAction((ctx, cancellationToken) => - { - ctx.Configuration.Output.WriteLine(ShellPrefixesToMatch(_suggestionRegistration)); - return Task.CompletedTask; - }); + GetCommand = new Command("get", "Gets suggestions from the specified executable") + { + ExecutableOption, + PositionOption + }; + GetCommand.SetAction(Get); - GetCommand = new Command("get", "Gets suggestions from the specified executable") - { - ExecutableOption, - PositionOption - }; - GetCommand.SetAction(Get); + var commandPathOption = new Option("--command-path") { Description = "The path to the command for which to register suggestions" }; - var commandPathOption = new Option("--command-path") { Description = "The path to the command for which to register suggestions" }; + RegisterCommand = new Command("register", "Registers an app for suggestions") + { + commandPathOption, + new Option("--suggestion-command") { Description = "The command to invoke to retrieve suggestions" } + }; - RegisterCommand = new Command("register", "Registers an app for suggestions") - { - commandPathOption, - new Option("--suggestion-command") { Description = "The command to invoke to retrieve suggestions" } - }; + RegisterCommand.SetAction((context, cancellationToken) => + { + Register(context.GetValue(commandPathOption), context.Configuration.Output); + return Task.CompletedTask; + }); - RegisterCommand.SetAction((context, cancellationToken) => - { - Register(context.GetValue(commandPathOption), context.Configuration.Output); - return Task.CompletedTask; - }); + var root = new RootCommand + { + ListCommand, + GetCommand, + RegisterCommand, + CompleteScriptCommand, + }; + root.TreatUnmatchedTokensAsErrors = false; + Configuration = new CommandLineConfiguration(root); + } - var root = new RootCommand - { - ListCommand, - GetCommand, - RegisterCommand, - CompleteScriptCommand, - }; - root.TreatUnmatchedTokensAsErrors = false; - Configuration = new CommandLineConfiguration(root); - } + private Command CompleteScriptCommand { get; } - private Command CompleteScriptCommand { get; } + private Command GetCommand { get; } - private Command GetCommand { get; } + private Option ExecutableOption { get; } = GetExecutableOption(); - private Option ExecutableOption { get; } = GetExecutableOption(); + private static Option GetExecutableOption() + { + var option = new Option("--executable", "-e") { Description = "The executable to call for suggestions" }; + option.AcceptLegalFilePathsOnly(); - private static Option GetExecutableOption() - { - var option = new Option("--executable", "-e") { Description = "The executable to call for suggestions" }; - option.AcceptLegalFilePathsOnly(); + return option; + } - return option; - } + private Command ListCommand { get; } - private Command ListCommand { get; } + private Option PositionOption { get; } = new("--position", "-p") + { + Description = "The current character position on the command line", + DefaultValueFactory = (_) => short.MaxValue + }; - private Option PositionOption { get; } = new("--position", "-p") - { - Description = "The current character position on the command line", - DefaultValueFactory = (_) => short.MaxValue - }; + private Command RegisterCommand { get; } - private Command RegisterCommand { get; } + public CommandLineConfiguration Configuration { get; } - public CommandLineConfiguration Configuration { get; } + public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(5000); - public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(5000); + public Task InvokeAsync(string[] args) => Configuration.InvokeAsync(args); - public Task InvokeAsync(string[] args) => Configuration.InvokeAsync(args); + private void Register( + string commandPath, + TextWriter output) + { + var existingRegistration = _suggestionRegistration.FindRegistration(new FileInfo(commandPath)); - private void Register( - string commandPath, - TextWriter output) + if (existingRegistration is null) { - var existingRegistration = _suggestionRegistration.FindRegistration(new FileInfo(commandPath)); - - if (existingRegistration is null) - { - _suggestionRegistration.AddSuggestionRegistration( - new Registration(commandPath)); + _suggestionRegistration.AddSuggestionRegistration( + new Registration(commandPath)); - output.WriteLine($"Registered {commandPath}"); - } - else - { - output.WriteLine($"Registered {commandPath}"); - } + output.WriteLine($"Registered {commandPath}"); } - - private Task Get(ParseResult parseResult, CancellationToken cancellationToken) + else { - var commandPath = parseResult.GetValue(ExecutableOption); + output.WriteLine($"Registered {commandPath}"); + } + } - Registration suggestionRegistration; - if (commandPath.FullName == DotnetMuxer.Path.FullName) - { - suggestionRegistration = new Registration(commandPath.FullName); - } - else - { - suggestionRegistration = _suggestionRegistration.FindRegistration(commandPath); - } + private Task Get(ParseResult parseResult, CancellationToken cancellationToken) + { + var commandPath = parseResult.GetValue(ExecutableOption); + + Registration suggestionRegistration; + if (commandPath.FullName == DotnetMuxer.Path.FullName) + { + suggestionRegistration = new Registration(commandPath.FullName); + } + else + { + suggestionRegistration = _suggestionRegistration.FindRegistration(commandPath); + } - var position = parseResult.GetValue(PositionOption); + var position = parseResult.GetValue(PositionOption); - if (suggestionRegistration is null) - { - // Can't find a completion exe to call + if (suggestionRegistration is null) + { + // Can't find a completion exe to call #if DEBUG - Program.LogDebug($"Couldn't find registration for parse result: {parseResult}"); + Program.LogDebug($"Couldn't find registration for parse result: {parseResult}"); #endif - return Task.FromResult(0); - } + return Task.FromResult(0); + } - var targetExePath = suggestionRegistration.ExecutablePath; + var targetExePath = suggestionRegistration.ExecutablePath; - string targetArgs = FormatSuggestionArguments( - parseResult, - position, - targetExePath); + string targetArgs = FormatSuggestionArguments( + parseResult, + position, + targetExePath); #if DEBUG - Program.LogDebug($"dotnet-suggest sending: {targetArgs}"); + Program.LogDebug($"dotnet-suggest sending: {targetArgs}"); #endif - string completions = _suggestionStore.GetCompletions( - targetExePath, - targetArgs, - Timeout).Trim(); + string completions = _suggestionStore.GetCompletions( + targetExePath, + targetArgs, + Timeout).Trim(); #if DEBUG - Program.LogDebug($"dotnet-suggest returning: \"{completions.Replace("\r", "\\r").Replace("\n", "\\n")}\""); + Program.LogDebug($"dotnet-suggest returning: \"{completions.Replace("\r", "\\r").Replace("\n", "\\n")}\""); #endif - parseResult.Configuration.Output.Write(completions); + parseResult.Configuration.Output.Write(completions); - return Task.FromResult(0); - } + return Task.FromResult(0); + } - private static string ShellPrefixesToMatch( - ISuggestionRegistration suggestionProvider) - { - var registrations = suggestionProvider.FindAllRegistrations(); + private static string ShellPrefixesToMatch( + ISuggestionRegistration suggestionProvider) + { + var registrations = suggestionProvider.FindAllRegistrations(); - return string.Join(Environment.NewLine, Prefixes()); + return string.Join(Environment.NewLine, Prefixes()); - IEnumerable Prefixes() + IEnumerable Prefixes() + { + foreach (var r in registrations) { - foreach (var r in registrations) - { - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(r.ExecutablePath); + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(r.ExecutablePath); - yield return fileNameWithoutExtension; + yield return fileNameWithoutExtension; - if (fileNameWithoutExtension?.StartsWith("dotnet-", StringComparison.Ordinal) == true) - { - yield return "dotnet " + fileNameWithoutExtension.Substring("dotnet-".Length); - } + if (fileNameWithoutExtension?.StartsWith("dotnet-", StringComparison.Ordinal) == true) + { + yield return "dotnet " + fileNameWithoutExtension.Substring("dotnet-".Length); } } } + } - public static string FormatSuggestionArguments( - ParseResult parseResult, - int position, - string targetExeName) - { - var tokens = parseResult.UnmatchedTokens; + public static string FormatSuggestionArguments( + ParseResult parseResult, + int position, + string targetExeName) + { + var tokens = parseResult.UnmatchedTokens; - var commandLine = tokens.FirstOrDefault() ?? ""; + var commandLine = tokens.FirstOrDefault() ?? ""; - targetExeName = Path.GetFileName(targetExeName).RemoveExeExtension(); + targetExeName = Path.GetFileName(targetExeName).RemoveExeExtension(); - int offset = 0; + int offset = 0; - if (targetExeName == "dotnet") - { - // e.g. - int? endOfWhitespace = null; - int? endOfSecondtoken = null; + if (targetExeName == "dotnet") + { + // e.g. + int? endOfWhitespace = null; + int? endOfSecondtoken = null; - var choppedCommandLine = commandLine; + var choppedCommandLine = commandLine; + + for (var i = "dotnet".Length; i < commandLine.Length; i++) + { + if (!char.IsWhiteSpace(commandLine[i])) + { + endOfWhitespace = i; + break; + } + } - for (var i = "dotnet".Length; i < commandLine.Length; i++) + if (endOfWhitespace != null) + { + for (var i = endOfWhitespace.Value; i < commandLine.Length; i++) { - if (!char.IsWhiteSpace(commandLine[i])) + if (char.IsWhiteSpace(commandLine[i])) { - endOfWhitespace = i; + endOfSecondtoken = i; break; } } - if (endOfWhitespace != null) + if (endOfSecondtoken != null) { - for (var i = endOfWhitespace.Value; i < commandLine.Length; i++) + for (var i = endOfSecondtoken.Value; i < commandLine.Length; i++) { - if (char.IsWhiteSpace(commandLine[i])) + if (!char.IsWhiteSpace(commandLine[i])) { - endOfSecondtoken = i; + choppedCommandLine = commandLine.Substring(i); break; } } - - if (endOfSecondtoken != null) - { - for (var i = endOfSecondtoken.Value; i < commandLine.Length; i++) - { - if (!char.IsWhiteSpace(commandLine[i])) - { - choppedCommandLine = commandLine.Substring(i); - break; - } - } - } - else - { - choppedCommandLine = ""; - } } else { choppedCommandLine = ""; } + } + else + { + choppedCommandLine = ""; + } - if (choppedCommandLine.Length > 0) - { - offset = commandLine.Length - choppedCommandLine.Length; - } - else - { - offset = position; - } - - commandLine = choppedCommandLine; + if (choppedCommandLine.Length > 0) + { + offset = commandLine.Length - choppedCommandLine.Length; } - else if (commandLine.StartsWith(targetExeName)) + else { - if (commandLine.Length > targetExeName.Length) - { - commandLine = commandLine.Substring(targetExeName.Length + 1); - } - else - { - commandLine = ""; - } + offset = position; + } - offset = targetExeName.Length + 1; + commandLine = choppedCommandLine; + } + else if (commandLine.StartsWith(targetExeName)) + { + if (commandLine.Length > targetExeName.Length) + { + commandLine = commandLine.Substring(targetExeName.Length + 1); + } + else + { + commandLine = ""; } - position = position - offset; + offset = targetExeName.Length + 1; + } - var suggestDirective = $"[suggest:{position}]"; + position = position - offset; - return $"{suggestDirective} \"{commandLine.Escape()}\""; - } + var suggestDirective = $"[suggest:{position}]"; + + return $"{suggestDirective} \"{commandLine.Escape()}\""; } } \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/SuggestionShellScriptException.cs b/src/System.CommandLine.Suggest/SuggestionShellScriptException.cs index b73a999d99..aa40ffc5ae 100644 --- a/src/System.CommandLine.Suggest/SuggestionShellScriptException.cs +++ b/src/System.CommandLine.Suggest/SuggestionShellScriptException.cs @@ -1,20 +1,19 @@ // 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. -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public class SuggestionShellScriptException : Exception { - public class SuggestionShellScriptException : Exception + public SuggestionShellScriptException() { - public SuggestionShellScriptException() - { - } + } - public SuggestionShellScriptException(string message) : base(message) - { - } + public SuggestionShellScriptException(string message) : base(message) + { + } - public SuggestionShellScriptException(string message, Exception innerException) : base(message, innerException) - { - } + public SuggestionShellScriptException(string message, Exception innerException) : base(message, innerException) + { } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/SuggestionShellScriptHandler.cs b/src/System.CommandLine.Suggest/SuggestionShellScriptHandler.cs index 5b42745ce5..524b1eb178 100644 --- a/src/System.CommandLine.Suggest/SuggestionShellScriptHandler.cs +++ b/src/System.CommandLine.Suggest/SuggestionShellScriptHandler.cs @@ -4,46 +4,45 @@ using System.IO; using System.Reflection; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +internal static class SuggestionShellScriptHandler { - internal static class SuggestionShellScriptHandler + public static void Handle(TextWriter output, ShellType shellType) { - public static void Handle(TextWriter output, ShellType shellType) + switch (shellType) { - switch (shellType) - { - case ShellType.Bash: - PrintToConsoleFrom(output, "dotnet-suggest-shim.bash", useUnixLineEndings: true); - break; - case ShellType.PowerShell: - PrintToConsoleFrom(output, "dotnet-suggest-shim.ps1", useUnixLineEndings: false); - break; - case ShellType.Zsh: - PrintToConsoleFrom(output, "dotnet-suggest-shim.zsh", useUnixLineEndings: true); - break; - default: - throw new SuggestionShellScriptException($"Shell '{shellType}' is not supported."); - } + case ShellType.Bash: + PrintToConsoleFrom(output, "dotnet-suggest-shim.bash", useUnixLineEndings: true); + break; + case ShellType.PowerShell: + PrintToConsoleFrom(output, "dotnet-suggest-shim.ps1", useUnixLineEndings: false); + break; + case ShellType.Zsh: + PrintToConsoleFrom(output, "dotnet-suggest-shim.zsh", useUnixLineEndings: true); + break; + default: + throw new SuggestionShellScriptException($"Shell '{shellType}' is not supported."); } + } - private static void PrintToConsoleFrom(TextWriter output, string scriptName, bool useUnixLineEndings) + private static void PrintToConsoleFrom(TextWriter output, string scriptName, bool useUnixLineEndings) + { + var assemblyLocation = Assembly.GetAssembly(typeof(SuggestionShellScriptHandler)).Location; + var directory = Path.GetDirectoryName(assemblyLocation); + string scriptContent = File.ReadAllText(Path.Combine(directory, scriptName)); + bool hasUnixLineEndings = !scriptContent.Contains("\r\n"); + if (hasUnixLineEndings != useUnixLineEndings) { - var assemblyLocation = Assembly.GetAssembly(typeof(SuggestionShellScriptHandler)).Location; - var directory = Path.GetDirectoryName(assemblyLocation); - string scriptContent = File.ReadAllText(Path.Combine(directory, scriptName)); - bool hasUnixLineEndings = !scriptContent.Contains("\r\n"); - if (hasUnixLineEndings != useUnixLineEndings) + if (useUnixLineEndings) + { + scriptContent = scriptContent.Replace("\r\n", "\n"); + } + else { - if (useUnixLineEndings) - { - scriptContent = scriptContent.Replace("\r\n", "\n"); - } - else - { - scriptContent = scriptContent.Replace("\n", "\r\n"); - } + scriptContent = scriptContent.Replace("\n", "\r\n"); } - output.Write(scriptContent); } + output.Write(scriptContent); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Suggest/SuggestionStore.cs b/src/System.CommandLine.Suggest/SuggestionStore.cs index ac93972eb1..fdcf1810f8 100644 --- a/src/System.CommandLine.Suggest/SuggestionStore.cs +++ b/src/System.CommandLine.Suggest/SuggestionStore.cs @@ -6,72 +6,70 @@ using System.IO; using System.Threading.Tasks; -namespace System.CommandLine.Suggest +namespace System.CommandLine.Suggest; + +public class SuggestionStore : ISuggestionStore { - public class SuggestionStore : ISuggestionStore + public string GetCompletions(string exeFileName, string suggestionTargetArguments, TimeSpan timeout) { - public string GetCompletions(string exeFileName, string suggestionTargetArguments, TimeSpan timeout) + if (string.IsNullOrWhiteSpace(exeFileName)) { - if (string.IsNullOrWhiteSpace(exeFileName)) - { - throw new ArgumentException("Value cannot be null, empty, or consist entirely of whitespace.", nameof(exeFileName)); - } + throw new ArgumentException("Value cannot be null, empty, or consist entirely of whitespace.", nameof(exeFileName)); + } - if (string.IsNullOrWhiteSpace(suggestionTargetArguments)) - { - throw new ArgumentException("Value cannot be null, empty, or consist entirely of whitespace.", nameof(suggestionTargetArguments)); - } + if (string.IsNullOrWhiteSpace(suggestionTargetArguments)) + { + throw new ArgumentException("Value cannot be null, empty, or consist entirely of whitespace.", nameof(suggestionTargetArguments)); + } - string result = ""; + string result = ""; - try + try + { + // Invoke target with args + var processStartInfo = new ProcessStartInfo( + exeFileName, + suggestionTargetArguments) { - // Invoke target with args - var processStartInfo = new ProcessStartInfo( - exeFileName, - suggestionTargetArguments) - { - UseShellExecute = false, - RedirectStandardOutput = true - }; + UseShellExecute = false, + RedirectStandardOutput = true + }; - using (var process = new Process - { - StartInfo = processStartInfo - }) - { - process.Start(); + using (var process = new Process + { + StartInfo = processStartInfo + }) + { + process.Start(); - Task readToEndTask = process.StandardOutput.ReadToEndAsync(); + Task readToEndTask = process.StandardOutput.ReadToEndAsync(); - if (readToEndTask.Wait(timeout)) - { - result = readToEndTask.Result; - } - else - { - process.Kill(); - } + if (readToEndTask.Wait(timeout)) + { + result = readToEndTask.Result; + } + else + { + process.Kill(); } } - catch (Win32Exception exception) + } + catch (Win32Exception exception) + { + // We don't check for the existence of exeFileName until the exception in case + // it is a command that start process can resolve to a file name. + if (!File.Exists(exeFileName)) { - // We don't check for the existence of exeFileName until the exception in case - // it is a command that start process can resolve to a file name. - if (!File.Exists(exeFileName)) - { - var message = $"Unable to find the file '{exeFileName}'"; + var message = $"Unable to find the file '{exeFileName}'"; #if DEBUG - Program.LogDebug($"exception: {message}"); + Program.LogDebug($"exception: {message}"); #endif - throw new ArgumentException( - message, nameof(exeFileName), exception); - } + throw new ArgumentException( + message, nameof(exeFileName), exception); } - return result; } + return result; } -} - +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Binding/TestModels.cs b/src/System.CommandLine.Tests/Binding/TestModels.cs index 49cd584e22..94231b530e 100644 --- a/src/System.CommandLine.Tests/Binding/TestModels.cs +++ b/src/System.CommandLine.Tests/Binding/TestModels.cs @@ -5,125 +5,124 @@ using System.IO; using System.Threading.Tasks; -namespace System.CommandLine.Tests.Binding +namespace System.CommandLine.Tests.Binding; + +public class ClassWithMultiLetterCtorParameters { - public class ClassWithMultiLetterCtorParameters + public ClassWithMultiLetterCtorParameters( + int intOption = 123, + string stringOption = "the default", + bool boolOption = false) { - public ClassWithMultiLetterCtorParameters( - int intOption = 123, - string stringOption = "the default", - bool boolOption = false) - { - IntOption = intOption; - StringOption = stringOption; - BoolOption = boolOption; - } - - public int IntOption { get; } - public string StringOption { get; } - public bool BoolOption { get; } + IntOption = intOption; + StringOption = stringOption; + BoolOption = boolOption; } - public class ClassWithMultiLetterSetters - { - public int IntOption { get; set; } - public string StringOption { get; set; } - public bool BoolOption { get; set; } - } + public int IntOption { get; } + public string StringOption { get; } + public bool BoolOption { get; } +} - public class ClassWithComplexTypes - { - public int IntOption { get; set; } - public string StringOption { get; set; } - public bool BoolOption { get; set; } - public List ListOptionDefaultNull { get; set; } - public List> ListOptionDefaultEmpty { get; set; } = new (); - } +public class ClassWithMultiLetterSetters +{ + public int IntOption { get; set; } + public string StringOption { get; set; } + public bool BoolOption { get; set; } +} - public class ClassWithListTypePropertiesAndDefaultCtor - { - public List Strings { get; set; } +public class ClassWithComplexTypes +{ + public int IntOption { get; set; } + public string StringOption { get; set; } + public bool BoolOption { get; set; } + public List ListOptionDefaultNull { get; set; } + public List> ListOptionDefaultEmpty { get; set; } = new (); +} - public IEnumerable BoolOption { get; set; } - } +public class ClassWithListTypePropertiesAndDefaultCtor +{ + public List Strings { get; set; } - public class ClassWithCtorParameter - { - public ClassWithCtorParameter(T value) => Value = value; + public IEnumerable BoolOption { get; set; } +} - public T Value { get; } +public class ClassWithCtorParameter +{ + public ClassWithCtorParameter(T value) => Value = value; - public override string ToString() => - $"{nameof(ClassWithCtorParameter)}<{typeof(T).Name}>: {Value}"; - } + public T Value { get; } - public class ClassWithSetter - { - public T Value { get; set; } + public override string ToString() => + $"{nameof(ClassWithCtorParameter)}<{typeof(T).Name}>: {Value}"; +} - public override string ToString() => - $"{nameof(ClassWithSetter)}<{typeof(T).Name}>: {Value}"; +public class ClassWithSetter +{ + public T Value { get; set; } + + public override string ToString() => + $"{nameof(ClassWithSetter)}<{typeof(T).Name}>: {Value}"; +} + +public class ClassWithMethodHavingParameter +{ + private readonly TextWriter _output; + + public ClassWithMethodHavingParameter(ParseResult parseResult) + { + _output = parseResult.Configuration.Output; } - public class ClassWithMethodHavingParameter + public int Handle(T value) { - private readonly TextWriter _output; - - public ClassWithMethodHavingParameter(ParseResult parseResult) - { - _output = parseResult.Configuration.Output; - } - - public int Handle(T value) - { - ReceivedValue = value; - return 0; - } - - public Task HandleAsync(T value) - { - _output.Write(value.ToString()); - return Task.FromResult(Handle(value)); - } - - public T ReceivedValue { get; set; } + ReceivedValue = value; + return 0; } - public class ClassWithMultipleCtor + public Task HandleAsync(T value) { - public ClassWithMultipleCtor() - { - } + _output.Write(value.ToString()); + return Task.FromResult(Handle(value)); + } - public ClassWithMultipleCtor(int intProperty) - { - IntProperty = intProperty; - } + public T ReceivedValue { get; set; } +} - public int IntProperty { get; } +public class ClassWithMultipleCtor +{ + public ClassWithMultipleCtor() + { } - public class ClassWithSettersAndCtorParametersWithDifferentNames + public ClassWithMultipleCtor(int intProperty) { - public ClassWithSettersAndCtorParametersWithDifferentNames( - int i = 123, - string s = "the default", - bool b = false) - { - IntOption = i; - StringOption = s; - BoolOption = b; - } - - public int IntOption { get; set; } - public string StringOption { get; set; } - public bool BoolOption { get; set; } + IntProperty = intProperty; } - public class ClassWithOnePropertyNameThatIsSubstringOfAnother - { - public List Abc { get; set; } + public int IntProperty { get; } +} - public string AbcDef { get; set; } +public class ClassWithSettersAndCtorParametersWithDifferentNames +{ + public ClassWithSettersAndCtorParametersWithDifferentNames( + int i = 123, + string s = "the default", + bool b = false) + { + IntOption = i; + StringOption = s; + BoolOption = b; } + + public int IntOption { get; set; } + public string StringOption { get; set; } + public bool BoolOption { get; set; } } + +public class ClassWithOnePropertyNameThatIsSubstringOfAnother +{ + public List Abc { get; set; } + + public string AbcDef { get; set; } +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index 454942a873..8074f57b15 100644 --- a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -11,823 +11,822 @@ using FluentAssertions.Execution; using Xunit; -namespace System.CommandLine.Tests.Binding +namespace System.CommandLine.Tests.Binding; + +public class TypeConversionTests { - public class TypeConversionTests + protected T GetValue(Option option, string commandLine) { - protected T GetValue(Option option, string commandLine) - { - var result = new RootCommand { option }.Parse(commandLine); - return result.GetValue(option); - } + var result = new RootCommand { option }.Parse(commandLine); + return result.GetValue(option); + } - protected T GetValue(Argument argument, string commandLine) - { - var result = new RootCommand { argument }.Parse(commandLine); - return result.GetValue(argument); - } + protected T GetValue(Argument argument, string commandLine) + { + var result = new RootCommand { argument }.Parse(commandLine); + return result.GetValue(argument); + } - [Fact] - public void Option_argument_of_FileInfo_can_be_bound_without_custom_conversion_logic() - { - var option = new Option("--file"); + [Fact] + public void Option_argument_of_FileInfo_can_be_bound_without_custom_conversion_logic() + { + var option = new Option("--file"); - var file = new FileInfo(Path.Combine(new DirectoryInfo("temp").FullName, "the-file.txt")); + var file = new FileInfo(Path.Combine(new DirectoryInfo("temp").FullName, "the-file.txt")); - GetValue(option, $"--file {file.FullName}") - .Name - .Should() - .Be("the-file.txt"); - } + GetValue(option, $"--file {file.FullName}") + .Name + .Should() + .Be("the-file.txt"); + } - [Fact] - public void Command_argument_of_FileInfo_can_be_bound_without_custom_conversion_logic() - { - var argument = new Argument("the-arg"); + [Fact] + public void Command_argument_of_FileInfo_can_be_bound_without_custom_conversion_logic() + { + var argument = new Argument("the-arg"); - var command = new Command("the-command") - { - argument - }; + var command = new Command("the-command") + { + argument + }; - var file = new FileInfo(Path.Combine(new DirectoryInfo("temp").FullName, "the-file.txt")); - var result = command.Parse($"{file.FullName}"); + var file = new FileInfo(Path.Combine(new DirectoryInfo("temp").FullName, "the-file.txt")); + var result = command.Parse($"{file.FullName}"); - result.GetValue(argument) - .Name - .Should() - .Be("the-file.txt"); - } + result.GetValue(argument) + .Name + .Should() + .Be("the-file.txt"); + } - [Fact] - public void Command_argument_of_FileInfo_returns_null_when_argument_is_not_provided() + [Fact] + public void Command_argument_of_FileInfo_returns_null_when_argument_is_not_provided() + { + var argument = new Argument("the-arg") { - var argument = new Argument("the-arg") - { - Arity = ArgumentArity.ZeroOrOne - }; - var command = new Command("the-command") - { - argument - }; + Arity = ArgumentArity.ZeroOrOne + }; + var command = new Command("the-command") + { + argument + }; - var result = command.Parse(""); + var result = command.Parse(""); - result.GetValue(argument) - .Should() - .BeNull(); - } + result.GetValue(argument) + .Should() + .BeNull(); + } - [Fact] - public void Argument_of_FileInfo_that_is_empty_results_in_an_informative_error() - { - var option = new Option("--file"); - var result = new RootCommand { option }.Parse(new string[] { "--file", "" }); - - result.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Contain("Cannot parse argument '' for option '--file'"); - } - - [Fact] - public void Argument_of_array_of_FileInfo_can_be_called_without_custom_conversion_logic() - { - var option = new Option("--file"); + [Fact] + public void Argument_of_FileInfo_that_is_empty_results_in_an_informative_error() + { + var option = new Option("--file"); + var result = new RootCommand { option }.Parse(new string[] { "--file", "" }); + + result.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Contain("Cannot parse argument '' for option '--file'"); + } - var file1 = new FileInfo(Path.Combine(new DirectoryInfo("temp").FullName, "file1.txt")); - var file2 = new FileInfo(Path.Combine(new DirectoryInfo("temp").FullName, "file2.txt")); + [Fact] + public void Argument_of_array_of_FileInfo_can_be_called_without_custom_conversion_logic() + { + var option = new Option("--file"); - GetValue(option, $"--file {file1.FullName} --file {file2.FullName}") - .Select(fi => fi.Name) - .Should() - .BeEquivalentTo("file1.txt", "file2.txt"); - } + var file1 = new FileInfo(Path.Combine(new DirectoryInfo("temp").FullName, "file1.txt")); + var file2 = new FileInfo(Path.Combine(new DirectoryInfo("temp").FullName, "file2.txt")); - [Fact] - public void Argument_defaults_arity_to_One_for_non_IEnumerable_types() - { - var argument = new Argument("arg"); + GetValue(option, $"--file {file1.FullName} --file {file2.FullName}") + .Select(fi => fi.Name) + .Should() + .BeEquivalentTo("file1.txt", "file2.txt"); + } - argument.Arity.Should().BeEquivalentTo(ArgumentArity.ExactlyOne); - } + [Fact] + public void Argument_defaults_arity_to_One_for_non_IEnumerable_types() + { + var argument = new Argument("arg"); - [Fact] - public void Argument_defaults_arity_to_ExactlyOne_for_string() - { - var argument = new Argument("arg"); + argument.Arity.Should().BeEquivalentTo(ArgumentArity.ExactlyOne); + } - argument.Arity.Should().BeEquivalentTo(ArgumentArity.ExactlyOne); - } + [Fact] + public void Argument_defaults_arity_to_ExactlyOne_for_string() + { + var argument = new Argument("arg"); + + argument.Arity.Should().BeEquivalentTo(ArgumentArity.ExactlyOne); + } - [Fact] - public void Command_Argument_defaults_arity_to_ZeroOrOne_for_nullable_types() + [Fact] + public void Command_Argument_defaults_arity_to_ZeroOrOne_for_nullable_types() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Argument("arg") - }; + new Argument("arg") + }; - command.Arguments.Single().Arity.Should().BeEquivalentTo(ArgumentArity.ZeroOrOne); - } + command.Arguments.Single().Arity.Should().BeEquivalentTo(ArgumentArity.ZeroOrOne); + } - [Theory] - [InlineData(typeof(int[]))] - [InlineData(typeof(IEnumerable))] - [InlineData(typeof(List))] - public void Argument_infers_arity_of_IEnumerable_types_as_OneOrMore(Type type) - { - var argument = ArgumentBuilder.CreateArgument(type); + [Theory] + [InlineData(typeof(int[]))] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(List))] + public void Argument_infers_arity_of_IEnumerable_types_as_OneOrMore(Type type) + { + var argument = ArgumentBuilder.CreateArgument(type); - argument.Arity.Should().BeEquivalentTo(ArgumentArity.OneOrMore); - } + argument.Arity.Should().BeEquivalentTo(ArgumentArity.OneOrMore); + } + + [Fact] + public void Argument_parses_as_the_default_value_when_the_option_has_not_been_applied() + { + var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; - [Fact] - public void Argument_parses_as_the_default_value_when_the_option_has_not_been_applied() + var command = new Command("something") { - var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; + option + }; - var command = new Command("something") - { - option - }; + var result = command.Parse("something"); - var result = command.Parse("something"); + result.GetValue(option).Should().Be(123); + } - result.GetValue(option).Should().Be(123); - } + [Fact] + public void Option_does_not_parse_as_the_default_value_when_the_option_has_been_applied() + { + var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; - [Fact] - public void Option_does_not_parse_as_the_default_value_when_the_option_has_been_applied() + var command = new Command("something") { - var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; + option + }; - var command = new Command("something") - { - option - }; + var result = command.Parse("something -x 456"); - var result = command.Parse("something -x 456"); + result.GetValue(option).Should().Be(456); + } - result.GetValue(option).Should().Be(456); - } + [Theory] + [InlineData("the-command -x")] + [InlineData("the-command -x true")] + [InlineData("the-command -x:true")] + [InlineData("the-command -x=true")] + public void Bool_parses_as_true_when_the_option_has_been_applied(string commandLine) + { + var option = new Option("-x"); - [Theory] - [InlineData("the-command -x")] - [InlineData("the-command -x true")] - [InlineData("the-command -x:true")] - [InlineData("the-command -x=true")] - public void Bool_parses_as_true_when_the_option_has_been_applied(string commandLine) + var command = new Command("the-command") { - var option = new Option("-x"); + option + }; - var command = new Command("the-command") - { - option - }; + command + .Parse(commandLine) + .GetValue(option) + .Should() + .BeTrue(); + } - command - .Parse(commandLine) - .GetValue(option) - .Should() - .BeTrue(); - } + [Fact] // https://github.com/dotnet/command-line-api/issues/2210 + public void Nullable_bool_with_unparseable_argument_does_not_throw() + { + RootCommand rootCommand = new(); + Option option = new("--test"); + rootCommand.Options.Add(option); + var result = rootCommand.Parse("--test ouch"); - [Fact] // https://github.com/dotnet/command-line-api/issues/2210 - public void Nullable_bool_with_unparseable_argument_does_not_throw() - { - RootCommand rootCommand = new(); - Option option = new("--test"); - rootCommand.Options.Add(option); - var result = rootCommand.Parse("--test ouch"); + result.Invoking(r => r.GetValue(option)) + .Should().NotThrow(); + } - result.Invoking(r => r.GetValue(option)) - .Should().NotThrow(); - } + [Fact] // https://github.com/dotnet/command-line-api/issues/2210 + public void Bool_with_unparseable_argument_does_not_throw() + { + RootCommand rootCommand = new(); + Option option = new("--test"); + rootCommand.Options.Add(option); + var result = rootCommand.Parse("--test ouch"); - [Fact] // https://github.com/dotnet/command-line-api/issues/2210 - public void Bool_with_unparseable_argument_does_not_throw() - { - RootCommand rootCommand = new(); - Option option = new("--test"); - rootCommand.Options.Add(option); - var result = rootCommand.Parse("--test ouch"); - - result.Invoking(r => r.GetValue(option)) - .Should().NotThrow(); - } - - [Theory] - [InlineData("the-command -x")] - [InlineData("the-command -x true")] - [InlineData("the-command -x:true")] - [InlineData("the-command -x=true")] - public void Nullable_bool_parses_as_true_when_the_option_has_been_applied(string commandLine) - { - var option = new Option("-x"); + result.Invoking(r => r.GetValue(option)) + .Should().NotThrow(); + } - var command = new Command("the-command") - { - option - }; + [Theory] + [InlineData("the-command -x")] + [InlineData("the-command -x true")] + [InlineData("the-command -x:true")] + [InlineData("the-command -x=true")] + public void Nullable_bool_parses_as_true_when_the_option_has_been_applied(string commandLine) + { + var option = new Option("-x"); - command - .Parse(commandLine) - .GetValue(option) - .Should() - .BeTrue(); - } - - [Theory] - [InlineData("the-command -x false")] - [InlineData("the-command -x:false")] - [InlineData("the-command -x=false")] - public void Nullable_bool_parses_as_false_when_the_option_has_been_applied(string commandLine) + var command = new Command("the-command") { - var option = new Option("-x"); + option + }; - var command = new Command("the-command") - { - option - }; + command + .Parse(commandLine) + .GetValue(option) + .Should() + .BeTrue(); + } - command - .Parse(commandLine) - .GetValue(option) - .Should() - .BeFalse(); - } + [Theory] + [InlineData("the-command -x false")] + [InlineData("the-command -x:false")] + [InlineData("the-command -x=false")] + public void Nullable_bool_parses_as_false_when_the_option_has_been_applied(string commandLine) + { + var option = new Option("-x"); - [Fact] - public void Nullable_bool_parses_as_null_when_the_option_has_not_been_applied() - { - var option = new Option("-x"); - - GetValue(option, "") - .Should() - .Be(null); - } - - [Theory] - [InlineData("-x", true)] - [InlineData("-x:true", true)] - [InlineData("-x:false", false)] - public void Nullable_bool_option_result_casts_to_nullable_bool(string command, bool expectedValue) + var command = new Command("the-command") { - var option = new Option("-x"); + option + }; - var value = new RootCommand { option } - .Parse(command) - .GetResult(option) - .GetValueOrDefault(); + command + .Parse(commandLine) + .GetValue(option) + .Should() + .BeFalse(); + } - value.Should().BeAssignableTo(); - value.Should().Be(expectedValue); - } + [Fact] + public void Nullable_bool_parses_as_null_when_the_option_has_not_been_applied() + { + var option = new Option("-x"); - [Fact] - public void When_exactly_one_argument_is_expected_and_none_are_provided_then_getting_value_throws() - { - var option = new Option("-x"); + GetValue(option, "") + .Should() + .Be(null); + } - var command = new Command("the-command") - { - option - }; + [Theory] + [InlineData("-x", true)] + [InlineData("-x:true", true)] + [InlineData("-x:false", false)] + public void Nullable_bool_option_result_casts_to_nullable_bool(string command, bool expectedValue) + { + var option = new Option("-x"); - var result = command.Parse("the-command -x"); + var value = new RootCommand { option } + .Parse(command) + .GetResult(option) + .GetValueOrDefault(); - Action getValue = () => result.GetValue(option); + value.Should().BeAssignableTo(); + value.Should().Be(expectedValue); + } - getValue.Should() - .Throw() - .Which - .Message - .Should() - .Be("Required argument missing for option: '-x'."); - } + [Fact] + public void When_exactly_one_argument_is_expected_and_none_are_provided_then_getting_value_throws() + { + var option = new Option("-x"); - [Theory] - [InlineData("c -a o c c")] - [InlineData("c c -a o c")] - [InlineData("c c c")] - public void When_command_argument_has_arity_greater_than_one_it_captures_arguments_before_and_after_option(string commandLine) + var command = new Command("the-command") { - var argument = new Argument("the-arg") - { - Arity = ArgumentArity.ZeroOrMore - }; + option + }; - var command = new Command("the-command") - { - new Option("-a"), - argument - }; + var result = command.Parse("the-command -x"); - var result = command.Parse(commandLine); + Action getValue = () => result.GetValue(option); - result.GetValue(argument) - .Should() - .BeEquivalentTo(new[] { "c", "c", "c" }); - } + getValue.Should() + .Throw() + .Which + .Message + .Should() + .Be("Required argument missing for option: '-x'."); + } - [Fact] - public void The_default_value_of_a_bool_option_with_no_arguments_is_true() + [Theory] + [InlineData("c -a o c c")] + [InlineData("c c -a o c")] + [InlineData("c c c")] + public void When_command_argument_has_arity_greater_than_one_it_captures_arguments_before_and_after_option(string commandLine) + { + var argument = new Argument("the-arg") { - var option = new Option("-x"); + Arity = ArgumentArity.ZeroOrMore + }; - var command = - new Command("the-command") - { - option - }; + var command = new Command("the-command") + { + new Option("-a"), + argument + }; - var result = command.Parse("-x"); + var result = command.Parse(commandLine); - result.GetValue(option) - .Should() - .Be(true); - } + result.GetValue(argument) + .Should() + .BeEquivalentTo(new[] { "c", "c", "c" }); + } - [Fact] - public void By_default_a_bool_option_without_arguments_parses_as_false_when_it_is_not_applied() - { - var option = new Option("-x"); + [Fact] + public void The_default_value_of_a_bool_option_with_no_arguments_is_true() + { + var option = new Option("-x"); - var command = new Command("something") + var command = + new Command("the-command") { option }; - var result = command.Parse("something"); + var result = command.Parse("-x"); + + result.GetValue(option) + .Should() + .Be(true); + } + + [Fact] + public void By_default_a_bool_option_without_arguments_parses_as_false_when_it_is_not_applied() + { + var option = new Option("-x"); + + var command = new Command("something") + { + option + }; + + var result = command.Parse("something"); - result.GetValue(option) - .Should() - .Be(false); - } + result.GetValue(option) + .Should() + .Be(false); + } + + [Fact] + public void An_option_with_a_default_value_parses_as_the_default_value_when_the_option_has_not_been_applied() + { + var option = new Option("-x") { DefaultValueFactory = (_) => "123" }; - [Fact] - public void An_option_with_a_default_value_parses_as_the_default_value_when_the_option_has_not_been_applied() + var command = new Command("something") { - var option = new Option("-x") { DefaultValueFactory = (_) => "123" }; + option + }; - var command = new Command("something") - { - option - }; + var result = command.Parse("something"); - var result = command.Parse("something"); + result.GetValue(option) + .Should() + .Be("123"); + } - result.GetValue(option) - .Should() - .Be("123"); - } + [Fact] + public void An_option_with_a_default_value_of_null_parses_as_null_when_the_option_has_not_been_applied() + { + var option = new Option("-x") { DefaultValueFactory = (_) => null }; - [Fact] - public void An_option_with_a_default_value_of_null_parses_as_null_when_the_option_has_not_been_applied() + var command = new Command("something") { - var option = new Option("-x") { DefaultValueFactory = (_) => null }; + option + }; - var command = new Command("something") - { - option - }; + var result = command.Parse("something"); - var result = command.Parse("something"); + result.GetValue(option) + .Should() + .Be(null); + } - result.GetValue(option) - .Should() - .Be(null); - } + [Fact] + public void A_default_value_of_a_non_string_type_can_be_specified() + { + var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; - [Fact] - public void A_default_value_of_a_non_string_type_can_be_specified() + var command = new Command("something") { - var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; + option + }; - var command = new Command("something") - { - option - }; + command.Parse("something") + .GetValue(option) + .Should() + .Be(123); + } + + [Fact] + public void A_default_value_with_a_custom_constructor_can_be_specified_for_an_option_argument() + { + var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); - command.Parse("something") - .GetValue(option) - .Should() - .Be(123); - } + var option = new Option("-x") { DefaultValueFactory = (_) => directoryInfo }; - [Fact] - public void A_default_value_with_a_custom_constructor_can_be_specified_for_an_option_argument() + var command = new Command("something") { - var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); + option + }; - var option = new Option("-x") { DefaultValueFactory = (_) => directoryInfo }; + var result = command.Parse("something"); - var command = new Command("something") - { - option - }; + result.GetValue(option).Should().Be(directoryInfo); + } - var result = command.Parse("something"); + [Fact] + public void A_default_value_with_a_custom_constructor_can_be_specified_for_a_command_argument() + { + var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); - result.GetValue(option).Should().Be(directoryInfo); - } + var argument = new Argument("the-arg") { DefaultValueFactory = (_) => directoryInfo }; - [Fact] - public void A_default_value_with_a_custom_constructor_can_be_specified_for_a_command_argument() + var command = new Command("something") { - var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); + argument + }; - var argument = new Argument("the-arg") { DefaultValueFactory = (_) => directoryInfo }; + var result = command.Parse("something"); - var command = new Command("something") - { - argument - }; - - var result = command.Parse("something"); + result.Errors.Should().BeEmpty(); - result.Errors.Should().BeEmpty(); + var value = result.GetValue(argument); - var value = result.GetValue(argument); + value.Should().Be(directoryInfo); + } - value.Should().Be(directoryInfo); - } + [Fact] + public void Specifying_an_option_argument_overrides_the_default_value() + { + var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; - [Fact] - public void Specifying_an_option_argument_overrides_the_default_value() + var command = new Command("something") { - var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; - - var command = new Command("something") - { - option - }; + option + }; - var result = command.Parse("something -x 456"); + var result = command.Parse("something -x 456"); - var value = result.GetValue(option); + var value = result.GetValue(option); - value.Should().Be(456); - } + value.Should().Be(456); + } - [Fact] - public void Values_can_be_correctly_converted_to_DateTime_without_the_parser_specifying_a_custom_converter() - { - var option = new Option("-x"); + [Fact] + public void Values_can_be_correctly_converted_to_DateTime_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-x"); - var dateString = "2022-02-06T01:46:03.0000000-08:00"; + var dateString = "2022-02-06T01:46:03.0000000-08:00"; - GetValue(option, $"-x {dateString}").Should().Be(DateTime.Parse(dateString)); - } + GetValue(option, $"-x {dateString}").Should().Be(DateTime.Parse(dateString)); + } - [Fact] - public void Values_can_be_correctly_converted_to_nullable_DateTime_without_the_parser_specifying_a_custom_converter() - { - var option = new Option("-x"); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_DateTime_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-x"); - var dateString = "2022-02-06T01:46:03.0000000-08:00"; + var dateString = "2022-02-06T01:46:03.0000000-08:00"; - GetValue(option, $"-x {dateString}").Should().Be(DateTime.Parse(dateString)); - } + GetValue(option, $"-x {dateString}").Should().Be(DateTime.Parse(dateString)); + } - [Fact] - public void Values_can_be_correctly_converted_to_DateTimeOffset_without_the_parser_specifying_a_custom_converter() - { - var option = new Option("-x"); + [Fact] + public void Values_can_be_correctly_converted_to_DateTimeOffset_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-x"); - var dateString = "2022-02-06T09:52:54.5275055-08:00"; + var dateString = "2022-02-06T09:52:54.5275055-08:00"; - GetValue(option, $"-x {dateString}").Should().Be(DateTime.Parse(dateString)); - } + GetValue(option, $"-x {dateString}").Should().Be(DateTime.Parse(dateString)); + } - [Fact] - public void Values_can_be_correctly_converted_to_nullable_DateTimeOffset_without_the_parser_specifying_a_custom_converter() - { - var option = new Option("-x"); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_DateTimeOffset_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-x"); - var dateString = "2022-02-06T09:52:54.5275055-08:00"; + var dateString = "2022-02-06T09:52:54.5275055-08:00"; - GetValue(option, $"-x {dateString}").Should().Be(DateTime.Parse(dateString)); - } + GetValue(option, $"-x {dateString}").Should().Be(DateTime.Parse(dateString)); + } - [Fact] - public void Values_can_be_correctly_converted_to_decimal_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456m); + [Fact] + public void Values_can_be_correctly_converted_to_decimal_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456m); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_decimal_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456m); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_decimal_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456m); - [Fact] - public void Values_can_be_correctly_converted_to_double_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456d); + [Fact] + public void Values_can_be_correctly_converted_to_double_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456d); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_double_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456d); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_double_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456d); - [Fact] - public void Values_can_be_correctly_converted_to_float_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456f); + [Fact] + public void Values_can_be_correctly_converted_to_float_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456f); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_float_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456f); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_float_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 123.456").Should().Be(123.456f); - [Fact] - public void Values_can_be_correctly_converted_to_Guid_without_the_parser_specifying_a_custom_converter() - { - const string guidString = "75517282-018F-46BB-B15F-1D8DBFE23F6E"; + [Fact] + public void Values_can_be_correctly_converted_to_Guid_without_the_parser_specifying_a_custom_converter() + { + const string guidString = "75517282-018F-46BB-B15F-1D8DBFE23F6E"; - GetValue(new Option("-x"), $"-x {guidString}").Should().Be(Guid.Parse(guidString)); - } + GetValue(new Option("-x"), $"-x {guidString}").Should().Be(Guid.Parse(guidString)); + } - [Fact] - public void Values_can_be_correctly_converted_to_nullable_Guid_without_the_parser_specifying_a_custom_converter() - { - const string guidString = "75517282-018F-46BB-B15F-1D8DBFE23F6E"; + [Fact] + public void Values_can_be_correctly_converted_to_nullable_Guid_without_the_parser_specifying_a_custom_converter() + { + const string guidString = "75517282-018F-46BB-B15F-1D8DBFE23F6E"; - GetValue(new Option("-x"), $"-x {guidString}").Should().Be(Guid.Parse(guidString)); - } + GetValue(new Option("-x"), $"-x {guidString}").Should().Be(Guid.Parse(guidString)); + } - [Fact] - public void Values_can_be_correctly_converted_to_TimeSpan_without_the_parser_specifying_a_custom_converter() - { - const string timeSpanString = "30"; + [Fact] + public void Values_can_be_correctly_converted_to_TimeSpan_without_the_parser_specifying_a_custom_converter() + { + const string timeSpanString = "30"; - GetValue(new Option("-x"), $"-x {timeSpanString}").Should().Be(TimeSpan.Parse(timeSpanString)); - } + GetValue(new Option("-x"), $"-x {timeSpanString}").Should().Be(TimeSpan.Parse(timeSpanString)); + } - [Fact] - public void Values_can_be_correctly_converted_to_nullable_TimeSpan_without_the_parser_specifying_a_custom_converter() - { - const string timeSpanString = "30"; + [Fact] + public void Values_can_be_correctly_converted_to_nullable_TimeSpan_without_the_parser_specifying_a_custom_converter() + { + const string timeSpanString = "30"; - GetValue(new Option("-x"), $"-x {timeSpanString}").Should().Be(TimeSpan.Parse(timeSpanString)); - } + GetValue(new Option("-x"), $"-x {timeSpanString}").Should().Be(TimeSpan.Parse(timeSpanString)); + } - [Fact] - public void Values_can_be_correctly_converted_to_Uri_when_custom_parser_is_provided() + [Fact] + public void Values_can_be_correctly_converted_to_Uri_when_custom_parser_is_provided() + { + Option option = new ("-x") { - Option option = new ("-x") - { - CustomParser = (argumentResult) => Uri.TryCreate(argumentResult.Tokens.Last().Value, UriKind.RelativeOrAbsolute, out var uri) ? uri : null - }; + CustomParser = (argumentResult) => Uri.TryCreate(argumentResult.Tokens.Last().Value, UriKind.RelativeOrAbsolute, out var uri) ? uri : null + }; - GetValue(option, "-x http://example.com").Should().BeEquivalentTo(new Uri("http://example.com")); - } + GetValue(option, "-x http://example.com").Should().BeEquivalentTo(new Uri("http://example.com")); + } - [Fact] - public void Options_with_arguments_specified_can_be_correctly_converted_to_bool_without_the_parser_specifying_a_custom_converter() - { - using var _ = new AssertionScope(); - GetValue(new Option("-x"), "-x false").Should().BeFalse(); - GetValue(new Option("-x"), "-x true").Should().BeTrue(); - } + [Fact] + public void Options_with_arguments_specified_can_be_correctly_converted_to_bool_without_the_parser_specifying_a_custom_converter() + { + using var _ = new AssertionScope(); + GetValue(new Option("-x"), "-x false").Should().BeFalse(); + GetValue(new Option("-x"), "-x true").Should().BeTrue(); + } - [Fact] - public void Values_can_be_correctly_converted_to_long_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 123456790").Should().Be(123456790L); + [Fact] + public void Values_can_be_correctly_converted_to_long_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 123456790").Should().Be(123456790L); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_long_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 123456790").Should().Be(123456790L); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_long_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 123456790").Should().Be(123456790L); - [Fact] - public void Values_can_be_correctly_converted_to_short_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-s"), "-s 1234").Should().Be(1234); + [Fact] + public void Values_can_be_correctly_converted_to_short_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-s"), "-s 1234").Should().Be(1234); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_short_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-s"), "-s 1234").Should().Be(1234); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_short_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-s"), "-s 1234").Should().Be(1234); - [Fact] - public void Values_can_be_correctly_converted_to_ulong_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 1234").Should().Be(1234); + [Fact] + public void Values_can_be_correctly_converted_to_ulong_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 1234").Should().Be(1234); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_ulong_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 1234").Should().Be(1234); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_ulong_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 1234").Should().Be(1234); - [Fact] - public void Values_can_be_correctly_converted_to_ushort_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-s"), "-s 1234").Should().Be(1234); + [Fact] + public void Values_can_be_correctly_converted_to_ushort_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-s"), "-s 1234").Should().Be(1234); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_ushort_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-s"), "-s 1234").Should().Be(1234); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_ushort_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-s"), "-s 1234").Should().Be(1234); - [Fact] - public void Values_can_be_correctly_converted_to_sbyte_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-us"), "-us 123").Should().Be(123); + [Fact] + public void Values_can_be_correctly_converted_to_sbyte_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-us"), "-us 123").Should().Be(123); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_sbyte_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-us"), "-us 123").Should().Be(123); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_sbyte_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-us"), "-us 123").Should().Be(123); - [Fact] - public void Values_can_be_correctly_converted_to_ipaddress_when_custom_parser_is_provided() - { - Option option = new ("-us") - { - CustomParser = (argumentResult) => IPAddress.Parse(argumentResult.Tokens.Last().Value) - }; + [Fact] + public void Values_can_be_correctly_converted_to_ipaddress_when_custom_parser_is_provided() + { + Option option = new ("-us") + { + CustomParser = (argumentResult) => IPAddress.Parse(argumentResult.Tokens.Last().Value) + }; - GetValue(option, "-us 1.2.3.4").Should().Be(IPAddress.Parse("1.2.3.4")); - } + GetValue(option, "-us 1.2.3.4").Should().Be(IPAddress.Parse("1.2.3.4")); + } #if NETCOREAPP3_0_OR_GREATER - [Fact] - public void Values_can_be_correctly_converted_to_ipendpoint_when_custom_parser_is_provided() + [Fact] + public void Values_can_be_correctly_converted_to_ipendpoint_when_custom_parser_is_provided() + { + Option option = new("-us") { - Option option = new("-us") - { - CustomParser = (argumentResult) => IPEndPoint.Parse(argumentResult.Tokens.Last().Value) - }; + CustomParser = (argumentResult) => IPEndPoint.Parse(argumentResult.Tokens.Last().Value) + }; - GetValue(option, "-us 1.2.3.4:56").Should().Be(IPEndPoint.Parse("1.2.3.4:56")); - } + GetValue(option, "-us 1.2.3.4:56").Should().Be(IPEndPoint.Parse("1.2.3.4:56")); + } #endif #if NET6_0_OR_GREATER - [Fact] - public void Values_can_be_correctly_converted_to_dateonly_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-us"), "-us 2022-03-02").Should().Be(DateOnly.Parse("2022-03-02")); + [Fact] + public void Values_can_be_correctly_converted_to_dateonly_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-us"), "-us 2022-03-02").Should().Be(DateOnly.Parse("2022-03-02")); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_dateonly_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 2022-03-02").Should().Be(DateOnly.Parse("2022-03-02")); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_dateonly_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 2022-03-02").Should().Be(DateOnly.Parse("2022-03-02")); - [Fact] - public void Values_can_be_correctly_converted_to_timeonly_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-us"), "-us 12:34:56").Should().Be(TimeOnly.Parse("12:34:56")); + [Fact] + public void Values_can_be_correctly_converted_to_timeonly_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-us"), "-us 12:34:56").Should().Be(TimeOnly.Parse("12:34:56")); - [Fact] - public void Values_can_be_correctly_converted_to_nullable_timeonly_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 12:34:56").Should().Be(TimeOnly.Parse("12:34:56")); + [Fact] + public void Values_can_be_correctly_converted_to_nullable_timeonly_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 12:34:56").Should().Be(TimeOnly.Parse("12:34:56")); #endif - [Fact] - public void Values_can_be_correctly_converted_to_byte_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-us"), "-us 123").Should().Be(123); - - [Fact] - public void Values_can_be_correctly_converted_to_nullable_byte_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-us"), "-us 123").Should().Be(123); - - [Fact] - public void Values_can_be_correctly_converted_to_uint_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-us"), "-us 1234").Should().Be(1234); - - [Fact] - public void Values_can_be_correctly_converted_to_nullable_uint_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-us"), "-us 1234").Should().Be(1234); - - [Fact] - public void Values_can_be_correctly_converted_to_array_of_int_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x 1 -x 2 -x 3").Should().BeEquivalentTo(new[] { 1, 2, 3 }); - - [Theory] - [InlineData(0, 100_000, typeof(string[]))] - [InlineData(0, 3, typeof(string[]))] - [InlineData(0, 100_000, typeof(IEnumerable))] - [InlineData(0, 3, typeof(IEnumerable))] - [InlineData(0, 100_000, typeof(List))] - [InlineData(0, 3, typeof(List))] - [InlineData(0, 100_000, typeof(IList))] - [InlineData(0, 3, typeof(IList))] - [InlineData(0, 100_000, typeof(ICollection))] - [InlineData(0, 3, typeof(ICollection))] - - [InlineData(1, 100_000, typeof(string[]))] - [InlineData(1, 3, typeof(string[]))] - [InlineData(1, 100_000, typeof(IEnumerable))] - [InlineData(1, 3, typeof(IEnumerable))] - [InlineData(1, 100_000, typeof(List))] - [InlineData(1, 3, typeof(List))] - [InlineData(1, 100_000, typeof(IList))] - [InlineData(1, 3, typeof(IList))] - [InlineData(1, 100_000, typeof(ICollection))] - [InlineData(1, 3, typeof(ICollection))] - public void Max_arity_greater_than_1_converts_to_enumerable_types( - int minArity, - int maxArity, - Type argumentType) - { - var option = OptionBuilder.CreateOption("--items", valueType: argumentType); - option.Arity = new ArgumentArity(minArity, maxArity); + [Fact] + public void Values_can_be_correctly_converted_to_byte_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-us"), "-us 123").Should().Be(123); + + [Fact] + public void Values_can_be_correctly_converted_to_nullable_byte_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-us"), "-us 123").Should().Be(123); + + [Fact] + public void Values_can_be_correctly_converted_to_uint_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-us"), "-us 1234").Should().Be(1234); + + [Fact] + public void Values_can_be_correctly_converted_to_nullable_uint_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-us"), "-us 1234").Should().Be(1234); + + [Fact] + public void Values_can_be_correctly_converted_to_array_of_int_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x 1 -x 2 -x 3").Should().BeEquivalentTo(new[] { 1, 2, 3 }); + + [Theory] + [InlineData(0, 100_000, typeof(string[]))] + [InlineData(0, 3, typeof(string[]))] + [InlineData(0, 100_000, typeof(IEnumerable))] + [InlineData(0, 3, typeof(IEnumerable))] + [InlineData(0, 100_000, typeof(List))] + [InlineData(0, 3, typeof(List))] + [InlineData(0, 100_000, typeof(IList))] + [InlineData(0, 3, typeof(IList))] + [InlineData(0, 100_000, typeof(ICollection))] + [InlineData(0, 3, typeof(ICollection))] + + [InlineData(1, 100_000, typeof(string[]))] + [InlineData(1, 3, typeof(string[]))] + [InlineData(1, 100_000, typeof(IEnumerable))] + [InlineData(1, 3, typeof(IEnumerable))] + [InlineData(1, 100_000, typeof(List))] + [InlineData(1, 3, typeof(List))] + [InlineData(1, 100_000, typeof(IList))] + [InlineData(1, 3, typeof(IList))] + [InlineData(1, 100_000, typeof(ICollection))] + [InlineData(1, 3, typeof(ICollection))] + public void Max_arity_greater_than_1_converts_to_enumerable_types( + int minArity, + int maxArity, + Type argumentType) + { + var option = OptionBuilder.CreateOption("--items", valueType: argumentType); + option.Arity = new ArgumentArity(minArity, maxArity); - var command = new RootCommand - { - option - }; + var command = new RootCommand + { + option + }; - var result = command.Parse("--items one --items two --items three"); + var result = command.Parse("--items one --items two --items three"); - result.Errors.Should().BeEmpty(); - result.GetResult(option).GetValueOrDefault().Should().BeAssignableTo(argumentType); - } + result.Errors.Should().BeEmpty(); + result.GetResult(option).GetValueOrDefault().Should().BeAssignableTo(argumentType); + } - [Fact] - public void Values_can_be_correctly_converted_to_List_of_int_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option>("-x"), "-x 1 -x 2 -x 3").Should().BeEquivalentTo(new[] {1, 2, 3}); + [Fact] + public void Values_can_be_correctly_converted_to_List_of_int_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option>("-x"), "-x 1 -x 2 -x 3").Should().BeEquivalentTo(new[] {1, 2, 3}); - [Fact] - public void Values_can_be_correctly_converted_to_IEnumerable_of_int_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option>("-x"), "-x 1 -x 2 -x 3").Should().BeEquivalentTo(new[] { 1, 2, 3 }); + [Fact] + public void Values_can_be_correctly_converted_to_IEnumerable_of_int_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option>("-x"), "-x 1 -x 2 -x 3").Should().BeEquivalentTo(new[] { 1, 2, 3 }); - [Fact] - public void Enum_values_can_be_correctly_converted_based_on_enum_value_name_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x Monday").Should().Be(DayOfWeek.Monday); + [Fact] + public void Enum_values_can_be_correctly_converted_based_on_enum_value_name_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x Monday").Should().Be(DayOfWeek.Monday); - [Fact] - public void Nullable_enum_values_can_be_correctly_converted_based_on_enum_value_name_without_the_parser_specifying_a_custom_converter() - => GetValue(new Option("-x"), "-x Monday").Should().Be(DayOfWeek.Monday); + [Fact] + public void Nullable_enum_values_can_be_correctly_converted_based_on_enum_value_name_without_the_parser_specifying_a_custom_converter() + => GetValue(new Option("-x"), "-x Monday").Should().Be(DayOfWeek.Monday); - [Fact] - public void Enum_values_that_cannot_be_parsed_result_in_an_informative_error() - { - var option = new Option("-x"); + [Fact] + public void Enum_values_that_cannot_be_parsed_result_in_an_informative_error() + { + var option = new Option("-x"); - var value = new RootCommand { option }.Parse("-x Notaday"); + var value = new RootCommand { option }.Parse("-x Notaday"); - value.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Contain("Cannot parse argument 'Notaday' for option '-x' as expected type 'System.DayOfWeek'."); - } + value.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Contain("Cannot parse argument 'Notaday' for option '-x' as expected type 'System.DayOfWeek'."); + } - [Fact] - public void When_getting_a_single_value_and_specifying_a_conversion_type_that_is_not_supported_then_it_throws() - { - var option = new Option("-x"); + [Fact] + public void When_getting_a_single_value_and_specifying_a_conversion_type_that_is_not_supported_then_it_throws() + { + var option = new Option("-x"); - var result = new RootCommand { option }.Parse("-x not-an-int"); + var result = new RootCommand { option }.Parse("-x not-an-int"); - Action getValue = () => result.GetValue(option); + Action getValue = () => result.GetValue(option); - getValue.Should() - .Throw() - .Which - .Message - .Should() - .Be("Cannot parse argument 'not-an-int' for option '-x' as expected type 'System.Int32'."); - } + getValue.Should() + .Throw() + .Which + .Message + .Should() + .Be("Cannot parse argument 'not-an-int' for option '-x' as expected type 'System.Int32'."); + } - [Fact] - public void When_getting_an_array_of_values_and_specifying_a_conversion_type_that_is_not_supported_then_it_throws() - { - Action getValue = () => GetValue(new Option("-x"), "-x not-an-int -x 2"); - - getValue.Should() - .Throw() - .Which - .Message - .Should() - .Be("Cannot parse argument 'not-an-int' for option '-x' as expected type 'System.Int32'."); - } - - [Fact] - public void String_defaults_to_null_when_not_specified_only_for_not_required_arguments() - => GetValue( - new Argument("arg") - { - Arity = ArgumentArity.ZeroOrMore - }, "").Should().BeNull(); - - [Theory] - [InlineData(typeof(List))] - [InlineData(typeof(List))] - [InlineData(typeof(List))] - [InlineData(typeof(IEnumerable))] - [InlineData(typeof(IEnumerable))] - [InlineData(typeof(IEnumerable))] - [InlineData(typeof(ICollection))] - [InlineData(typeof(ICollection))] - [InlineData(typeof(ICollection))] - [InlineData(typeof(IList))] - [InlineData(typeof(IList))] - [InlineData(typeof(IList))] - [InlineData(typeof(string[]))] - [InlineData(typeof(int[]))] - [InlineData(typeof(FileAccess[]))] - public void Sequence_type_defaults_to_empty_when_not_specified(Type sequenceType) - { - var argument = Activator.CreateInstance(typeof(Argument<>).MakeGenericType(sequenceType), new object[] { "argName" }); + [Fact] + public void When_getting_an_array_of_values_and_specifying_a_conversion_type_that_is_not_supported_then_it_throws() + { + Action getValue = () => GetValue(new Option("-x"), "-x not-an-int -x 2"); + + getValue.Should() + .Throw() + .Which + .Message + .Should() + .Be("Cannot parse argument 'not-an-int' for option '-x' as expected type 'System.Int32'."); + } - AssertParsedValueIsEmpty((dynamic)argument); - } + [Fact] + public void String_defaults_to_null_when_not_specified_only_for_not_required_arguments() + => GetValue( + new Argument("arg") + { + Arity = ArgumentArity.ZeroOrMore + }, "").Should().BeNull(); + + [Theory] + [InlineData(typeof(List))] + [InlineData(typeof(List))] + [InlineData(typeof(List))] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(ICollection))] + [InlineData(typeof(ICollection))] + [InlineData(typeof(ICollection))] + [InlineData(typeof(IList))] + [InlineData(typeof(IList))] + [InlineData(typeof(IList))] + [InlineData(typeof(string[]))] + [InlineData(typeof(int[]))] + [InlineData(typeof(FileAccess[]))] + public void Sequence_type_defaults_to_empty_when_not_specified(Type sequenceType) + { + var argument = Activator.CreateInstance(typeof(Argument<>).MakeGenericType(sequenceType), new object[] { "argName" }); - private void AssertParsedValueIsEmpty(Argument argument) where T : IEnumerable - => GetValue(argument, "").Should().BeEquivalentTo(Enumerable.Empty()); + AssertParsedValueIsEmpty((dynamic)argument); } + + private void AssertParsedValueIsEmpty(Argument argument) where T : IEnumerable + => GetValue(argument, "").Should().BeEquivalentTo(Enumerable.Empty()); } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/CommandTests.cs b/src/System.CommandLine.Tests/CommandTests.cs index cc3a81e7b1..5e08421e64 100644 --- a/src/System.CommandLine.Tests/CommandTests.cs +++ b/src/System.CommandLine.Tests/CommandTests.cs @@ -6,291 +6,290 @@ using System.Linq; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class CommandTests { - public class CommandTests - { - private readonly Command _outerCommand; + private readonly Command _outerCommand; - public CommandTests() + public CommandTests() + { + _outerCommand = new Command("outer") { - _outerCommand = new Command("outer") + new Command("inner") { - new Command("inner") - { - new Option("--option") - } - }; - } - - [Fact] - public void Outer_command_is_identified_correctly_by_RootCommand() - { - var result = _outerCommand.Parse("outer inner --option argument1"); - - result - .RootCommandResult - .Command - .Name - .Should() - .Be("outer"); - } - - [Fact] - public void Outer_command_is_identified_correctly_by_Parent_property() - { - var result = _outerCommand.Parse("outer inner --option argument1"); - - result - .CommandResult - .Parent - .Should() - .BeOfType() - .Which - .Command - .Name - .Should() - .Be("outer"); - } - - [Fact] - public void Inner_command_is_identified_correctly() - { - var result = _outerCommand.Parse("outer inner --option argument1"); - - result.CommandResult - .Should() - .BeOfType() - .Which - .Command - .Name - .Should() - .Be("inner"); - } - - [Fact] - public void Inner_command_option_is_identified_correctly() - { - var result = _outerCommand.Parse("outer inner --option argument1"); - - result.CommandResult - .Children - .ElementAt(0) - .Should() - .BeOfType() - .Which - .Option - .Name - .Should() - .Be("--option"); - } - - [Fact] - public void Inner_command_option_argument_is_identified_correctly() - { - var result = _outerCommand.Parse("outer inner --option argument1"); - - result.CommandResult - .Children - .ElementAt(0) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("argument1"); - } - - [Fact] - public void Commands_at_multiple_levels_can_have_their_own_arguments() + new Option("--option") + } + }; + } + + [Fact] + public void Outer_command_is_identified_correctly_by_RootCommand() + { + var result = _outerCommand.Parse("outer inner --option argument1"); + + result + .RootCommandResult + .Command + .Name + .Should() + .Be("outer"); + } + + [Fact] + public void Outer_command_is_identified_correctly_by_Parent_property() + { + var result = _outerCommand.Parse("outer inner --option argument1"); + + result + .CommandResult + .Parent + .Should() + .BeOfType() + .Which + .Command + .Name + .Should() + .Be("outer"); + } + + [Fact] + public void Inner_command_is_identified_correctly() + { + var result = _outerCommand.Parse("outer inner --option argument1"); + + result.CommandResult + .Should() + .BeOfType() + .Which + .Command + .Name + .Should() + .Be("inner"); + } + + [Fact] + public void Inner_command_option_is_identified_correctly() + { + var result = _outerCommand.Parse("outer inner --option argument1"); + + result.CommandResult + .Children + .ElementAt(0) + .Should() + .BeOfType() + .Which + .Option + .Name + .Should() + .Be("--option"); + } + + [Fact] + public void Inner_command_option_argument_is_identified_correctly() + { + var result = _outerCommand.Parse("outer inner --option argument1"); + + result.CommandResult + .Children + .ElementAt(0) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("argument1"); + } + + [Fact] + public void Commands_at_multiple_levels_can_have_their_own_arguments() + { + var outer = new Command("outer") { - var outer = new Command("outer") + new Argument("outer_arg") + }; + outer.Subcommands.Add( + new Command("inner") { - new Argument("outer_arg") - }; - outer.Subcommands.Add( - new Command("inner") - { - new Argument("inner_arg") - }); - - var result = outer.Parse("outer arg1 inner arg2 arg3"); - - result.CommandResult - .Parent - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("arg1"); - - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("arg2", "arg3"); - } - - [Fact] - public void Aliases_is_aware_of_added_alias() - { - var command = new Command("original"); + new Argument("inner_arg") + }); + + var result = outer.Parse("outer arg1 inner arg2 arg3"); + + result.CommandResult + .Parent + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("arg1"); + + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("arg2", "arg3"); + } + + [Fact] + public void Aliases_is_aware_of_added_alias() + { + var command = new Command("original"); - command.Aliases.Add("added"); + command.Aliases.Add("added"); - command.Aliases.Should().Contain("added"); - } + command.Aliases.Should().Contain("added"); + } - [Theory] - [InlineData("aa ")] - [InlineData(" aa")] - [InlineData("aa aa")] - public void When_a_command_is_created_with_an_alias_that_contains_whitespace_then_an_informative_error_is_returned( - string alias) - { - Action create = () => new Command(alias); - - create.Should() - .Throw() - .Which - .Message - .Should() - .Contain($"Names and aliases cannot contain whitespace: \"{alias}\""); - } - - [Theory] - [InlineData("aa ")] - [InlineData(" aa")] - [InlineData("aa aa")] - public void When_a_command_alias_is_added_and_contains_whitespace_then_an_informative_error_is_returned( - string alias) - { - var command = new Command("-x"); - - Action addAlias = () => command.Aliases.Add(alias); - - addAlias - .Should() - .Throw() - .Which - .Message - .Should() - .Contain($"Names and aliases cannot contain whitespace: \"{alias}\""); - } - - [Theory] - [InlineData("outer", "outer")] - [InlineData("outer arg", "outer")] - [InlineData("outer inner", "inner")] - [InlineData("outer arg inner", "inner")] - [InlineData("outer arg inner arg", "inner")] - [InlineData("outer sibling", "sibling")] - [InlineData("outer inner inner-er", "inner-er")] - [InlineData("outer inner arg inner-er", "inner-er")] - [InlineData("outer inner arg inner-er arg", "inner-er")] - [InlineData("outer arg inner arg inner-er arg", "inner-er")] - public void ParseResult_Command_identifies_innermost_command(string input, string expectedCommand) + [Theory] + [InlineData("aa ")] + [InlineData(" aa")] + [InlineData("aa aa")] + public void When_a_command_is_created_with_an_alias_that_contains_whitespace_then_an_informative_error_is_returned( + string alias) + { + Action create = () => new Command(alias); + + create.Should() + .Throw() + .Which + .Message + .Should() + .Contain($"Names and aliases cannot contain whitespace: \"{alias}\""); + } + + [Theory] + [InlineData("aa ")] + [InlineData(" aa")] + [InlineData("aa aa")] + public void When_a_command_alias_is_added_and_contains_whitespace_then_an_informative_error_is_returned( + string alias) + { + var command = new Command("-x"); + + Action addAlias = () => command.Aliases.Add(alias); + + addAlias + .Should() + .Throw() + .Which + .Message + .Should() + .Contain($"Names and aliases cannot contain whitespace: \"{alias}\""); + } + + [Theory] + [InlineData("outer", "outer")] + [InlineData("outer arg", "outer")] + [InlineData("outer inner", "inner")] + [InlineData("outer arg inner", "inner")] + [InlineData("outer arg inner arg", "inner")] + [InlineData("outer sibling", "sibling")] + [InlineData("outer inner inner-er", "inner-er")] + [InlineData("outer inner arg inner-er", "inner-er")] + [InlineData("outer inner arg inner-er arg", "inner-er")] + [InlineData("outer arg inner arg inner-er arg", "inner-er")] + public void ParseResult_Command_identifies_innermost_command(string input, string expectedCommand) + { + var outer = new Command("outer") { - var outer = new Command("outer") + new Command("inner") { - new Command("inner") - { - new Command("inner-er") - }, - new Command("sibling") - }; + new Command("inner-er") + }, + new Command("sibling") + }; - var result = outer.Parse(input); + var result = outer.Parse(input); - result.CommandResult.Command.Name.Should().Be(expectedCommand); - } + result.CommandResult.Command.Name.Should().Be(expectedCommand); + } - [Fact] - public void Commands_can_have_aliases() - { - var command = new Command("this"); - command.Aliases.Add("that"); - command.Name.Should().Be("this"); - command.Aliases.Should().BeEquivalentTo("that"); - command.Aliases.Should().BeEquivalentTo("that"); + [Fact] + public void Commands_can_have_aliases() + { + var command = new Command("this"); + command.Aliases.Add("that"); + command.Name.Should().Be("this"); + command.Aliases.Should().BeEquivalentTo("that"); + command.Aliases.Should().BeEquivalentTo("that"); + + var result = command.Parse("that"); + + result.CommandResult.Command.Should().BeSameAs(command); + result.Errors.Should().BeEmpty(); + } + + [Fact] + public void RootCommand_can_have_aliases() + { + var command = new RootCommand(); + command.Aliases.Add("that"); + command.Aliases.Should().BeEquivalentTo("that"); + command.Aliases.Should().BeEquivalentTo("that"); + + var result = command.Parse("that"); - var result = command.Parse("that"); + result.CommandResult.Command.Should().BeSameAs(command); + result.Errors.Should().BeEmpty(); + } - result.CommandResult.Command.Should().BeSameAs(command); - result.Errors.Should().BeEmpty(); - } + [Fact] + public void Subcommands_can_have_aliases() + { + var subcommand = new Command("this"); + subcommand.Aliases.Add("that"); - [Fact] - public void RootCommand_can_have_aliases() + var rootCommand = new RootCommand { - var command = new RootCommand(); - command.Aliases.Add("that"); - command.Aliases.Should().BeEquivalentTo("that"); - command.Aliases.Should().BeEquivalentTo("that"); + subcommand + }; - var result = command.Parse("that"); + var result = rootCommand.Parse("that"); - result.CommandResult.Command.Should().BeSameAs(command); - result.Errors.Should().BeEmpty(); - } + result.CommandResult.Command.Should().BeSameAs(subcommand); + result.Errors.Should().BeEmpty(); + } - [Fact] - public void Subcommands_can_have_aliases() + [Fact] + public void It_retains_argument_name_when_it_is_provided() + { + var command = new Command("-alias") { - var subcommand = new Command("this"); - subcommand.Aliases.Add("that"); + new Argument("arg") + }; - var rootCommand = new RootCommand - { - subcommand - }; + command.Arguments.Single().Name.Should().Be("arg"); + } - var result = rootCommand.Parse("that"); + [Fact] + public void AddGlobalOption_updates_Options_property() + { + var option = new Option("-x") { Recursive = true }; + var command = new Command("mycommand"); + command.Options.Add(option); - result.CommandResult.Command.Should().BeSameAs(subcommand); - result.Errors.Should().BeEmpty(); - } + command.Options + .Should() + .Contain(option); + } - [Fact] - public void It_retains_argument_name_when_it_is_provided() - { - var command = new Command("-alias") - { - new Argument("arg") - }; + // https://github.com/dotnet/command-line-api/issues/1437 + [Fact] + public void When_Options_is_referenced_before_a_global_option_is_added_then_adding_a_global_option_updates_the_Options_collection() + { + var option = new Option("-x"); + var command = new Command("mycommand"); - command.Arguments.Single().Name.Should().Be("arg"); - } + // referencing command.Options here would reproduce the above bug before the fix + // keeping it ensures the fix works and doesn't regress + command.Options + .Should() + .BeEmpty(); - [Fact] - public void AddGlobalOption_updates_Options_property() - { - var option = new Option("-x") { Recursive = true }; - var command = new Command("mycommand"); - command.Options.Add(option); - - command.Options - .Should() - .Contain(option); - } - - // https://github.com/dotnet/command-line-api/issues/1437 - [Fact] - public void When_Options_is_referenced_before_a_global_option_is_added_then_adding_a_global_option_updates_the_Options_collection() - { - var option = new Option("-x"); - var command = new Command("mycommand"); - - // referencing command.Options here would reproduce the above bug before the fix - // keeping it ensures the fix works and doesn't regress - command.Options - .Should() - .BeEmpty(); - - option.Recursive = true; - command.Options.Add(option); - - command.Options - .Should() - .Contain(option); - } + option.Recursive = true; + command.Options.Add(option); + + command.Options + .Should() + .Contain(option); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/CompletionContextTests.cs b/src/System.CommandLine.Tests/CompletionContextTests.cs index 09d805d6e1..a26e295b26 100644 --- a/src/System.CommandLine.Tests/CompletionContextTests.cs +++ b/src/System.CommandLine.Tests/CompletionContextTests.cs @@ -5,223 +5,222 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class CompletionContextTests { - public class CompletionContextTests + [Fact] + public void CommandLineText_preserves_command_line_prior_to_splitting_when_complete_command_line_is_parsed() { - [Fact] - public void CommandLineText_preserves_command_line_prior_to_splitting_when_complete_command_line_is_parsed() + var command = new RootCommand { - var command = new RootCommand + new Command("verb") { - new Command("verb") - { - new Option("-x") - } - }; + new Option("-x") + } + }; - var commandLine = "verb -x 123"; + var commandLine = "verb -x 123"; - var parseResult = command.Parse(commandLine); + var parseResult = command.Parse(commandLine); - parseResult.GetCompletionContext() - .Should() - .BeOfType() - .Which - .CommandLineText - .Should() - .Be(commandLine); - } + parseResult.GetCompletionContext() + .Should() + .BeOfType() + .Which + .CommandLineText + .Should() + .Be(commandLine); + } - [Fact] - public void CommandLineText_is_preserved_when_adjusting_position() + [Fact] + public void CommandLineText_is_preserved_when_adjusting_position() + { + var command = new RootCommand { - var command = new RootCommand + new Command("verb") { - new Command("verb") - { - new Option("-x") - } - }; + new Option("-x") + } + }; - var commandLine = "verb -x 123"; + var commandLine = "verb -x 123"; - var completionContext1 = (TextCompletionContext)command.Parse(commandLine).GetCompletionContext(); + var completionContext1 = (TextCompletionContext)command.Parse(commandLine).GetCompletionContext(); - var completionContext2 = completionContext1.AtCursorPosition(4); + var completionContext2 = completionContext1.AtCursorPosition(4); - completionContext2.CommandLineText.Should().Be(commandLine); - } + completionContext2.CommandLineText.Should().Be(commandLine); + } - [Fact] - public void CommandLineText_is_unavailable_when_string_array_is_parsed() + [Fact] + public void CommandLineText_is_unavailable_when_string_array_is_parsed() + { + var command = new RootCommand { - var command = new RootCommand + new Command("verb") { - new Command("verb") - { - new Option("-x") - } - }; + new Option("-x") + } + }; - var parseResult = command.Parse(new[] { "verb", "-x", "123" }); + var parseResult = command.Parse(new[] { "verb", "-x", "123" }); - parseResult.GetCompletionContext() - .Should() - .BeOfType(); - } + parseResult.GetCompletionContext() + .Should() + .BeOfType(); + } - [Fact] - public void When_position_is_unspecified_in_string_command_line_not_ending_with_a_space_then_it_returns_final_token() + [Fact] + public void When_position_is_unspecified_in_string_command_line_not_ending_with_a_space_then_it_returns_final_token() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Option("--option1"), - new Option("--option2") - }; + new Option("--option1"), + new Option("--option2") + }; - string textToMatch = command.Parse("the-command t") - .GetCompletionContext() - .WordToComplete; + string textToMatch = command.Parse("the-command t") + .GetCompletionContext() + .WordToComplete; - textToMatch.Should().Be("t"); - } + textToMatch.Should().Be("t"); + } - [Fact] - public void When_position_is_unspecified_in_string_command_line_ending_with_a_space_then_it_returns_empty() + [Fact] + public void When_position_is_unspecified_in_string_command_line_ending_with_a_space_then_it_returns_empty() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Option("--option1"), - new Option("--option2") - }; - - var commandLine = "the-command t"; - string textToMatch = command.Parse(commandLine) - .GetCompletionContext() - .As() - .AtCursorPosition(commandLine.Length + 1) - .WordToComplete; + new Option("--option1"), + new Option("--option2") + }; + + var commandLine = "the-command t"; + string textToMatch = command.Parse(commandLine) + .GetCompletionContext() + .As() + .AtCursorPosition(commandLine.Length + 1) + .WordToComplete; + + textToMatch.Should().Be(""); + } - textToMatch.Should().Be(""); - } + [Fact] + public void When_position_is_greater_than_input_length_in_a_string_command_line_then_it_returns_empty() + { + Option option1 = new ("--option1"); + option1.AcceptOnlyFromAmong("apple", "banana", "cherry", "durian"); - [Fact] - public void When_position_is_greater_than_input_length_in_a_string_command_line_then_it_returns_empty() + var command = new Command("the-command") { - Option option1 = new ("--option1"); - option1.AcceptOnlyFromAmong("apple", "banana", "cherry", "durian"); + new Argument("arg"), + option1, + new Option("--option2") + }; + + var textToMatch = command.Parse("the-command --option1 a") + .GetCompletionContext() + .As() + .AtCursorPosition(1000) + .WordToComplete; + + textToMatch.Should().Be(""); + } - var command = new Command("the-command") - { - new Argument("arg"), - option1, - new Option("--option2") - }; + [Fact] + public void When_position_is_unspecified_in_array_command_line_and_final_token_is_unmatched_then_it_returns_final_token() + { + var command = new Command("the-command") + { + new Option("--option1"), + new Option("--option2") + }; - var textToMatch = command.Parse("the-command --option1 a") - .GetCompletionContext() - .As() - .AtCursorPosition(1000) - .WordToComplete; + string textToMatch = command.Parse(new[] { "the-command", "opt" }) + .GetCompletionContext() + .WordToComplete; - textToMatch.Should().Be(""); - } + textToMatch.Should().Be("opt"); + } - [Fact] - public void When_position_is_unspecified_in_array_command_line_and_final_token_is_unmatched_then_it_returns_final_token() + [Fact] + public void When_position_is_unspecified_in_array_command_line_and_final_token_matches_an_command_then_it_returns_empty() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Option("--option1"), - new Option("--option2") - }; + new Option("--option1"), + new Option("--option2") + }; - string textToMatch = command.Parse(new[] { "the-command", "opt" }) - .GetCompletionContext() - .WordToComplete; + string textToMatch = command.Parse(new[] { "the-command" }) + .GetCompletionContext() + .WordToComplete; - textToMatch.Should().Be("opt"); - } + textToMatch.Should().Be(""); + } - [Fact] - public void When_position_is_unspecified_in_array_command_line_and_final_token_matches_an_command_then_it_returns_empty() + [Fact] + public void When_position_is_unspecified_in_array_command_line_and_final_token_matches_an_option_then_it_returns_empty() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Option("--option1"), - new Option("--option2") - }; + new Option("--option1"), + new Option("--option2") + }; + + string textToMatch = command.Parse(new[] { "the-command", "--option1" }) + .GetCompletionContext() + .WordToComplete; - string textToMatch = command.Parse(new[] { "the-command" }) - .GetCompletionContext() - .WordToComplete; + textToMatch.Should().Be(""); + } - textToMatch.Should().Be(""); - } + [Fact] + public void When_position_is_unspecified_in_array_command_line_and_final_token_matches_an_argument_then_it_returns_the_argument_value() + { + Option option1 = new("--option1"); + option1.AcceptOnlyFromAmong("apple", "banana", "cherry", "durian"); - [Fact] - public void When_position_is_unspecified_in_array_command_line_and_final_token_matches_an_option_then_it_returns_empty() + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Option("--option1"), - new Option("--option2") - }; - - string textToMatch = command.Parse(new[] { "the-command", "--option1" }) - .GetCompletionContext() - .WordToComplete; + option1, + new Option("--option2"), + new Argument("arg") + }; - textToMatch.Should().Be(""); - } + string textToMatch = command.Parse(new[] { "the-command", "--option1", "a" }) + .GetCompletionContext() + .WordToComplete; - [Fact] - public void When_position_is_unspecified_in_array_command_line_and_final_token_matches_an_argument_then_it_returns_the_argument_value() - { - Option option1 = new("--option1"); - option1.AcceptOnlyFromAmong("apple", "banana", "cherry", "durian"); + textToMatch.Should().Be("a"); + } - var command = new Command("the-command") + [Theory] + [InlineData("the-command $one --two", "one")] + [InlineData("the-command one$ --two", "one")] + [InlineData("the-command on$e --two ", "one")] + [InlineData(" the-command $one --two ", "one")] + [InlineData(" the-command one$ --two ", "one")] + [InlineData(" the-command on$e --two ", "one")] + public void When_position_is_specified_in_string_command_line_then_it_returns_argument_at_cursor_position( + string commandLine, + string expected) + { + var command = + new Command("the-command") { - option1, - new Option("--option2"), - new Argument("arg") + new Argument("arg") }; - string textToMatch = command.Parse(new[] { "the-command", "--option1", "a" }) - .GetCompletionContext() - .WordToComplete; - - textToMatch.Should().Be("a"); - } - - [Theory] - [InlineData("the-command $one --two", "one")] - [InlineData("the-command one$ --two", "one")] - [InlineData("the-command on$e --two ", "one")] - [InlineData(" the-command $one --two ", "one")] - [InlineData(" the-command one$ --two ", "one")] - [InlineData(" the-command on$e --two ", "one")] - public void When_position_is_specified_in_string_command_line_then_it_returns_argument_at_cursor_position( - string commandLine, - string expected) - { - var command = - new Command("the-command") - { - new Argument("arg") - }; - - var position = commandLine.IndexOf("$", StringComparison.Ordinal); - - var textToMatch = command.Parse(commandLine.Replace("$", "")) - .GetCompletionContext() - .As() - .AtCursorPosition(position) - .WordToComplete; - - textToMatch.Should().Be(expected); - } + var position = commandLine.IndexOf("$", StringComparison.Ordinal); + + var textToMatch = command.Parse(commandLine.Replace("$", "")) + .GetCompletionContext() + .As() + .AtCursorPosition(position) + .WordToComplete; + + textToMatch.Should().Be(expected); } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/CompletionTests.cs b/src/System.CommandLine.Tests/CompletionTests.cs index 84d931f863..8ac83dc0bb 100644 --- a/src/System.CommandLine.Tests/CompletionTests.cs +++ b/src/System.CommandLine.Tests/CompletionTests.cs @@ -10,1013 +10,1012 @@ using Xunit.Abstractions; using static System.Environment; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class CompletionTests { - public class CompletionTests - { - private readonly ITestOutputHelper _output; + private readonly ITestOutputHelper _output; - public CompletionTests(ITestOutputHelper output) - { - _output = output; - } + public CompletionTests(ITestOutputHelper output) + { + _output = output; + } - [Fact] - public void Option_GetCompletions_returns_argument_completions_if_configured() - { - var option = new Option("--hello"); - option.CompletionSources.Add("one", "two", "three"); + [Fact] + public void Option_GetCompletions_returns_argument_completions_if_configured() + { + var option = new Option("--hello"); + option.CompletionSources.Add("one", "two", "three"); - var completions = option.GetCompletions(CompletionContext.Empty); + var completions = option.GetCompletions(CompletionContext.Empty); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentTo("one", "two", "three"); - } + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo("one", "two", "three"); + } - [Fact] - public void Command_GetCompletions_returns_available_option_aliases() + [Fact] + public void Command_GetCompletions_returns_available_option_aliases() + { + var command = new Command("command") { - var command = new Command("command") - { - new Option("--one") { Description = "option one" }, - new Option("--two") { Description = "option two" }, - new Option("--three") { Description = "option three" }, - }; + new Option("--one") { Description = "option one" }, + new Option("--two") { Description = "option two" }, + new Option("--three") { Description = "option three" }, + }; - var completions = command.GetCompletions(CompletionContext.Empty); + var completions = command.GetCompletions(CompletionContext.Empty); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--one", "--two", "--three"); - } + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--one", "--two", "--three"); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1563 - public void Command_GetCompletions_returns_available_option_aliases_for_global_options() + [Fact] // https://github.com/dotnet/command-line-api/issues/1563 + public void Command_GetCompletions_returns_available_option_aliases_for_global_options() + { + var subcommand2 = new Command("command2") { - var subcommand2 = new Command("command2") - { - new Option("--one") { Description = "option one" }, - new Option("--two") { Description = "option two" } - }; + new Option("--one") { Description = "option one" }, + new Option("--two") { Description = "option two" } + }; - var subcommand1 = new Command("command1") - { - subcommand2 - }; + var subcommand1 = new Command("command1") + { + subcommand2 + }; - var rootCommand = new Command("root") - { - subcommand1 - }; + var rootCommand = new Command("root") + { + subcommand1 + }; - rootCommand.Options.Add(new Option("--three") - { - Description = "option three", - Recursive = true - }); + rootCommand.Options.Add(new Option("--three") + { + Description = "option three", + Recursive = true + }); - var completions = subcommand2.GetCompletions(CompletionContext.Empty); + var completions = subcommand2.GetCompletions(CompletionContext.Empty); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--one", "--two", "--three"); - } + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--one", "--two", "--three"); + } - [Fact] - public void Command_GetCompletions_returns_available_subcommands() + [Fact] + public void Command_GetCompletions_returns_available_subcommands() + { + var command = new Command("command") { - var command = new Command("command") - { - new Command("one"), - new Command("two"), - new Command("three") - }; + new Command("one"), + new Command("two"), + new Command("three") + }; - var completions = command.GetCompletions(CompletionContext.Empty); + var completions = command.GetCompletions(CompletionContext.Empty); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentTo("one", "two", "three"); - } + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo("one", "two", "three"); + } - [Fact] - public void Command_GetCompletions_returns_available_subcommands_and_option_aliases() + [Fact] + public void Command_GetCompletions_returns_available_subcommands_and_option_aliases() + { + var command = new Command("command") { - var command = new Command("command") - { - new Command("subcommand"), - new Option("--option") - }; + new Command("subcommand"), + new Option("--option") + }; - var completions = command.GetCompletions(CompletionContext.Empty); + var completions = command.GetCompletions(CompletionContext.Empty); - completions.Select(item => item.Label) - .Should() - .BeEquivalentTo("subcommand", "--option"); - } + completions.Select(item => item.Label) + .Should() + .BeEquivalentTo("subcommand", "--option"); + } - [Fact] - public void Command_GetCompletions_returns_available_subcommands_and_option_aliases_and_configured_arguments() + [Fact] + public void Command_GetCompletions_returns_available_subcommands_and_option_aliases_and_configured_arguments() + { + var command = new Command("command") { - var command = new Command("command") + new Command("subcommand", "subcommand"), + new Option("--option") { Description = "option" }, + new Argument("args") { - new Command("subcommand", "subcommand"), - new Option("--option") { Description = "option" }, - new Argument("args") - { - Arity = ArgumentArity.OneOrMore, - CompletionSources = { "command-argument" } - } - }; + Arity = ArgumentArity.OneOrMore, + CompletionSources = { "command-argument" } + } + }; - var completions = command.GetCompletions(CompletionContext.Empty); + var completions = command.GetCompletions(CompletionContext.Empty); - completions.Select(item => item.Label) - .Should() - .BeEquivalentTo("subcommand", "--option", "command-argument"); - } + completions.Select(item => item.Label) + .Should() + .BeEquivalentTo("subcommand", "--option", "command-argument"); + } - [Fact] - public void Command_GetCompletions_without_text_to_match_orders_alphabetically() + [Fact] + public void Command_GetCompletions_without_text_to_match_orders_alphabetically() + { + var command = new Command("command") { - var command = new Command("command") - { - new Command("andmythirdsubcommand"), - new Command("mysubcommand"), - new Command("andmyothersubcommand"), - }; + new Command("andmythirdsubcommand"), + new Command("mysubcommand"), + new Command("andmyothersubcommand"), + }; - var completions = command.GetCompletions(CompletionContext.Empty); + var completions = command.GetCompletions(CompletionContext.Empty); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentSequenceTo("andmyothersubcommand", "andmythirdsubcommand", "mysubcommand"); - } + completions + .Select(item => item.Label) + .Should() + .BeEquivalentSequenceTo("andmyothersubcommand", "andmythirdsubcommand", "mysubcommand"); + } - [Fact] - public void Command_GetCompletions_does_not_return_argument_names() + [Fact] + public void Command_GetCompletions_does_not_return_argument_names() + { + var command = new Command("command") { - var command = new Command("command") - { - new Argument("the-argument") - }; + new Argument("the-argument") + }; - var completions = command.GetCompletions(CompletionContext.Empty); + var completions = command.GetCompletions(CompletionContext.Empty); - completions - .Select(item => item.Label) - .Should() - .NotContain("the-argument"); - } + completions + .Select(item => item.Label) + .Should() + .NotContain("the-argument"); + } - [Fact] - public void Command_GetCompletions_with_text_to_match_orders_by_match_position_then_alphabetically() + [Fact] + public void Command_GetCompletions_with_text_to_match_orders_by_match_position_then_alphabetically() + { + var command = new Command("command") { - var command = new Command("command") - { - new Command("andmythirdsubcommand"), - new Command("mysubcommand"), - new Command("andmyothersubcommand"), - }; + new Command("andmythirdsubcommand"), + new Command("mysubcommand"), + new Command("andmyothersubcommand"), + }; - CommandLineConfiguration simpleConfig = new (command); - var completions = command.Parse("my", simpleConfig).GetCompletions(); + CommandLineConfiguration simpleConfig = new (command); + var completions = command.Parse("my", simpleConfig).GetCompletions(); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentSequenceTo("mysubcommand", "andmyothersubcommand", "andmythirdsubcommand"); - } + completions + .Select(item => item.Label) + .Should() + .BeEquivalentSequenceTo("mysubcommand", "andmyothersubcommand", "andmythirdsubcommand"); + } - [Fact] - public void When_an_option_has_a_default_value_it_will_still_be_suggested() + [Fact] + public void When_an_option_has_a_default_value_it_will_still_be_suggested() + { + var command = new Command("test") { - var command = new Command("test") - { - new Option("--apple") { DefaultValueFactory = (_) => "cortland" }, - new Option("--banana"), - new Option("--cherry") - }; + new Option("--apple") { DefaultValueFactory = (_) => "cortland" }, + new Option("--banana"), + new Option("--cherry") + }; - CommandLineConfiguration simpleConfig = new (command); - var result = command.Parse("", simpleConfig); + CommandLineConfiguration simpleConfig = new (command); + var result = command.Parse("", simpleConfig); - _output.WriteLine(result.ToString()); + _output.WriteLine(result.ToString()); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--apple", - "--banana", - "--cherry"); - } + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--apple", + "--banana", + "--cherry"); + } - [Fact] - public void Command_GetCompletions_can_access_ParseResult() - { - var originOption = new Option("--origin"); - var cloneOption = new Option("--clone"); + [Fact] + public void Command_GetCompletions_can_access_ParseResult() + { + var originOption = new Option("--origin"); + var cloneOption = new Option("--clone"); - cloneOption.CompletionSources.Add(ctx => - { - var opt1Value = ctx.ParseResult.GetValue(originOption); - return !string.IsNullOrWhiteSpace(opt1Value) ? new[] { opt1Value } : Array.Empty(); - }); + cloneOption.CompletionSources.Add(ctx => + { + var opt1Value = ctx.ParseResult.GetValue(originOption); + return !string.IsNullOrWhiteSpace(opt1Value) ? new[] { opt1Value } : Array.Empty(); + }); - RootCommand rootCommand = new RootCommand - { - originOption, - cloneOption - }; + RootCommand rootCommand = new RootCommand + { + originOption, + cloneOption + }; - CommandLineConfiguration simpleConfig = new (rootCommand); - var result = rootCommand.Parse("--origin test --clone ", simpleConfig); + CommandLineConfiguration simpleConfig = new (rootCommand); + var result = rootCommand.Parse("--origin test --clone ", simpleConfig); - _output.WriteLine(result.ToString()); + _output.WriteLine(result.ToString()); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("test"); - } + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("test"); + } - [Fact] - public void Command_GetCompletions_include_recursive_options_of_root_command() + [Fact] + public void Command_GetCompletions_include_recursive_options_of_root_command() + { + RootCommand rootCommand = new() { - RootCommand rootCommand = new() + new Command("sub") { - new Command("sub") - { - new Option("--option") - } - }; - - var result = rootCommand.Parse("sub --option 123 "); - - _output.WriteLine(result.ToString()); + new Option("--option") + } + }; - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--help", "-?", "-h", "/?", "/h"); - } + var result = rootCommand.Parse("sub --option 123 "); - [Fact] - public void When_one_option_has_been_specified_then_it_and_its_siblings_will_still_be_suggested() - { - var command = new Command("command") - { - new Option("--apple"), - new Option("--banana"), - new Option("--cherry") - }; - - var commandLine = "--apple grannysmith"; - CommandLineConfiguration simpleConfig = new (command); - var result = command.Parse(commandLine, simpleConfig); - - result.GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--banana", - "--cherry"); - } - - [Fact] - public void When_a_subcommand_has_been_specified_then_its_sibling_commands_will_not_be_suggested() - { - var rootCommand = new RootCommand - { - new Command("apple") - { - new Option("--cortland") - }, - new Command("banana") - { - new Option("--cavendish") - }, - new Command("cherry") - { - new Option("--rainier") - } - }; - CommandLineConfiguration simpleConfig = new (rootCommand); + _output.WriteLine(result.ToString()); - var result = rootCommand.Parse("cherry ", simpleConfig); + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--help", "-?", "-h", "/?", "/h"); + } - result.GetCompletions() - .Select(item => item.Label) - .Should() - .NotContain(new[]{"apple", "banana", "cherry"}); - } + [Fact] + public void When_one_option_has_been_specified_then_it_and_its_siblings_will_still_be_suggested() + { + var command = new Command("command") + { + new Option("--apple"), + new Option("--banana"), + new Option("--cherry") + }; + + var commandLine = "--apple grannysmith"; + CommandLineConfiguration simpleConfig = new (command); + var result = command.Parse(commandLine, simpleConfig); + + result.GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--banana", + "--cherry"); + } - [Fact] - public void When_a_subcommand_has_been_specified_then_its_sibling_commands_aliases_will_not_be_suggested() + [Fact] + public void When_a_subcommand_has_been_specified_then_its_sibling_commands_will_not_be_suggested() + { + var rootCommand = new RootCommand { - var apple = new Command("apple") + new Command("apple") { new Option("--cortland") - }; - apple.Aliases.Add("apl"); - - var banana = new Command("banana") + }, + new Command("banana") { new Option("--cavendish") - }; - banana.Aliases.Add("bnn"); - - var rootCommand = new RootCommand + }, + new Command("cherry") { - apple, - banana - }; - CommandLineConfiguration simpleConfig = new (rootCommand); + new Option("--rainier") + } + }; + CommandLineConfiguration simpleConfig = new (rootCommand); - var result = rootCommand.Parse("banana ", simpleConfig); + var result = rootCommand.Parse("cherry ", simpleConfig); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .NotContain(new[] { "apl", "bnn" }); - } + result.GetCompletions() + .Select(item => item.Label) + .Should() + .NotContain(new[]{"apple", "banana", "cherry"}); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1494 - public void When_a_subcommand_has_been_specified_then_its_sibling_options_will_not_be_suggested() + [Fact] + public void When_a_subcommand_has_been_specified_then_its_sibling_commands_aliases_will_not_be_suggested() + { + var apple = new Command("apple") { - var command = new RootCommand("parent") - { - new Command("child"), - new Option("--parent-option") - }; - - var commandLine = "child"; - CommandLineConfiguration simpleConfig = new (command); - var parseResult = command.Parse(commandLine, simpleConfig); + new Option("--cortland") + }; + apple.Aliases.Add("apl"); - parseResult - .GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .NotContain("--parent-option"); - } + var banana = new Command("banana") + { + new Option("--cavendish") + }; + banana.Aliases.Add("bnn"); - [Fact] - public void When_a_subcommand_has_been_specified_then_its_sibling_options_with_argument_limit_reached_will_be_not_be_suggested() + var rootCommand = new RootCommand { - var command = new RootCommand("parent") - { - new Command("child"), - new Option("--parent-option"), - new Argument("arg") - }; + apple, + banana + }; + CommandLineConfiguration simpleConfig = new (rootCommand); - var commandLine = "--parent-option 123 child"; - CommandLineConfiguration simpleConfig = new (command); - var parseResult = command.Parse(commandLine, simpleConfig); + var result = rootCommand.Parse("banana ", simpleConfig); - parseResult - .GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .NotContain("--parent-option"); - } + result.GetCompletions() + .Select(item => item.Label) + .Should() + .NotContain(new[] { "apl", "bnn" }); + } - [Fact] - public void When_a_subcommand_has_been_specified_then_its_child_options_will_be_suggested() + [Fact] // https://github.com/dotnet/command-line-api/issues/1494 + public void When_a_subcommand_has_been_specified_then_its_sibling_options_will_not_be_suggested() + { + var command = new RootCommand("parent") { - var command = new RootCommand("parent") - { - new Argument("arg"), - new Command("child") - { - new Option("--child-option") - } - }; + new Command("child"), + new Option("--parent-option") + }; + + var commandLine = "child"; + CommandLineConfiguration simpleConfig = new (command); + var parseResult = command.Parse(commandLine, simpleConfig); - var commandLine = "child "; - CommandLineConfiguration simpleConfig = new (command); - var parseResult = command.Parse(commandLine, simpleConfig); + parseResult + .GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .NotContain("--parent-option"); + } - parseResult - .GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .Contain("--child-option"); - } + [Fact] + public void When_a_subcommand_has_been_specified_then_its_sibling_options_with_argument_limit_reached_will_be_not_be_suggested() + { + var command = new RootCommand("parent") + { + new Command("child"), + new Option("--parent-option"), + new Argument("arg") + }; + + var commandLine = "--parent-option 123 child"; + CommandLineConfiguration simpleConfig = new (command); + var parseResult = command.Parse(commandLine, simpleConfig); + + parseResult + .GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .NotContain("--parent-option"); + } - [Fact] - public void When_a_subcommand_with_subcommands_has_been_specified_then_its_sibling_commands_will_not_be_suggested() + [Fact] + public void When_a_subcommand_has_been_specified_then_its_child_options_will_be_suggested() + { + var command = new RootCommand("parent") { - var rootCommand = new RootCommand + new Argument("arg"), + new Command("child") { - new Command("apple") - { - new Command("cortland") - }, - new Command("banana") - { - new Command("cavendish") - }, - new Command("cherry") - { - new Command("rainier") - } - }; + new Option("--child-option") + } + }; - var commandLine = "cherry"; - CommandLineConfiguration simpleConfig = new (rootCommand); - var result = rootCommand.Parse(commandLine, simpleConfig); + var commandLine = "child "; + CommandLineConfiguration simpleConfig = new (command); + var parseResult = command.Parse(commandLine, simpleConfig); - result.GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--help", "-?", "-h", "/?", "/h", "rainier"); - } + parseResult + .GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .Contain("--child-option"); + } - [Fact] - public void When_one_option_has_been_partially_specified_then_nonmatching_siblings_will_not_be_suggested() + [Fact] + public void When_a_subcommand_with_subcommands_has_been_specified_then_its_sibling_commands_will_not_be_suggested() + { + var rootCommand = new RootCommand { - var command = new Command("the-command") + new Command("apple") { - new Option("--apple"), - new Option("--banana"), - new Option("--cherry") - }; - - var input = "a"; - CommandLineConfiguration simpleConfig = new (command); - var result = command.Parse(input, simpleConfig); - - result.GetCompletions(input.Length) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--apple", - "--banana"); - } - - [Fact] - public void An_option_can_be_hidden_from_completions_by_setting_IsHidden_to_true() - { - var command = new Command("the-command") + new Command("cortland") + }, + new Command("banana") { - new Option("--hide-me") - { - Hidden = true - }, - new Option("-n") { Description = "Not hidden" } - }; + new Command("cavendish") + }, + new Command("cherry") + { + new Command("rainier") + } + }; + + var commandLine = "cherry"; + CommandLineConfiguration simpleConfig = new (rootCommand); + var result = rootCommand.Parse(commandLine, simpleConfig); - CommandLineConfiguration simpleConfig = new (command); - var completions = command.Parse("the-command ", simpleConfig).GetCompletions(); + result.GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--help", "-?", "-h", "/?", "/h", "rainier"); + } - completions.Select(item => item.Label).Should().NotContain("--hide-me"); - } + [Fact] + public void When_one_option_has_been_partially_specified_then_nonmatching_siblings_will_not_be_suggested() + { + var command = new Command("the-command") + { + new Option("--apple"), + new Option("--banana"), + new Option("--cherry") + }; + + var input = "a"; + CommandLineConfiguration simpleConfig = new (command); + var result = command.Parse(input, simpleConfig); + + result.GetCompletions(input.Length) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--apple", + "--banana"); + } - [Fact] - public void Parser_options_can_supply_context_sensitive_matches() + [Fact] + public void An_option_can_be_hidden_from_completions_by_setting_IsHidden_to_true() + { + var command = new Command("the-command") { - var command = new RootCommand + new Option("--hide-me") { - CreateOptionWithAcceptOnlyFromAmong(name: "--bread", "wheat", "sourdough", "rye"), - CreateOptionWithAcceptOnlyFromAmong(name: "--cheese", "provolone", "cheddar", "cream cheese") - }; + Hidden = true + }, + new Option("-n") { Description = "Not hidden" } + }; - var commandLine = "--bread"; - CommandLineConfiguration simpleConfig = new (command); - var result = command.Parse(commandLine, simpleConfig); + CommandLineConfiguration simpleConfig = new (command); + var completions = command.Parse("the-command ", simpleConfig).GetCompletions(); - result.GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("rye", "sourdough", "wheat"); + completions.Select(item => item.Label).Should().NotContain("--hide-me"); + } - commandLine = "--bread wheat --cheese "; - result = command.Parse(commandLine, simpleConfig); + [Fact] + public void Parser_options_can_supply_context_sensitive_matches() + { + var command = new RootCommand + { + CreateOptionWithAcceptOnlyFromAmong(name: "--bread", "wheat", "sourdough", "rye"), + CreateOptionWithAcceptOnlyFromAmong(name: "--cheese", "provolone", "cheddar", "cream cheese") + }; - result.GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("cheddar", "cream cheese", "provolone"); - } + var commandLine = "--bread"; + CommandLineConfiguration simpleConfig = new (command); + var result = command.Parse(commandLine, simpleConfig); - [Fact] - public void Subcommand_names_are_available_as_suggestions() - { - var command = new Command("test") - { - new Command("one", "Command one"), - new Command("two", "Command two"), - new Argument("arg") - }; + result.GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("rye", "sourdough", "wheat"); - var commandLine = "test"; - CommandLineConfiguration simpleConfig = new (command); - command.Parse(commandLine, simpleConfig) - .GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("one", "two"); - } + commandLine = "--bread wheat --cheese "; + result = command.Parse(commandLine, simpleConfig); - [Fact] - public void Both_subcommands_and_options_are_available_as_suggestions() - { - var command = new Command("test") - { - new Command("one"), - new Option("--one"), - new Argument("arg") - }; - - var commandLine = "test"; - CommandLineConfiguration simpleConfig = new (command); - command.Parse(commandLine, simpleConfig) - .GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("one", "--one"); - } - - [Theory(Skip = "Needs discussion, Issue #19")] - [InlineData("outer ")] - [InlineData("outer -")] - public void Option_GetCompletions_are_not_provided_without_matching_prefix(string input) - { - var command = new Command("outer") - { - new Option("--one"), - new Option("--two"), - new Option("--three") - }; - - CommandLineConfiguration simpleConfig = new (command); - ParseResult result = command.Parse(input, simpleConfig); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEmpty(); - } - - [Fact] - public void Option_GetCompletions_can_be_based_on_the_proximate_option() - { - Command outer = new Command("outer") - { - new Option("--one"), - new Option("--two"), - new Option("--three") - }; + result.GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("cheddar", "cream cheese", "provolone"); + } - var commandLine = "outer"; - CommandLineConfiguration simpleConfig = new (outer); - ParseResult result = outer.Parse(commandLine, simpleConfig); + [Fact] + public void Subcommand_names_are_available_as_suggestions() + { + var command = new Command("test") + { + new Command("one", "Command one"), + new Command("two", "Command two"), + new Argument("arg") + }; + + var commandLine = "test"; + CommandLineConfiguration simpleConfig = new (command); + command.Parse(commandLine, simpleConfig) + .GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("one", "two"); + } - result.GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--one", "--two", "--three"); - } + [Fact] + public void Both_subcommands_and_options_are_available_as_suggestions() + { + var command = new Command("test") + { + new Command("one"), + new Option("--one"), + new Argument("arg") + }; + + var commandLine = "test"; + CommandLineConfiguration simpleConfig = new (command); + command.Parse(commandLine, simpleConfig) + .GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("one", "--one"); + } - [Fact] - public void Argument_completions_can_be_based_on_the_proximate_option() + [Theory(Skip = "Needs discussion, Issue #19")] + [InlineData("outer ")] + [InlineData("outer -")] + public void Option_GetCompletions_are_not_provided_without_matching_prefix(string input) + { + var command = new Command("outer") + { + new Option("--one"), + new Option("--two"), + new Option("--three") + }; + + CommandLineConfiguration simpleConfig = new (command); + ParseResult result = command.Parse(input, simpleConfig); + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEmpty(); + } + + [Fact] + public void Option_GetCompletions_can_be_based_on_the_proximate_option() + { + Command outer = new Command("outer") { - var outer = new Command("outer") - { - CreateOptionWithAcceptOnlyFromAmong(name: "--one", "one-a", "one-b"), - CreateOptionWithAcceptOnlyFromAmong(name: "--two", "two-a", "two-b") - }; + new Option("--one"), + new Option("--two"), + new Option("--three") + }; - var commandLine = "outer --two"; - CommandLineConfiguration simpleConfig = new (outer); - ParseResult result = outer.Parse(commandLine, simpleConfig); + var commandLine = "outer"; + CommandLineConfiguration simpleConfig = new (outer); + ParseResult result = outer.Parse(commandLine, simpleConfig); - result.GetCompletions(commandLine.Length + 1) - .Select(item => item.Label) - .Should() - .BeEquivalentTo("two-a", "two-b"); - } + result.GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--one", "--two", "--three"); + } - [Fact] - public void Option_GetCompletions_can_be_based_on_the_proximate_option_and_partial_input() + [Fact] + public void Argument_completions_can_be_based_on_the_proximate_option() + { + var outer = new Command("outer") { - var outer = new Command("outer") - { - new Command("one", "Command one"), - new Command("two", "Command two"), - new Command("three", "Command three") - }; + CreateOptionWithAcceptOnlyFromAmong(name: "--one", "one-a", "one-b"), + CreateOptionWithAcceptOnlyFromAmong(name: "--two", "two-a", "two-b") + }; - CommandLineConfiguration simpleConfig = new (outer); - ParseResult result = outer.Parse("outer o", simpleConfig); + var commandLine = "outer --two"; + CommandLineConfiguration simpleConfig = new (outer); + ParseResult result = outer.Parse(commandLine, simpleConfig); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("one", "two"); - } + result.GetCompletions(commandLine.Length + 1) + .Select(item => item.Label) + .Should() + .BeEquivalentTo("two-a", "two-b"); + } - [Fact] - public void Completions_can_be_provided_in_the_absence_of_validation() + [Fact] + public void Option_GetCompletions_can_be_based_on_the_proximate_option_and_partial_input() + { + var outer = new Command("outer") { - Option option = new ("-t"); - option.CompletionSources.Add("vegetable", "mineral", "animal"); + new Command("one", "Command one"), + new Command("two", "Command two"), + new Command("three", "Command three") + }; - var command = new Command("the-command") - { - option - }; - - CommandLineConfiguration simpleConfig = new (command); - command.Parse("the-command -t m", simpleConfig) - .GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("animal", - "mineral"); - - command.Parse("the-command -t something-else", simpleConfig) - .Errors - .Should() - .BeEmpty(); - } - - [Fact] - public void Command_argument_completions_can_be_provided_using_a_delegate() - { - var command = new Command("the-command") + CommandLineConfiguration simpleConfig = new (outer); + ParseResult result = outer.Parse("outer o", simpleConfig); + + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("one", "two"); + } + + [Fact] + public void Completions_can_be_provided_in_the_absence_of_validation() + { + Option option = new ("-t"); + option.CompletionSources.Add("vegetable", "mineral", "animal"); + + var command = new Command("the-command") + { + option + }; + + CommandLineConfiguration simpleConfig = new (command); + command.Parse("the-command -t m", simpleConfig) + .GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("animal", + "mineral"); + + command.Parse("the-command -t something-else", simpleConfig) + .Errors + .Should() + .BeEmpty(); + } + + [Fact] + public void Command_argument_completions_can_be_provided_using_a_delegate() + { + var command = new Command("the-command") + { + new Command("one") { - new Command("one") + new Argument("arg") { - new Argument("arg") - { - CompletionSources = { _ => new[] { "vegetable", "mineral", "animal" } } - } + CompletionSources = { _ => new[] { "vegetable", "mineral", "animal" } } } - }; + } + }; + + command.Parse("the-command one m") + .GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("animal", "mineral"); + } - command.Parse("the-command one m") - .GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("animal", "mineral"); - } + [Fact] + public void Option_argument_completions_can_be_provided_using_a_delegate() + { + var option = new Option("-x"); + option.CompletionSources.Add(_ => new[] { "vegetable", "mineral", "animal" }); - [Fact] - public void Option_argument_completions_can_be_provided_using_a_delegate() + var command = new Command("the-command") { - var option = new Option("-x"); - option.CompletionSources.Add(_ => new[] { "vegetable", "mineral", "animal" }); + option + }; - var command = new Command("the-command") - { - option - }; + var parseResult = command.Parse("the-command -x m"); - var parseResult = command.Parse("the-command -x m"); - - parseResult - .GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("animal", "mineral"); - } + parseResult + .GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("animal", "mineral"); + } - [Fact] - public void When_caller_does_the_tokenizing_then_argument_completions_are_based_on_the_proximate_option() + [Fact] + public void When_caller_does_the_tokenizing_then_argument_completions_are_based_on_the_proximate_option() + { + var command = new Command("outer") { - var command = new Command("outer") - { - CreateOptionWithAcceptOnlyFromAmong(name: "one", "one-a", "one-b", "one-c"), - CreateOptionWithAcceptOnlyFromAmong(name: "two", "two-a", "two-b", "two-c"), - CreateOptionWithAcceptOnlyFromAmong(name: "three", "three-a", "three-b", "three-c") - }; + CreateOptionWithAcceptOnlyFromAmong(name: "one", "one-a", "one-b", "one-c"), + CreateOptionWithAcceptOnlyFromAmong(name: "two", "two-a", "two-b", "two-c"), + CreateOptionWithAcceptOnlyFromAmong(name: "three", "three-a", "three-b", "three-c") + }; - var configuration = new CommandLineConfiguration(command); + var configuration = new CommandLineConfiguration(command); - var result = command.Parse("outer two b", configuration); + var result = command.Parse("outer two b", configuration); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("two-b"); - } + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("two-b"); + } - [Fact] - public void When_parsing_from_array_then_argument_completions_are_based_on_the_proximate_option() + [Fact] + public void When_parsing_from_array_then_argument_completions_are_based_on_the_proximate_option() + { + var command = new Command("outer") { - var command = new Command("outer") - { - CreateOptionWithAcceptOnlyFromAmong(name: "one", "one-a", "one-b", "one-c"), - CreateOptionWithAcceptOnlyFromAmong(name: "two", "two-a", "two-b", "two-c"), - CreateOptionWithAcceptOnlyFromAmong(name: "three", "three-a", "three-b", "three-c") - }; + CreateOptionWithAcceptOnlyFromAmong(name: "one", "one-a", "one-b", "one-c"), + CreateOptionWithAcceptOnlyFromAmong(name: "two", "two-a", "two-b", "two-c"), + CreateOptionWithAcceptOnlyFromAmong(name: "three", "three-a", "three-b", "three-c") + }; - var result = command.Parse("outer two b"); + var result = command.Parse("outer two b"); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("two-b"); - } + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("two-b"); + } - [Fact] - public void When_parsing_from_text_then_argument_completions_are_based_on_the_proximate_command() + [Fact] + public void When_parsing_from_text_then_argument_completions_are_based_on_the_proximate_command() + { + var outer = new Command("outer") { - var outer = new Command("outer") + new Command("one") { - new Command("one") - { - CreateArgumentWithAcceptOnlyFromAmong("one-a", "one-b", "one-c") - }, - new Command("two") - { - CreateArgumentWithAcceptOnlyFromAmong("two-a", "two-b", "two-c") - }, - new Command("three") - { - CreateArgumentWithAcceptOnlyFromAmong("three-a", "three-b", "three-c") - } - }; + CreateArgumentWithAcceptOnlyFromAmong("one-a", "one-b", "one-c") + }, + new Command("two") + { + CreateArgumentWithAcceptOnlyFromAmong("two-a", "two-b", "two-c") + }, + new Command("three") + { + CreateArgumentWithAcceptOnlyFromAmong("three-a", "three-b", "three-c") + } + }; - var result = outer.Parse("outer two b"); + var result = outer.Parse("outer two b"); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("two-b"); - } + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("two-b"); + } - [Fact] - public void When_parsing_from_array_then_argument_completions_are_based_on_the_proximate_command() + [Fact] + public void When_parsing_from_array_then_argument_completions_are_based_on_the_proximate_command() + { + var outer = new Command("outer") { - var outer = new Command("outer") + new Command("one") { - new Command("one") - { - CreateArgumentWithAcceptOnlyFromAmong("one-a", "one-b", "one-c") - }, - new Command("two") - { - CreateArgumentWithAcceptOnlyFromAmong("two-a", "two-b", "two-c") - }, - new Command("three") - { - CreateArgumentWithAcceptOnlyFromAmong("three-a", "three-b", "three-c") - } - }; + CreateArgumentWithAcceptOnlyFromAmong("one-a", "one-b", "one-c") + }, + new Command("two") + { + CreateArgumentWithAcceptOnlyFromAmong("two-a", "two-b", "two-c") + }, + new Command("three") + { + CreateArgumentWithAcceptOnlyFromAmong("three-a", "three-b", "three-c") + } + }; - ParseResult result = outer.Parse("outer two b"); + ParseResult result = outer.Parse("outer two b"); - result.GetCompletions() - .Select(item => item.Label) - .Should() - .BeEquivalentTo("two-b"); - } + result.GetCompletions() + .Select(item => item.Label) + .Should() + .BeEquivalentTo("two-b"); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1518 - public void When_parsing_from_text_if_the_proximate_option_is_completed_then_completions_consider_other_option_tokens() + [Fact] // https://github.com/dotnet/command-line-api/issues/1518 + public void When_parsing_from_text_if_the_proximate_option_is_completed_then_completions_consider_other_option_tokens() + { + var command = new RootCommand + { + CreateOptionWithAcceptOnlyFromAmong(name: "--framework", "net8.0"), + CreateOptionWithAcceptOnlyFromAmong(name: "--language", "C#"), + new Option("--langVersion") + }; + var configuration = new CommandLineConfiguration(command); + var completions = command.Parse("--framework net8.0 --l", configuration).GetCompletions(); + + completions.Select(item => item.Label) + .Should() + .BeEquivalentTo("--language", "--langVersion"); + } + + [Fact] + public void When_parsing_from_array_if_the_proximate_option_is_completed_then_completions_consider_other_option_tokens() + { + var command = new RootCommand + { + CreateOptionWithAcceptOnlyFromAmong(name: "--framework", "net8.0"), + CreateOptionWithAcceptOnlyFromAmong(name: "--language", "C#"), + new Option("--langVersion") + }; + var configuration = new CommandLineConfiguration(command); + var completions = command.Parse(new[]{"--framework","net8.0","--l"}, configuration).GetCompletions(); + + completions.Select(item => item.Label) + .Should() + .BeEquivalentTo("--language", "--langVersion"); + } + + [Fact] + public void Arguments_of_type_enum_provide_enum_values_as_suggestions() + { + var command = new Command("the-command") { - var command = new RootCommand - { - CreateOptionWithAcceptOnlyFromAmong(name: "--framework", "net8.0"), - CreateOptionWithAcceptOnlyFromAmong(name: "--language", "C#"), - new Option("--langVersion") - }; - var configuration = new CommandLineConfiguration(command); - var completions = command.Parse("--framework net8.0 --l", configuration).GetCompletions(); - - completions.Select(item => item.Label) - .Should() - .BeEquivalentTo("--language", "--langVersion"); - } - - [Fact] - public void When_parsing_from_array_if_the_proximate_option_is_completed_then_completions_consider_other_option_tokens() - { - var command = new RootCommand - { - CreateOptionWithAcceptOnlyFromAmong(name: "--framework", "net8.0"), - CreateOptionWithAcceptOnlyFromAmong(name: "--language", "C#"), - new Option("--langVersion") - }; - var configuration = new CommandLineConfiguration(command); - var completions = command.Parse(new[]{"--framework","net8.0","--l"}, configuration).GetCompletions(); - - completions.Select(item => item.Label) - .Should() - .BeEquivalentTo("--language", "--langVersion"); - } - - [Fact] - public void Arguments_of_type_enum_provide_enum_values_as_suggestions() - { - var command = new Command("the-command") - { - new Argument("arg") - }; + new Argument("arg") + }; - var completions = command.Parse("the-command create") - .GetCompletions(); + var completions = command.Parse("the-command create") + .GetCompletions(); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentTo("CreateNew", "Create", "OpenOrCreate"); - } + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo("CreateNew", "Create", "OpenOrCreate"); + } - [Fact] - public void Options_that_have_been_specified_to_their_maximum_arity_are_not_suggested() + [Fact] + public void Options_that_have_been_specified_to_their_maximum_arity_are_not_suggested() + { + var command = new Command("command") { - var command = new Command("command") - { - new Option("--allows-one"), - new Option("--allows-many") - }; + new Option("--allows-one"), + new Option("--allows-many") + }; - var commandLine = "--allows-one x"; - CommandLineConfiguration simpleConfig = new (command); - var completions = command.Parse(commandLine, simpleConfig).GetCompletions(commandLine.Length + 1); + var commandLine = "--allows-one x"; + CommandLineConfiguration simpleConfig = new (command); + var completions = command.Parse(commandLine, simpleConfig).GetCompletions(commandLine.Length + 1); - completions.Select(item => item.Label) - .Should() - .BeEquivalentTo("--allows-many"); - } + completions.Select(item => item.Label) + .Should() + .BeEquivalentTo("--allows-many"); + } - [Fact] - public void When_current_symbol_is_an_option_that_requires_arguments_then_parent_symbol_completions_are_omitted() + [Fact] + public void When_current_symbol_is_an_option_that_requires_arguments_then_parent_symbol_completions_are_omitted() + { + var configuration = new CommandLineConfiguration(new RootCommand { - var configuration = new CommandLineConfiguration(new RootCommand - { - new Option("--allows-one"), - new Option("--allows-many") - }); + new Option("--allows-one"), + new Option("--allows-many") + }); - var completions = configuration.Parse("--allows-one ").GetCompletions(); + var completions = configuration.Parse("--allows-one ").GetCompletions(); - completions.Should().BeEmpty(); - } + completions.Should().BeEmpty(); + } - [Fact] - public void Option_substring_matching_when_arguments_have_default_values() + [Fact] + public void Option_substring_matching_when_arguments_have_default_values() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Option("--implicit") { DefaultValueFactory = (_) => "the-default" }, - new Option("--not") { DefaultValueFactory = (_) => "the-default" } - }; + new Option("--implicit") { DefaultValueFactory = (_) => "the-default" }, + new Option("--not") { DefaultValueFactory = (_) => "the-default" } + }; - var completions = command.Parse("m").GetCompletions(); + var completions = command.Parse("m").GetCompletions(); - completions.Select(item => item.Label) - .Should() - .BeEquivalentTo("--implicit"); - } + completions.Select(item => item.Label) + .Should() + .BeEquivalentTo("--implicit"); + } - [Theory(Skip = "work in progress")] - [InlineData("#r \"nuget: ", 11)] - [InlineData("#r \"nuget:", 10)] - public void It_can_provide_completions_within_quotes(string commandLine, int position) + [Theory(Skip = "work in progress")] + [InlineData("#r \"nuget: ", 11)] + [InlineData("#r \"nuget:", 10)] + public void It_can_provide_completions_within_quotes(string commandLine, int position) + { + var expectedSuggestions = new[] { - var expectedSuggestions = new[] - { - "\"nuget:NewtonSoft.Json\"", - "\"nuget:Spectre.Console\"", - "\"nuget:Microsoft.DotNet.Interactive\"" - }; + "\"nuget:NewtonSoft.Json\"", + "\"nuget:Spectre.Console\"", + "\"nuget:Microsoft.DotNet.Interactive\"" + }; - var argument = new Argument("arg"); - argument.CompletionSources.Add(expectedSuggestions); + var argument = new Argument("arg"); + argument.CompletionSources.Add(expectedSuggestions); - var r = new Command("#r") - { - argument - }; + var r = new Command("#r") + { + argument + }; - var completions = r.Parse(commandLine).GetCompletions(position); + var completions = r.Parse(commandLine).GetCompletions(position); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentTo(expectedSuggestions); + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo(expectedSuggestions); - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - [Fact] - public void Default_completions_can_be_cleared_and_replaced() - { - var argument = new Argument("day"); - argument.CompletionSources.Clear(); - argument.CompletionSources.Add(new[] { "mon", "tues", "wed", "thur", "fri", "sat", "sun" }); - var command = new Command("the-command") - { - argument - }; - CommandLineConfiguration simpleConfig = new (command); - var completions = command.Parse("the-command s", simpleConfig) - .GetCompletions(); + [Fact] + public void Default_completions_can_be_cleared_and_replaced() + { + var argument = new Argument("day"); + argument.CompletionSources.Clear(); + argument.CompletionSources.Add(new[] { "mon", "tues", "wed", "thur", "fri", "sat", "sun" }); + var command = new Command("the-command") + { + argument + }; + CommandLineConfiguration simpleConfig = new (command); + var completions = command.Parse("the-command s", simpleConfig) + .GetCompletions(); + + completions.Select(item => item.Label) + .Should() + .BeEquivalentTo("sat", "sun", "tues"); + } - completions.Select(item => item.Label) - .Should() - .BeEquivalentTo("sat", "sun", "tues"); - } + [Fact] + public void Default_completions_can_be_appended_to() + { + var command = new Command("the-command") + { + new Argument("day") + { + CompletionSources = { "mon", "tues", "wed", "thur", "fri", "sat", "sun" } + } + }; + + CommandLineConfiguration simpleConfig = new (command); + var completions = command.Parse("the-command s", simpleConfig) + .GetCompletions(); + + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo( + "sat", + nameof(DayOfWeek.Saturday), + "sun", + nameof(DayOfWeek.Sunday), + "tues", + nameof(DayOfWeek.Tuesday), + nameof(DayOfWeek.Thursday), + nameof(DayOfWeek.Wednesday)); + } - [Fact] - public void Default_completions_can_be_appended_to() - { - var command = new Command("the-command") - { - new Argument("day") - { - CompletionSources = { "mon", "tues", "wed", "thur", "fri", "sat", "sun" } - } - }; - - CommandLineConfiguration simpleConfig = new (command); - var completions = command.Parse("the-command s", simpleConfig) - .GetCompletions(); - - completions - .Select(item => item.Label) - .Should() - .BeEquivalentTo( - "sat", - nameof(DayOfWeek.Saturday), - "sun", - nameof(DayOfWeek.Sunday), - "tues", - nameof(DayOfWeek.Tuesday), - nameof(DayOfWeek.Thursday), - nameof(DayOfWeek.Wednesday)); - } - - [Fact] - public void Completions_for_options_provide_a_description() - { - var description = "The option before -y."; - var option = new Option("-x") { Description = description }; - - var completions = new Command("test") { option }.GetCompletions(CompletionContext.Empty); - - completions.Should().ContainSingle() - .Which - .Detail - .Should() - .Be(description); - } - - [Fact] - public void Completions_for_subcommands_provide_a_description() - { - var description = "The description for the subcommand"; - var subcommand = new Command("-x", description); + [Fact] + public void Completions_for_options_provide_a_description() + { + var description = "The option before -y."; + var option = new Option("-x") { Description = description }; - var completions = new Command("test") { subcommand }.GetCompletions(CompletionContext.Empty); + var completions = new Command("test") { option }.GetCompletions(CompletionContext.Empty); - completions.Should().ContainSingle() - .Which - .Detail - .Should() - .Be(description); - } + completions.Should().ContainSingle() + .Which + .Detail + .Should() + .Be(description); + } + + [Fact] + public void Completions_for_subcommands_provide_a_description() + { + var description = "The description for the subcommand"; + var subcommand = new Command("-x", description); - [Fact] // https://github.com/dotnet/command-line-api/issues/1629 - public void When_option_completions_are_available_then_they_are_suggested_when_a_validation_error_occurs() - { - Option option = new ("--day"); - RootCommand rootCommand = new () { option }; - CommandLineConfiguration simpleConfig = new (rootCommand); + var completions = new Command("test") { subcommand }.GetCompletions(CompletionContext.Empty); - var result = rootCommand.Parse("--day SleepyDay", simpleConfig); + completions.Should().ContainSingle() + .Which + .Detail + .Should() + .Be(description); + } - result.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be( - $"Cannot parse argument 'SleepyDay' for option '--day' as expected type 'System.DayOfWeek'. Did you mean one of the following?{NewLine}Friday{NewLine}Monday{NewLine}Saturday{NewLine}Sunday{NewLine}Thursday{NewLine}Tuesday{NewLine}Wednesday"); - } + [Fact] // https://github.com/dotnet/command-line-api/issues/1629 + public void When_option_completions_are_available_then_they_are_suggested_when_a_validation_error_occurs() + { + Option option = new ("--day"); + RootCommand rootCommand = new () { option }; + CommandLineConfiguration simpleConfig = new (rootCommand); + + var result = rootCommand.Parse("--day SleepyDay", simpleConfig); + + result.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be( + $"Cannot parse argument 'SleepyDay' for option '--day' as expected type 'System.DayOfWeek'. Did you mean one of the following?{NewLine}Friday{NewLine}Monday{NewLine}Saturday{NewLine}Sunday{NewLine}Thursday{NewLine}Tuesday{NewLine}Wednesday"); + } - private static Argument CreateArgumentWithAcceptOnlyFromAmong(params string[] values) - { - Argument argument = new("arg"); - argument.AcceptOnlyFromAmong(values); - return argument; - } + private static Argument CreateArgumentWithAcceptOnlyFromAmong(params string[] values) + { + Argument argument = new("arg"); + argument.AcceptOnlyFromAmong(values); + return argument; + } - private static Option CreateOptionWithAcceptOnlyFromAmong(string name, params string[] values) - { - Option option = new(name); - option.AcceptOnlyFromAmong(values); - return option; - } + private static Option CreateOptionWithAcceptOnlyFromAmong(string name, params string[] values) + { + Option option = new(name); + option.AcceptOnlyFromAmong(values); + return option; } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/DirectiveTests.cs b/src/System.CommandLine.Tests/DirectiveTests.cs index 9d2851726d..685d72487e 100644 --- a/src/System.CommandLine.Tests/DirectiveTests.cs +++ b/src/System.CommandLine.Tests/DirectiveTests.cs @@ -8,215 +8,214 @@ using FluentAssertions.Execution; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class DirectiveTests { - public class DirectiveTests + [Fact] + public void Directives_should_be_considered_as_unmatched_tokens_when_they_are_not_matched() { - [Fact] - public void Directives_should_be_considered_as_unmatched_tokens_when_they_are_not_matched() - { - Directive directive = new("parse"); + Directive directive = new("parse"); - ParseResult result = Parse(new Option("-y"), directive, $"{RootCommand.ExecutableName} [nonExisting] -y"); + ParseResult result = Parse(new Option("-y"), directive, $"{RootCommand.ExecutableName} [nonExisting] -y"); - result.UnmatchedTokens.Should().ContainSingle("[nonExisting]"); - } + result.UnmatchedTokens.Should().ContainSingle("[nonExisting]"); + } - [Fact] - public void Tokens_still_hold_directives() - { - Directive directive = new ("parse"); + [Fact] + public void Tokens_still_hold_directives() + { + Directive directive = new ("parse"); - ParseResult result = Parse(new Option("-y"), directive, "[parse] -y"); + ParseResult result = Parse(new Option("-y"), directive, "[parse] -y"); - result.GetResult(directive).Should().NotBeNull(); - result.Tokens.Should().Contain(t => t.Value == "[parse]" && t.Type == TokenType.Directive); - } + result.GetResult(directive).Should().NotBeNull(); + result.Tokens.Should().Contain(t => t.Value == "[parse]" && t.Type == TokenType.Directive); + } - [Fact] - public void Directives_must_precede_other_symbols() - { - Directive directive = new("parse"); + [Fact] + public void Directives_must_precede_other_symbols() + { + Directive directive = new("parse"); - ParseResult result = Parse(new Option("-y"), directive, "-y [parse]"); + ParseResult result = Parse(new Option("-y"), directive, "-y [parse]"); - result.GetResult(directive).Should().BeNull(); - } + result.GetResult(directive).Should().BeNull(); + } - [Fact] - public void Multiple_directives_are_allowed() + [Fact] + public void Multiple_directives_are_allowed() + { + RootCommand root = new() { new Option("-y") }; + Directive parseDirective = new ("parse"); + Directive suggestDirective = new ("suggest"); + CommandLineConfiguration config = new(root); + root.Add(parseDirective); + root.Add(suggestDirective); + + var result = root.Parse("[parse] [suggest] -y", config); + + result.GetResult(parseDirective).Should().NotBeNull(); + result.GetResult(suggestDirective).Should().NotBeNull(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Multiple_instances_of_the_same_directive_can_be_invoked(bool invokeAsync) + { + var commandActionWasCalled = false; + var directiveCallCount = 0; + + Action incrementCallCount = _ => directiveCallCount++; + Action verifyActionWasCalled = _ => commandActionWasCalled = true; + + var testDirective = new TestDirective("test") { - RootCommand root = new() { new Option("-y") }; - Directive parseDirective = new ("parse"); - Directive suggestDirective = new ("suggest"); - CommandLineConfiguration config = new(root); - root.Add(parseDirective); - root.Add(suggestDirective); + Action = invokeAsync + ? new AsynchronousTestAction(incrementCallCount, terminating: false) + : new SynchronousTestAction(incrementCallCount, terminating: false) + }; - var result = root.Parse("[parse] [suggest] -y", config); + var config = new CommandLineConfiguration(new RootCommand + { + Action = invokeAsync + ? new AsynchronousTestAction(verifyActionWasCalled, terminating: false) + : new SynchronousTestAction(verifyActionWasCalled, terminating: false), + Directives = { testDirective } + }); - result.GetResult(parseDirective).Should().NotBeNull(); - result.GetResult(suggestDirective).Should().NotBeNull(); + if (invokeAsync) + { + await config.InvokeAsync("[test:1] [test:2]"); } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task Multiple_instances_of_the_same_directive_can_be_invoked(bool invokeAsync) + else { - var commandActionWasCalled = false; - var directiveCallCount = 0; - - Action incrementCallCount = _ => directiveCallCount++; - Action verifyActionWasCalled = _ => commandActionWasCalled = true; - - var testDirective = new TestDirective("test") - { - Action = invokeAsync - ? new AsynchronousTestAction(incrementCallCount, terminating: false) - : new SynchronousTestAction(incrementCallCount, terminating: false) - }; - - var config = new CommandLineConfiguration(new RootCommand - { - Action = invokeAsync - ? new AsynchronousTestAction(verifyActionWasCalled, terminating: false) - : new SynchronousTestAction(verifyActionWasCalled, terminating: false), - Directives = { testDirective } - }); - - if (invokeAsync) - { - await config.InvokeAsync("[test:1] [test:2]"); - } - else - { - config.Invoke("[test:1] [test:2]"); - } - - using var _ = new AssertionScope(); - - commandActionWasCalled.Should().BeTrue(); - directiveCallCount.Should().Be(2); + config.Invoke("[test:1] [test:2]"); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task Multiple_different_directives_can_be_invoked(bool invokeAsync) + using var _ = new AssertionScope(); + + commandActionWasCalled.Should().BeTrue(); + directiveCallCount.Should().Be(2); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Multiple_different_directives_can_be_invoked(bool invokeAsync) + { + bool commandActionWasCalled = false; + bool directiveOneActionWasCalled = false; + bool directiveTwoActionWasCalled = false; + + var directiveOne = new TestDirective("one") { - bool commandActionWasCalled = false; - bool directiveOneActionWasCalled = false; - bool directiveTwoActionWasCalled = false; - - var directiveOne = new TestDirective("one") - { - Action = new SynchronousTestAction(_ => directiveOneActionWasCalled = true, terminating: false) - }; - var directiveTwo = new TestDirective("two") - { - Action = new SynchronousTestAction(_ => directiveTwoActionWasCalled = true, terminating: false) - }; - var config = new CommandLineConfiguration(new RootCommand - { - Action = new SynchronousTestAction(_ => commandActionWasCalled = true, terminating: false), Directives = { directiveOne, directiveTwo } - }); - - if (invokeAsync) - { - await config.InvokeAsync("[one] [two]"); - } - else - { - config.Invoke("[one] [two]"); - } - - using var _ = new AssertionScope(); - - commandActionWasCalled.Should().BeTrue(); - directiveOneActionWasCalled.Should().BeTrue(); - directiveTwoActionWasCalled.Should().BeTrue(); - } + Action = new SynchronousTestAction(_ => directiveOneActionWasCalled = true, terminating: false) + }; + var directiveTwo = new TestDirective("two") + { + Action = new SynchronousTestAction(_ => directiveTwoActionWasCalled = true, terminating: false) + }; + var config = new CommandLineConfiguration(new RootCommand + { + Action = new SynchronousTestAction(_ => commandActionWasCalled = true, terminating: false), Directives = { directiveOne, directiveTwo } + }); - public class TestDirective : Directive + if (invokeAsync) { - public TestDirective(string name) : base(name) - { - } + await config.InvokeAsync("[one] [two]"); } - - [Theory] - [InlineData("[key:value]", "key", "value")] - [InlineData("[key:value:more]", "key", "value:more")] - [InlineData("[key:]", "key", "")] - public void Directives_can_have_a_value_which_is_everything_after_the_first_colon( - string wholeText, - string key, - string expectedValue) + else { - Directive directive = new(key); + config.Invoke("[one] [two]"); + } - ParseResult result = Parse(new Option("-y"), directive, $"{wholeText} -y"); + using var _ = new AssertionScope(); - result.GetResult(directive).Values.Single().Should().Be(expectedValue); - } + commandActionWasCalled.Should().BeTrue(); + directiveOneActionWasCalled.Should().BeTrue(); + directiveTwoActionWasCalled.Should().BeTrue(); + } - [Fact] - public void Directives_without_a_value_specified_have_no_values() + public class TestDirective : Directive + { + public TestDirective(string name) : base(name) { - Directive directive = new("parse"); + } + } - ParseResult result = Parse(new Option("-y"), directive, "[parse] -y"); + [Theory] + [InlineData("[key:value]", "key", "value")] + [InlineData("[key:value:more]", "key", "value:more")] + [InlineData("[key:]", "key", "")] + public void Directives_can_have_a_value_which_is_everything_after_the_first_colon( + string wholeText, + string key, + string expectedValue) + { + Directive directive = new(key); - result.GetResult(directive).Values.Should().BeEmpty(); - } + ParseResult result = Parse(new Option("-y"), directive, $"{wholeText} -y"); - [Theory] - [InlineData("[]")] - [InlineData("[:value]")] - public void Directives_must_have_a_non_empty_key(string directive) - { - Option option = new ("-a"); - RootCommand root = new () { option }; + result.GetResult(directive).Values.Single().Should().Be(expectedValue); + } - var result = root.Parse($"{directive} -a"); + [Fact] + public void Directives_without_a_value_specified_have_no_values() + { + Directive directive = new("parse"); - result.UnmatchedTokens.Should().Contain(directive); - } + ParseResult result = Parse(new Option("-y"), directive, "[parse] -y"); - [Theory] - [InlineData("[par se]", "[par", "se]")] - [InlineData("[ parse]", "[", "parse]")] - [InlineData("[parse ]", "[parse", "]")] - public void Directives_cannot_contain_spaces(string value, string firstUnmatchedToken, string secondUnmatchedToken) - { - Action create = () => new Directive(value); - create.Should().Throw(); + result.GetResult(directive).Values.Should().BeEmpty(); + } - Directive directive = new("parse"); - ParseResult result = Parse(new Option("-y"), directive, $"{value} -y"); - result.GetResult(directive).Should().BeNull(); + [Theory] + [InlineData("[]")] + [InlineData("[:value]")] + public void Directives_must_have_a_non_empty_key(string directive) + { + Option option = new ("-a"); + RootCommand root = new () { option }; - result.UnmatchedTokens.Should().BeEquivalentTo(firstUnmatchedToken, secondUnmatchedToken); - } + var result = root.Parse($"{directive} -a"); - [Fact] - public void When_a_directive_is_specified_more_than_once_then_its_values_are_aggregated() - { - Directive directive = new("directive"); + result.UnmatchedTokens.Should().Contain(directive); + } - ParseResult result = Parse(new Option("-a"), directive, "[directive:one] [directive:two] -a"); + [Theory] + [InlineData("[par se]", "[par", "se]")] + [InlineData("[ parse]", "[", "parse]")] + [InlineData("[parse ]", "[parse", "]")] + public void Directives_cannot_contain_spaces(string value, string firstUnmatchedToken, string secondUnmatchedToken) + { + Action create = () => new Directive(value); + create.Should().Throw(); - result.GetResult(directive).Values.Should().BeEquivalentTo("one", "two"); - } + Directive directive = new("parse"); + ParseResult result = Parse(new Option("-y"), directive, $"{value} -y"); + result.GetResult(directive).Should().BeNull(); - private static ParseResult Parse(Option option, Directive directive, string commandLine) - { - RootCommand root = new() { option }; - CommandLineConfiguration config = new(root); - root.Directives.Add(directive); + result.UnmatchedTokens.Should().BeEquivalentTo(firstUnmatchedToken, secondUnmatchedToken); + } - return root.Parse(commandLine, config); - } + [Fact] + public void When_a_directive_is_specified_more_than_once_then_its_values_are_aggregated() + { + Directive directive = new("directive"); + + ParseResult result = Parse(new Option("-a"), directive, "[directive:one] [directive:two] -a"); + + result.GetResult(directive).Values.Should().BeEquivalentTo("one", "two"); + } + + private static ParseResult Parse(Option option, Directive directive, string commandLine) + { + RootCommand root = new() { option }; + CommandLineConfiguration config = new(root); + root.Directives.Add(directive); + + return root.Parse(commandLine, config); } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/EnvironmentVariableDirectiveTests.cs b/src/System.CommandLine.Tests/EnvironmentVariableDirectiveTests.cs index 47ee79f8ce..2fb33d0bc5 100644 --- a/src/System.CommandLine.Tests/EnvironmentVariableDirectiveTests.cs +++ b/src/System.CommandLine.Tests/EnvironmentVariableDirectiveTests.cs @@ -6,143 +6,142 @@ using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class EnvironmentVariableDirectiveTests { - public class EnvironmentVariableDirectiveTests - { - private static readonly Random _random = new(); - private readonly string _testVariableName = $"TEST_ENVIRONMENT_VARIABLE_{_random.Next()}"; + private static readonly Random _random = new(); + private readonly string _testVariableName = $"TEST_ENVIRONMENT_VARIABLE_{_random.Next()}"; - [Fact] - public async Task Sets_environment_variable_to_value() + [Fact] + public async Task Sets_environment_variable_to_value() + { + bool asserted = false; + const string value = "hello"; + var rootCommand = new RootCommand { - bool asserted = false; - const string value = "hello"; - var rootCommand = new RootCommand - { - new EnvironmentVariablesDirective() - }; - rootCommand.SetAction(_ => - { - asserted = true; - Environment.GetEnvironmentVariable(_testVariableName).Should().Be(value); - }); - - var config = new CommandLineConfiguration(rootCommand) - { - EnableDefaultExceptionHandler = false - }; - - await config.InvokeAsync($"[env:{_testVariableName}={value}]"); - - asserted.Should().BeTrue(); - } + new EnvironmentVariablesDirective() + }; + rootCommand.SetAction(_ => + { + asserted = true; + Environment.GetEnvironmentVariable(_testVariableName).Should().Be(value); + }); - [Fact] - public async Task Sets_environment_variable_value_containing_equals_sign() + var config = new CommandLineConfiguration(rootCommand) { - bool asserted = false; - const string value = "1=2"; - var rootCommand = new RootCommand - { - new EnvironmentVariablesDirective() - }; - rootCommand.SetAction(_ => - { - asserted = true; - Environment.GetEnvironmentVariable(_testVariableName).Should().Be(value); - }); - - var config = new CommandLineConfiguration(rootCommand) - { - EnableDefaultExceptionHandler = false - }; - - await config.InvokeAsync($"[env:{_testVariableName}={value}]" ); - - asserted.Should().BeTrue(); - } + EnableDefaultExceptionHandler = false + }; + + await config.InvokeAsync($"[env:{_testVariableName}={value}]"); - [Fact] - public async Task Ignores_environment_directive_without_equals_sign() + asserted.Should().BeTrue(); + } + + [Fact] + public async Task Sets_environment_variable_value_containing_equals_sign() + { + bool asserted = false; + const string value = "1=2"; + var rootCommand = new RootCommand { - bool asserted = false; - string variable = _testVariableName; - var rootCommand = new RootCommand - { - new EnvironmentVariablesDirective() - }; - rootCommand.SetAction(_ => - { - asserted = true; - Environment.GetEnvironmentVariable(variable).Should().BeNull(); - }); - - var config = new CommandLineConfiguration(rootCommand) - { - EnableDefaultExceptionHandler = false - }; - - await config.InvokeAsync( $"[env:{variable}]" ); - - asserted.Should().BeTrue(); - } + new EnvironmentVariablesDirective() + }; + rootCommand.SetAction(_ => + { + asserted = true; + Environment.GetEnvironmentVariable(_testVariableName).Should().Be(value); + }); - [Fact] - public static async Task Ignores_environment_directive_with_empty_variable_name() + var config = new CommandLineConfiguration(rootCommand) { - bool asserted = false; - string value = "value"; - var rootCommand = new RootCommand - { - new EnvironmentVariablesDirective() - }; - rootCommand.SetAction(_ => - { - asserted = true; - var env = Environment.GetEnvironmentVariables(); - env.Values.Cast().Should().NotContain(value); - }); - - var config = new CommandLineConfiguration(rootCommand) - { - EnableDefaultExceptionHandler = false - }; - - var result = config.Parse($"[env:={value}]"); - - await result.InvokeAsync(); - - asserted.Should().BeTrue(); - } + EnableDefaultExceptionHandler = false + }; - [Fact] - public void It_does_not_prevent_help_from_being_invoked() + await config.InvokeAsync($"[env:{_testVariableName}={value}]" ); + + asserted.Should().BeTrue(); + } + + [Fact] + public async Task Ignores_environment_directive_without_equals_sign() + { + bool asserted = false; + string variable = _testVariableName; + var rootCommand = new RootCommand { - var root = new RootCommand(); - root.SetAction(_ => { }); + new EnvironmentVariablesDirective() + }; + rootCommand.SetAction(_ => + { + asserted = true; + Environment.GetEnvironmentVariable(variable).Should().BeNull(); + }); - var customHelpAction = new CustomHelpAction(); - root.Options.OfType().Single().Action = customHelpAction; + var config = new CommandLineConfiguration(rootCommand) + { + EnableDefaultExceptionHandler = false + }; - var config = new CommandLineConfiguration(root); - root.Directives.Add(new EnvironmentVariablesDirective()); + await config.InvokeAsync( $"[env:{variable}]" ); - root.Parse($"[env:{_testVariableName}=1] -h", config).Invoke(); + asserted.Should().BeTrue(); + } - customHelpAction.WasCalled.Should().BeTrue(); - Environment.GetEnvironmentVariable(_testVariableName).Should().Be("1"); - } + [Fact] + public static async Task Ignores_environment_directive_with_empty_variable_name() + { + bool asserted = false; + string value = "value"; + var rootCommand = new RootCommand + { + new EnvironmentVariablesDirective() + }; + rootCommand.SetAction(_ => + { + asserted = true; + var env = Environment.GetEnvironmentVariables(); + env.Values.Cast().Should().NotContain(value); + }); - private class CustomHelpAction : SynchronousCommandLineAction + var config = new CommandLineConfiguration(rootCommand) { - public bool WasCalled { get; private set; } + EnableDefaultExceptionHandler = false + }; - public override int Invoke(ParseResult parseResult) - { - WasCalled = true; - return 0; - } + var result = config.Parse($"[env:={value}]"); + + await result.InvokeAsync(); + + asserted.Should().BeTrue(); + } + + [Fact] + public void It_does_not_prevent_help_from_being_invoked() + { + var root = new RootCommand(); + root.SetAction(_ => { }); + + var customHelpAction = new CustomHelpAction(); + root.Options.OfType().Single().Action = customHelpAction; + + var config = new CommandLineConfiguration(root); + root.Directives.Add(new EnvironmentVariablesDirective()); + + root.Parse($"[env:{_testVariableName}=1] -h", config).Invoke(); + + customHelpAction.WasCalled.Should().BeTrue(); + Environment.GetEnvironmentVariable(_testVariableName).Should().Be("1"); + } + + private class CustomHelpAction : SynchronousCommandLineAction + { + public bool WasCalled { get; private set; } + + public override int Invoke(ParseResult parseResult) + { + WasCalled = true; + return 0; } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/GlobalOptionTests.cs b/src/System.CommandLine.Tests/GlobalOptionTests.cs index 7c51a2e751..8471e7e501 100644 --- a/src/System.CommandLine.Tests/GlobalOptionTests.cs +++ b/src/System.CommandLine.Tests/GlobalOptionTests.cs @@ -4,120 +4,119 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class GlobalOptionTests { - public class GlobalOptionTests + [Fact] + public void Global_options_appear_in_options_list_of_symbols_they_are_directly_added_to() { - [Fact] - public void Global_options_appear_in_options_list_of_symbols_they_are_directly_added_to() - { - var root = new Command("parent"); + var root = new Command("parent"); - var option = new Option("--global") { Recursive = true }; + var option = new Option("--global") { Recursive = true }; - root.Options.Add(option); + root.Options.Add(option); - root.Options.Should().Contain(option); - } + root.Options.Should().Contain(option); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1540 - public void When_a_required_global_option_is_omitted_it_results_in_an_error() - { - var command = new Command("child"); - var rootCommand = new RootCommand { command }; - command.SetAction(_ => { }); - var requiredOption = new Option("--i-must-be-set") - { - Required = true, - Recursive = true - }; - rootCommand.Options.Add(requiredOption); - - var result = rootCommand.Parse("child"); - - result.Errors - .Should() - .ContainSingle() - .Which.Message.Should().Be("Option '--i-must-be-set' is required."); - } - - [Fact] - public void When_a_required_global_option_has_multiple_aliases_the_error_message_uses_the_name() + [Fact] // https://github.com/dotnet/command-line-api/issues/1540 + public void When_a_required_global_option_is_omitted_it_results_in_an_error() + { + var command = new Command("child"); + var rootCommand = new RootCommand { command }; + command.SetAction(_ => { }); + var requiredOption = new Option("--i-must-be-set") { - var rootCommand = new RootCommand(); - var requiredOption = new Option("-i", "--i-must-be-set") - { - Required = true, - Recursive = true - }; - rootCommand.Options.Add(requiredOption); - - var result = rootCommand.Parse(""); - - result.Errors - .Should() - .ContainSingle() - .Which.Message.Should().Be("Option '-i' is required."); - } - - [Fact] - public void When_a_required_global_option_is_present_on_child_of_command_it_was_added_to_it_does_not_result_in_an_error() + Required = true, + Recursive = true + }; + rootCommand.Options.Add(requiredOption); + + var result = rootCommand.Parse("child"); + + result.Errors + .Should() + .ContainSingle() + .Which.Message.Should().Be("Option '--i-must-be-set' is required."); + } + + [Fact] + public void When_a_required_global_option_has_multiple_aliases_the_error_message_uses_the_name() + { + var rootCommand = new RootCommand(); + var requiredOption = new Option("-i", "--i-must-be-set") { - var command = new Command("child"); - var rootCommand = new RootCommand { command }; - command.SetAction((_) => { }); - var requiredOption = new Option("--i-must-be-set") - { - Required = true, - Recursive = true - }; - rootCommand.Options.Add(requiredOption); - - var result = rootCommand.Parse("child --i-must-be-set"); - - result.Errors.Should().BeEmpty(); - } - - [Fact] - public void Subcommands_added_after_a_global_option_is_added_to_parent_will_recognize_the_global_option() + Required = true, + Recursive = true + }; + rootCommand.Options.Add(requiredOption); + + var result = rootCommand.Parse(""); + + result.Errors + .Should() + .ContainSingle() + .Which.Message.Should().Be("Option '-i' is required."); + } + + [Fact] + public void When_a_required_global_option_is_present_on_child_of_command_it_was_added_to_it_does_not_result_in_an_error() + { + var command = new Command("child"); + var rootCommand = new RootCommand { command }; + command.SetAction((_) => { }); + var requiredOption = new Option("--i-must-be-set") { - var root = new Command("parent"); + Required = true, + Recursive = true + }; + rootCommand.Options.Add(requiredOption); - var option = new Option("--global") { Recursive = true }; + var result = rootCommand.Parse("child --i-must-be-set"); - root.Options.Add(option); + result.Errors.Should().BeEmpty(); + } - var child = new Command("child"); + [Fact] + public void Subcommands_added_after_a_global_option_is_added_to_parent_will_recognize_the_global_option() + { + var root = new Command("parent"); - root.Subcommands.Add(child); + var option = new Option("--global") { Recursive = true }; - root.Parse("child --global 123").GetValue(option).Should().Be(123); + root.Options.Add(option); - child.Parse("--global 123").GetValue(option).Should().Be(123); - } + var child = new Command("child"); - [Fact] - public void Subcommands_with_global_option_should_propagate_option_to_children() - { - var root = new Command("parent"); + root.Subcommands.Add(child); + + root.Parse("child --global 123").GetValue(option).Should().Be(123); + + child.Parse("--global 123").GetValue(option).Should().Be(123); + } + + [Fact] + public void Subcommands_with_global_option_should_propagate_option_to_children() + { + var root = new Command("parent"); - var firstChild = new Command("first"); + var firstChild = new Command("first"); - root.Subcommands.Add(firstChild); + root.Subcommands.Add(firstChild); - var option = new Option("--global") { Recursive = true }; + var option = new Option("--global") { Recursive = true }; - firstChild.Options.Add(option); + firstChild.Options.Add(option); - var secondChild = new Command("second"); + var secondChild = new Command("second"); - firstChild.Subcommands.Add(secondChild); + firstChild.Subcommands.Add(secondChild); - root.Parse("first second --global 123").GetValue(option).Should().Be(123); + root.Parse("first second --global 123").GetValue(option).Should().Be(123); - firstChild.Parse("second --global 123").GetValue(option).Should().Be(123); + firstChild.Parse("second --global 123").GetValue(option).Should().Be(123); - secondChild.Parse("--global 123").GetValue(option).Should().Be(123); - } + secondChild.Parse("--global 123").GetValue(option).Should().Be(123); } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Help/CustomHelpAction.cs b/src/System.CommandLine.Tests/Help/CustomHelpAction.cs index cac66dc599..9a963c676a 100644 --- a/src/System.CommandLine.Tests/Help/CustomHelpAction.cs +++ b/src/System.CommandLine.Tests/Help/CustomHelpAction.cs @@ -1,35 +1,34 @@ using System.CommandLine.Invocation; -namespace System.CommandLine.Help +namespace System.CommandLine.Help; + +/// +/// Provides command line help. +/// +public sealed class CustomHelpAction : SynchronousCommandLineAction { + private HelpBuilder? _builder; + /// - /// Provides command line help. + /// Specifies an to be used to format help output when help is requested. /// - public sealed class CustomHelpAction : SynchronousCommandLineAction + internal HelpBuilder Builder { - private HelpBuilder? _builder; - - /// - /// Specifies an to be used to format help output when help is requested. - /// - internal HelpBuilder Builder - { - get => _builder ??= new HelpBuilder(Console.IsOutputRedirected ? int.MaxValue : Console.WindowWidth); - set => _builder = value ?? throw new ArgumentNullException(nameof(value)); - } + get => _builder ??= new HelpBuilder(Console.IsOutputRedirected ? int.MaxValue : Console.WindowWidth); + set => _builder = value ?? throw new ArgumentNullException(nameof(value)); + } - /// - public override int Invoke(ParseResult parseResult) - { - var output = parseResult.Configuration.Output; + /// + public override int Invoke(ParseResult parseResult) + { + var output = parseResult.Configuration.Output; - var helpContext = new HelpContext(Builder, - parseResult.CommandResult.Command, - output); + var helpContext = new HelpContext(Builder, + parseResult.CommandResult.Command, + output); - Builder.Write(helpContext); + Builder.Write(helpContext); - return 0; - } + return 0; } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Help/HelpBuilderExtensions.cs b/src/System.CommandLine.Tests/Help/HelpBuilderExtensions.cs index 6fbc826bd0..bcfbc0e6d0 100644 --- a/src/System.CommandLine.Tests/Help/HelpBuilderExtensions.cs +++ b/src/System.CommandLine.Tests/Help/HelpBuilderExtensions.cs @@ -4,14 +4,13 @@ using System.CommandLine.Help; using System.IO; -namespace System.CommandLine.Tests.Help +namespace System.CommandLine.Tests.Help; + +public static class HelpBuilderExtensions { - public static class HelpBuilderExtensions - { - internal static void Write( - this HelpBuilder builder, - Command command, - TextWriter writer) => - builder.Write(new HelpContext(builder, command, writer)); - } + internal static void Write( + this HelpBuilder builder, + Command command, + TextWriter writer) => + builder.Write(new HelpContext(builder, command, writer)); } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Help/HelpBuilderTests.Approval.cs b/src/System.CommandLine.Tests/Help/HelpBuilderTests.Approval.cs index 0cb2b097ce..a99c523ac3 100644 --- a/src/System.CommandLine.Tests/Help/HelpBuilderTests.Approval.cs +++ b/src/System.CommandLine.Tests/Help/HelpBuilderTests.Approval.cs @@ -6,76 +6,75 @@ using ApprovalTests; using ApprovalTests.Reporters; -namespace System.CommandLine.Tests.Help +namespace System.CommandLine.Tests.Help; + +public partial class HelpBuilderTests { - public partial class HelpBuilderTests + [Fact] + [UseReporter(typeof(DiffReporter))] + public void Help_layout_has_not_changed() { - [Fact] - [UseReporter(typeof(DiffReporter))] - public void Help_layout_has_not_changed() + var command = new Command("the-root-command", "Test description") { - var command = new Command("the-root-command", "Test description") + new Argument("the-root-arg-no-description-no-default"), + new Argument("the-root-arg-no-description-default") + { + DefaultValueFactory = (_) => "the-root-arg-no-description-default-value", + }, + new Argument("the-root-arg-no-default") + { + Description = "the-root-arg-no-default-description", + }, + new Argument("the-root-arg") + { + DefaultValueFactory = (_) => "the-root-arg-one-value", + Description = "the-root-arg-description" + }, + new Argument("the-root-arg-enum-default") + { + DefaultValueFactory = (_) => FileAccess.Read, + Description = "the-root-arg-enum-default-description" + }, + new Option("--the-root-option-no-arg", "-trna") + { + Description = "the-root-option-no-arg-description", + Required = true + }, + new Option("--the-root-option-no-description-default-arg", "-trondda") + { + DefaultValueFactory = (_) => "the-root-option--no-description-default-arg-value", + }, + new Option("--the-root-option-no-default-arg", "-tronda") + { + Description = "the-root-option-no-default-description", + HelpName = "the-root-option-arg-no-default-arg", + Required = true + }, + new Option("--the-root-option-default-arg", "-troda") + { + DefaultValueFactory = (_) => "the-root-option-arg-value", + Description = "the-root-option-default-arg-description", + HelpName = "the-root-option-arg", + }, + new Option("--the-root-option-enum-arg", "-troea") + { + DefaultValueFactory = (_) => FileAccess.Read, + Description = "the-root-option-description", + }, + new Option("--the-root-option-required-enum-arg", "-trorea") + { + DefaultValueFactory = (_) => FileAccess.Read, + Description = "the-root-option-description", + Required = true + }, + new Option("--the-root-option-multi-line-description", "-tromld") { - new Argument("the-root-arg-no-description-no-default"), - new Argument("the-root-arg-no-description-default") - { - DefaultValueFactory = (_) => "the-root-arg-no-description-default-value", - }, - new Argument("the-root-arg-no-default") - { - Description = "the-root-arg-no-default-description", - }, - new Argument("the-root-arg") - { - DefaultValueFactory = (_) => "the-root-arg-one-value", - Description = "the-root-arg-description" - }, - new Argument("the-root-arg-enum-default") - { - DefaultValueFactory = (_) => FileAccess.Read, - Description = "the-root-arg-enum-default-description" - }, - new Option("--the-root-option-no-arg", "-trna") - { - Description = "the-root-option-no-arg-description", - Required = true - }, - new Option("--the-root-option-no-description-default-arg", "-trondda") - { - DefaultValueFactory = (_) => "the-root-option--no-description-default-arg-value", - }, - new Option("--the-root-option-no-default-arg", "-tronda") - { - Description = "the-root-option-no-default-description", - HelpName = "the-root-option-arg-no-default-arg", - Required = true - }, - new Option("--the-root-option-default-arg", "-troda") - { - DefaultValueFactory = (_) => "the-root-option-arg-value", - Description = "the-root-option-default-arg-description", - HelpName = "the-root-option-arg", - }, - new Option("--the-root-option-enum-arg", "-troea") - { - DefaultValueFactory = (_) => FileAccess.Read, - Description = "the-root-option-description", - }, - new Option("--the-root-option-required-enum-arg", "-trorea") - { - DefaultValueFactory = (_) => FileAccess.Read, - Description = "the-root-option-description", - Required = true - }, - new Option("--the-root-option-multi-line-description", "-tromld") - { - Description = "the-root-option\r\nmulti-line\ndescription" - }, - }; + Description = "the-root-option\r\nmulti-line\ndescription" + }, + }; - StringWriter writer = new(); - GetHelpBuilder(LargeMaxWidth).Write(command, writer); - Approvals.Verify(writer.ToString()); - } + StringWriter writer = new(); + GetHelpBuilder(LargeMaxWidth).Write(command, writer); + Approvals.Verify(writer.ToString()); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs b/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs index 4a735a4aad..832d7b577d 100644 --- a/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs +++ b/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs @@ -9,1626 +9,1625 @@ using Xunit; using static System.Environment; -namespace System.CommandLine.Tests.Help +namespace System.CommandLine.Tests.Help; + +public partial class HelpBuilderTests { - public partial class HelpBuilderTests + private const int SmallMaxWidth = 70; + private const int LargeMaxWidth = 200; + private const int ColumnGutterWidth = 2; + private const int IndentationWidth = 2; + + private readonly HelpBuilder _helpBuilder; + private readonly StringWriter _console; + private readonly string _executableName; + private readonly string _columnPadding; + private readonly string _indentation; + + public HelpBuilderTests() { - private const int SmallMaxWidth = 70; - private const int LargeMaxWidth = 200; - private const int ColumnGutterWidth = 2; - private const int IndentationWidth = 2; - - private readonly HelpBuilder _helpBuilder; - private readonly StringWriter _console; - private readonly string _executableName; - private readonly string _columnPadding; - private readonly string _indentation; - - public HelpBuilderTests() - { - _console = new(); - _helpBuilder = GetHelpBuilder(LargeMaxWidth); - _columnPadding = new string(' ', ColumnGutterWidth); - _indentation = new string(' ', IndentationWidth); - _executableName = RootCommand.ExecutableName; - } + _console = new(); + _helpBuilder = GetHelpBuilder(LargeMaxWidth); + _columnPadding = new string(' ', ColumnGutterWidth); + _indentation = new string(' ', IndentationWidth); + _executableName = RootCommand.ExecutableName; + } - private HelpBuilder GetHelpBuilder(int maxWidth = SmallMaxWidth) => new(maxWidth); + private HelpBuilder GetHelpBuilder(int maxWidth = SmallMaxWidth) => new(maxWidth); - #region Synopsis + #region Synopsis - [Fact] - public void Synopsis_section_keeps_added_newlines() - { - var command = new RootCommand( - $"test{NewLine}\r\ndescription with\nline breaks"); + [Fact] + public void Synopsis_section_keeps_added_newlines() + { + var command = new RootCommand( + $"test{NewLine}\r\ndescription with\nline breaks"); - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var expected = - $"{_indentation}test{NewLine}" + - $"{_indentation}{NewLine}" + - $"{_indentation}description with{NewLine}" + - $"{_indentation}line breaks{NewLine}{NewLine}"; + var expected = + $"{_indentation}test{NewLine}" + + $"{_indentation}{NewLine}" + + $"{_indentation}description with{NewLine}" + + $"{_indentation}line breaks{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Synopsis_section_properly_wraps_description() - { - var longSynopsisText = - "test\t" + - "description with some tabs that is long enough to wrap to a\t" + - "new line"; + [Fact] + public void Synopsis_section_properly_wraps_description() + { + var longSynopsisText = + "test\t" + + "description with some tabs that is long enough to wrap to a\t" + + "new line"; - var command = new RootCommand(description: longSynopsisText); + var command = new RootCommand(description: longSynopsisText); - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - var expected = - $"{_indentation}test\tdescription with some tabs that is long enough to wrap to a\t{NewLine}" + - $"{_indentation}new line{NewLine}{NewLine}"; + var expected = + $"{_indentation}test\tdescription with some tabs that is long enough to wrap to a\t{NewLine}" + + $"{_indentation}new line{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Command_name_in_synopsis_can_be_specified() - { - var command = new Command("custom-name"); + [Fact] + public void Command_name_in_synopsis_can_be_specified() + { + var command = new Command("custom-name"); - var helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + var helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - var expected = $"custom-name{NewLine}"; + var expected = $"custom-name{NewLine}"; - _console.ToString().Should().Contain(expected); - _console.ToString().Should().NotContain(_executableName); - } + _console.ToString().Should().Contain(expected); + _console.ToString().Should().NotContain(_executableName); + } - #endregion Synopsis + #endregion Synopsis - #region Usage + #region Usage - [Theory] - [InlineData(1, 1, "")] - [InlineData(1, 2, "...")] - [InlineData(0, 2, "[...]")] - public void Usage_section_shows_arguments_if_there_are_arguments_for_command_when_there_is_one_argument( - int minArity, - int maxArity, - string expectedArgsUsage) + [Theory] + [InlineData(1, 1, "")] + [InlineData(1, 2, "...")] + [InlineData(0, 2, "[...]")] + public void Usage_section_shows_arguments_if_there_are_arguments_for_command_when_there_is_one_argument( + int minArity, + int maxArity, + string expectedArgsUsage) + { + var argument = new Argument("the-args") { - var argument = new Argument("the-args") - { - Arity = new ArgumentArity(minArity, maxArity) - }; - var command = new Command("the-command", "command help") - { - argument, - new Option("--verbosity", "-v") - { - Description = "Sets the verbosity" - } - }; - var rootCommand = new RootCommand(); - rootCommand.Subcommands.Add(command); - - new HelpBuilder(LargeMaxWidth).Write(command, _console); - - var expected = - $"Usage:{NewLine}" + - $"{_indentation}{_executableName} the-command {expectedArgsUsage} [options]"; - - _console.ToString().Should().Contain(expected); - } - - [Theory] - [InlineData(1, 1, 1, 1, " ")] - [InlineData(0, 1, 0, 1, "[ []]")] - [InlineData(0, 1, 0, 2, "[ [...]]")] - public void Usage_section_shows_arguments_if_there_are_arguments_for_command_when_there_is_more_than_one_argument( - int minArityForArg1, - int maxArityForArg1, - int minArityForArg2, - int maxArityForArg2, - string expectedArgsUsage) - { - var arg1 = new Argument("arg1") - { - Arity = new ArgumentArity( - minArityForArg1, - maxArityForArg1) - }; - var arg2 = new Argument("arg2") - { - Arity = new ArgumentArity( - minArityForArg2, - maxArityForArg2) - }; - var command = new Command("the-command", "command help") + Arity = new ArgumentArity(minArity, maxArity) + }; + var command = new Command("the-command", "command help") + { + argument, + new Option("--verbosity", "-v") { - arg1, - arg2, - new Option("--verbosity", "-v") { Description = "Sets the verbosity" } - }; + Description = "Sets the verbosity" + } + }; + var rootCommand = new RootCommand(); + rootCommand.Subcommands.Add(command); - var rootCommand = new RootCommand(); - rootCommand.Subcommands.Add(command); + new HelpBuilder(LargeMaxWidth).Write(command, _console); - _helpBuilder.Write(command, _console); + var expected = + $"Usage:{NewLine}" + + $"{_indentation}{_executableName} the-command {expectedArgsUsage} [options]"; - var expected = - $"Usage:{NewLine}" + - $"{_indentation}{_executableName} the-command {expectedArgsUsage} [options]"; - - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Usage_section_for_subcommand_shows_names_of_parent_commands() + [Theory] + [InlineData(1, 1, 1, 1, " ")] + [InlineData(0, 1, 0, 1, "[ []]")] + [InlineData(0, 1, 0, 2, "[ [...]]")] + public void Usage_section_shows_arguments_if_there_are_arguments_for_command_when_there_is_more_than_one_argument( + int minArityForArg1, + int maxArityForArg1, + int minArityForArg2, + int maxArityForArg2, + string expectedArgsUsage) + { + var arg1 = new Argument("arg1") + { + Arity = new ArgumentArity( + minArityForArg1, + maxArityForArg1) + }; + var arg2 = new Argument("arg2") + { + Arity = new ArgumentArity( + minArityForArg2, + maxArityForArg2) + }; + var command = new Command("the-command", "command help") { - var outer = new Command("outer", "the outer command"); - var inner = new Command("inner", "the inner command"); - outer.Subcommands.Add(inner); - var innerEr = new Command("inner-er", "the inner-er command"); - inner.Subcommands.Add(innerEr); - innerEr.Options.Add(new Option("--some-option") { Description = "some option" }); - var rootCommand = new RootCommand(); - rootCommand.Add(outer); + arg1, + arg2, + new Option("--verbosity", "-v") { Description = "Sets the verbosity" } + }; - _helpBuilder.Write(innerEr, _console); + var rootCommand = new RootCommand(); + rootCommand.Subcommands.Add(command); - var expected = - $"Usage:{NewLine}" + - $"{_indentation}{_executableName} outer inner inner-er [options]"; + _helpBuilder.Write(command, _console); - _console.ToString().Should().Contain(expected); - } + var expected = + $"Usage:{NewLine}" + + $"{_indentation}{_executableName} the-command {expectedArgsUsage} [options]"; - [Fact] - public void Usage_section_for_subcommand_shows_arguments_for_subcommand_and_parent_command() + _console.ToString().Should().Contain(expected); + } + + [Fact] + public void Usage_section_for_subcommand_shows_names_of_parent_commands() + { + var outer = new Command("outer", "the outer command"); + var inner = new Command("inner", "the inner command"); + outer.Subcommands.Add(inner); + var innerEr = new Command("inner-er", "the inner-er command"); + inner.Subcommands.Add(innerEr); + innerEr.Options.Add(new Option("--some-option") { Description = "some option" }); + var rootCommand = new RootCommand(); + rootCommand.Add(outer); + + _helpBuilder.Write(innerEr, _console); + + var expected = + $"Usage:{NewLine}" + + $"{_indentation}{_executableName} outer inner inner-er [options]"; + + _console.ToString().Should().Contain(expected); + } + + [Fact] + public void Usage_section_for_subcommand_shows_arguments_for_subcommand_and_parent_command() + { + var inner = new Command("inner", "command help") { - var inner = new Command("inner", "command help") - { - new Option("-v") {Description = "Sets the verbosity" }, - new Argument("inner-args") - }; - _ = new Command("outer", "command help") - { - inner, - new Argument("outer-args") - }; + new Option("-v") {Description = "Sets the verbosity" }, + new Argument("inner-args") + }; + _ = new Command("outer", "command help") + { + inner, + new Argument("outer-args") + }; - _helpBuilder.Write(inner, _console); + _helpBuilder.Write(inner, _console); - var expected = - $"Usage:{NewLine}" + - $"{_indentation}outer [...] inner [...] [options]"; + var expected = + $"Usage:{NewLine}" + + $"{_indentation}outer [...] inner [...] [options]"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Usage_section_does_not_show_additional_arguments_when_TreatUnmatchedTokensAsErrors_is_not_specified() - { - var command = new Command( - "some-command", - "Does something"); - command.Options.Add( - new Option("-x") { Description = "Indicates whether x" }); + [Fact] + public void Usage_section_does_not_show_additional_arguments_when_TreatUnmatchedTokensAsErrors_is_not_specified() + { + var command = new Command( + "some-command", + "Does something"); + command.Options.Add( + new Option("-x") { Description = "Indicates whether x" }); - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - _console.ToString().Should().NotContain("additional arguments"); - } + _console.ToString().Should().NotContain("additional arguments"); + } - [Fact] - public void Usage_section_does_not_show_additional_arguments_when_TreatUnmatchedTokensAsErrors_is_true() - { - var command = new RootCommand(); - var subcommand = new Command("some-command", "Does something"); - command.Subcommands.Add(subcommand); - subcommand.Options.Add(new Option("-x") { Description = "Indicates whether x" }); - subcommand.TreatUnmatchedTokensAsErrors = true; + [Fact] + public void Usage_section_does_not_show_additional_arguments_when_TreatUnmatchedTokensAsErrors_is_true() + { + var command = new RootCommand(); + var subcommand = new Command("some-command", "Does something"); + command.Subcommands.Add(subcommand); + subcommand.Options.Add(new Option("-x") { Description = "Indicates whether x" }); + subcommand.TreatUnmatchedTokensAsErrors = true; - _helpBuilder.Write(subcommand, _console); + _helpBuilder.Write(subcommand, _console); - _console.ToString().Should().NotContain(""); - } + _console.ToString().Should().NotContain(""); + } - [Fact] - public void Usage_section_shows_additional_arguments_when_TreatUnmatchedTokensAsErrors_is_false() - { - var command = new RootCommand(); - var subcommand = new Command("some-command", "Does something"); - command.Subcommands.Add(subcommand); - subcommand.Options.Add(new Option("-x") { Description = "Indicates whether x" }); - subcommand.TreatUnmatchedTokensAsErrors = false; + [Fact] + public void Usage_section_shows_additional_arguments_when_TreatUnmatchedTokensAsErrors_is_false() + { + var command = new RootCommand(); + var subcommand = new Command("some-command", "Does something"); + command.Subcommands.Add(subcommand); + subcommand.Options.Add(new Option("-x") { Description = "Indicates whether x" }); + subcommand.TreatUnmatchedTokensAsErrors = false; - _helpBuilder.Write(subcommand, _console); + _helpBuilder.Write(subcommand, _console); - _console.ToString().Should().Contain(""); - } + _console.ToString().Should().Contain(""); + } - [Fact] - public void Usage_section_keeps_added_newlines() + [Fact] + public void Usage_section_keeps_added_newlines() + { + var outer = new Command("outer-command", "command help") { - var outer = new Command("outer-command", "command help") + new Argument($"outer args {NewLine}\r\nwith new\nlines"), + new Command("inner-command", "command help") { - new Argument($"outer args {NewLine}\r\nwith new\nlines"), - new Command("inner-command", "command help") - { - new Argument("inner-args") - } - }; + new Argument("inner-args") + } + }; - _helpBuilder.Write(outer, _console); + _helpBuilder.Write(outer, _console); - var expected = - $"Usage:{NewLine}" + - $"{_indentation}outer-command [...] [command]{NewLine}{NewLine}"; + var expected = + $"Usage:{NewLine}" + + $"{_indentation}outer-command [...] [command]{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Usage_section_properly_wraps_description() - { - var helpBuilder = GetHelpBuilder(SmallMaxWidth); + [Fact] + public void Usage_section_properly_wraps_description() + { + var helpBuilder = GetHelpBuilder(SmallMaxWidth); - var outerCommand = new Command("outer-command", "command help") - { - new Argument("outer args long enough to wrap to a new line"), - new Command("inner-command", "command help") - { - new Argument("inner-args") - } - }; - //NB: Using Command with a fixed name, rather than RootCommand here - //because RootCommand.ExecutableName returns different values when - //run under net5 vs net462 - _ = new Command("System.CommandLine") + var outerCommand = new Command("outer-command", "command help") + { + new Argument("outer args long enough to wrap to a new line"), + new Command("inner-command", "command help") { - outerCommand - }; + new Argument("inner-args") + } + }; + //NB: Using Command with a fixed name, rather than RootCommand here + //because RootCommand.ExecutableName returns different values when + //run under net5 vs net462 + _ = new Command("System.CommandLine") + { + outerCommand + }; - helpBuilder.Write(outerCommand, _console); + helpBuilder.Write(outerCommand, _console); - var expected = - $"Usage:{NewLine}" + - $"{_indentation}System.CommandLine outer-command [...] [command]{NewLine}{NewLine}"; + var expected = + $"Usage:{NewLine}" + + $"{_indentation}System.CommandLine outer-command [...] [command]{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Usage_section_does_not_contain_hidden_argument() + [Fact] + public void Usage_section_does_not_contain_hidden_argument() + { + var commandName = "the-command"; + var visibleArgName = "visible"; + var command = new Command(commandName, "Does things"); + var hiddenArg = new Argument("hidden") { - var commandName = "the-command"; - var visibleArgName = "visible"; - var command = new Command(commandName, "Does things"); - var hiddenArg = new Argument("hidden") - { - Hidden = true - }; - var visibleArg = new Argument(visibleArgName) - { - Hidden = false - }; - command.Arguments.Add(hiddenArg); - command.Arguments.Add(visibleArg); + Hidden = true + }; + var visibleArg = new Argument(visibleArgName) + { + Hidden = false + }; + command.Arguments.Add(hiddenArg); + command.Arguments.Add(visibleArg); - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var expected = - $"Usage:{NewLine}" + - $"{_indentation}{commandName} <{visibleArgName}>{NewLine}{NewLine}"; + var expected = + $"Usage:{NewLine}" + + $"{_indentation}{commandName} <{visibleArgName}>{NewLine}{NewLine}"; - string help = _console.ToString(); - help.Should().Contain(expected); - help.Should().NotContain("hidden"); - } + string help = _console.ToString(); + help.Should().Contain(expected); + help.Should().NotContain("hidden"); + } - #endregion Usage + #endregion Usage - #region Arguments + #region Arguments - [Fact] - public void Arguments_section_is_not_included_if_there_are_no_commands_configured() - { - _helpBuilder.Write(new RootCommand(), _console); + [Fact] + public void Arguments_section_is_not_included_if_there_are_no_commands_configured() + { + _helpBuilder.Write(new RootCommand(), _console); - _console.ToString().Should().NotContain("Arguments:"); - } + _console.ToString().Should().NotContain("Arguments:"); + } - [Fact] - public void Arguments_section_is_not_included_if_there_are_commands_but_no_arguments_configured() - { - var command = new Command("the-command", "command help"); + [Fact] + public void Arguments_section_is_not_included_if_there_are_commands_but_no_arguments_configured() + { + var command = new Command("the-command", "command help"); - _helpBuilder.Write(command, _console); - _console.ToString().Should().NotContain("Arguments:"); + _helpBuilder.Write(command, _console); + _console.ToString().Should().NotContain("Arguments:"); - _helpBuilder.Write(command, _console); - _console.ToString().Should().NotContain("Arguments:"); - } + _helpBuilder.Write(command, _console); + _console.ToString().Should().NotContain("Arguments:"); + } - [Fact] - public void Arguments_section_is_included_if_there_are_commands_with_arguments_configured() + [Fact] + public void Arguments_section_is_included_if_there_are_commands_with_arguments_configured() + { + var command = new Command("the-command", "command help") { - var command = new Command("the-command", "command help") + new Argument("arg command name") { - new Argument("arg command name") - { - Description = "test" - } - }; + Description = "test" + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - _console.ToString().Should().Contain("Arguments:"); - } + _console.ToString().Should().Contain("Arguments:"); + } - [Fact] - public void Arguments_section_is_not_included_if_there_are_options_with_no_arguments_configured() + [Fact] + public void Arguments_section_is_not_included_if_there_are_options_with_no_arguments_configured() + { + var command = new RootCommand { - var command = new RootCommand - { - new Option("--verbosity", "-v") { Description = "Sets the verbosity." } - }; + new Option("--verbosity", "-v") { Description = "Sets the verbosity." } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - _console.ToString().Should().NotContain("Arguments:"); - } + _console.ToString().Should().NotContain("Arguments:"); + } - [Fact] - public void Arguments_section_is_not_included_if_there_are_only_options_with_arguments_configured() + [Fact] + public void Arguments_section_is_not_included_if_there_are_only_options_with_arguments_configured() + { + var command = new Command("command") { - var command = new Command("command") + new Option("-v") { - new Option("-v") - { - Description = "Sets the verbosity.", - HelpName = "argument for options" - } - }; + Description = "Sets the verbosity.", + HelpName = "argument for options" + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - _console.ToString().Should().NotContain("Arguments:"); - } + _console.ToString().Should().NotContain("Arguments:"); + } - [Fact] - public void Arguments_section_includes_configured_argument_aliases() + [Fact] + public void Arguments_section_includes_configured_argument_aliases() + { + var command = new Command("the-command", "command help") { - var command = new Command("the-command", "command help") + new Option("--verbosity", "-v") { - new Option("--verbosity", "-v") - { - HelpName = "LEVEL", - Description = "Sets the verbosity." - } - }; + HelpName = "LEVEL", + Description = "Sets the verbosity." + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); - help.Should().Contain("-v, --verbosity "); - help.Should().Contain("Sets the verbosity."); - } + var help = _console.ToString(); + help.Should().Contain("-v, --verbosity "); + help.Should().Contain("Sets the verbosity."); + } - private enum VerbosityOptions - { - q, - m, - n, - d, - } + private enum VerbosityOptions + { + q, + m, + n, + d, + } - [Fact] - public void Arguments_section_uses_name_over_suggestions_if_specified() + [Fact] + public void Arguments_section_uses_name_over_suggestions_if_specified() + { + var command = new Command("the-command") { - var command = new Command("the-command") + new Option("--verbosity", "-v") { - new Option("--verbosity", "-v") - { - HelpName = "LEVEL" - } - }; + HelpName = "LEVEL" + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); - help.Should().Contain("-v, --verbosity "); - } + var help = _console.ToString(); + help.Should().Contain("-v, --verbosity "); + } - [Fact] - public void Arguments_section_uses_description_if_provided() + [Fact] + public void Arguments_section_uses_description_if_provided() + { + var command = new Command("the-command", "Help text from description") { - var command = new Command("the-command", "Help text from description") + new Argument("the-arg") { - new Argument("the-arg") - { - Description = "Help text from HelpDetail" - } - }; + Description = "Help text from HelpDetail" + } + }; - var expected = - $"Arguments:{NewLine}" + - $"{_indentation}{_columnPadding}Help text from HelpDetail"; + var expected = + $"Arguments:{NewLine}" + + $"{_indentation}{_columnPadding}Help text from HelpDetail"; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - _console.ToString().Should().Contain(expected); - } - - [Fact] - public void Arguments_section_does_not_contain_hidden_argument() - { - var command = new Command("the-command"); - var hiddenArgName = "the-hidden"; - var hiddenDesc = "the hidden desc"; - var visibleArgName = "the-visible"; - var visibleDesc = "the visible desc"; - var hiddenArg = new Argument(hiddenArgName) - { - Description = hiddenDesc, - Hidden = true - }; - var visibleArg = new Argument(visibleArgName) - { - Description = visibleDesc, - Hidden = false - }; - command.Arguments.Add(hiddenArg); - command.Arguments.Add(visibleArg); - - var expected = - $"Arguments:{NewLine}" + - $"{_indentation}<{visibleArgName}>{_columnPadding}{visibleDesc}{NewLine}{NewLine}"; - - _helpBuilder.Write(command, _console); - var help = _console.ToString(); + _console.ToString().Should().Contain(expected); + } - help.Should().Contain(expected); - help.Should().NotContain(hiddenArgName); - help.Should().NotContain(hiddenDesc); - } + [Fact] + public void Arguments_section_does_not_contain_hidden_argument() + { + var command = new Command("the-command"); + var hiddenArgName = "the-hidden"; + var hiddenDesc = "the hidden desc"; + var visibleArgName = "the-visible"; + var visibleDesc = "the visible desc"; + var hiddenArg = new Argument(hiddenArgName) + { + Description = hiddenDesc, + Hidden = true + }; + var visibleArg = new Argument(visibleArgName) + { + Description = visibleDesc, + Hidden = false + }; + command.Arguments.Add(hiddenArg); + command.Arguments.Add(visibleArg); + + var expected = + $"Arguments:{NewLine}" + + $"{_indentation}<{visibleArgName}>{_columnPadding}{visibleDesc}{NewLine}{NewLine}"; + + _helpBuilder.Write(command, _console); + var help = _console.ToString(); + + help.Should().Contain(expected); + help.Should().NotContain(hiddenArgName); + help.Should().NotContain(hiddenDesc); + } - [Fact] - public void Arguments_section_does_not_repeat_arguments_that_appear_on_parent_command() + [Fact] + public void Arguments_section_does_not_repeat_arguments_that_appear_on_parent_command() + { + var reused = new Argument("reused") { - var reused = new Argument("reused") - { - Description = "This argument is valid on both outer and inner commands" - }; - var inner = new Command("inner", "The inner command") - { - reused - }; - _ = new Command("outer") - { - reused, - inner - }; + Description = "This argument is valid on both outer and inner commands" + }; + var inner = new Command("inner", "The inner command") + { + reused + }; + _ = new Command("outer") + { + reused, + inner + }; - _helpBuilder.Write(inner, _console); + _helpBuilder.Write(inner, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should().Contain($"Arguments:{NewLine}" + - $" {_columnPadding}This argument is valid on both outer and inner commands{NewLine}{NewLine}"); - } + help.Should().Contain($"Arguments:{NewLine}" + + $" {_columnPadding}This argument is valid on both outer and inner commands{NewLine}{NewLine}"); + } - [Fact] - public void Arguments_section_aligns_arguments_on_new_lines() + [Fact] + public void Arguments_section_aligns_arguments_on_new_lines() + { + var inner = new Command("inner", "HelpDetail text for the inner command") { - var inner = new Command("inner", "HelpDetail text for the inner command") + new Argument("the-inner-command-arg") { - new Argument("the-inner-command-arg") - { - Description = "The argument for the inner command", - } - }; - _ = new Command("outer", "HelpDetail text for the outer command") + Description = "The argument for the inner command", + } + }; + _ = new Command("outer", "HelpDetail text for the outer command") + { + new Argument("outer-command-arg") { - new Argument("outer-command-arg") - { - Description = "The argument for the outer command" - }, - inner - }; + Description = "The argument for the outer command" + }, + inner + }; - var expected = - $"Arguments:{NewLine}" + - $"{_indentation} {_columnPadding}The argument for the outer command{NewLine}" + - $"{_indentation}{_columnPadding}The argument for the inner command"; + var expected = + $"Arguments:{NewLine}" + + $"{_indentation} {_columnPadding}The argument for the outer command{NewLine}" + + $"{_indentation}{_columnPadding}The argument for the inner command"; - _helpBuilder.Write(inner, _console); + _helpBuilder.Write(inner, _console); - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Arguments_section_keeps_added_newlines() + [Fact] + public void Arguments_section_keeps_added_newlines() + { + var command = new Command("outer", "Help text for the outer command") { - var command = new Command("outer", "Help text for the outer command") + new Argument("outer-command-arg") { - new Argument("outer-command-arg") - { - Description = $"The argument\r\nfor the\ninner command" - } - }; + Description = $"The argument\r\nfor the\ninner command" + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var expected = - $"Arguments:{NewLine}" + - $"{_indentation}{_columnPadding}The argument{NewLine}" + - $"{_indentation} {_columnPadding}for the{NewLine}" + - $"{_indentation} {_columnPadding}inner command{NewLine}{NewLine}"; + var expected = + $"Arguments:{NewLine}" + + $"{_indentation}{_columnPadding}The argument{NewLine}" + + $"{_indentation} {_columnPadding}for the{NewLine}" + + $"{_indentation} {_columnPadding}inner command{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Arguments_section_keeps_added_newlines_when_width_is_very_small() + [Fact] + public void Arguments_section_keeps_added_newlines_when_width_is_very_small() + { + var command = new Command("outer", "Help text for the outer command") { - var command = new Command("outer", "Help text for the outer command") + new Argument("outer-command-arg") { - new Argument("outer-command-arg") - { - Description = $"The argument\r\nfor the\ninner command", - } - }; + Description = $"The argument\r\nfor the\ninner command", + } + }; + + var helpBuilder = GetHelpBuilder(25); - var helpBuilder = GetHelpBuilder(25); + helpBuilder.Write(command, _console); - helpBuilder.Write(command, _console); + var expected = + $"Arguments:{NewLine}" + + $"{_indentation} {_columnPadding}argument{NewLine}" + + $"{_indentation} {_columnPadding}for the{NewLine}" + + $"{_indentation} {_columnPadding}inner {NewLine}" + + $"{_indentation} {_columnPadding}command{NewLine}{NewLine}"; - var expected = - $"Arguments:{NewLine}" + - $"{_indentation} {_columnPadding}argument{NewLine}" + - $"{_indentation} {_columnPadding}for the{NewLine}" + - $"{_indentation} {_columnPadding}inner {NewLine}" + - $"{_indentation} {_columnPadding}command{NewLine}{NewLine}"; + _console.ToString().Should().Contain(expected); + } - _console.ToString().Should().Contain(expected); - } + [Fact] + public void Arguments_section_properly_wraps_description() + { + var longCmdText = + $"Argument\t" + + $"for inner command with some tabs that is long enough to wrap to a\t" + + $"new line"; - [Fact] - public void Arguments_section_properly_wraps_description() + var command = new Command("outer", "Help text for the outer command") { - var longCmdText = - $"Argument\t" + - $"for inner command with some tabs that is long enough to wrap to a\t" + - $"new line"; - - var command = new Command("outer", "Help text for the outer command") + new Argument("outer-command-arg") { - new Argument("outer-command-arg") - { - Description = longCmdText - } - }; + Description = longCmdText + } + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var expected = - $"Arguments:{NewLine}" + - $"{_indentation}{_columnPadding}Argument\tfor inner command with some tabs that {NewLine}" + - $"{_indentation} {_columnPadding}is long enough to wrap to a\tnew line{NewLine}{NewLine}"; + var expected = + $"Arguments:{NewLine}" + + $"{_indentation}{_columnPadding}Argument\tfor inner command with some tabs that {NewLine}" + + $"{_indentation} {_columnPadding}is long enough to wrap to a\tnew line{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Arguments_section_properly_wraps() - { - var name = "argument-name-for-a-command-that-is-long-enough-to-wrap-to-a-new-line"; - var description = "Argument description for a command with line breaks that is long enough to wrap to a new line."; + [Fact] + public void Arguments_section_properly_wraps() + { + var name = "argument-name-for-a-command-that-is-long-enough-to-wrap-to-a-new-line"; + var description = "Argument description for a command with line breaks that is long enough to wrap to a new line."; - var command = new RootCommand + var command = new RootCommand + { + new Argument(name) { - new Argument(name) - { - Description = description - } - }; + Description = description + } + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - var expected = - $"Arguments:{NewLine}" + - $"{_indentation} {_columnPadding}long enough to wrap to a new {NewLine}" + - $"{_indentation} {_columnPadding}line.{NewLine}{NewLine}"; + var expected = + $"Arguments:{NewLine}" + + $"{_indentation} {_columnPadding}long enough to wrap to a new {NewLine}" + + $"{_indentation} {_columnPadding}line.{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Command_argument_usage_indicates_enums_values(bool nullable) - { - var description = "This is the argument description"; + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Command_argument_usage_indicates_enums_values(bool nullable) + { + var description = "This is the argument description"; - Argument argument = nullable - ? new Argument("arg") - : new Argument("arg"); - argument.Description = description; + Argument argument = nullable + ? new Argument("arg") + : new Argument("arg"); + argument.Description = description; - var command = new Command("outer", "Help text for the outer command") - { - argument - }; + var command = new Command("outer", "Help text for the outer command") + { + argument + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var expected = - $"Arguments:{NewLine}" + - $"{_indentation}{_columnPadding}{description}"; + var expected = + $"Arguments:{NewLine}" + + $"{_indentation}{_columnPadding}{description}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Option_argument_usage_is_empty_for_boolean_values(bool nullable) - { - var description = "This is the option description"; + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Option_argument_usage_is_empty_for_boolean_values(bool nullable) + { + var description = "This is the option description"; - Option option = nullable - ? new Option("--opt") { Description = description } - : new Option("--opt") { Description = description }; + Option option = nullable + ? new Option("--opt") { Description = description } + : new Option("--opt") { Description = description }; - var command = new Command( - "outer", "Help text for the outer command") - { - option - }; + var command = new Command( + "outer", "Help text for the outer command") + { + option + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - _console.ToString().Should().Contain($"--opt{_columnPadding}{description}"); - } + _console.ToString().Should().Contain($"--opt{_columnPadding}{description}"); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1157 - public void Command_arguments_show_argument_name_in_first_column() + [Fact] // https://github.com/dotnet/command-line-api/issues/1157 + public void Command_arguments_show_argument_name_in_first_column() + { + var command = new RootCommand { - var command = new RootCommand - { - new Argument("boolArgument") { Description = "Some value" }, - new Argument("intArgument") { Description = "Another value" }, - }; + new Argument("boolArgument") { Description = "Some value" }, + new Argument("intArgument") { Description = "Another value" }, + }; - var helpBuilder = GetHelpBuilder(SmallMaxWidth); + var helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var expected = - $"Arguments:{NewLine}" + - $"{_indentation}{_columnPadding}Some value{NewLine}" + - $"{_indentation} {_columnPadding}Another value{NewLine}"; + var expected = + $"Arguments:{NewLine}" + + $"{_indentation}{_columnPadding}Some value{NewLine}" + + $"{_indentation} {_columnPadding}Another value{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Option_argument_first_column_indicates_enums_values(bool nullable) - { - var description = "This is the argument description"; + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Option_argument_first_column_indicates_enums_values(bool nullable) + { + var description = "This is the argument description"; - Option option = nullable - ? new Option("--opt") { Description = description } - : new Option("--opt") { Description = description }; + Option option = nullable + ? new Option("--opt") { Description = description } + : new Option("--opt") { Description = description }; - var command = new Command( - "outer", "Help text for the outer command") - { - option - }; + var command = new Command( + "outer", "Help text for the outer command") + { + option + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - _console.ToString().Should().Contain($"--opt {_columnPadding}{description}"); - } + _console.ToString().Should().Contain($"--opt {_columnPadding}{description}"); + } - [Fact] - public void Help_describes_default_value_for_argument() + [Fact] + public void Help_describes_default_value_for_argument() + { + var argument = new Argument("the-arg") { - var argument = new Argument("the-arg") - { - Description = "Help text from HelpDetail", - DefaultValueFactory = (_) => "the-arg-value" - }; + Description = "Help text from HelpDetail", + DefaultValueFactory = (_) => "the-arg-value" + }; - var command = new Command("the-command", - "Help text from description") { argument }; + var command = new Command("the-command", + "Help text from description") { argument }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should().Contain("[default: the-arg-value]"); - } + help.Should().Contain("[default: the-arg-value]"); + } - [Theory] - [InlineData("")] - [InlineData(null)] - public void Help_does_not_show_default_value_for_argument_when_default_value_is_null_or_empty(string defaultValue) + [Theory] + [InlineData("")] + [InlineData(null)] + public void Help_does_not_show_default_value_for_argument_when_default_value_is_null_or_empty(string defaultValue) + { + var argument = new Argument("the-arg") { - var argument = new Argument("the-arg") - { - Description = "The argument description", - DefaultValueFactory = _ => defaultValue - }; + Description = "The argument description", + DefaultValueFactory = _ => defaultValue + }; - var command = new Command("the-command", "The command description") - { - argument - }; + var command = new Command("the-command", "The command description") + { + argument + }; - var helpBuilder = GetHelpBuilder(SmallMaxWidth); + var helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should().NotContain("[]"); - } + help.Should().NotContain("[]"); + } - [Theory] - [InlineData("")] - [InlineData(null)] - public void Help_does_not_show_default_value_for_option_when_default_value_is_null_or_empty(string defaultValue) + [Theory] + [InlineData("")] + [InlineData(null)] + public void Help_does_not_show_default_value_for_option_when_default_value_is_null_or_empty(string defaultValue) + { + var argument = new Option("--opt") { - var argument = new Option("--opt") - { - Description = "The option description", - DefaultValueFactory = _ => defaultValue - }; + Description = "The option description", + DefaultValueFactory = _ => defaultValue + }; - var command = new Command("the-command", "The command description") - { - argument - }; + var command = new Command("the-command", "The command description") + { + argument + }; + + var helpBuilder = GetHelpBuilder(SmallMaxWidth); - var helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - helpBuilder.Write(command, _console); + var help = _console.ToString(); - var help = _console.ToString(); + help.Should().NotContain("[]"); + } - help.Should().NotContain("[]"); - } + [Fact] + public void Help_does_not_show_default_value_for_option_when_default_value_is_empty() + { + var option = new Option("-x") + { + Description = "The option description", + DefaultValueFactory = (_) => "", + }; - [Fact] - public void Help_does_not_show_default_value_for_option_when_default_value_is_empty() + var command = new Command("the-command", "The command description") { - var option = new Option("-x") - { - Description = "The option description", - DefaultValueFactory = (_) => "", - }; + option + }; - var command = new Command("the-command", "The command description") - { - option - }; + var helpBuilder = GetHelpBuilder(SmallMaxWidth); - var helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - helpBuilder.Write(command, _console); + var help = _console.ToString(); - var help = _console.ToString(); + help.Should().NotContain("[default"); + } - help.Should().NotContain("[default"); - } + [Flags] + public enum Letters + { + A = 1, + B = 2 + } - [Flags] - public enum Letters + [Fact] + public void Help_does_not_show_arguments_for_enum_backed_option_when_arity_is_zero() + { + var option = new Option("--all") { - A = 1, - B = 2 - } + Description = "Passes both A and B", + Arity = ArgumentArity.Zero, + CustomParser = _ => Letters.A | Letters.B + }; - [Fact] - public void Help_does_not_show_arguments_for_enum_backed_option_when_arity_is_zero() + var command = new Command("the-command", "The command description") { - var option = new Option("--all") - { - Description = "Passes both A and B", - Arity = ArgumentArity.Zero, - CustomParser = _ => Letters.A | Letters.B - }; + option + }; - var command = new Command("the-command", "The command description") - { - option - }; + var helpBuilder = GetHelpBuilder(SmallMaxWidth); - var helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - helpBuilder.Write(command, _console); + var help = _console.ToString(); - var help = _console.ToString(); - - help.Should().NotContain("--all "); - } + help.Should().NotContain("--all "); + } - [Fact] - public void Command_arguments_default_value_provided() + [Fact] + public void Command_arguments_default_value_provided() + { + var argument = new Argument("the-arg") { - var argument = new Argument("the-arg") - { - DefaultValueFactory = (_) => "the-arg-value", - }; - var otherArgument = new Argument("the-other-arg") - { - DefaultValueFactory = (_) => "the-other-arg-value" - }; - var command = new Command("the-command", - "Help text from description") - { - argument, - otherArgument - }; + DefaultValueFactory = (_) => "the-arg-value", + }; + var otherArgument = new Argument("the-other-arg") + { + DefaultValueFactory = (_) => "the-other-arg-value" + }; + var command = new Command("the-command", + "Help text from description") + { + argument, + otherArgument + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - var expected = - $"Arguments:{NewLine}" + - $"{_indentation} {_columnPadding}[default: the-arg-value]{NewLine}" + - $"{_indentation}{_columnPadding}[default: the-other-arg-value]{NewLine}"; + var expected = + $"Arguments:{NewLine}" + + $"{_indentation} {_columnPadding}[default: the-arg-value]{NewLine}" + + $"{_indentation}{_columnPadding}[default: the-other-arg-value]{NewLine}"; - help.Should().Contain(expected); - } + help.Should().Contain(expected); + } - [Fact] - public void Command_arguments_with_default_values_that_are_enumerable_display_pipe_delimited_list() + [Fact] + public void Command_arguments_with_default_values_that_are_enumerable_display_pipe_delimited_list() + { + var command = new Command("the-command", "command help") { - var command = new Command("the-command", "command help") + new Argument>("filter-size") { - new Argument>("filter-size") - { - DefaultValueFactory = (_) => new List() { 0, 2, 4 } - } - }; + DefaultValueFactory = (_) => new List() { 0, 2, 4 } + } + }; - _helpBuilder.Write(command, _console); - var expected = - $"Arguments:{NewLine}" + - $"{_indentation}{_columnPadding}[default: 0|2|4]{NewLine}{NewLine}"; + _helpBuilder.Write(command, _console); + var expected = + $"Arguments:{NewLine}" + + $"{_indentation}{_columnPadding}[default: 0|2|4]{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Command_shared_arguments_with_one_or_more_arity_are_displayed_as_being_required() + [Fact] + public void Command_shared_arguments_with_one_or_more_arity_are_displayed_as_being_required() + { + var arg = new Argument("shared-args") { - var arg = new Argument("shared-args") - { - Arity = ArgumentArity.OneOrMore - }; + Arity = ArgumentArity.OneOrMore + }; - var inner = new Command("inner", "command help") - { - arg - }; - _ = new Command("outer", "command help") - { - inner, - arg - }; - _ = new Command("unused", "command help") - { - arg - }; + var inner = new Command("inner", "command help") + { + arg + }; + _ = new Command("outer", "command help") + { + inner, + arg + }; + _ = new Command("unused", "command help") + { + arg + }; - _helpBuilder.Write(inner, _console); + _helpBuilder.Write(inner, _console); - var expected = - $"Usage:{NewLine}" + - $"{_indentation}outer ... inner ..."; + var expected = + $"Usage:{NewLine}" + + $"{_indentation}outer ... inner ..."; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - #endregion Arguments + #endregion Arguments - #region Options + #region Options - [Fact] - public void Options_section_is_not_included_if_no_options_configured() + [Fact] + public void Options_section_is_not_included_if_no_options_configured() + { + var commandLineBuilder = new Command("noOptions") { - var commandLineBuilder = new Command("noOptions") - { - new Command("outer", "description for outer") - }; + new Command("outer", "description for outer") + }; - _helpBuilder.Write(commandLineBuilder, _console); + _helpBuilder.Write(commandLineBuilder, _console); - _console.ToString().Should().NotContain("Options:"); - } + _console.ToString().Should().NotContain("Options:"); + } - [Fact] - public void Options_section_is_not_included_if_only_subcommands_configured() - { - var command = new Command("outer", "description for outer"); - command.Subcommands.Add(new Command("inner")); + [Fact] + public void Options_section_is_not_included_if_only_subcommands_configured() + { + var command = new Command("outer", "description for outer"); + command.Subcommands.Add(new Command("inner")); - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - _console.ToString().Should().NotContain("Options:"); - } + _console.ToString().Should().NotContain("Options:"); + } - [Fact] - public void Options_section_includes_option_with_empty_description() + [Fact] + public void Options_section_includes_option_with_empty_description() + { + var command = new Command("the-command", "Does things.") { - var command = new Command("the-command", "Does things.") - { - new Option("-x"), - new Option("-n"), - new HelpOption() - }; + new Option("-x"), + new Option("-n"), + new HelpOption() + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); - help.Should().Contain("-x"); - help.Should().Contain("-n"); - } + var help = _console.ToString(); + help.Should().Contain("-x"); + help.Should().Contain("-n"); + } - [Fact] - public void Options_section_does_not_contain_option_with_HelpDefinition_that_IsHidden() + [Fact] + public void Options_section_does_not_contain_option_with_HelpDefinition_that_IsHidden() + { + var command = new Command("the-command"); + command.Options.Add(new Option("-x") { - var command = new Command("the-command"); - command.Options.Add(new Option("-x") - { - Description = "Is Hidden", - Hidden = true - }); - command.Options.Add(new Option("-n") - { - Description = "Not Hidden", - Hidden = false - }); + Description = "Is Hidden", + Hidden = true + }); + command.Options.Add(new Option("-n") + { + Description = "Not Hidden", + Hidden = false + }); - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); - help.Should().Contain("-n"); - help.Should().Contain("Not Hidden"); - help.Should().NotContain("-x"); - help.Should().NotContain("Is hidden"); - } + var help = _console.ToString(); + help.Should().Contain("-n"); + help.Should().Contain("Not Hidden"); + help.Should().NotContain("-x"); + help.Should().NotContain("Is hidden"); + } - [Fact] - public void Options_section_aligns_options_on_new_lines() + [Fact] + public void Options_section_aligns_options_on_new_lines() + { + var command = new Command("the-command", "Help text for the command") { - var command = new Command("the-command", "Help text for the command") + new Option("--aaa", "-a") { - new Option("--aaa", "-a") - { - Description = "An option with 8 characters", - }, - new Option("--bbbbbbbbbb","-b") - { - Description = "An option with 15 characters" - } - }; + Description = "An option with 8 characters", + }, + new Option("--bbbbbbbbbb","-b") + { + Description = "An option with 15 characters" + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); - var lines = help.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + var help = _console.ToString(); + var lines = help.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - var optionA = lines.Last(line => line.Contains("-a")); - var optionB = lines.Last(line => line.Contains("-b")); + var optionA = lines.Last(line => line.Contains("-a")); + var optionB = lines.Last(line => line.Contains("-b")); - optionA.IndexOf("An option", StringComparison.Ordinal) - .Should() - .Be(optionB.IndexOf("An option", StringComparison.Ordinal)); - } + optionA.IndexOf("An option", StringComparison.Ordinal) + .Should() + .Be(optionB.IndexOf("An option", StringComparison.Ordinal)); + } - [Fact] - public void Retains_single_dash_on_multi_char_option() + [Fact] + public void Retains_single_dash_on_multi_char_option() + { + var command = new Command("command", "Help Test") { - var command = new Command("command", "Help Test") - { - new Option("-multi", "--alt-option") { Description = "HelpDetail for option" } - }; + new Option("-multi", "--alt-option") { Description = "HelpDetail for option" } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); - help.Should().Contain("-multi"); - help.Should().NotContain("--multi"); - } + var help = _console.ToString(); + help.Should().Contain("-multi"); + help.Should().NotContain("--multi"); + } - [Fact] - public void Options_section_retains_multiple_dashes_on_single_char_option() + [Fact] + public void Options_section_retains_multiple_dashes_on_single_char_option() + { + var command = new Command("command", "Help Test") { - var command = new Command("command", "Help Test") - { - new Option("--m", "--alt-option") { Description = "HelpDetail for option" } - }; + new Option("--m", "--alt-option") { Description = "HelpDetail for option" } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - _console.ToString().Should().Contain("--m"); - } + _console.ToString().Should().Contain("--m"); + } - [Fact] - public void Options_section_keeps_added_newlines() - { - var command = - new Command( - "test-command", - "Help text for the command") + [Fact] + public void Options_section_keeps_added_newlines() + { + var command = + new Command( + "test-command", + "Help text for the command") + { + new Option("--aaa", "-a") { - new Option("--aaa", "-a") - { - Description = $"Help{NewLine}for \r\n the\noption" - } - }; + Description = $"Help{NewLine}for \r\n the\noption" + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var expected = - $"Options:{NewLine}" + - $"{_indentation}-a, --aaa{_columnPadding}Help{NewLine}" + - $"{_indentation} {_columnPadding}for {NewLine}" + - $"{_indentation} {_columnPadding} the{NewLine}" + - $"{_indentation} {_columnPadding}option{NewLine}{NewLine}"; + var expected = + $"Options:{NewLine}" + + $"{_indentation}-a, --aaa{_columnPadding}Help{NewLine}" + + $"{_indentation} {_columnPadding}for {NewLine}" + + $"{_indentation} {_columnPadding} the{NewLine}" + + $"{_indentation} {_columnPadding}option{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } + + [Fact] + public void Options_section_properly_wraps_description() + { + var longOptionText = + "The option whose description is long enough that it wraps to a new line"; - [Fact] - public void Options_section_properly_wraps_description() + var command = new Command("test-command", "Help text for the command") { - var longOptionText = - "The option whose description is long enough that it wraps to a new line"; + new Option("-x") { Description = "Option with a short description" }, + new Option("--aaa", "-a") { Description = longOptionText }, + new Option("-y") { Description = "Option with a short description" }, + }; - var command = new Command("test-command", "Help text for the command") - { - new Option("-x") { Description = "Option with a short description" }, - new Option("--aaa", "-a") { Description = longOptionText }, - new Option("-y") { Description = "Option with a short description" }, - }; + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + var expected = + $"{_indentation}-a, --aaa{_columnPadding}The option whose description is long enough that it {NewLine}" + + $"{_indentation} {_columnPadding}wraps to a new line{NewLine}"; - var expected = - $"{_indentation}-a, --aaa{_columnPadding}The option whose description is long enough that it {NewLine}" + - $"{_indentation} {_columnPadding}wraps to a new line{NewLine}"; + Console.WriteLine(_console.ToString()); - Console.WriteLine(_console.ToString()); + _console.ToString().Should().Contain(expected); + } - _console.ToString().Should().Contain(expected); - } + [Fact] + public void Options_section_properly_wraps_description_when_long_default_value_is_specified() + { + var longOptionText = + "The option whose description is long enough that it wraps to a new line"; - [Fact] - public void Options_section_properly_wraps_description_when_long_default_value_is_specified() + var command = new Command("test-command", "Help text for the command") { - var longOptionText = - "The option whose description is long enough that it wraps to a new line"; - - var command = new Command("test-command", "Help text for the command") + new Option("-x") { Description = "Option with a short description" }, + new Option("--aaa", "-a") { - new Option("-x") { Description = "Option with a short description" }, - new Option("--aaa", "-a") - { - Description = longOptionText, - DefaultValueFactory = (_) => "the quick brown fox jumps over the lazy dog" - }, - new Option("-y") { Description = "Option with a short description" }, - }; + Description = longOptionText, + DefaultValueFactory = (_) => "the quick brown fox jumps over the lazy dog" + }, + new Option("-y") { Description = "Option with a short description" }, + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - var expected = - $"{_indentation}-a, --aaa{_columnPadding}The option whose description is long enough that it {NewLine}" + - $"{_indentation} {_columnPadding}wraps to a new line [default: the quick brown fox jumps {NewLine}" + - $"{_indentation} {_columnPadding}over the lazy dog]{NewLine}"; + var expected = + $"{_indentation}-a, --aaa{_columnPadding}The option whose description is long enough that it {NewLine}" + + $"{_indentation} {_columnPadding}wraps to a new line [default: the quick brown fox jumps {NewLine}" + + $"{_indentation} {_columnPadding}over the lazy dog]{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Options_section_properly_wraps() - { - var alias = "--option-alias-for-a-command-that-is-long-enough-to-wrap-to-a-new-line"; - var description = "Option description that is long enough to wrap."; + [Fact] + public void Options_section_properly_wraps() + { + var alias = "--option-alias-for-a-command-that-is-long-enough-to-wrap-to-a-new-line"; + var description = "Option description that is long enough to wrap."; - var command = new Command("test") - { - new Option(alias) { Description = description } - }; + var command = new Command("test") + { + new Option(alias) { Description = description } + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - var expected = - $"Options:{NewLine}" + - $"{_indentation}--option-alias-for-a-command-that{_columnPadding}Option description that is long {NewLine}" + - $"{_indentation}-is-long-enough-to-wrap-to-a-new-{_columnPadding}enough to wrap.{NewLine}" + - $"{_indentation}line{NewLine}{NewLine}"; + var expected = + $"Options:{NewLine}" + + $"{_indentation}--option-alias-for-a-command-that{_columnPadding}Option description that is long {NewLine}" + + $"{_indentation}-is-long-enough-to-wrap-to-a-new-{_columnPadding}enough to wrap.{NewLine}" + + $"{_indentation}line{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Required_options_are_indicated() + [Fact] + public void Required_options_are_indicated() + { + var command = new RootCommand { - var command = new RootCommand + new Option("--required") { - new Option("--required") - { - Required = true - } - }; + Required = true + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should() - .Contain("--required (REQUIRED)"); - } + help.Should() + .Contain("--required (REQUIRED)"); + } - [Fact] - public void Required_options_are_indicated_when_argument_is_named() + [Fact] + public void Required_options_are_indicated_when_argument_is_named() + { + var command = new RootCommand { - var command = new RootCommand + new Option("--required", "-r") { - new Option("--required", "-r") - { - Required = true, - HelpName = "ARG" - } - }; + Required = true, + HelpName = "ARG" + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should() - .Contain("-r, --required (REQUIRED)"); - } + help.Should() + .Contain("-r, --required (REQUIRED)"); + } - [Fact] - public void Help_option_is_shown_in_help() - { - var configuration = new CommandLineConfiguration(new RootCommand()); + [Fact] + public void Help_option_is_shown_in_help() + { + var configuration = new CommandLineConfiguration(new RootCommand()); - _helpBuilder.Write(configuration.RootCommand, _console); + _helpBuilder.Write(configuration.RootCommand, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should() - .Contain($"-?, -h, --help{_columnPadding}Show help and usage information"); - } + help.Should() + .Contain($"-?, -h, --help{_columnPadding}Show help and usage information"); + } - // TODO: use HiddenAliases here - [Fact] - public void Options_aliases_differing_only_by_prefix_are_deduplicated_favoring_dashed_prefixes() + // TODO: use HiddenAliases here + [Fact] + public void Options_aliases_differing_only_by_prefix_are_deduplicated_favoring_dashed_prefixes() + { + var command = new RootCommand { - var command = new RootCommand - { - new Option("-x", "/x") - }; + new Option("-x", "/x") + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should().NotContain("/x"); - } + help.Should().NotContain("/x"); + } - [Fact] - public void Options_aliases_differing_only_by_prefix_are_deduplicated_favoring_double_dashed_prefixes() + [Fact] + public void Options_aliases_differing_only_by_prefix_are_deduplicated_favoring_double_dashed_prefixes() + { + var command = new RootCommand { - var command = new RootCommand - { - new Option("--long", "/long") - }; + new Option("--long", "/long") + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should().NotContain("/long"); - } - - [Fact] - public void Options_help_preserves_the_order_options_are_added_the_the_parent_command() - { - var command = new RootCommand - { - new Option("--first", "-f"), - new Option("--second", "-s"), - new Option("--third"), - new Option("--last", "-l") - }; - - _helpBuilder.Write(command, _console); - var help = _console - .ToString() - .Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) - .Select(l => l.Trim()); + help.Should().NotContain("/long"); + } - help.Should().ContainInOrder( - "-f, --first", - "-s, --second", - "--third", - "-l, --last"); - } + [Fact] + public void Options_help_preserves_the_order_options_are_added_the_the_parent_command() + { + var command = new RootCommand + { + new Option("--first", "-f"), + new Option("--second", "-s"), + new Option("--third"), + new Option("--last", "-l") + }; + + _helpBuilder.Write(command, _console); + var help = _console + .ToString() + .Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) + .Select(l => l.Trim()); + + help.Should().ContainInOrder( + "-f, --first", + "-s, --second", + "--third", + "-l, --last"); + } - [Fact] - public void Option_aliases_are_shown_before_long_names_regardless_of_alphabetical_order() + [Fact] + public void Option_aliases_are_shown_before_long_names_regardless_of_alphabetical_order() + { + var command = new RootCommand { - var command = new RootCommand - { - new Option("-z", "-a", "--zzz", "--aaa") - }; + new Option("-z", "-a", "--zzz", "--aaa") + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - _console.ToString().Should().Contain("-a, -z, --aaa, --zzz"); - } + _console.ToString().Should().Contain("-a, -z, --aaa, --zzz"); + } - [Fact] - public void Help_describes_default_value_for_option_with_argument_having_default_value() + [Fact] + public void Help_describes_default_value_for_option_with_argument_having_default_value() + { + var command = new Command("the-command", "command help") { - var command = new Command("the-command", "command help") + new Option("-arg") { - new Option("-arg") - { - DefaultValueFactory = (_) => "the-arg-value", - HelpName = "the-arg" - } - }; + DefaultValueFactory = (_) => "the-arg-value", + HelpName = "the-arg" + } + }; - HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); + HelpBuilder helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should().Contain($"[default: the-arg-value]"); - } + help.Should().Contain($"[default: the-arg-value]"); + } - [Fact] - public void Option_arguments_with_default_values_that_are_enumerable_display_pipe_delimited_list() + [Fact] + public void Option_arguments_with_default_values_that_are_enumerable_display_pipe_delimited_list() + { + var command = new Command("the-command", "command help") { - var command = new Command("the-command", "command help") + new Option>("--filter-size") { - new Option>("--filter-size") - { - DefaultValueFactory = (_) => new List { 0, 2, 4 } - } - }; + DefaultValueFactory = (_) => new List { 0, 2, 4 } + } + }; - _helpBuilder.Write(command, _console); - var expected = - $"Options:{NewLine}" + - $"{_indentation}--filter-size{_columnPadding}[default: 0|2|4]{NewLine}{NewLine}"; + _helpBuilder.Write(command, _console); + var expected = + $"Options:{NewLine}" + + $"{_indentation}--filter-size{_columnPadding}[default: 0|2|4]{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Option_arguments_with_default_values_that_are_array_display_pipe_delimited_list() + [Fact] + public void Option_arguments_with_default_values_that_are_array_display_pipe_delimited_list() + { + var command = new Command("the-command", "command help") { - var command = new Command("the-command", "command help") + new Option("--prefixes") { - new Option("--prefixes") - { - DefaultValueFactory = (_) => new[]{ "^(TODO|BUG)", "^HACK" } - } - }; + DefaultValueFactory = (_) => new[]{ "^(TODO|BUG)", "^HACK" } + } + }; - _helpBuilder.Write(command, _console); - var expected = - $"Options:{NewLine}" + - $"{_indentation}--prefixes{_columnPadding}[default: ^(TODO|BUG)|^HACK]{NewLine}{NewLine}"; + _helpBuilder.Write(command, _console); + var expected = + $"Options:{NewLine}" + + $"{_indentation}--prefixes{_columnPadding}[default: ^(TODO|BUG)|^HACK]{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - #endregion Options + #endregion Options - #region Subcommands + #region Subcommands - [Fact] - public void Subcommand_help_does_not_include_names_of_sibling_commands() + [Fact] + public void Subcommand_help_does_not_include_names_of_sibling_commands() + { + var inner = new Command("inner", "inner description") { - var inner = new Command("inner", "inner description") + new Command("inner-er", "inner-er description") { - new Command("inner-er", "inner-er description") - { - new Option("some-option") { Description = "some-option description" } - } - }; + new Option("some-option") { Description = "some-option description" } + } + }; - var sibling = new Command("sibling", "sibling description"); + var sibling = new Command("sibling", "sibling description"); - var outer = new Command("outer", "outer description") - { - sibling, - inner - }; + var outer = new Command("outer", "outer description") + { + sibling, + inner + }; - _helpBuilder.Write(inner, _console); + _helpBuilder.Write(inner, _console); - _console.ToString().Should().NotContain("sibling"); - } + _console.ToString().Should().NotContain("sibling"); + } - [Fact] - public void Subcommands_keep_added_newlines() + [Fact] + public void Subcommands_keep_added_newlines() + { + var command = new Command("outer", "outer command help") { - var command = new Command("outer", "outer command help") + new Argument("outer-args"), + new Command("inner", $"inner{NewLine}command help \r\n with \nnewlines") { - new Argument("outer-args"), - new Command("inner", $"inner{NewLine}command help \r\n with \nnewlines") - { - new Argument("inner-args") - } - }; + new Argument("inner-args") + } + }; - _helpBuilder.Write(command, _console); + _helpBuilder.Write(command, _console); - var expected = - $"Commands:{NewLine}" + - $"{_indentation}inner {_columnPadding}inner{NewLine}" + - $"{_indentation} {_columnPadding}command help {NewLine}" + - $"{_indentation} {_columnPadding} with {NewLine}" + - $"{_indentation} {_columnPadding}newlines{NewLine}{NewLine}"; + var expected = + $"Commands:{NewLine}" + + $"{_indentation}inner {_columnPadding}inner{NewLine}" + + $"{_indentation} {_columnPadding}command help {NewLine}" + + $"{_indentation} {_columnPadding} with {NewLine}" + + $"{_indentation} {_columnPadding}newlines{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Subcommands_properly_wraps_description() - { - var longSubcommandDescription = - $"The\t" + - $"subcommand with some tabs that is long enough to wrap to a\t" + - $"new line"; + [Fact] + public void Subcommands_properly_wraps_description() + { + var longSubcommandDescription = + $"The\t" + + $"subcommand with some tabs that is long enough to wrap to a\t" + + $"new line"; - var helpBuilder = GetHelpBuilder(SmallMaxWidth); + var helpBuilder = GetHelpBuilder(SmallMaxWidth); - var command = new Command("outer-command", "outer command help") + var command = new Command("outer-command", "outer command help") + { + new Argument("outer-args"), + new Command("inner-command", longSubcommandDescription) { - new Argument("outer-args"), - new Command("inner-command", longSubcommandDescription) - { - new Argument("inner-args"), - new Option("--verbosity", "-v") - } - }; + new Argument("inner-args"), + new Option("--verbosity", "-v") + } + }; - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var expected = - $"Commands:{NewLine}" + - $"{_indentation}inner-command {_columnPadding}The\tsubcommand with some tabs that is {NewLine}" + - $"{_indentation} {_columnPadding}long enough to wrap to a\tnew line{NewLine}"; + var expected = + $"Commands:{NewLine}" + + $"{_indentation}inner-command {_columnPadding}The\tsubcommand with some tabs that is {NewLine}" + + $"{_indentation} {_columnPadding}long enough to wrap to a\tnew line{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Subcommands_section_properly_wraps() - { - var name = "subcommand-name-that-is-long-enough-to-wrap-to-a-new-line"; - var description = "Subcommand description that is really long. So long that it caused the line to wrap."; + [Fact] + public void Subcommands_section_properly_wraps() + { + var name = "subcommand-name-that-is-long-enough-to-wrap-to-a-new-line"; + var description = "Subcommand description that is really long. So long that it caused the line to wrap."; - var command = new RootCommand - { - new Command(name, description) - }; + var command = new RootCommand + { + new Command(name, description) + }; - var helpBuilder = GetHelpBuilder(SmallMaxWidth); - helpBuilder.Write(command, _console); + var helpBuilder = GetHelpBuilder(SmallMaxWidth); + helpBuilder.Write(command, _console); - var expected = - $"Commands:{NewLine}" + - $"{_indentation}subcommand-name-that-is-long-enou{_columnPadding}Subcommand description that is {NewLine}" + - $"{_indentation}gh-to-wrap-to-a-new-line {_columnPadding}really long. So long that it {NewLine}" + - $"{_indentation} {_columnPadding}caused the line to wrap.{NewLine}{NewLine}"; + var expected = + $"Commands:{NewLine}" + + $"{_indentation}subcommand-name-that-is-long-enou{_columnPadding}Subcommand description that is {NewLine}" + + $"{_indentation}gh-to-wrap-to-a-new-line {_columnPadding}really long. So long that it {NewLine}" + + $"{_indentation} {_columnPadding}caused the line to wrap.{NewLine}{NewLine}"; - _console.ToString().Should().Contain(expected); - } + _console.ToString().Should().Contain(expected); + } - [Fact] - public void Subcommand_help_contains_command_with_empty_description() - { - var command = new Command("the-command", "Does things."); - var subCommand = new Command("the-subcommand", description: null); - command.Subcommands.Add(subCommand); + [Fact] + public void Subcommand_help_contains_command_with_empty_description() + { + var command = new Command("the-command", "Does things."); + var subCommand = new Command("the-subcommand", description: null); + command.Subcommands.Add(subCommand); - _helpBuilder.Write(command, _console); - var help = _console.ToString(); + _helpBuilder.Write(command, _console); + var help = _console.ToString(); - help.Should().Contain("the-subcommand"); - } + help.Should().Contain("the-subcommand"); + } - [Fact] - public void Subcommand_help_does_not_contain_hidden_command() + [Fact] + public void Subcommand_help_does_not_contain_hidden_command() + { + var command = new Command("the-command", "Does things."); + var hiddenSubCommand = new Command("the-hidden") { - var command = new Command("the-command", "Does things."); - var hiddenSubCommand = new Command("the-hidden") - { - Hidden = true - }; - var visibleSubCommand = new Command("the-visible") - { - Hidden = false - }; - command.Subcommands.Add(hiddenSubCommand); - command.Subcommands.Add(visibleSubCommand); + Hidden = true + }; + var visibleSubCommand = new Command("the-visible") + { + Hidden = false + }; + command.Subcommands.Add(hiddenSubCommand); + command.Subcommands.Add(visibleSubCommand); - _helpBuilder.Write(command, _console); - var help = _console.ToString(); + _helpBuilder.Write(command, _console); + var help = _console.ToString(); - help.Should().NotContain("the-hidden"); - help.Should().Contain("the-visible"); - } + help.Should().NotContain("the-hidden"); + help.Should().Contain("the-visible"); + } - [Fact] - public void Subcommand_help_does_not_contain_hidden_argument() + [Fact] + public void Subcommand_help_does_not_contain_hidden_argument() + { + var command = new Command("the-command", "Does things."); + var subCommand = new Command("the-subcommand"); + var hidden = new Argument("the-hidden") { - var command = new Command("the-command", "Does things."); - var subCommand = new Command("the-subcommand"); - var hidden = new Argument("the-hidden") - { - Hidden = true - }; - var visible = new Argument("the-visible") - { - Hidden = false - }; - subCommand.Arguments.Add(hidden); - subCommand.Arguments.Add(visible); - command.Subcommands.Add(subCommand); + Hidden = true + }; + var visible = new Argument("the-visible") + { + Hidden = false + }; + subCommand.Arguments.Add(hidden); + subCommand.Arguments.Add(visible); + command.Subcommands.Add(subCommand); - _helpBuilder.Write(command, _console); - var help = _console.ToString(); + _helpBuilder.Write(command, _console); + var help = _console.ToString(); - help.Should().NotContain("the-hidden"); - help.Should().Contain("the-visible"); - } + help.Should().NotContain("the-hidden"); + help.Should().Contain("the-visible"); + } - #endregion Subcommands + #endregion Subcommands - [Fact] - public void Help_describes_default_value_for_subcommand_with_arguments_and_only_defaultable_is_shown() + [Fact] + public void Help_describes_default_value_for_subcommand_with_arguments_and_only_defaultable_is_shown() + { + var argument = new Argument("the-arg"); + var otherArgumentHidden = new Argument("the-other-hidden-arg") + { + Hidden = true + }; + argument.DefaultValueFactory = _ => "the-arg-value"; + otherArgumentHidden.DefaultValueFactory = _ => "the-other-hidden-arg-value"; + + var command = new Command("outer", "outer command help") { - var argument = new Argument("the-arg"); - var otherArgumentHidden = new Argument("the-other-hidden-arg") + new Argument("outer-args"), + new Command("inner", $"inner command help") { - Hidden = true - }; - argument.DefaultValueFactory = _ => "the-arg-value"; - otherArgumentHidden.DefaultValueFactory = _ => "the-other-hidden-arg-value"; + argument, + otherArgumentHidden, + new Argument("inner-other-arg-no-default") + } + }; - var command = new Command("outer", "outer command help") - { - new Argument("outer-args"), - new Command("inner", $"inner command help") - { - argument, - otherArgumentHidden, - new Argument("inner-other-arg-no-default") - } - }; + HelpBuilder helpBuilder = GetHelpBuilder(LargeMaxWidth); - HelpBuilder helpBuilder = GetHelpBuilder(LargeMaxWidth); + helpBuilder.Write(command, _console); - helpBuilder.Write(command, _console); + var help = _console.ToString(); - var help = _console.ToString(); + help.Should().Contain("[default: the-arg-value]"); + } - help.Should().Contain("[default: the-arg-value]"); - } + [Fact] + public void Help_describes_default_values_for_subcommand_with_multiple_defaultable_arguments() + { + var argument = new Argument("the-arg") + { + DefaultValueFactory = (_) => "the-arg-value" + }; + var otherArgument = new Argument("the-other-arg") + { + DefaultValueFactory = (_) => "the-other-arg-value" + }; - [Fact] - public void Help_describes_default_values_for_subcommand_with_multiple_defaultable_arguments() + var command = new Command("outer", "outer command help") { - var argument = new Argument("the-arg") - { - DefaultValueFactory = (_) => "the-arg-value" - }; - var otherArgument = new Argument("the-other-arg") + new Argument("outer-args"), + new Command("inner", "inner command help") { - DefaultValueFactory = (_) => "the-other-arg-value" - }; + argument, otherArgument + } + }; - var command = new Command("outer", "outer command help") - { - new Argument("outer-args"), - new Command("inner", "inner command help") - { - argument, otherArgument - } - }; - - HelpBuilder helpBuilder = GetHelpBuilder(LargeMaxWidth); + HelpBuilder helpBuilder = GetHelpBuilder(LargeMaxWidth); - helpBuilder.Write(command, _console); + helpBuilder.Write(command, _console); - var help = _console.ToString(); + var help = _console.ToString(); - help.Should().Contain("[the-arg: the-arg-value, the-other-arg: the-other-arg-value]"); - } + help.Should().Contain("[the-arg: the-arg-value, the-other-arg: the-other-arg-value]"); + } - [Theory] - [InlineData(0)] - [InlineData(-1)] - [InlineData(int.MinValue)] - public void Constructor_ignores_non_positive_max_width(int maxWidth) - { - var helpBuilder = new HelpBuilder(maxWidth); - Assert.Equal(int.MaxValue, helpBuilder.MaxWidth); - } + [Theory] + [InlineData(0)] + [InlineData(-1)] + [InlineData(int.MinValue)] + public void Constructor_ignores_non_positive_max_width(int maxWidth) + { + var helpBuilder = new HelpBuilder(maxWidth); + Assert.Equal(int.MaxValue, helpBuilder.MaxWidth); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1506 - public void Commands_without_arguments_do_not_produce_extra_newlines_between_usage_and_options_sections() + [Fact] // https://github.com/dotnet/command-line-api/issues/1506 + public void Commands_without_arguments_do_not_produce_extra_newlines_between_usage_and_options_sections() + { + var command = new RootCommand { - var command = new RootCommand - { - new Option("-x") { Description = "the-option-description" } - }; + new Option("-x") { Description = "the-option-description" } + }; - var helpBuilder = GetHelpBuilder(); + var helpBuilder = GetHelpBuilder(); - using var writer = new StringWriter(); - helpBuilder.Write(command, writer); + using var writer = new StringWriter(); + helpBuilder.Write(command, writer); - var output = writer.ToString(); + var output = writer.ToString(); - output.Should().Contain($"{LocalizationResources.HelpUsageOptions()}{NewLine}{NewLine}{LocalizationResources.HelpOptionsTitle()}"); - } + output.Should().Contain($"{LocalizationResources.HelpUsageOptions()}{NewLine}{NewLine}{LocalizationResources.HelpOptionsTitle()}"); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs b/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs index 656bba1424..48aed22e74 100644 --- a/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs +++ b/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs @@ -11,108 +11,107 @@ using Xunit; using Process = System.Diagnostics.Process; -namespace System.CommandLine.Tests.Invocation +namespace System.CommandLine.Tests.Invocation; + +public class CancelOnProcessTerminationTests { - public class CancelOnProcessTerminationTests - { - private const string ChildProcessWaiting = "Waiting for the command to be cancelled"; - private const int SIGINT_EXIT_CODE = 130; - private const int SIGTERM_EXIT_CODE = 143; - private const int GracefulExitCode = 42; + private const string ChildProcessWaiting = "Waiting for the command to be cancelled"; + private const int SIGINT_EXIT_CODE = 130; + private const int SIGTERM_EXIT_CODE = 143; + private const int GracefulExitCode = 42; - private static readonly Option InfiniteDelayOption = new("--infiniteDelay"); + private static readonly Option InfiniteDelayOption = new("--infiniteDelay"); + + public enum Signals + { + SIGINT = 2, // Console.CancelKeyPress + SIGTERM = 15 // AppDomain.CurrentDomain.ProcessExit + } - public enum Signals + [Fact] + public async Task CancellableHandler_is_cancelled_on_process_termination() + { + // The feature is supported on Windows, but it's simply harder to send SIGINT to test it properly. + // Same for macOS, where RemoteExecutor does not support getting application arguments. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - SIGINT = 2, // Console.CancelKeyPress - SIGTERM = 15 // AppDomain.CurrentDomain.ProcessExit + await StartKillAndVerify(new[] { "--infiniteDelay", "false" }, Signals.SIGINT, GracefulExitCode); } + } - [Fact] - public async Task CancellableHandler_is_cancelled_on_process_termination() + [Fact] + public async Task NonCancellableHandler_is_interrupted_on_process_termination() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - // The feature is supported on Windows, but it's simply harder to send SIGINT to test it properly. - // Same for macOS, where RemoteExecutor does not support getting application arguments. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - await StartKillAndVerify(new[] { "--infiniteDelay", "false" }, Signals.SIGINT, GracefulExitCode); - } + await StartKillAndVerify(new[] { "--infiniteDelay", "true" }, Signals.SIGTERM, SIGTERM_EXIT_CODE); } + } - [Fact] - public async Task NonCancellableHandler_is_interrupted_on_process_termination() + private static Task Program(string[] args) + { + RootCommand command = new () { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - await StartKillAndVerify(new[] { "--infiniteDelay", "true" }, Signals.SIGTERM, SIGTERM_EXIT_CODE); - } - } + InfiniteDelayOption + }; + command.Action = new CustomCliAction(); - private static Task Program(string[] args) + return new CommandLineConfiguration(command) { - RootCommand command = new () - { - InfiniteDelayOption - }; - command.Action = new CustomCliAction(); + ProcessTerminationTimeout = TimeSpan.FromSeconds(2) + }.InvokeAsync(args); + } + + private sealed class CustomCliAction : AsynchronousCommandLineAction + { + public override async Task InvokeAsync(ParseResult context, CancellationToken cancellationToken = default) + { + Console.WriteLine(ChildProcessWaiting); - return new CommandLineConfiguration(command) + bool infiniteDelay = context.GetValue(InfiniteDelayOption); + + try { - ProcessTerminationTimeout = TimeSpan.FromSeconds(2) - }.InvokeAsync(args); - } + // Passing CancellationToken.None here is an example of bad pattern + // and reason why we need a timeout on termination processing. + CancellationToken token = infiniteDelay ? CancellationToken.None : cancellationToken; + await Task.Delay(Timeout.InfiniteTimeSpan, token); - private sealed class CustomCliAction : AsynchronousCommandLineAction - { - public override async Task InvokeAsync(ParseResult context, CancellationToken cancellationToken = default) + return 0; + } + catch (OperationCanceledException) { - Console.WriteLine(ChildProcessWaiting); - - bool infiniteDelay = context.GetValue(InfiniteDelayOption); - - try - { - // Passing CancellationToken.None here is an example of bad pattern - // and reason why we need a timeout on termination processing. - CancellationToken token = infiniteDelay ? CancellationToken.None : cancellationToken; - await Task.Delay(Timeout.InfiniteTimeSpan, token); - - return 0; - } - catch (OperationCanceledException) - { - return GracefulExitCode; - } + return GracefulExitCode; } } + } - private async Task StartKillAndVerify(string[] args, Signals signal, int expectedExitCode) - { - using RemoteExecution program = RemoteExecutor.Execute( - Program, - args, - new ProcessStartInfo { RedirectStandardOutput = true }); + private async Task StartKillAndVerify(string[] args, Signals signal, int expectedExitCode) + { + using RemoteExecution program = RemoteExecutor.Execute( + Program, + args, + new ProcessStartInfo { RedirectStandardOutput = true }); - Process process = program.Process; + Process process = program.Process; - // Wait for the child to be in the command handler. - string childState = await process.StandardOutput.ReadLineAsync(); - childState.Should().Be(ChildProcessWaiting); + // Wait for the child to be in the command handler. + string childState = await process.StandardOutput.ReadLineAsync(); + childState.Should().Be(ChildProcessWaiting); - // Request termination - kill(process.Id, (int)signal).Should().Be(0); + // Request termination + kill(process.Id, (int)signal).Should().Be(0); - // Verify the process terminates timely - if (!process.WaitForExit((int)TimeSpan.FromSeconds(10).TotalMilliseconds)) - { - process.Kill(); - } + // Verify the process terminates timely + if (!process.WaitForExit((int)TimeSpan.FromSeconds(10).TotalMilliseconds)) + { + process.Kill(); + } - // Verify the process exit code - process.ExitCode.Should().Be(expectedExitCode); + // Verify the process exit code + process.ExitCode.Should().Be(expectedExitCode); - [DllImport("libc", SetLastError = true)] - static extern int kill(int pid, int sig); - } + [DllImport("libc", SetLastError = true)] + static extern int kill(int pid, int sig); } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Invocation/InvocationTests.cs b/src/System.CommandLine.Tests/Invocation/InvocationTests.cs index b9cc930bef..e491559a87 100644 --- a/src/System.CommandLine.Tests/Invocation/InvocationTests.cs +++ b/src/System.CommandLine.Tests/Invocation/InvocationTests.cs @@ -11,363 +11,362 @@ using FluentAssertions.Execution; using Xunit; -namespace System.CommandLine.Tests.Invocation +namespace System.CommandLine.Tests.Invocation; + +public class InvocationTests { - public class InvocationTests + [Fact] + public async Task Command_InvokeAsync_enables_help_by_default() { - [Fact] - public async Task Command_InvokeAsync_enables_help_by_default() + var command = new Command("the-command") { - var command = new Command("the-command") - { - new HelpOption() - }; - var theHelpText = "the help text"; - command.Description = theHelpText; + new HelpOption() + }; + var theHelpText = "the help text"; + command.Description = theHelpText; - StringWriter output = new(); - CommandLineConfiguration config = new(command) - { - Output = output - }; + StringWriter output = new(); + CommandLineConfiguration config = new(command) + { + Output = output + }; - await command.Parse("-h", config).InvokeAsync(); + await command.Parse("-h", config).InvokeAsync(); - output.ToString() - .Should() - .Contain(theHelpText); - } + output.ToString() + .Should() + .Contain(theHelpText); + } - [Fact] - public void Command_Invoke_enables_help_by_default() + [Fact] + public void Command_Invoke_enables_help_by_default() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new HelpOption() - }; - var theHelpText = "the help text"; - command.Description = theHelpText; + new HelpOption() + }; + var theHelpText = "the help text"; + command.Description = theHelpText; - StringWriter output = new (); - CommandLineConfiguration config = new(command) - { - Output = output - }; + StringWriter output = new (); + CommandLineConfiguration config = new(command) + { + Output = output + }; - command.Parse("-h", config).Invoke(); + command.Parse("-h", config).Invoke(); - output.ToString() - .Should() - .Contain(theHelpText); - } + output.ToString() + .Should() + .Contain(theHelpText); + } - [Fact] - public async Task RootCommand_InvokeAsync_returns_0_when_handler_is_successful() - { - var wasCalled = false; - var rootCommand = new RootCommand(); + [Fact] + public async Task RootCommand_InvokeAsync_returns_0_when_handler_is_successful() + { + var wasCalled = false; + var rootCommand = new RootCommand(); - rootCommand.SetAction((_) => wasCalled = true); + rootCommand.SetAction((_) => wasCalled = true); - var result = await rootCommand.Parse("").InvokeAsync(); + var result = await rootCommand.Parse("").InvokeAsync(); - wasCalled.Should().BeTrue(); - result.Should().Be(0); - } + wasCalled.Should().BeTrue(); + result.Should().Be(0); + } - [Fact] - public void RootCommand_Invoke_returns_0_when_handler_is_successful() - { - var wasCalled = false; - var rootCommand = new RootCommand(); + [Fact] + public void RootCommand_Invoke_returns_0_when_handler_is_successful() + { + var wasCalled = false; + var rootCommand = new RootCommand(); - rootCommand.SetAction(_ => wasCalled = true); + rootCommand.SetAction(_ => wasCalled = true); - int result = rootCommand.Parse("").Invoke(); + int result = rootCommand.Parse("").Invoke(); - wasCalled.Should().BeTrue(); - result.Should().Be(0); - } + wasCalled.Should().BeTrue(); + result.Should().Be(0); + } - [Fact] - public async Task RootCommand_InvokeAsync_returns_1_when_handler_throws() + [Fact] + public async Task RootCommand_InvokeAsync_returns_1_when_handler_throws() + { + var wasCalled = false; + var rootCommand = new RootCommand(); + + rootCommand.SetAction((_, _) => { - var wasCalled = false; - var rootCommand = new RootCommand(); + wasCalled = true; - rootCommand.SetAction((_, _) => - { - wasCalled = true; + return Task.FromException(new Exception("oops!")); + }); - return Task.FromException(new Exception("oops!")); - }); + var resultCode = await rootCommand.Parse("").InvokeAsync(); - var resultCode = await rootCommand.Parse("").InvokeAsync(); + wasCalled.Should().BeTrue(); + resultCode.Should().Be(1); + } - wasCalled.Should().BeTrue(); - resultCode.Should().Be(1); - } + [Fact] + public void RootCommand_Invoke_returns_1_when_handler_throws() + { + var wasCalled = false; + var rootCommand = new RootCommand(); - [Fact] - public void RootCommand_Invoke_returns_1_when_handler_throws() + rootCommand.SetAction((_, _) => { - var wasCalled = false; - var rootCommand = new RootCommand(); + wasCalled = true; + throw new Exception("oops!"); - rootCommand.SetAction((_, _) => - { - wasCalled = true; - throw new Exception("oops!"); - - // Help the compiler pick a CommandHandler.Create overload. + // Help the compiler pick a CommandHandler.Create overload. #pragma warning disable CS0162 // Unreachable code detected - return Task.FromResult(0); + return Task.FromResult(0); #pragma warning restore CS0162 - }); + }); - var resultCode = rootCommand.Parse("").Invoke(); + var resultCode = rootCommand.Parse("").Invoke(); - wasCalled.Should().BeTrue(); - resultCode.Should().Be(1); - } + wasCalled.Should().BeTrue(); + resultCode.Should().Be(1); + } - [Fact] - public void Custom_RootCommand_Action_can_set_custom_result_code_via_Invoke() - { - var rootCommand = new RootCommand(); - rootCommand.SetAction(_ => 123); + [Fact] + public void Custom_RootCommand_Action_can_set_custom_result_code_via_Invoke() + { + var rootCommand = new RootCommand(); + rootCommand.SetAction(_ => 123); - rootCommand.Parse("").Invoke().Should().Be(123); - } + rootCommand.Parse("").Invoke().Should().Be(123); + } - [Fact] - public async Task Custom_RootCommand_Action_can_set_custom_result_code_via_InvokeAsync() - { - var rootCommand = new RootCommand(); - rootCommand.SetAction((_, _) => Task.FromResult(456)); + [Fact] + public async Task Custom_RootCommand_Action_can_set_custom_result_code_via_InvokeAsync() + { + var rootCommand = new RootCommand(); + rootCommand.SetAction((_, _) => Task.FromResult(456)); - (await rootCommand.Parse("").InvokeAsync()).Should().Be(456); - } + (await rootCommand.Parse("").InvokeAsync()).Should().Be(456); + } - [Fact] - public void Anonymous_RootCommand_Task_returning_Action_can_set_custom_result_code_via_Invoke() - { - var rootCommand = new RootCommand(); + [Fact] + public void Anonymous_RootCommand_Task_returning_Action_can_set_custom_result_code_via_Invoke() + { + var rootCommand = new RootCommand(); - rootCommand.SetAction((_, _) => Task.FromResult(123)); + rootCommand.SetAction((_, _) => Task.FromResult(123)); - rootCommand.Parse("").Invoke().Should().Be(123); - } + rootCommand.Parse("").Invoke().Should().Be(123); + } - [Fact] - public async Task Anonymous_RootCommand_Task_returning_Action_can_set_custom_result_code_via_InvokeAsync() - { - var rootCommand = new RootCommand(); + [Fact] + public async Task Anonymous_RootCommand_Task_returning_Action_can_set_custom_result_code_via_InvokeAsync() + { + var rootCommand = new RootCommand(); - rootCommand.SetAction((_, _) => Task.FromResult(123)); + rootCommand.SetAction((_, _) => Task.FromResult(123)); - (await rootCommand.Parse("").InvokeAsync()).Should().Be(123); - } + (await rootCommand.Parse("").InvokeAsync()).Should().Be(123); + } - [Fact] - public void Anonymous_RootCommand_int_returning_Action_can_set_custom_result_code_via_Invoke() - { - var rootCommand = new RootCommand(); + [Fact] + public void Anonymous_RootCommand_int_returning_Action_can_set_custom_result_code_via_Invoke() + { + var rootCommand = new RootCommand(); - rootCommand.SetAction(_ => 123); + rootCommand.SetAction(_ => 123); - rootCommand.Parse("").Invoke().Should().Be(123); - } + rootCommand.Parse("").Invoke().Should().Be(123); + } - [Fact] - public async Task Anonymous_RootCommand_int_returning_Action_can_set_custom_result_code_via_InvokeAsync() - { - var rootCommand = new RootCommand(); + [Fact] + public async Task Anonymous_RootCommand_int_returning_Action_can_set_custom_result_code_via_InvokeAsync() + { + var rootCommand = new RootCommand(); - rootCommand.SetAction(_ => 123); + rootCommand.SetAction(_ => 123); - (await rootCommand.Parse("").InvokeAsync()).Should().Be(123); - } + (await rootCommand.Parse("").InvokeAsync()).Should().Be(123); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/2562 - public void Anonymous_async_action_is_not_mapped_into_sync_void_with_fire_and_forget() + [Fact] // https://github.com/dotnet/command-line-api/issues/2562 + public void Anonymous_async_action_is_not_mapped_into_sync_void_with_fire_and_forget() + { + RootCommand rootCommand = new(); + using CancellationTokenSource cts = new(); + Task delay = Task.Delay(TimeSpan.FromHours(1), cts.Token); + + rootCommand.SetAction(async parseResult => { - RootCommand rootCommand = new(); - using CancellationTokenSource cts = new(); - Task delay = Task.Delay(TimeSpan.FromHours(1), cts.Token); + await delay; + }); - rootCommand.SetAction(async parseResult => - { - await delay; - }); + Task started = rootCommand.Parse("").InvokeAsync(); - Task started = rootCommand.Parse("").InvokeAsync(); + // The action is supposed to wait for an hour, so it should not complete immediately. + started.IsCompleted.Should().BeFalse(); - // The action is supposed to wait for an hour, so it should not complete immediately. - started.IsCompleted.Should().BeFalse(); + cts.Cancel(); + } - cts.Cancel(); - } + [Fact] + public void Terminating_option_action_short_circuits_command_action() + { + bool optionActionWasCalled = false; + SynchronousTestAction optionAction = new(_ => optionActionWasCalled = true, terminating: true); + bool commandActionWasCalled = false; - [Fact] - public void Terminating_option_action_short_circuits_command_action() + Option option = new("--test") + { + Action = optionAction + }; + Command command = new Command("cmd") { - bool optionActionWasCalled = false; - SynchronousTestAction optionAction = new(_ => optionActionWasCalled = true, terminating: true); - bool commandActionWasCalled = false; + option + }; + command.SetAction(_ => + { + commandActionWasCalled = true; + }); - Option option = new("--test") - { - Action = optionAction - }; - Command command = new Command("cmd") - { - option - }; - command.SetAction(_ => - { - commandActionWasCalled = true; - }); + ParseResult parseResult = command.Parse("cmd --test"); - ParseResult parseResult = command.Parse("cmd --test"); + parseResult.Action.Should().NotBeNull(); + optionActionWasCalled.Should().BeFalse(); + commandActionWasCalled.Should().BeFalse(); - parseResult.Action.Should().NotBeNull(); - optionActionWasCalled.Should().BeFalse(); - commandActionWasCalled.Should().BeFalse(); + parseResult.Invoke().Should().Be(0); + optionActionWasCalled.Should().BeTrue(); + commandActionWasCalled.Should().BeFalse(); + } - parseResult.Invoke().Should().Be(0); - optionActionWasCalled.Should().BeTrue(); - commandActionWasCalled.Should().BeFalse(); - } + [Fact] + public void Nonterminating_option_action_does_not_short_circuit_command_action() + { + bool optionActionWasCalled = false; + SynchronousTestAction optionAction = new(_ => optionActionWasCalled = true, terminating: false); + bool commandActionWasCalled = false; - [Fact] - public void Nonterminating_option_action_does_not_short_circuit_command_action() + Option option = new("--test") + { + Action = optionAction + }; + Command command = new Command("cmd") { - bool optionActionWasCalled = false; - SynchronousTestAction optionAction = new(_ => optionActionWasCalled = true, terminating: false); - bool commandActionWasCalled = false; + option + }; + command.SetAction(_ => commandActionWasCalled = true); - Option option = new("--test") - { - Action = optionAction - }; - Command command = new Command("cmd") - { - option - }; - command.SetAction(_ => commandActionWasCalled = true); + ParseResult parseResult = command.Parse("cmd --test"); - ParseResult parseResult = command.Parse("cmd --test"); + parseResult.Invoke().Should().Be(0); + optionActionWasCalled.Should().BeTrue(); + commandActionWasCalled.Should().BeTrue(); + } - parseResult.Invoke().Should().Be(0); - optionActionWasCalled.Should().BeTrue(); - commandActionWasCalled.Should().BeTrue(); - } + [Fact] + public void When_multiple_options_with_actions_are_present_then_only_the_last_one_is_invoked() + { + bool optionAction1WasCalled = false; + bool optionAction2WasCalled = false; + bool optionAction3WasCalled = false; + + SynchronousTestAction optionAction1 = new(_ => optionAction1WasCalled = true); + SynchronousTestAction optionAction2 = new(_ => optionAction2WasCalled = true); + SynchronousTestAction optionAction3 = new(_ => optionAction3WasCalled = true); - [Fact] - public void When_multiple_options_with_actions_are_present_then_only_the_last_one_is_invoked() + Command command = new Command("cmd") { - bool optionAction1WasCalled = false; - bool optionAction2WasCalled = false; - bool optionAction3WasCalled = false; + new Option("--1") { Action = optionAction1 }, + new Option("--2") { Action = optionAction2 }, + new Option("--3") { Action = optionAction3 } + }; - SynchronousTestAction optionAction1 = new(_ => optionAction1WasCalled = true); - SynchronousTestAction optionAction2 = new(_ => optionAction2WasCalled = true); - SynchronousTestAction optionAction3 = new(_ => optionAction3WasCalled = true); + ParseResult parseResult = command.Parse("cmd --1 true --3 false --2 true"); - Command command = new Command("cmd") - { - new Option("--1") { Action = optionAction1 }, - new Option("--2") { Action = optionAction2 }, - new Option("--3") { Action = optionAction3 } - }; + using var _ = new AssertionScope(); - ParseResult parseResult = command.Parse("cmd --1 true --3 false --2 true"); + parseResult.Action.Should().Be(optionAction2); + parseResult.Invoke().Should().Be(0); + optionAction1WasCalled.Should().BeFalse(); + optionAction2WasCalled.Should().BeTrue(); + optionAction3WasCalled.Should().BeFalse(); + } - using var _ = new AssertionScope(); + [Fact] + public void Directive_action_takes_precedence_over_option_action() + { + bool optionActionWasCalled = false; + bool directiveActionWasCalled = false; - parseResult.Action.Should().Be(optionAction2); - parseResult.Invoke().Should().Be(0); - optionAction1WasCalled.Should().BeFalse(); - optionAction2WasCalled.Should().BeTrue(); - optionAction3WasCalled.Should().BeFalse(); - } + SynchronousTestAction optionAction = new(_ => optionActionWasCalled = true); + SynchronousTestAction directiveAction = new(_ => directiveActionWasCalled = true); - [Fact] - public void Directive_action_takes_precedence_over_option_action() + var directive = new Directive("directive") { - bool optionActionWasCalled = false; - bool directiveActionWasCalled = false; - - SynchronousTestAction optionAction = new(_ => optionActionWasCalled = true); - SynchronousTestAction directiveAction = new(_ => directiveActionWasCalled = true); + Action = directiveAction + }; - var directive = new Directive("directive") - { - Action = directiveAction - }; + RootCommand command = new("cmd") + { + new Option("-x") { Action = optionAction }, + directive + }; - RootCommand command = new("cmd") - { - new Option("-x") { Action = optionAction }, - directive - }; + ParseResult parseResult = command.Parse("[directive] cmd -x", new CommandLineConfiguration(command)); - ParseResult parseResult = command.Parse("[directive] cmd -x", new CommandLineConfiguration(command)); + using var _ = new AssertionScope(); - using var _ = new AssertionScope(); + parseResult.Action.Should().Be(directiveAction); + parseResult.Invoke().Should().Be(0); + optionActionWasCalled.Should().BeFalse(); + directiveActionWasCalled.Should().BeTrue(); + } - parseResult.Action.Should().Be(directiveAction); - parseResult.Invoke().Should().Be(0); - optionActionWasCalled.Should().BeFalse(); - directiveActionWasCalled.Should().BeTrue(); - } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Nontermninating_option_actions_handle_exceptions_and_return_an_error_return_code(bool invokeAsync) + { + var nonexclusiveAction = new SynchronousTestAction(_ => throw new Exception("oops!"), terminating: false); - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task Nontermninating_option_actions_handle_exceptions_and_return_an_error_return_code(bool invokeAsync) + var command = new RootCommand { - var nonexclusiveAction = new SynchronousTestAction(_ => throw new Exception("oops!"), terminating: false); - - var command = new RootCommand + new Option("-x") { - new Option("-x") - { - Action = nonexclusiveAction - } - }; - command.SetAction(_ => 0); + Action = nonexclusiveAction + } + }; + command.SetAction(_ => 0); - int returnCode; + int returnCode; - var parseResult = CommandLineParser.Parse(command, "-x"); + var parseResult = CommandLineParser.Parse(command, "-x"); - if (invokeAsync) - { - returnCode = await parseResult.InvokeAsync(); - } - else - { - returnCode = parseResult.Invoke(); - } - - returnCode.Should().Be(1); + if (invokeAsync) + { + returnCode = await parseResult.InvokeAsync(); + } + else + { + returnCode = parseResult.Invoke(); } + + returnCode.Should().Be(1); + } - [Fact] - public async Task Command_InvokeAsync_with_cancelation_token_invokes_command_handler() + [Fact] + public async Task Command_InvokeAsync_with_cancelation_token_invokes_command_handler() + { + using CancellationTokenSource cts = new(); + var command = new Command("test"); + command.SetAction((_, cancellationToken) => { - using CancellationTokenSource cts = new(); - var command = new Command("test"); - command.SetAction((_, cancellationToken) => - { - cancellationToken.Should().Be(cts.Token); - return Task.CompletedTask; - }); + cancellationToken.Should().Be(cts.Token); + return Task.CompletedTask; + }); - cts.Cancel(); - await command.Parse("test").InvokeAsync(cancellationToken: cts.Token); - } + cts.Cancel(); + await command.Parse("test").InvokeAsync(cancellationToken: cts.Token); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Invocation/TypoCorrectionTests.cs b/src/System.CommandLine.Tests/Invocation/TypoCorrectionTests.cs index 9a9322dc35..1759b71d18 100644 --- a/src/System.CommandLine.Tests/Invocation/TypoCorrectionTests.cs +++ b/src/System.CommandLine.Tests/Invocation/TypoCorrectionTests.cs @@ -7,210 +7,209 @@ using Xunit; using static System.Environment; -namespace System.CommandLine.Tests.Invocation +namespace System.CommandLine.Tests.Invocation; + +public class TypoCorrectionTests { - public class TypoCorrectionTests + [Fact] + public async Task When_option_is_mistyped_it_is_suggested() { - [Fact] - public async Task When_option_is_mistyped_it_is_suggested() + RootCommand rootCommand = new () { - RootCommand rootCommand = new () - { - new Option("info") - }; + new Option("info") + }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter() + }; - var result = rootCommand.Parse("niof", config); + var result = rootCommand.Parse("niof", config); - await result.InvokeAsync(); + await result.InvokeAsync(); - config.Output.ToString().Should().Contain($"'niof' was not matched. Did you mean one of the following?{NewLine}info"); - } + config.Output.ToString().Should().Contain($"'niof' was not matched. Did you mean one of the following?{NewLine}info"); + } - [Fact] - public async Task Typo_corrections_can_be_disabled() + [Fact] + public async Task Typo_corrections_can_be_disabled() + { + RootCommand rootCommand = new() { - RootCommand rootCommand = new() - { - new Option("info") - }; + new Option("info") + }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter() + }; - var result = rootCommand.Parse("niof", config); + var result = rootCommand.Parse("niof", config); - if (result.Action is ParseErrorAction parseError) - { - parseError.ShowTypoCorrections = false; - } + if (result.Action is ParseErrorAction parseError) + { + parseError.ShowTypoCorrections = false; + } - await result.InvokeAsync(); + await result.InvokeAsync(); - config.Output.ToString().Should().NotContain("Did you mean"); - } + config.Output.ToString().Should().NotContain("Did you mean"); + } - [Fact] - public async Task When_there_are_no_matches_then_nothing_is_suggested() + [Fact] + public async Task When_there_are_no_matches_then_nothing_is_suggested() + { + var option = new Option("info"); + RootCommand rootCommand = new() { option }; + + CommandLineConfiguration configuration = new(rootCommand) { - var option = new Option("info"); - RootCommand rootCommand = new() { option }; + Output = new StringWriter() + }; - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + var result = rootCommand.Parse("zzzzzzz", configuration); - var result = rootCommand.Parse("zzzzzzz", configuration); + await result.InvokeAsync(); - await result.InvokeAsync(); + configuration.Output.ToString().Should().NotContain("was not matched"); + } - configuration.Output.ToString().Should().NotContain("was not matched"); - } + [Fact] + public async Task When_command_is_mistyped_it_is_suggested() + { + var command = new Command("restore"); + RootCommand rootCommand = new() { command }; - [Fact] - public async Task When_command_is_mistyped_it_is_suggested() + CommandLineConfiguration configuration = new(rootCommand) { - var command = new Command("restore"); - RootCommand rootCommand = new() { command }; + Output = new StringWriter() + }; - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + var result = rootCommand.Parse("sertor", configuration); - var result = rootCommand.Parse("sertor", configuration); + await result.InvokeAsync(); - await result.InvokeAsync(); + configuration.Output.ToString().Should().Contain($"'sertor' was not matched. Did you mean one of the following?{NewLine}restore"); + } - configuration.Output.ToString().Should().Contain($"'sertor' was not matched. Did you mean one of the following?{NewLine}restore"); - } + [Fact] + public async Task When_there_are_multiple_matches_it_picks_the_best_matches() + { + var fromCommand = new Command("from"); + var seenCommand = new Command("seen"); + var aOption = new Option("a"); + var beenOption = new Option("been"); + RootCommand rootCommand = new () + { + fromCommand, + seenCommand, + aOption, + beenOption + }; + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - [Fact] - public async Task When_there_are_multiple_matches_it_picks_the_best_matches() - { - var fromCommand = new Command("from"); - var seenCommand = new Command("seen"); - var aOption = new Option("a"); - var beenOption = new Option("been"); - RootCommand rootCommand = new () - { - fromCommand, - seenCommand, - aOption, - beenOption - }; - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; - - var result = rootCommand.Parse("een", configuration); - - await result.InvokeAsync(); - - configuration.Output.ToString().Should().Contain($"'een' was not matched. Did you mean one of the following?{NewLine}seen{NewLine}been"); - } + var result = rootCommand.Parse("een", configuration); + + await result.InvokeAsync(); - [Fact] - public async Task Hidden_commands_are_not_suggested() + configuration.Output.ToString().Should().Contain($"'een' was not matched. Did you mean one of the following?{NewLine}seen{NewLine}been"); + } + + [Fact] + public async Task Hidden_commands_are_not_suggested() + { + var fromCommand = new Command("from"); + var seenCommand = new Command("seen") { Hidden = true }; + var beenCommand = new Command("been"); + RootCommand rootCommand = new RootCommand { - var fromCommand = new Command("from"); - var seenCommand = new Command("seen") { Hidden = true }; - var beenCommand = new Command("been"); - RootCommand rootCommand = new RootCommand - { - fromCommand, - seenCommand, - beenCommand - }; + fromCommand, + seenCommand, + beenCommand + }; - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - var result = rootCommand.Parse("een", configuration); + var result = rootCommand.Parse("een", configuration); - await result.InvokeAsync(); + await result.InvokeAsync(); - configuration.Output.ToString().Should().Contain($"'een' was not matched. Did you mean one of the following?{NewLine}been"); - } + configuration.Output.ToString().Should().Contain($"'een' was not matched. Did you mean one of the following?{NewLine}been"); + } - [Fact] - public async Task Arguments_are_not_suggested() + [Fact] + public async Task Arguments_are_not_suggested() + { + var argument = new Argument("the-argument"); + var command = new Command("been"); + var rootCommand = new RootCommand { - var argument = new Argument("the-argument"); - var command = new Command("been"); - var rootCommand = new RootCommand - { - argument, - command - }; + argument, + command + }; - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - var result = rootCommand.Parse("een", configuration); + var result = rootCommand.Parse("een", configuration); - var parseErrorAction = (ParseErrorAction)result.Action; - parseErrorAction.ShowHelp = false; - parseErrorAction.ShowTypoCorrections = true; + var parseErrorAction = (ParseErrorAction)result.Action; + parseErrorAction.ShowHelp = false; + parseErrorAction.ShowTypoCorrections = true; - await result.InvokeAsync(); + await result.InvokeAsync(); - configuration.Output.ToString().Should().NotContain("the-argument"); - } + configuration.Output.ToString().Should().NotContain("the-argument"); + } - [Fact] - public async Task Hidden_options_are_not_suggested() + [Fact] + public async Task Hidden_options_are_not_suggested() + { + var fromOption = new Option("from"); + var seenOption = new Option("seen") { Hidden = true }; + var beenOption = new Option("been"); + var rootCommand = new RootCommand { - var fromOption = new Option("from"); - var seenOption = new Option("seen") { Hidden = true }; - var beenOption = new Option("been"); - var rootCommand = new RootCommand - { - fromOption, - seenOption, - beenOption - }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; + fromOption, + seenOption, + beenOption + }; + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter() + }; - var result = rootCommand.Parse("een", config); + var result = rootCommand.Parse("een", config); - await result.InvokeAsync(); + await result.InvokeAsync(); - config.Output.ToString().Should().Contain($"'een' was not matched. Did you mean one of the following?{NewLine}been"); - } + config.Output.ToString().Should().Contain($"'een' was not matched. Did you mean one of the following?{NewLine}been"); + } - [Fact] - public async Task Suggestions_favor_matches_with_prefix() + [Fact] + public async Task Suggestions_favor_matches_with_prefix() + { + var rootCommand = new RootCommand { - var rootCommand = new RootCommand - { - new Option("/call", "-call", "--call"), - new Option("/email", "-email", "--email") - }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; - var result = rootCommand.Parse("-all", config); + new Option("/call", "-call", "--call"), + new Option("/email", "-email", "--email") + }; + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter() + }; + var result = rootCommand.Parse("-all", config); - await result.InvokeAsync(); + await result.InvokeAsync(); - config.Output.ToString().Should().Contain($"'-all' was not matched. Did you mean one of the following?{NewLine}-call"); - } + config.Output.ToString().Should().Contain($"'-all' was not matched. Did you mean one of the following?{NewLine}-call"); } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/OptionTests.MultipleArgumentsPerToken.cs b/src/System.CommandLine.Tests/OptionTests.MultipleArgumentsPerToken.cs index 994ecfd06e..de37bdd7f5 100644 --- a/src/System.CommandLine.Tests/OptionTests.MultipleArgumentsPerToken.cs +++ b/src/System.CommandLine.Tests/OptionTests.MultipleArgumentsPerToken.cs @@ -8,205 +8,204 @@ using Xunit; using Xunit.Abstractions; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public partial class OptionTests { - public partial class OptionTests + public class MultipleArgumentsPerToken { - public class MultipleArgumentsPerToken + public class Allowed { - public class Allowed + private readonly ITestOutputHelper _output; + + public Allowed(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void When_option_is_not_respecified_but_limit_is_not_reached_then_the_following_token_is_used_as_value() { - private readonly ITestOutputHelper _output; + var animalsOption = new Option("-a", "--animals") + { + AllowMultipleArgumentsPerToken = true, + }; + var vegetablesOption = new Option("-v", "--vegetables"); + + var command = new RootCommand + { + animalsOption, + vegetablesOption + }; + + var result = command.Parse("-a cat dog -v carrot"); + + result + .GetResult(animalsOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo(new[] { "cat", "dog" }); + + result + .GetResult(vegetablesOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("carrot"); + + result + .UnmatchedTokens + .Should() + .BeNullOrEmpty(); + } - public Allowed(ITestOutputHelper output) + [Fact] + public void When_option_is_not_respecified_and_limit_is_reached_then_the_following_token_is_unmatched() + { + var animalsOption = new Option("-a", "--animals") { - _output = output; - } + AllowMultipleArgumentsPerToken = true + }; + var vegetablesOption = new Option("-v", "--vegetables"); - [Fact] - public void When_option_is_not_respecified_but_limit_is_not_reached_then_the_following_token_is_used_as_value() + var command = new RootCommand { - var animalsOption = new Option("-a", "--animals") - { - AllowMultipleArgumentsPerToken = true, - }; - var vegetablesOption = new Option("-v", "--vegetables"); - - var command = new RootCommand - { - animalsOption, - vegetablesOption - }; - - var result = command.Parse("-a cat dog -v carrot"); - - result - .GetResult(animalsOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo(new[] { "cat", "dog" }); - - result - .GetResult(vegetablesOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("carrot"); - - result - .UnmatchedTokens - .Should() - .BeNullOrEmpty(); - } - - [Fact] - public void When_option_is_not_respecified_and_limit_is_reached_then_the_following_token_is_unmatched() + animalsOption, + vegetablesOption + }; + + var result = command.Parse("-a cat some-arg -v carrot"); + + result.GetResult(animalsOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("cat"); + + result.GetResult(vegetablesOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("carrot"); + + result + .UnmatchedTokens + .Should() + .BeEquivalentTo("some-arg"); + } + + [Theory] + [InlineData("--option 1 --option 2")] + [InlineData("xyz --option 1 --option 2")] + [InlineData("--option 1 xyz --option 2")] + [InlineData("--option 1 --option 2 xyz")] + public void When_max_arity_is_1_then_subsequent_option_args_overwrite_previous_ones(string commandLine) + { + var option = new Option("--option") { - var animalsOption = new Option("-a", "--animals") - { - AllowMultipleArgumentsPerToken = true - }; - var vegetablesOption = new Option("-v", "--vegetables"); - - var command = new RootCommand - { - animalsOption, - vegetablesOption - }; - - var result = command.Parse("-a cat some-arg -v carrot"); - - result.GetResult(animalsOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("cat"); - - result.GetResult(vegetablesOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("carrot"); - - result - .UnmatchedTokens - .Should() - .BeEquivalentTo("some-arg"); - } - - [Theory] - [InlineData("--option 1 --option 2")] - [InlineData("xyz --option 1 --option 2")] - [InlineData("--option 1 xyz --option 2")] - [InlineData("--option 1 --option 2 xyz")] - public void When_max_arity_is_1_then_subsequent_option_args_overwrite_previous_ones(string commandLine) + AllowMultipleArgumentsPerToken = true + }; + var command = new Command("the-command") { - var option = new Option("--option") - { - AllowMultipleArgumentsPerToken = true - }; - var command = new Command("the-command") - { - option, - new Argument("arg") - }; + option, + new Argument("arg") + }; - var result = command.Parse(commandLine); + var result = command.Parse(commandLine); - var value = result.GetValue(option); + var value = result.GetValue(option); - value.Should().Be("2"); - } + value.Should().Be("2"); + } - [Fact] - public void All_consumed_tokens_are_present_in_option_result() + [Fact] + public void All_consumed_tokens_are_present_in_option_result() + { + var option = new Option("-x") { - var option = new Option("-x") - { - AllowMultipleArgumentsPerToken = true - }; + AllowMultipleArgumentsPerToken = true + }; + + var result = new RootCommand { option }.Parse("-x 1 -x 2 -x 3 -x 4"); - var result = new RootCommand { option }.Parse("-x 1 -x 2 -x 3 -x 4"); + _output.WriteLine(result.Diagram()); - _output.WriteLine(result.Diagram()); + var optionResult = result.GetResult(option); - var optionResult = result.GetResult(option); + optionResult + .Tokens + .Select(t => t.Value) + .Should().BeEquivalentSequenceTo("1", "2", "3", "4"); + } - optionResult - .Tokens - .Select(t => t.Value) - .Should().BeEquivalentSequenceTo("1", "2", "3", "4"); - } + [Fact] + public void Multiple_option_arguments_that_match_single_arity_option_aliases_are_parsed_correctly() + { + var optionX = new Option("-x") + { + AllowMultipleArgumentsPerToken = true + }; + var optionY = new Option("-y") + { + AllowMultipleArgumentsPerToken = true + }; - [Fact] - public void Multiple_option_arguments_that_match_single_arity_option_aliases_are_parsed_correctly() + var command = new RootCommand { - var optionX = new Option("-x") - { - AllowMultipleArgumentsPerToken = true - }; - var optionY = new Option("-y") - { - AllowMultipleArgumentsPerToken = true - }; - - var command = new RootCommand - { - optionX, - optionY - }; - - var result = command.Parse("-x -x -x -y -y -x -y -y -y -x -x -y"); - - _output.WriteLine(result.Diagram()); - - result.Errors.Should().BeEmpty(); - result.GetValue(optionY).Should().Be("-x"); - result.GetValue(optionX).Should().Be("-y"); - } + optionX, + optionY + }; + + var result = command.Parse("-x -x -x -y -y -x -y -y -y -x -x -y"); + + _output.WriteLine(result.Diagram()); + + result.Errors.Should().BeEmpty(); + result.GetValue(optionY).Should().Be("-x"); + result.GetValue(optionX).Should().Be("-y"); } + } - public class Disallowed + public class Disallowed + { + [Fact] + public void Single_option_arg_is_matched() { - [Fact] - public void Single_option_arg_is_matched() - { - var option = new Option("--option") { AllowMultipleArgumentsPerToken = false }; - var command = new Command("the-command") { option }; + var option = new Option("--option") { AllowMultipleArgumentsPerToken = false }; + var command = new Command("the-command") { option }; - var result = command.Parse("--option 1 2"); + var result = command.Parse("--option 1 2"); - var value = result.GetValue(option); + var value = result.GetValue(option); - value.Should().BeEquivalentTo(new[] { "1" }); - } + value.Should().BeEquivalentTo(new[] { "1" }); + } - [Fact] - public void Subsequent_matched_arguments_result_in_errors() - { - var option = new Option("--option") { AllowMultipleArgumentsPerToken = false }; - var command = new Command("the-command") { option }; + [Fact] + public void Subsequent_matched_arguments_result_in_errors() + { + var option = new Option("--option") { AllowMultipleArgumentsPerToken = false }; + var command = new Command("the-command") { option }; - var result = command.Parse("--option 1 2"); + var result = command.Parse("--option 1 2"); - result.UnmatchedTokens.Should().BeEquivalentTo(new[] { "2" }); - result.Errors.Should().Contain(e => e.Message == LocalizationResources.UnrecognizedCommandOrArgument("2")); - } + result.UnmatchedTokens.Should().BeEquivalentTo(new[] { "2" }); + result.Errors.Should().Contain(e => e.Message == LocalizationResources.UnrecognizedCommandOrArgument("2")); + } - [Fact] - public void When_max_arity_is_greater_than_1_then_multiple_option_args_are_matched() - { - var option = new Option("--option") { AllowMultipleArgumentsPerToken = false }; - var command = new Command("the-command") { option }; + [Fact] + public void When_max_arity_is_greater_than_1_then_multiple_option_args_are_matched() + { + var option = new Option("--option") { AllowMultipleArgumentsPerToken = false }; + var command = new Command("the-command") { option }; - var result = command.Parse("--option 1 --option 2"); + var result = command.Parse("--option 1 --option 2"); - var value = result.GetValue(option); + var value = result.GetValue(option); - value.Should().BeEquivalentTo(new[] { "1", "2" }); - result.Errors.Should().BeEmpty(); - } + value.Should().BeEquivalentTo(new[] { "1", "2" }); + result.Errors.Should().BeEmpty(); } } } diff --git a/src/System.CommandLine.Tests/OptionTests.cs b/src/System.CommandLine.Tests/OptionTests.cs index 804af82386..f18a4467e9 100644 --- a/src/System.CommandLine.Tests/OptionTests.cs +++ b/src/System.CommandLine.Tests/OptionTests.cs @@ -7,510 +7,509 @@ using FluentAssertions.Execution; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public partial class OptionTests { - public partial class OptionTests + [Fact] + public void By_default_there_is_no_default_value() { - [Fact] - public void By_default_there_is_no_default_value() - { - Option option = new("name"); + Option option = new("name"); - option.HasDefaultValue.Should().BeFalse(); - } + option.HasDefaultValue.Should().BeFalse(); + } - [Fact] - public void When_default_value_factory_is_set_then_HasDefaultValue_is_true() - { - Option option = new("name"); + [Fact] + public void When_default_value_factory_is_set_then_HasDefaultValue_is_true() + { + Option option = new("name"); - option.DefaultValueFactory = (_) => Array.Empty(); + option.DefaultValueFactory = (_) => Array.Empty(); - option.HasDefaultValue.Should().BeTrue(); - } + option.HasDefaultValue.Should().BeTrue(); + } - [Fact] - public void When_an_option_has_only_name_then_it_has_no_aliases() - { - var option = new Option("myname"); + [Fact] + public void When_an_option_has_only_name_then_it_has_no_aliases() + { + var option = new Option("myname"); - option.Name.Should().Be("myname"); - option.Aliases.Should().BeEmpty(); - } + option.Name.Should().Be("myname"); + option.Aliases.Should().BeEmpty(); + } - [Fact] - public void When_an_option_has_several_aliases_then_they_do_not_affect_its_name() - { - var option = new Option(name: "m", aliases: new[] { "longer" }); + [Fact] + public void When_an_option_has_several_aliases_then_they_do_not_affect_its_name() + { + var option = new Option(name: "m", aliases: new[] { "longer" }); - option.Name.Should().Be("m"); - } + option.Name.Should().Be("m"); + } - [Fact] - public void Option_names_can_contain_prefix_characters() - { - var option = new Option("--myname"); + [Fact] + public void Option_names_can_contain_prefix_characters() + { + var option = new Option("--myname"); - option.Name.Should().Be("--myname"); - } + option.Name.Should().Be("--myname"); + } - [Fact] - public void Aliases_is_aware_of_added_alias() - { - var option = new Option("--original"); + [Fact] + public void Aliases_is_aware_of_added_alias() + { + var option = new Option("--original"); - option.Aliases.Add("--added"); + option.Aliases.Add("--added"); - option.Aliases.Should().Contain("--added"); - } + option.Aliases.Should().Contain("--added"); + } - [Fact] - public void RawAliases_is_aware_of_added_alias() - { - var option = new Option("--original"); + [Fact] + public void RawAliases_is_aware_of_added_alias() + { + var option = new Option("--original"); - option.Aliases.Add("--added"); + option.Aliases.Add("--added"); - option.Aliases.Should().Contain("--added"); - } + option.Aliases.Should().Contain("--added"); + } - [Fact] - public void A_prefixed_alias_can_be_added_to_an_option() - { - var option = new Option("--apple"); + [Fact] + public void A_prefixed_alias_can_be_added_to_an_option() + { + var option = new Option("--apple"); - option.Aliases.Add("-a"); + option.Aliases.Add("-a"); - option.Aliases.Contains("-a").Should().BeTrue(); - } + option.Aliases.Contains("-a").Should().BeTrue(); + } - [Fact] - public void Option_aliases_are_case_sensitive() - { - var option = new Option("name", "o"); + [Fact] + public void Option_aliases_are_case_sensitive() + { + var option = new Option("name", "o"); - option.Aliases.Contains("O").Should().BeFalse(); - } + option.Aliases.Contains("O").Should().BeFalse(); + } - [Fact] - public void Aliases_contains_prefixed_short_value() - { - var option = new Option("--option", "-o"); + [Fact] + public void Aliases_contains_prefixed_short_value() + { + var option = new Option("--option", "-o"); - option.Aliases.Contains("-o").Should().BeTrue(); - } + option.Aliases.Contains("-o").Should().BeTrue(); + } - [Fact] - public void Aliases_contains_prefixed_long_value() - { - var option = new Option("-o", "--option"); + [Fact] + public void Aliases_contains_prefixed_long_value() + { + var option = new Option("-o", "--option"); - option.Aliases.Contains("--option").Should().BeTrue(); - } + option.Aliases.Contains("--option").Should().BeTrue(); + } - [Fact] - public void It_is_not_necessary_to_specify_a_prefix_when_adding_an_option() - { - var option = new Option("o"); + [Fact] + public void It_is_not_necessary_to_specify_a_prefix_when_adding_an_option() + { + var option = new Option("o"); - option.Name.Should().Be("o"); - option.Aliases.Should().BeEmpty(); - } + option.Name.Should().Be("o"); + option.Aliases.Should().BeEmpty(); + } - [Fact] - public void An_option_does_not_need_to_have_at_any_aliases() - { - var option = new Option("justName"); + [Fact] + public void An_option_does_not_need_to_have_at_any_aliases() + { + var option = new Option("justName"); - option.Aliases.Should().BeEmpty(); - } + option.Aliases.Should().BeEmpty(); + } - [Fact] - public void An_option_cannot_have_an_empty_alias() - { - Action create = () => new Option("name", ""); - - create.Should() - .Throw() - .Which - .Message - .Should() - .Be("Names and aliases cannot be null, empty, or consist entirely of whitespace."); - } - - [Fact] - public void An_option_cannot_have_an_alias_consisting_entirely_of_whitespace() - { - Action create = () => new Option("name", " \t"); - - create.Should() - .Throw() - .Which - .Message - .Should() - .Be("Names and aliases cannot be null, empty, or consist entirely of whitespace."); - } - - [Fact] - public void Raw_aliases_are_exposed_by_an_option() - { - var option = new Option("--help", "-h", "/?"); - - option.Aliases - .Should() - .BeEquivalentTo("-h", "/?"); - } - - [Theory] - [InlineData("-x ")] - [InlineData(" -x")] - [InlineData("--aa aa")] - public void When_an_option_is_created_with_a_name_that_contains_whitespace_then_an_informative_error_is_returned( - string name) - { - Action create = () => new Option(name); - - create.Should() - .Throw() - .Which - .Message - .Should() - .Contain($"Names and aliases cannot contain whitespace: \"{name}\""); - } - - [Theory] - [InlineData("-x ")] - [InlineData(" -x")] - [InlineData("--aa aa")] - public void When_an_option_alias_is_added_and_contains_whitespace_then_an_informative_error_is_returned(string alias) - { - var option = new Option("-x"); - - Action addAlias = () => option.Aliases.Add(alias); - - addAlias.Should() - .Throw() - .Which - .Message - .Should() - .Contain($"Names and aliases cannot contain whitespace: \"{alias}\""); - } - - [Theory] - [InlineData("-")] - [InlineData("--")] - [InlineData("/")] - public void When_options_use_different_prefixes_they_still_work(string prefix) - { - var optionA = new Option(prefix + "a"); - var optionB = new Option(prefix + "b"); - var optionC = new Option(prefix + "c"); - - var rootCommand = new RootCommand - { - optionA, - optionB, - optionC - }; - - var result = rootCommand.Parse(prefix + "c value-for-c " + prefix + "a value-for-a"); - - result.GetValue(optionA).Should().Be("value-for-a"); - result.GetRequiredValue(optionA).Should().Be("value-for-a"); - result.GetRequiredValue(optionA.Name).Should().Be("value-for-a"); - result.GetResult(optionB).Should().BeNull(); - result.Invoking(result => result.GetRequiredValue(optionB)).Should().Throw(); - result.Invoking(result => result.GetRequiredValue(optionB.Name)).Should().Throw(); - result.GetValue(optionC).Should().Be("value-for-c"); - result.GetRequiredValue(optionC).Should().Be("value-for-c"); - result.GetRequiredValue(optionC.Name).Should().Be("value-for-c"); - } - - [Fact] - public void Option_T_default_value_can_be_set_via_the_constructor() + [Fact] + public void An_option_cannot_have_an_empty_alias() + { + Action create = () => new Option("name", ""); + + create.Should() + .Throw() + .Which + .Message + .Should() + .Be("Names and aliases cannot be null, empty, or consist entirely of whitespace."); + } + + [Fact] + public void An_option_cannot_have_an_alias_consisting_entirely_of_whitespace() + { + Action create = () => new Option("name", " \t"); + + create.Should() + .Throw() + .Which + .Message + .Should() + .Be("Names and aliases cannot be null, empty, or consist entirely of whitespace."); + } + + [Fact] + public void Raw_aliases_are_exposed_by_an_option() + { + var option = new Option("--help", "-h", "/?"); + + option.Aliases + .Should() + .BeEquivalentTo("-h", "/?"); + } + + [Theory] + [InlineData("-x ")] + [InlineData(" -x")] + [InlineData("--aa aa")] + public void When_an_option_is_created_with_a_name_that_contains_whitespace_then_an_informative_error_is_returned( + string name) + { + Action create = () => new Option(name); + + create.Should() + .Throw() + .Which + .Message + .Should() + .Contain($"Names and aliases cannot contain whitespace: \"{name}\""); + } + + [Theory] + [InlineData("-x ")] + [InlineData(" -x")] + [InlineData("--aa aa")] + public void When_an_option_alias_is_added_and_contains_whitespace_then_an_informative_error_is_returned(string alias) + { + var option = new Option("-x"); + + Action addAlias = () => option.Aliases.Add(alias); + + addAlias.Should() + .Throw() + .Which + .Message + .Should() + .Contain($"Names and aliases cannot contain whitespace: \"{alias}\""); + } + + [Theory] + [InlineData("-")] + [InlineData("--")] + [InlineData("/")] + public void When_options_use_different_prefixes_they_still_work(string prefix) + { + var optionA = new Option(prefix + "a"); + var optionB = new Option(prefix + "b"); + var optionC = new Option(prefix + "c"); + + var rootCommand = new RootCommand + { + optionA, + optionB, + optionC + }; + + var result = rootCommand.Parse(prefix + "c value-for-c " + prefix + "a value-for-a"); + + result.GetValue(optionA).Should().Be("value-for-a"); + result.GetRequiredValue(optionA).Should().Be("value-for-a"); + result.GetRequiredValue(optionA.Name).Should().Be("value-for-a"); + result.GetResult(optionB).Should().BeNull(); + result.Invoking(result => result.GetRequiredValue(optionB)).Should().Throw(); + result.Invoking(result => result.GetRequiredValue(optionB.Name)).Should().Throw(); + result.GetValue(optionC).Should().Be("value-for-c"); + result.GetRequiredValue(optionC).Should().Be("value-for-c"); + result.GetRequiredValue(optionC.Name).Should().Be("value-for-c"); + } + + [Fact] + public void Option_T_default_value_can_be_set_via_the_constructor() + { + var option = new Option("-x") { - var option = new Option("-x") - { - DefaultValueFactory = _ => 123 - }; - - new RootCommand { option } - .Parse("") - .GetResult(option) - .GetValueOrDefault() - .Should() - .Be(123); - } - - [Fact] - public void Option_T_default_value_can_be_set_after_instantiation() + DefaultValueFactory = _ => 123 + }; + + new RootCommand { option } + .Parse("") + .GetResult(option) + .GetValueOrDefault() + .Should() + .Be(123); + } + + [Fact] + public void Option_T_default_value_can_be_set_after_instantiation() + { + var option = new Option("-x") { - var option = new Option("-x") - { - DefaultValueFactory = (_) => 123 - }; - - var result = new RootCommand { option } - .Parse("") - .GetResult(option); - - result - .GetValueOrDefault() - .Should() - .Be(123); - - result.GetRequiredValue(option) - .Should() - .Be(123); - - result.GetRequiredValue(option.Name) - .Should() - .Be(123); - } + DefaultValueFactory = (_) => 123 + }; + + var result = new RootCommand { option } + .Parse("") + .GetResult(option); + + result + .GetValueOrDefault() + .Should() + .Be(123); + + result.GetRequiredValue(option) + .Should() + .Be(123); + + result.GetRequiredValue(option.Name) + .Should() + .Be(123); + } - [Fact] - public void Option_T_default_value_factory_can_be_set_after_instantiation() - { - var option = new Option("-x"); + [Fact] + public void Option_T_default_value_factory_can_be_set_after_instantiation() + { + var option = new Option("-x"); - option.DefaultValueFactory = _ => 123; + option.DefaultValueFactory = _ => 123; - var parseResult = new RootCommand { option }.Parse(""); + var parseResult = new RootCommand { option }.Parse(""); - parseResult - .GetResult(option) - .GetValueOrDefault() - .Should() - .Be(123); - } + parseResult + .GetResult(option) + .GetValueOrDefault() + .Should() + .Be(123); + } - [Fact] - public void When_there_is_no_default_value_then_GetRequiredValue_does_not_throw_for_bool() - { - var option = new Option("-x"); + [Fact] + public void When_there_is_no_default_value_then_GetRequiredValue_does_not_throw_for_bool() + { + var option = new Option("-x"); - var result = new RootCommand { option }.Parse(""); + var result = new RootCommand { option }.Parse(""); - using var _ = new AssertionScope(); + using var _ = new AssertionScope(); - result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); - result.GetRequiredValue(option).Should().BeFalse(); + result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); + result.GetRequiredValue(option).Should().BeFalse(); - result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); - result.GetRequiredValue("-x").Should().BeFalse(); + result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); + result.GetRequiredValue("-x").Should().BeFalse(); - result.Errors.Should().BeEmpty(); - } + result.Errors.Should().BeEmpty(); + } - [Fact] - public void GetRequiredValue_does_not_throw_when_help_is_requested_and_DefaultValueFactory_is_set() + [Fact] + public void GetRequiredValue_does_not_throw_when_help_is_requested_and_DefaultValueFactory_is_set() + { + var option = new Option("-x") { - var option = new Option("-x") - { - DefaultValueFactory = _ => "default" - }; + DefaultValueFactory = _ => "default" + }; - var result = new RootCommand { option }.Parse("-h"); + var result = new RootCommand { option }.Parse("-h"); - using var _ = new AssertionScope(); + using var _ = new AssertionScope(); - result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); - result.GetRequiredValue(option).Should().Be("default"); + result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); + result.GetRequiredValue(option).Should().Be("default"); - result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); - result.GetRequiredValue("-x").Should().Be("default"); + result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); + result.GetRequiredValue("-x").Should().Be("default"); - result.Errors.Should().BeEmpty(); - } + result.Errors.Should().BeEmpty(); + } - [Fact] - public void When_there_is_no_default_value_then_GetDefaultValue_does_not_throw_for_bool() - { - var option = new Option("-x"); + [Fact] + public void When_there_is_no_default_value_then_GetDefaultValue_does_not_throw_for_bool() + { + var option = new Option("-x"); - option.GetDefaultValue().Should().Be(false); - } + option.GetDefaultValue().Should().Be(false); + } - [Fact] - public void When_there_is_a_default_value_then_GetRequiredValue_does_not_throw() + [Fact] + public void When_there_is_a_default_value_then_GetRequiredValue_does_not_throw() + { + var option = new Option("-x") { - var option = new Option("-x") - { - Required = true, - DefaultValueFactory = _ => "default" - }; + Required = true, + DefaultValueFactory = _ => "default" + }; - var result = new RootCommand { option }.Parse(""); + var result = new RootCommand { option }.Parse(""); - using var _ = new AssertionScope(); + using var _ = new AssertionScope(); - result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); - result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); - result.GetRequiredValue(option).Should().Be("default"); - } + result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); + result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); + result.GetRequiredValue(option).Should().Be("default"); + } - [Fact] - public void Option_T_default_value_is_validated() - { - var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; - option.Validators.Add(symbol => - symbol.AddError(symbol.Tokens - .Select(t => t.Value) - .Where(v => v == "123") - .Select(_ => "ERR") - .First())); - - new RootCommand { option } - .Parse("-x 123") - .Errors - .Select(e => e.Message) - .Should() - .BeEquivalentTo(new[] { "ERR" }); - } - - [Fact] - public void Option_of_string_defaults_to_null_when_not_specified() - { - var option = new Option("-x"); - - var result = new RootCommand { option }.Parse(""); - result.GetResult(option) - .Should() - .BeNull(); - result.GetValue(option) - .Should() - .BeNull(); - } + [Fact] + public void Option_T_default_value_is_validated() + { + var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; + option.Validators.Add(symbol => + symbol.AddError(symbol.Tokens + .Select(t => t.Value) + .Where(v => v == "123") + .Select(_ => "ERR") + .First())); + + new RootCommand { option } + .Parse("-x 123") + .Errors + .Select(e => e.Message) + .Should() + .BeEquivalentTo(new[] { "ERR" }); + } + + [Fact] + public void Option_of_string_defaults_to_null_when_not_specified() + { + var option = new Option("-x"); + + var result = new RootCommand { option }.Parse(""); + result.GetResult(option) + .Should() + .BeNull(); + result.GetValue(option) + .Should() + .BeNull(); + } - [Fact] - public void Option_of_boolean_defaults_to_false_when_not_specified() - { - var option = new Option("-x"); + [Fact] + public void Option_of_boolean_defaults_to_false_when_not_specified() + { + var option = new Option("-x"); + + var result = new RootCommand { option }.Parse(""); - var result = new RootCommand { option }.Parse(""); + result.GetValue(option) + .Should() + .BeFalse(); + } - result.GetValue(option) - .Should() - .BeFalse(); - } + [Fact] + public void Option_of_enum_can_limit_enum_members_as_valid_values() + { + Option option = new("--color"); + option.AcceptOnlyFromAmong(ConsoleColor.Red.ToString(), ConsoleColor.Green.ToString()); + + var result = new RootCommand { option }.Parse("--color Fuschia"); + + result.Errors + .Select(e => e.Message) + .Should() + .BeEquivalentTo(new[] { $"Argument 'Fuschia' not recognized. Must be one of:\n\t'Red'\n\t'Green'" }); + } - [Fact] - public void Option_of_enum_can_limit_enum_members_as_valid_values() + [Fact] + public void Option_result_provides_identifier_token_if_name_was_provided() + { + var option = new Option("--name") { - Option option = new("--color"); - option.AcceptOnlyFromAmong(ConsoleColor.Red.ToString(), ConsoleColor.Green.ToString()); + Aliases = { "-n" } + }; - var result = new RootCommand { option }.Parse("--color Fuschia"); + var result = new RootCommand { option }.Parse("--name 123"); - result.Errors - .Select(e => e.Message) - .Should() - .BeEquivalentTo(new[] { $"Argument 'Fuschia' not recognized. Must be one of:\n\t'Red'\n\t'Green'" }); - } + result.GetResult(option).IdentifierToken.Value.Should().Be("--name"); + } - [Fact] - public void Option_result_provides_identifier_token_if_name_was_provided() + [Fact] + public void Option_result_provides_identifier_token_if_alias_was_provided() + { + var option = new Option("--name") { - var option = new Option("--name") - { - Aliases = { "-n" } - }; + Aliases = { "-n" } + }; - var result = new RootCommand { option }.Parse("--name 123"); + var result = new RootCommand { option }.Parse("-n 123"); - result.GetResult(option).IdentifierToken.Value.Should().Be("--name"); - } + result.GetResult(option).IdentifierToken.Value.Should().Be("-n"); + } - [Fact] - public void Option_result_provides_identifier_token_if_alias_was_provided() + [Theory] + [InlineData("--name 123", 1)] + [InlineData("--name 123 --name 456", 2)] + [InlineData("-n 123 --name 456", 2)] + [InlineData("--name 123 -x different-option --name 456", 2)] + public void Number_of_occurrences_of_identifier_token_is_exposed_by_option_result(string commandLine, int expectedCount) + { + var option = new Option("--name") { - var option = new Option("--name") - { - Aliases = { "-n" } - }; - - var result = new RootCommand { option }.Parse("-n 123"); - - result.GetResult(option).IdentifierToken.Value.Should().Be("-n"); - } - - [Theory] - [InlineData("--name 123", 1)] - [InlineData("--name 123 --name 456", 2)] - [InlineData("-n 123 --name 456", 2)] - [InlineData("--name 123 -x different-option --name 456", 2)] - public void Number_of_occurrences_of_identifier_token_is_exposed_by_option_result(string commandLine, int expectedCount) + Aliases = { "-n" } + }; + + var root = new RootCommand { - var option = new Option("--name") - { - Aliases = { "-n" } - }; + option, + new Option("-x") + }; - var root = new RootCommand - { - option, - new Option("-x") - }; + var optionResult = root.Parse(commandLine).GetResult(option); - var optionResult = root.Parse(commandLine).GetResult(option); + optionResult.IdentifierTokenCount.Should().Be(expectedCount); + } - optionResult.IdentifierTokenCount.Should().Be(expectedCount); - } + [Fact] + public void Multiple_identifier_token_instances_without_argument_tokens_can_be_parsed() + { + var option = new Option("-v"); - [Fact] - public void Multiple_identifier_token_instances_without_argument_tokens_can_be_parsed() + var root = new RootCommand { - var option = new Option("-v"); + option + }; - var root = new RootCommand - { - option - }; + var result = root.Parse("-v -v -v"); - var result = root.Parse("-v -v -v"); + using var _ = new AssertionScope(); - using var _ = new AssertionScope(); + result.GetValue(option).Should().BeTrue(); + result.GetRequiredValue(option).Should().BeTrue(); + result.GetRequiredValue(option.Name).Should().BeTrue(); + } - result.GetValue(option).Should().BeTrue(); - result.GetRequiredValue(option).Should().BeTrue(); - result.GetRequiredValue(option.Name).Should().BeTrue(); - } + [Fact] + public void Multiple_bundled_identifier_token_instances_without_argument_tokens_can_be_parsed() + { + var option = new Option("-v"); - [Fact] - public void Multiple_bundled_identifier_token_instances_without_argument_tokens_can_be_parsed() + var root = new RootCommand { - var option = new Option("-v"); - - var root = new RootCommand - { - option - }; + option + }; - var result = root.Parse("-vvv"); + var result = root.Parse("-vvv"); - result.GetValue(option).Should().BeTrue(); - } + result.GetValue(option).Should().BeTrue(); + } - [Theory] // https://github.com/dotnet/command-line-api/issues/669 - [InlineData("-vvv")] - [InlineData("-v -v -v")] - public void Custom_parser_can_be_used_to_implement_int_binding_based_on_token_count(string commandLine) + [Theory] // https://github.com/dotnet/command-line-api/issues/669 + [InlineData("-vvv")] + [InlineData("-v -v -v")] + public void Custom_parser_can_be_used_to_implement_int_binding_based_on_token_count(string commandLine) + { + var option = new Option("-v") { - var option = new Option("-v") - { - Arity = ArgumentArity.Zero, - AllowMultipleArgumentsPerToken = true, - CustomParser = argumentResult => ((OptionResult)argumentResult.Parent).IdentifierTokenCount, - }; + Arity = ArgumentArity.Zero, + AllowMultipleArgumentsPerToken = true, + CustomParser = argumentResult => ((OptionResult)argumentResult.Parent).IdentifierTokenCount, + }; - var root = new RootCommand - { - option - }; + var root = new RootCommand + { + option + }; - var result = root.Parse(commandLine); + var result = root.Parse(commandLine); - result.GetValue(option).Should().Be(3); - } + result.GetValue(option).Should().Be(3); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParseDiagramTests.cs b/src/System.CommandLine.Tests/ParseDiagramTests.cs index bbcd1cf7e9..b16058f9df 100644 --- a/src/System.CommandLine.Tests/ParseDiagramTests.cs +++ b/src/System.CommandLine.Tests/ParseDiagramTests.cs @@ -6,112 +6,111 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class ParseDiagramTests { - public class ParseDiagramTests + [Fact] + public void Parse_result_diagram_helps_explain_parse_operation() { - [Fact] - public void Parse_result_diagram_helps_explain_parse_operation() - { - Command command = - new Command( - "the-command") - { - new Option("-x"), - new Option("-y"), - new Argument("arg") - }; - - var result = command.Parse("the-command -x one -y two three"); - - result.Diagram() - .Should() - .Be("[ the-command [ -x ] [ -y ] ]"); - } - - [Fact] - public void Parse_result_diagram_displays_unmatched_tokens() - { - Option option = new ("-x"); - option.AcceptOnlyFromAmong("arg1", "arg2", "arg3"); - - var command = new Command("command") + Command command = + new Command( + "the-command") { - option + new Option("-x"), + new Option("-y"), + new Argument("arg") }; - var result = command.Parse("command -x ar"); + var result = command.Parse("the-command -x one -y two three"); - result.Diagram() - .Should() - .Be("[ command ![ -x ] ]"); - } + result.Diagram() + .Should() + .Be("[ the-command [ -x ] [ -y ] ]"); + } + + [Fact] + public void Parse_result_diagram_displays_unmatched_tokens() + { + Option option = new ("-x"); + option.AcceptOnlyFromAmong("arg1", "arg2", "arg3"); - [Fact] - public void Parse_diagram_shows_type_conversion_errors() + var command = new Command("command") { - var command = new RootCommand - { - new Option("-f") - }; + option + }; - var result = command.Parse("-f not-an-int"); + var result = command.Parse("command -x ar"); - result.Diagram() - .Should() - .Be($"[ {RootCommand.ExecutableName} ![ -f ] ]"); - } + result.Diagram() + .Should() + .Be("[ command ![ -x ] ]"); + } - [Fact] - public void Parse_diagram_identifies_options_where_default_values_have_been_applied() + [Fact] + public void Parse_diagram_shows_type_conversion_errors() + { + var command = new RootCommand { - var rootCommand = new RootCommand - { - new Option("--height", "-h") { DefaultValueFactory = _ => 10 }, - new Option("-w", "--width") { DefaultValueFactory = _ => 15 }, - new Option("--color", "-c") { DefaultValueFactory = _ => ConsoleColor.Cyan } - }; + new Option("-f") + }; - var result = rootCommand.Parse("-w 9000"); + var result = command.Parse("-f not-an-int"); - var diagram = result.Diagram(); + result.Diagram() + .Should() + .Be($"[ {RootCommand.ExecutableName} ![ -f ] ]"); + } - diagram.Should() - .Be($"[ {RootCommand.ExecutableName} [ -w <9000> ] *[ --height <10> ] *[ --color ] ]"); - } + [Fact] + public void Parse_diagram_identifies_options_where_default_values_have_been_applied() + { + var rootCommand = new RootCommand + { + new Option("--height", "-h") { DefaultValueFactory = _ => 10 }, + new Option("-w", "--width") { DefaultValueFactory = _ => 15 }, + new Option("--color", "-c") { DefaultValueFactory = _ => ConsoleColor.Cyan } + }; + + var result = rootCommand.Parse("-w 9000"); + + var diagram = result.Diagram(); - [Fact] - public void Parse_diagram_indicates_which_tokens_were_applied_to_which_command_argument() + diagram.Should() + .Be($"[ {RootCommand.ExecutableName} [ -w <9000> ] *[ --height <10> ] *[ --color ] ]"); + } + + [Fact] + public void Parse_diagram_indicates_which_tokens_were_applied_to_which_command_argument() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Argument("first"), - new Argument ("second"), - new Argument ("third") - }; + new Argument("first"), + new Argument ("second"), + new Argument ("third") + }; - var result = command.Parse("one two three four five"); + var result = command.Parse("one two three four five"); - result.Diagram() - .Should() - .Be("[ the-command [ first ] [ second ] [ third ] ]"); - } + result.Diagram() + .Should() + .Be("[ the-command [ first ] [ second ] [ third ] ]"); + } - [Fact] - public void Parse_diagram_indicates_which_tokens_were_applied_to_which_command_argument_for_sequences_of_complex_types() + [Fact] + public void Parse_diagram_indicates_which_tokens_were_applied_to_which_command_argument_for_sequences_of_complex_types() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Argument ("first"), - new Argument ("second"), - new Argument ("third") - }; + new Argument ("first"), + new Argument ("second"), + new Argument ("third") + }; - var result = command.Parse("one two three four five"); + var result = command.Parse("one two three four five"); - result.Diagram() - .Should() - .Be("[ the-command [ first ] [ second ] [ third ] ]"); - } + result.Diagram() + .Should() + .Be("[ the-command [ first ] [ second ] [ third ] ]"); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParseDirectiveTests.cs b/src/System.CommandLine.Tests/ParseDirectiveTests.cs index 8ed409e34d..d50af4b8ac 100644 --- a/src/System.CommandLine.Tests/ParseDirectiveTests.cs +++ b/src/System.CommandLine.Tests/ParseDirectiveTests.cs @@ -7,158 +7,157 @@ using Xunit; using Xunit.Abstractions; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class DiagramDirectiveTests { - public class DiagramDirectiveTests - { - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper output; - public DiagramDirectiveTests(ITestOutputHelper output) - { - this.output = output; - } + public DiagramDirectiveTests(ITestOutputHelper output) + { + this.output = output; + } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task Diagram_directive_writes_parse_diagram(bool treatUnmatchedTokensAsErrors) + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Diagram_directive_writes_parse_diagram(bool treatUnmatchedTokensAsErrors) + { + var rootCommand = new RootCommand { new DiagramDirective() }; + var subcommand = new Command("subcommand"); + rootCommand.Subcommands.Add(subcommand); + var option = new Option("-c", "--count"); + subcommand.Options.Add(option); + subcommand.TreatUnmatchedTokensAsErrors = treatUnmatchedTokensAsErrors; + + CommandLineConfiguration config = new(rootCommand) { - var rootCommand = new RootCommand { new DiagramDirective() }; - var subcommand = new Command("subcommand"); - rootCommand.Subcommands.Add(subcommand); - var option = new Option("-c", "--count"); - subcommand.Options.Add(option); - subcommand.TreatUnmatchedTokensAsErrors = treatUnmatchedTokensAsErrors; - - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; + Output = new StringWriter() + }; - var result = rootCommand.Parse("[diagram] subcommand -c 34 --nonexistent wat", config); + var result = rootCommand.Parse("[diagram] subcommand -c 34 --nonexistent wat", config); - output.WriteLine(result.Diagram()); + output.WriteLine(result.Diagram()); - await result.InvokeAsync(); + await result.InvokeAsync(); - string expected = treatUnmatchedTokensAsErrors - ? $"[ {RootCommand.ExecutableName} ![ subcommand [ -c <34> ] ] ] ???--> --nonexistent wat" + Environment.NewLine - : $"[ {RootCommand.ExecutableName} [ subcommand [ -c <34> ] ] ] ???--> --nonexistent wat" + Environment.NewLine; + string expected = treatUnmatchedTokensAsErrors + ? $"[ {RootCommand.ExecutableName} ![ subcommand [ -c <34> ] ] ] ???--> --nonexistent wat" + Environment.NewLine + : $"[ {RootCommand.ExecutableName} [ subcommand [ -c <34> ] ] ] ???--> --nonexistent wat" + Environment.NewLine; - config.Output - .ToString() - .Should() - .Be(expected); - } + config.Output + .ToString() + .Should() + .Be(expected); + } - [Fact] - public async Task When_diagram_directive_is_used_the_help_is_not_displayed() + [Fact] + public async Task When_diagram_directive_is_used_the_help_is_not_displayed() + { + RootCommand rootCommand = new() { - RootCommand rootCommand = new() - { - new DiagramDirective() - }; + new DiagramDirective() + }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter(), - }; + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter(), + }; - var result = rootCommand.Parse("[diagram] --help", config); + var result = rootCommand.Parse("[diagram] --help", config); - output.WriteLine(result.Diagram()); + output.WriteLine(result.Diagram()); - await result.InvokeAsync(); + await result.InvokeAsync(); - config.Output - .ToString() - .Should() - .Be($"[ {RootCommand.ExecutableName} [ --help ] ]" + Environment.NewLine); - } + config.Output + .ToString() + .Should() + .Be($"[ {RootCommand.ExecutableName} [ --help ] ]" + Environment.NewLine); + } - [Fact] - public async Task When_diagram_directive_is_used_the_version_is_not_displayed() + [Fact] + public async Task When_diagram_directive_is_used_the_version_is_not_displayed() + { + RootCommand rootCommand = new() { - RootCommand rootCommand = new() - { - new DiagramDirective() - }; + new DiagramDirective() + }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter() + }; - var result = rootCommand.Parse("[diagram] --version", config); + var result = rootCommand.Parse("[diagram] --version", config); - output.WriteLine(result.Diagram()); + output.WriteLine(result.Diagram()); - await result.InvokeAsync(); + await result.InvokeAsync(); - config.Output - .ToString() - .Should() - .Be($"[ {RootCommand.ExecutableName} [ --version ] ]" + Environment.NewLine); - } + config.Output + .ToString() + .Should() + .Be($"[ {RootCommand.ExecutableName} [ --version ] ]" + Environment.NewLine); + } - [Fact] - public async Task When_there_are_no_errors_then_diagram_directive_sets_exit_code_0() + [Fact] + public async Task When_there_are_no_errors_then_diagram_directive_sets_exit_code_0() + { + RootCommand command = new () { - RootCommand command = new () - { - new Option("-x"), - new DiagramDirective() - }; + new Option("-x"), + new DiagramDirective() + }; - CommandLineConfiguration config = new(command) - { - Output = new StringWriter(), - }; + CommandLineConfiguration config = new(command) + { + Output = new StringWriter(), + }; - var exitCode = await command.Parse("[diagram] -x 123", config).InvokeAsync(); + var exitCode = await command.Parse("[diagram] -x 123", config).InvokeAsync(); - exitCode.Should().Be(0); - } + exitCode.Should().Be(0); + } - [Fact] - public async Task When_there_are_errors_then_diagram_directive_sets_exit_code_1() + [Fact] + public async Task When_there_are_errors_then_diagram_directive_sets_exit_code_1() + { + RootCommand command = new() { - RootCommand command = new() - { - new Option("-x"), - new DiagramDirective() - }; + new Option("-x"), + new DiagramDirective() + }; - CommandLineConfiguration config = new(command) - { - Output = new StringWriter(), - }; + CommandLineConfiguration config = new(command) + { + Output = new StringWriter(), + }; - var exitCode = await command.Parse("[diagram] -x not-an-int", config).InvokeAsync(); + var exitCode = await command.Parse("[diagram] -x not-an-int", config).InvokeAsync(); - exitCode.Should().Be(1); - } + exitCode.Should().Be(1); + } - [Fact] - public async Task When_there_are_errors_then_diagram_directive_sets_exit_code_to_custom_value() + [Fact] + public async Task When_there_are_errors_then_diagram_directive_sets_exit_code_to_custom_value() + { + RootCommand command = new() { - RootCommand command = new() - { - new Option("-x"), - new DiagramDirective - { - ParseErrorReturnValue = 42 - } - }; - - CommandLineConfiguration config = new(command) + new Option("-x"), + new DiagramDirective { - Output = new StringWriter() - }; + ParseErrorReturnValue = 42 + } + }; + + CommandLineConfiguration config = new(command) + { + Output = new StringWriter() + }; - int exitCode = await config.InvokeAsync("[diagram] -x not-an-int"); + int exitCode = await config.InvokeAsync("[diagram] -x not-an-int"); - exitCode.Should().Be(42); - } + exitCode.Should().Be(42); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParseResultTests.cs b/src/System.CommandLine.Tests/ParseResultTests.cs index 6fa859cd9d..cb07ec6102 100644 --- a/src/System.CommandLine.Tests/ParseResultTests.cs +++ b/src/System.CommandLine.Tests/ParseResultTests.cs @@ -7,158 +7,157 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class ParseResultTests { - public class ParseResultTests + [Fact] + public void An_option_with_a_default_value_and_no_explicitly_provided_argument_has_an_empty_arguments_property() { - [Fact] - public void An_option_with_a_default_value_and_no_explicitly_provided_argument_has_an_empty_arguments_property() - { - var option = new Option("-x") { DefaultValueFactory = (_) => "default" }; + var option = new Option("-x") { DefaultValueFactory = (_) => "default" }; - var result = new RootCommand + var result = new RootCommand { option }.Parse("-x") - .GetResult(option); + .GetResult(option); - result.Tokens.Should().BeEmpty(); - } + result.Tokens.Should().BeEmpty(); + } - [Fact] - public void FindResult_can_be_used_to_check_the_presence_of_an_option() - { - var option = new Option("-h", "--help"); + [Fact] + public void FindResult_can_be_used_to_check_the_presence_of_an_option() + { + var option = new Option("-h", "--help"); - var command = new Command("the-command") - { - option - }; + var command = new Command("the-command") + { + option + }; - var result = command.Parse("the-command -h"); + var result = command.Parse("the-command -h"); - result.GetResult(option).Should().NotBeNull(); - } + result.GetResult(option).Should().NotBeNull(); + } - [Fact] - public void GetResult_can_be_used_to_check_the_presence_of_an_implicit_option() + [Fact] + public void GetResult_can_be_used_to_check_the_presence_of_an_implicit_option() + { + var option = new Option("-c", "--count") { DefaultValueFactory = (_) => 5 }; + var command = new Command("the-command") { - var option = new Option("-c", "--count") { DefaultValueFactory = (_) => 5 }; - var command = new Command("the-command") - { - option - }; + option + }; - var result = command.Parse("the-command"); + var result = command.Parse("the-command"); - result.GetResult(option).Should().NotBeNull(); - } + result.GetResult(option).Should().NotBeNull(); + } - [Fact] - public void GetResult_can_be_used_for_root_command_itself() + [Fact] + public void GetResult_can_be_used_for_root_command_itself() + { + RootCommand rootCommand = new() { - RootCommand rootCommand = new() + new Command("the-command") { - new Command("the-command") - { - new Option("-c") - } - }; + new Option("-c") + } + }; - var result = rootCommand.Parse("the-command -c 123"); + var result = rootCommand.Parse("the-command -c 123"); - result.RootCommandResult.Command.Should().BeSameAs(rootCommand); - result.GetResult(rootCommand).Should().BeSameAs(result.RootCommandResult); - } + result.RootCommandResult.Command.Should().BeSameAs(rootCommand); + result.GetResult(rootCommand).Should().BeSameAs(result.RootCommandResult); + } - [Fact] - public void Command_will_not_accept_a_command_if_a_sibling_command_has_already_been_accepted() + [Fact] + public void Command_will_not_accept_a_command_if_a_sibling_command_has_already_been_accepted() + { + var command = new Command("outer") { - var command = new Command("outer") + new Command("inner-one") { - new Command("inner-one") + new Argument("arg1") { - new Argument("arg1") - { - Arity = ArgumentArity.Zero - } - }, - new Command("inner-two") + Arity = ArgumentArity.Zero + } + }, + new Command("inner-two") + { + new Argument("arg2") { - new Argument("arg2") - { - Arity = ArgumentArity.Zero - } + Arity = ArgumentArity.Zero } - }; + } + }; - var result = CommandLineParser.Parse(command, "outer inner-one inner-two"); + var result = CommandLineParser.Parse(command, "outer inner-one inner-two"); - result.CommandResult.Command.Name.Should().Be("inner-one"); - result.Errors.Count.Should().Be(1); + result.CommandResult.Command.Name.Should().Be("inner-one"); + result.Errors.Count.Should().Be(1); - var result2 = CommandLineParser.Parse(command, "outer inner-two inner-one"); + var result2 = CommandLineParser.Parse(command, "outer inner-two inner-one"); - result2.CommandResult.Command.Name.Should().Be("inner-two"); - result2.Errors.Count.Should().Be(1); - } + result2.CommandResult.Command.Name.Should().Be("inner-two"); + result2.Errors.Count.Should().Be(1); + } - [Fact] // https://github.com/dotnet/command-line-api/pull/2030#issuecomment-1400275332 - public void ParseResult_GetCompletions_returns_global_options_of_given_command_only() + [Fact] // https://github.com/dotnet/command-line-api/pull/2030#issuecomment-1400275332 + public void ParseResult_GetCompletions_returns_global_options_of_given_command_only() + { + var leafCommand = new Command("leafCommand") { - var leafCommand = new Command("leafCommand") - { - new Option("--one") { Description = "option one" }, - new Option("--two") { Description = "option two" } - }; + new Option("--one") { Description = "option one" }, + new Option("--two") { Description = "option two" } + }; - var midCommand1 = new Command("midCommand1") - { - leafCommand - }; - midCommand1.Options.Add(new Option("--three1") { Description = "option three 1", Recursive = true }); + var midCommand1 = new Command("midCommand1") + { + leafCommand + }; + midCommand1.Options.Add(new Option("--three1") { Description = "option three 1", Recursive = true }); - var midCommand2 = new Command("midCommand2") - { - leafCommand - }; - midCommand2.Options.Add(new Option("--three2") { Description = "option three 2", Recursive = true }); + var midCommand2 = new Command("midCommand2") + { + leafCommand + }; + midCommand2.Options.Add(new Option("--three2") { Description = "option three 2", Recursive = true }); - var rootCommand = new Command("root") - { - midCommand1, - midCommand2 - }; + var rootCommand = new Command("root") + { + midCommand1, + midCommand2 + }; - var result = CommandLineParser.Parse(rootCommand, "root midCommand2 leafCommand --"); + var result = CommandLineParser.Parse(rootCommand, "root midCommand2 leafCommand --"); - var completions = result.GetCompletions(); + var completions = result.GetCompletions(); - completions - .Select(item => item.Label) - .Should() - .BeEquivalentTo("--one", "--two", "--three2"); - } + completions + .Select(item => item.Label) + .Should() + .BeEquivalentTo("--one", "--two", "--three2"); + } - [Fact] - public void Handler_is_null_when_parsed_command_did_not_specify_handler() - => new RootCommand().Parse("").Action.Should().BeNull(); + [Fact] + public void Handler_is_null_when_parsed_command_did_not_specify_handler() + => new RootCommand().Parse("").Action.Should().BeNull(); - [Fact] - public void Handler_is_not_null_when_parsed_command_specified_handler() - { - bool handlerWasCalled = false; + [Fact] + public void Handler_is_not_null_when_parsed_command_specified_handler() + { + bool handlerWasCalled = false; - RootCommand command = new(); - command.SetAction((_) => handlerWasCalled = true); + RootCommand command = new(); + command.SetAction((_) => handlerWasCalled = true); - ParseResult parseResult = command.Parse(""); + ParseResult parseResult = command.Parse(""); - parseResult.Action.Should().NotBeNull(); - handlerWasCalled.Should().BeFalse(); + parseResult.Action.Should().NotBeNull(); + handlerWasCalled.Should().BeFalse(); - ((SynchronousCommandLineAction)parseResult.Action!).Invoke(null!).Should().Be(0); - handlerWasCalled.Should().BeTrue(); - } + ((SynchronousCommandLineAction)parseResult.Action!).Invoke(null!).Should().Be(0); + handlerWasCalled.Should().BeTrue(); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParserTests.DoubleDash.cs b/src/System.CommandLine.Tests/ParserTests.DoubleDash.cs index 6049a4a221..edce0e07aa 100644 --- a/src/System.CommandLine.Tests/ParserTests.DoubleDash.cs +++ b/src/System.CommandLine.Tests/ParserTests.DoubleDash.cs @@ -5,81 +5,80 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public partial class ParserTests { - public partial class ParserTests + public class DefaultDoubleDashBehavior { - public class DefaultDoubleDashBehavior + [Fact] // https://github.com/dotnet/command-line-api/issues/1238 + public void Subsequent_tokens_are_parsed_as_arguments_even_if_they_match_option_identifiers() { - [Fact] // https://github.com/dotnet/command-line-api/issues/1238 - public void Subsequent_tokens_are_parsed_as_arguments_even_if_they_match_option_identifiers() + var option = new Option("-o", "--one"); + var argument = new Argument("arg"); + var rootCommand = new RootCommand { - var option = new Option("-o", "--one"); - var argument = new Argument("arg"); - var rootCommand = new RootCommand - { - option, - argument - }; + option, + argument + }; - var result = rootCommand.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo"); + var result = rootCommand.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo"); - result.GetResult(option).Should().NotBeNull(); + result.GetResult(option).Should().NotBeNull(); - result.GetValue(option).Should().BeEquivalentTo("some stuff"); + result.GetValue(option).Should().BeEquivalentTo("some stuff"); - result.GetValue(argument).Should().BeEquivalentSequenceTo("-o", "--one", "-x", "-y", "-z", "-o:foo"); + result.GetValue(argument).Should().BeEquivalentSequenceTo("-o", "--one", "-x", "-y", "-z", "-o:foo"); - result.UnmatchedTokens.Should().BeEmpty(); - } + result.UnmatchedTokens.Should().BeEmpty(); + } - [Fact] - public void Unmatched_tokens_is_empty() + [Fact] + public void Unmatched_tokens_is_empty() + { + var option = new Option("-o", "--one"); + var argument = new Argument("arg"); + var rootCommand = new RootCommand { - var option = new Option("-o", "--one"); - var argument = new Argument("arg"); - var rootCommand = new RootCommand - { - option, - argument - }; + option, + argument + }; - var result = rootCommand.Parse("-o \"some stuff\" -- --one -x -y -z -o:foo"); + var result = rootCommand.Parse("-o \"some stuff\" -- --one -x -y -z -o:foo"); - result.UnmatchedTokens.Should().BeEmpty(); - } + result.UnmatchedTokens.Should().BeEmpty(); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1631 - public void No_errors_are_generated() + [Fact] // https://github.com/dotnet/command-line-api/issues/1631 + public void No_errors_are_generated() + { + var option = new Option("-o", "--one"); + var argument = new Argument("arg"); + var rootCommand = new RootCommand { - var option = new Option("-o", "--one"); - var argument = new Argument("arg"); - var rootCommand = new RootCommand - { - option, - argument - }; + option, + argument + }; - var result = rootCommand.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo"); + var result = rootCommand.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo"); - result.Errors.Should().BeEmpty(); - } + result.Errors.Should().BeEmpty(); + } - [Fact] - public void A_second_double_dash_is_parsed_as_an_argument() + [Fact] + public void A_second_double_dash_is_parsed_as_an_argument() + { + var argument = new Argument("arg"); + var rootCommand = new RootCommand { - var argument = new Argument("arg"); - var rootCommand = new RootCommand - { - argument - }; + argument + }; - var result = rootCommand.Parse("a b c -- -- d"); + var result = rootCommand.Parse("a b c -- -- d"); - var strings = result.GetValue(argument); + var strings = result.GetValue(argument); - strings.Should().BeEquivalentSequenceTo("a", "b", "c", "--", "d"); - } + strings.Should().BeEquivalentSequenceTo("a", "b", "c", "--", "d"); } } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParserTests.MultipleArguments.cs b/src/System.CommandLine.Tests/ParserTests.MultipleArguments.cs index 4f90bb8ea8..4a1887ca96 100644 --- a/src/System.CommandLine.Tests/ParserTests.MultipleArguments.cs +++ b/src/System.CommandLine.Tests/ParserTests.MultipleArguments.cs @@ -8,302 +8,301 @@ using FluentAssertions.Execution; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public partial class ParserTests { - public partial class ParserTests + public class MultipleArguments { - public class MultipleArguments + [Fact] + public void Multiple_arguments_can_differ_by_arity() { - [Fact] - public void Multiple_arguments_can_differ_by_arity() + var multipleArityArg = new Argument>("several") { - var multipleArityArg = new Argument>("several") - { - Arity = new ArgumentArity(3, 3), - }; + Arity = new ArgumentArity(3, 3), + }; - var singleArityArg = new Argument>("one") - { - Arity = ArgumentArity.ZeroOrMore, - }; + var singleArityArg = new Argument>("one") + { + Arity = ArgumentArity.ZeroOrMore, + }; - var command = new Command("the-command") - { - multipleArityArg, - singleArityArg - }; - - var result = command.Parse("1 2 3 4"); - - result.GetValue(multipleArityArg) - .Should() - .BeEquivalentSequenceTo("1", "2", "3"); - result.GetValue(singleArityArg) - .Should() - .BeEquivalentSequenceTo("4"); - } - - [Fact] - public void Multiple_arguments_can_differ_by_type() + var command = new Command("the-command") { - var stringArg = new Argument("the-string"); - var intArg = new Argument("the-int"); + multipleArityArg, + singleArityArg + }; + + var result = command.Parse("1 2 3 4"); + + result.GetValue(multipleArityArg) + .Should() + .BeEquivalentSequenceTo("1", "2", "3"); + result.GetValue(singleArityArg) + .Should() + .BeEquivalentSequenceTo("4"); + } - var command = new Command("the-command") - { - stringArg, - intArg - }; - - var result = command.Parse("1 2"); - - result.GetValue(stringArg).Should().Be("1"); - result.GetValue(intArg).Should().Be(2); - } - - [Theory] - [InlineData("--verbose one two three four five")] - [InlineData("one --verbose two three four five")] - [InlineData("one two --verbose three four five")] - [InlineData("one two three --verbose four five")] - [InlineData("one two three four --verbose five")] - [InlineData("one two three four five --verbose")] - [InlineData("--verbose true one two three four five")] - [InlineData("one --verbose true two three four five")] - [InlineData("one two --verbose true three four five")] - [InlineData("one two three --verbose true four five")] - [InlineData("one two three four --verbose true five")] - [InlineData("one two three four five --verbose true")] - public void When_multiple_arguments_are_present_then_their_order_relative_to_sibling_options_is_not_significant(string commandLine) + [Fact] + public void Multiple_arguments_can_differ_by_type() + { + var stringArg = new Argument("the-string"); + var intArg = new Argument("the-int"); + + var command = new Command("the-command") { - var first = new Argument("first"); - var second = new Argument("second"); - var third = new Argument("third"); - var verbose = new Option("--verbose"); + stringArg, + intArg + }; - var command = new Command("the-command") - { - first, - second, - third, - verbose - }; - - var parseResult = command.Parse(commandLine); - - parseResult - .GetValue(first) - .Should() - .Be("one"); - - parseResult - .GetValue(second) - .Should() - .Be("two"); - - parseResult - .GetValue(third) - .Should() - .BeEquivalentSequenceTo("three", "four", "five"); - - parseResult - .GetValue(verbose) - .Should() - .BeTrue(); - } - - [Fact] - public void Multiple_arguments_of_unspecified_type_are_parsed_correctly() + var result = command.Parse("1 2"); + + result.GetValue(stringArg).Should().Be("1"); + result.GetValue(intArg).Should().Be(2); + } + + [Theory] + [InlineData("--verbose one two three four five")] + [InlineData("one --verbose two three four five")] + [InlineData("one two --verbose three four five")] + [InlineData("one two three --verbose four five")] + [InlineData("one two three four --verbose five")] + [InlineData("one two three four five --verbose")] + [InlineData("--verbose true one two three four five")] + [InlineData("one --verbose true two three four five")] + [InlineData("one two --verbose true three four five")] + [InlineData("one two three --verbose true four five")] + [InlineData("one two three four --verbose true five")] + [InlineData("one two three four five --verbose true")] + public void When_multiple_arguments_are_present_then_their_order_relative_to_sibling_options_is_not_significant(string commandLine) + { + var first = new Argument("first"); + var second = new Argument("second"); + var third = new Argument("third"); + var verbose = new Option("--verbose"); + + var command = new Command("the-command") { - var sourceArg = new Argument("source"); - var destinationArg = new Argument("destination"); - var root = new RootCommand - { - sourceArg, - destinationArg - }; + first, + second, + third, + verbose + }; + + var parseResult = command.Parse(commandLine); + + parseResult + .GetValue(first) + .Should() + .Be("one"); + + parseResult + .GetValue(second) + .Should() + .Be("two"); + + parseResult + .GetValue(third) + .Should() + .BeEquivalentSequenceTo("three", "four", "five"); + + parseResult + .GetValue(verbose) + .Should() + .BeTrue(); + } + + [Fact] + public void Multiple_arguments_of_unspecified_type_are_parsed_correctly() + { + var sourceArg = new Argument("source"); + var destinationArg = new Argument("destination"); + var root = new RootCommand + { + sourceArg, + destinationArg + }; - var result = root.Parse("src.txt dest.txt"); + var result = root.Parse("src.txt dest.txt"); - result.GetResult(sourceArg) - .GetValueOrDefault() - .Should() - .Be("src.txt"); + result.GetResult(sourceArg) + .GetValueOrDefault() + .Should() + .Be("src.txt"); - result.GetResult(destinationArg) - .GetValueOrDefault() - .Should() - .Be("dest.txt"); - } + result.GetResult(destinationArg) + .GetValueOrDefault() + .Should() + .Be("dest.txt"); + } - [Fact] - public void When_multiple_arguments_are_defined_but_not_provided_then_option_parses_correctly() + [Fact] + public void When_multiple_arguments_are_defined_but_not_provided_then_option_parses_correctly() + { + var option = new Option("-e"); + var command = new Command("the-command") { - var option = new Option("-e"); - var command = new Command("the-command") - { - option, - new Argument("arg1"), - new Argument("arg2") - }; + option, + new Argument("arg1"), + new Argument("arg2") + }; - var result = command.Parse("-e foo"); + var result = command.Parse("-e foo"); - var optionResult = result.GetValue(option); + var optionResult = result.GetValue(option); - optionResult.Should().Be("foo"); - } + optionResult.Should().Be("foo"); + } + + [Fact] + public void Tokens_that_cannot_be_converted_by_multiple_arity_argument_flow_to_next_multiple_arity_argument() + { + var ints = new Argument("ints"); + var strings = new Argument("strings"); - [Fact] - public void Tokens_that_cannot_be_converted_by_multiple_arity_argument_flow_to_next_multiple_arity_argument() + var root = new RootCommand { - var ints = new Argument("ints"); - var strings = new Argument("strings"); + ints, + strings + }; - var root = new RootCommand - { - ints, - strings - }; + var result = root.Parse("1 2 3 one two"); - var result = root.Parse("1 2 3 one two"); + var _ = new AssertionScope(); - var _ = new AssertionScope(); + result.GetValue(ints) + .Should() + .BeEquivalentTo(new[] { 1, 2, 3 }, + options => options.WithStrictOrdering()); - result.GetValue(ints) - .Should() - .BeEquivalentTo(new[] { 1, 2, 3 }, - options => options.WithStrictOrdering()); + result.GetValue(strings) + .Should() + .BeEquivalentTo(new[] { "one", "two" }, + options => options.WithStrictOrdering()); + } - result.GetValue(strings) - .Should() - .BeEquivalentTo(new[] { "one", "two" }, - options => options.WithStrictOrdering()); - } + [Fact] + public void Tokens_that_cannot_be_converted_by_multiple_arity_argument_flow_to_next_single_arity_argument() + { + var ints = new Argument("arg1"); + var strings = new Argument("arg2"); - [Fact] - public void Tokens_that_cannot_be_converted_by_multiple_arity_argument_flow_to_next_single_arity_argument() + var root = new RootCommand { - var ints = new Argument("arg1"); - var strings = new Argument("arg2"); + ints, + strings + }; - var root = new RootCommand - { - ints, - strings - }; + var result = root.Parse("1 2 3 four five"); - var result = root.Parse("1 2 3 four five"); + var _ = new AssertionScope(); - var _ = new AssertionScope(); + result.GetValue(ints) + .Should() + .BeEquivalentTo(new[] { 1, 2, 3 }, + options => options.WithStrictOrdering()); - result.GetValue(ints) - .Should() - .BeEquivalentTo(new[] { 1, 2, 3 }, - options => options.WithStrictOrdering()); + result.GetValue(strings) + .Should() + .Be("four"); - result.GetValue(strings) - .Should() - .Be("four"); - - result.UnmatchedTokens - .Should() - .ContainSingle() - .Which - .Should() - .Be("five"); - } + result.UnmatchedTokens + .Should() + .ContainSingle() + .Which + .Should() + .Be("five"); + } - [Fact] - public void Unsatisfied_subsequent_argument_with_min_arity_0_parses_as_default_value() + [Fact] + public void Unsatisfied_subsequent_argument_with_min_arity_0_parses_as_default_value() + { + var arg1 = new Argument("arg1") { - var arg1 = new Argument("arg1") - { - Arity = ArgumentArity.ExactlyOne - }; - var arg2 = new Argument("arg2") - { - Arity = ArgumentArity.ZeroOrOne, - DefaultValueFactory = (_) => "the-default" - }; - var rootCommand = new RootCommand - { - arg1, - arg2, - }; + Arity = ArgumentArity.ExactlyOne + }; + var arg2 = new Argument("arg2") + { + Arity = ArgumentArity.ZeroOrOne, + DefaultValueFactory = (_) => "the-default" + }; + var rootCommand = new RootCommand + { + arg1, + arg2, + }; - var result = rootCommand.Parse("value-1"); + var result = rootCommand.Parse("value-1"); - result.GetValue(arg1).Should().Be("value-1"); - result.GetValue(arg2).Should().Be("the-default"); - } + result.GetValue(arg1).Should().Be("value-1"); + result.GetValue(arg2).Should().Be("the-default"); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1403 - public void Unsatisfied_subsequent_argument_with_min_arity_1_parses_as_default_value() + [Fact] // https://github.com/dotnet/command-line-api/issues/1403 + public void Unsatisfied_subsequent_argument_with_min_arity_1_parses_as_default_value() + { + Argument arg1 = new(name: "arg1"); + Argument arg2 = new(name: "arg2") { - Argument arg1 = new(name: "arg1"); - Argument arg2 = new(name: "arg2") - { - DefaultValueFactory = (_) => "the-default" - }; + DefaultValueFactory = (_) => "the-default" + }; - var rootCommand = new RootCommand - { - arg1, - arg2, - }; + var rootCommand = new RootCommand + { + arg1, + arg2, + }; - var result = rootCommand.Parse(""); + var result = rootCommand.Parse(""); - result.GetResult(arg1).Should().NotBeNull(); - result.GetValue(arg2).Should().Be("the-default"); - } + result.GetResult(arg1).Should().NotBeNull(); + result.GetValue(arg2).Should().Be("the-default"); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1395 - public void When_subsequent_argument_with_ZeroOrOne_arity_is_not_provided_then_parse_is_correct() + [Fact] // https://github.com/dotnet/command-line-api/issues/1395 + public void When_subsequent_argument_with_ZeroOrOne_arity_is_not_provided_then_parse_is_correct() + { + var argument1 = new Argument("arg1"); + var rootCommand = new RootCommand { - var argument1 = new Argument("arg1"); - var rootCommand = new RootCommand + argument1, + new Argument("arg2") { - argument1, - new Argument("arg2") - { - Arity = ArgumentArity.ZeroOrOne - }, - }; - - var result = rootCommand.Parse("one"); - - result.Errors.Should().BeEmpty(); - - result.GetValue(argument1).Should().Be("one"); - } - - [Theory] // https://github.com/dotnet/command-line-api/issues/1711 - [InlineData("")] - [InlineData("a")] - [InlineData("a b")] - [InlineData("a b c")] - public void When_there_are_not_enough_tokens_for_all_arguments_then_the_correct_number_of_errors_is_reported( - string providedArgs) + Arity = ArgumentArity.ZeroOrOne + }, + }; + + var result = rootCommand.Parse("one"); + + result.Errors.Should().BeEmpty(); + + result.GetValue(argument1).Should().Be("one"); + } + + [Theory] // https://github.com/dotnet/command-line-api/issues/1711 + [InlineData("")] + [InlineData("a")] + [InlineData("a b")] + [InlineData("a b c")] + public void When_there_are_not_enough_tokens_for_all_arguments_then_the_correct_number_of_errors_is_reported( + string providedArgs) + { + var command = new Command("command") { - var command = new Command("command") - { - new Argument("arg1"), - new Argument("arg2"), - new Argument("arg3"), - new Argument("arg4") - }; - - var result = CommandLineParser.Parse(command, providedArgs); - - result - .Errors - .Count - .Should() - .Be(4 - providedArgs.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length); - } + new Argument("arg1"), + new Argument("arg2"), + new Argument("arg3"), + new Argument("arg4") + }; + + var result = CommandLineParser.Parse(command, providedArgs); + + result + .Errors + .Count + .Should() + .Be(4 - providedArgs.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length); } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParserTests.MultiplePositions.cs b/src/System.CommandLine.Tests/ParserTests.MultiplePositions.cs index 4ec7352a62..0f37d31d15 100644 --- a/src/System.CommandLine.Tests/ParserTests.MultiplePositions.cs +++ b/src/System.CommandLine.Tests/ParserTests.MultiplePositions.cs @@ -6,185 +6,184 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public partial class ParserTests { - public partial class ParserTests + public class MultiplePositions { - public class MultiplePositions + [Theory] + [InlineData("outer xyz")] + [InlineData("outer inner xyz")] + public void An_argument_can_be_specified_in_more_than_one_position(string commandLine) { - [Theory] - [InlineData("outer xyz")] - [InlineData("outer inner xyz")] - public void An_argument_can_be_specified_in_more_than_one_position(string commandLine) - { - var argument = new Argument("the-argument"); + var argument = new Argument("the-argument"); - var command = new Command("outer") + var command = new Command("outer") + { + new Command("inner") { - new Command("inner") - { - argument - }, argument - }; + }, + argument + }; + + var parseResult = command.Parse(commandLine); - var parseResult = command.Parse(commandLine); + var argumentResult = parseResult.GetResult(argument); - var argumentResult = parseResult.GetResult(argument); + argumentResult.Should().NotBeNull(); - argumentResult.Should().NotBeNull(); + argumentResult + .GetValueOrDefault() + .Should() + .Be("xyz"); + } - argumentResult - .GetValueOrDefault() - .Should() - .Be("xyz"); - } + [Theory] + [InlineData("outer xyz inner")] + [InlineData("outer inner xyz")] + public void When_an_argument_is_shared_between_an_outer_and_inner_command_then_specifying_in_one_does_not_result_in_error_on_other(string commandLine) + { + var argument = new Argument("the-argument"); - [Theory] - [InlineData("outer xyz inner")] - [InlineData("outer inner xyz")] - public void When_an_argument_is_shared_between_an_outer_and_inner_command_then_specifying_in_one_does_not_result_in_error_on_other(string commandLine) + var command = new Command("outer") { - var argument = new Argument("the-argument"); - - var command = new Command("outer") + new Command("inner") { - new Command("inner") - { - argument - }, argument - }; + }, + argument + }; - var parseResult = command.Parse(commandLine); + var parseResult = command.Parse(commandLine); - parseResult.Errors.Should().BeEmpty(); - } + parseResult.Errors.Should().BeEmpty(); + } - [Theory] - [InlineData("outer --the-option xyz")] - [InlineData("outer inner --the-option xyz")] - public void An_option_can_be_specified_in_more_than_one_position(string commandLine) - { - var option = new Option("--the-option"); + [Theory] + [InlineData("outer --the-option xyz")] + [InlineData("outer inner --the-option xyz")] + public void An_option_can_be_specified_in_more_than_one_position(string commandLine) + { + var option = new Option("--the-option"); - var command = new Command("outer") + var command = new Command("outer") + { + new Command("inner") { - new Command("inner") - { - option - }, option - }; + }, + option + }; - var parseResult = command.Parse(commandLine); + var parseResult = command.Parse(commandLine); - var optionResult = parseResult.GetResult(option); + var optionResult = parseResult.GetResult(option); - optionResult.Should().NotBeNull(); + optionResult.Should().NotBeNull(); - optionResult - .GetValueOrDefault() - .Should() - .Be("xyz"); - } + optionResult + .GetValueOrDefault() + .Should() + .Be("xyz"); + } - [Theory] - [InlineData("outer --the-option xyz inner")] - [InlineData("outer inner --the-option xyz")] - public void When_an_option_is_shared_between_an_outer_and_inner_command_then_specifying_in_one_does_not_result_in_error_on_other(string commandLine) - { - var option = new Option("--the-option"); + [Theory] + [InlineData("outer --the-option xyz inner")] + [InlineData("outer inner --the-option xyz")] + public void When_an_option_is_shared_between_an_outer_and_inner_command_then_specifying_in_one_does_not_result_in_error_on_other(string commandLine) + { + var option = new Option("--the-option"); - var command = new Command("outer") + var command = new Command("outer") + { + new Command("inner") { - new Command("inner") - { - option - }, option - }; + }, + option + }; - var parseResult = command.Parse(commandLine); + var parseResult = command.Parse(commandLine); - parseResult.Errors.Should().BeEmpty(); - } + parseResult.Errors.Should().BeEmpty(); + } - [Theory] - [InlineData("outer inner1 reused --the-option 123", "inner1")] - [InlineData("outer inner2 reused --the-option 456", "inner2")] - public void A_command_can_be_specified_in_more_than_one_position( - string commandLine, - string expectedParent) - { - var reusedCommand = new Command("reused"); - reusedCommand.SetAction((_) => { }); - reusedCommand.Add(new Option("--the-option")); + [Theory] + [InlineData("outer inner1 reused --the-option 123", "inner1")] + [InlineData("outer inner2 reused --the-option 456", "inner2")] + public void A_command_can_be_specified_in_more_than_one_position( + string commandLine, + string expectedParent) + { + var reusedCommand = new Command("reused"); + reusedCommand.SetAction((_) => { }); + reusedCommand.Add(new Option("--the-option")); - var outer = new Command("outer") - { - new Command("inner1") - { - reusedCommand - }, - new Command("inner2") - { - reusedCommand - } - }; - - var result = outer.Parse(commandLine); - - result.Errors.Should().BeEmpty(); - result.CommandResult - .Parent - .Should() - .BeOfType() - .Which - .Command - .Name - .Should() - .Be(expectedParent); - } - - [Fact] - public void An_option_can_have_multiple_parents_with_the_same_name() + var outer = new Command("outer") { - var option = new Option("--the-option"); - - var sprocket = new Command("sprocket") + new Command("inner1") + { + reusedCommand + }, + new Command("inner2") { - new Command("add") - { - option - } - }; + reusedCommand + } + }; + + var result = outer.Parse(commandLine); + + result.Errors.Should().BeEmpty(); + result.CommandResult + .Parent + .Should() + .BeOfType() + .Which + .Command + .Name + .Should() + .Be(expectedParent); + } - var widget = new Command("widget") + [Fact] + public void An_option_can_have_multiple_parents_with_the_same_name() + { + var option = new Option("--the-option"); + + var sprocket = new Command("sprocket") + { + new Command("add") { - new Command("add") - { - option - } - }; + option + } + }; - var root = new RootCommand + var widget = new Command("widget") + { + new Command("add") { - sprocket, - widget - }; - - option.Parents - .Select(p => p.Name) - .Should() - .BeEquivalentTo("add", "add"); - - option.Parents - .SelectMany(p => p.Parents) - .Select(p => p.Name) - .Should() - .BeEquivalentTo("sprocket", "widget"); - } + option + } + }; + + var root = new RootCommand + { + sprocket, + widget + }; + + option.Parents + .Select(p => p.Name) + .Should() + .BeEquivalentTo("add", "add"); + + option.Parents + .SelectMany(p => p.Parents) + .Select(p => p.Name) + .Should() + .BeEquivalentTo("sprocket", "widget"); } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParserTests.cs b/src/System.CommandLine.Tests/ParserTests.cs index 410c67bc82..e4f682d44a 100644 --- a/src/System.CommandLine.Tests/ParserTests.cs +++ b/src/System.CommandLine.Tests/ParserTests.cs @@ -11,1644 +11,1643 @@ using FluentAssertions.Common; using Xunit; -namespace System.CommandLine.Tests -{ - public partial class ParserTests - { - private T GetValue(ParseResult parseResult, Option option) - => parseResult.GetValue(option); +namespace System.CommandLine.Tests; - private T GetValue(ParseResult parseResult, Argument argument) - => parseResult.GetValue(argument); - - [Fact] - public void An_option_can_be_checked_by_object_instance() - { - var option1 = new Option("--option1"); - var option2 = new Option("--option2"); +public partial class ParserTests +{ + private T GetValue(ParseResult parseResult, Option option) + => parseResult.GetValue(option); - var result = new RootCommand { option1, option2 }.Parse("--option1"); + private T GetValue(ParseResult parseResult, Argument argument) + => parseResult.GetValue(argument); - result.GetResult(option1).Should().NotBeNull(); - result.GetResult(option2).Should().BeNull(); - } + [Fact] + public void An_option_can_be_checked_by_object_instance() + { + var option1 = new Option("--option1"); + var option2 = new Option("--option2"); - [Fact] - public void Two_options_are_parsed_correctly() - { - var optionOne = new Option("-o", "--one"); + var result = new RootCommand { option1, option2 }.Parse("--option1"); - var optionTwo = new Option("-t", "--two"); + result.GetResult(option1).Should().NotBeNull(); + result.GetResult(option2).Should().BeNull(); + } - var result = new RootCommand { optionOne, optionTwo }.Parse("-o -t"); + [Fact] + public void Two_options_are_parsed_correctly() + { + var optionOne = new Option("-o", "--one"); - result.GetResult(optionOne).Should().NotBeNull(); - result.GetResult(optionTwo).Should().NotBeNull(); - } + var optionTwo = new Option("-t", "--two"); - [Theory] - [InlineData("-")] - [InlineData("/")] - public void When_a_token_is_just_a_prefix_then_an_error_is_returned(string prefix) - { - var result = new RootCommand().Parse(prefix); + var result = new RootCommand { optionOne, optionTwo }.Parse("-o -t"); - result.Errors - .Select(e => e.Message) - .Should() - .Contain(LocalizationResources.UnrecognizedCommandOrArgument(prefix)); - } + result.GetResult(optionOne).Should().NotBeNull(); + result.GetResult(optionTwo).Should().NotBeNull(); + } - [Fact] - public void Short_form_options_can_be_specified_using_equals_delimiter() - { - var option = new Option("-x"); + [Theory] + [InlineData("-")] + [InlineData("/")] + public void When_a_token_is_just_a_prefix_then_an_error_is_returned(string prefix) + { + var result = new RootCommand().Parse(prefix); - var result = new RootCommand { option }.Parse("-x=some-value"); + result.Errors + .Select(e => e.Message) + .Should() + .Contain(LocalizationResources.UnrecognizedCommandOrArgument(prefix)); + } - result.Errors.Should().BeEmpty(); + [Fact] + public void Short_form_options_can_be_specified_using_equals_delimiter() + { + var option = new Option("-x"); - result.GetResult(option).Tokens.Should().ContainSingle(a => a.Value == "some-value"); - } + var result = new RootCommand { option }.Parse("-x=some-value"); - [Fact] - public void Long_form_options_can_be_specified_using_equals_delimiter() - { - var option = new Option("--hello"); + result.Errors.Should().BeEmpty(); - var result = new RootCommand { option }.Parse("--hello=there"); + result.GetResult(option).Tokens.Should().ContainSingle(a => a.Value == "some-value"); + } - result.Errors.Should().BeEmpty(); + [Fact] + public void Long_form_options_can_be_specified_using_equals_delimiter() + { + var option = new Option("--hello"); - result.GetResult(option).Tokens.Should().ContainSingle(a => a.Value == "there"); - } + var result = new RootCommand { option }.Parse("--hello=there"); - [Fact] - public void Short_form_options_can_be_specified_using_colon_delimiter() - { - var option = new Option("-x"); + result.Errors.Should().BeEmpty(); - var result = new RootCommand { option }.Parse("-x:some-value"); + result.GetResult(option).Tokens.Should().ContainSingle(a => a.Value == "there"); + } - result.Errors.Should().BeEmpty(); + [Fact] + public void Short_form_options_can_be_specified_using_colon_delimiter() + { + var option = new Option("-x"); - result.GetResult(option).Tokens.Should().ContainSingle(a => a.Value == "some-value"); - } + var result = new RootCommand { option }.Parse("-x:some-value"); - [Fact] - public void Long_form_options_can_be_specified_using_colon_delimiter() - { - var option = new Option("--hello"); + result.Errors.Should().BeEmpty(); - var result = new RootCommand { option }.Parse("--hello:there"); + result.GetResult(option).Tokens.Should().ContainSingle(a => a.Value == "some-value"); + } - result.Errors.Should().BeEmpty(); + [Fact] + public void Long_form_options_can_be_specified_using_colon_delimiter() + { + var option = new Option("--hello"); - result.GetResult(option).Tokens.Should().ContainSingle(a => a.Value == "there"); - } + var result = new RootCommand { option }.Parse("--hello:there"); - [Fact] - public void Option_short_forms_can_be_bundled() - { - var command = new Command("the-command"); - command.Options.Add(new Option("-x")); - command.Options.Add(new Option("-y")); - command.Options.Add(new Option("-z")); + result.Errors.Should().BeEmpty(); - var result = command.Parse("the-command -xyz"); + result.GetResult(option).Tokens.Should().ContainSingle(a => a.Value == "there"); + } - result.CommandResult - .Children - .Select(o => ((OptionResult)o).Option.Name) - .Should() - .BeEquivalentTo("-x", "-y", "-z"); - } + [Fact] + public void Option_short_forms_can_be_bundled() + { + var command = new Command("the-command"); + command.Options.Add(new Option("-x")); + command.Options.Add(new Option("-y")); + command.Options.Add(new Option("-z")); + + var result = command.Parse("the-command -xyz"); + + result.CommandResult + .Children + .Select(o => ((OptionResult)o).Option.Name) + .Should() + .BeEquivalentTo("-x", "-y", "-z"); + } - [Fact] - public void Options_short_forms_do_not_get_unbundled_if_unbundling_is_turned_off() + [Fact] + public void Options_short_forms_do_not_get_unbundled_if_unbundling_is_turned_off() + { + RootCommand rootCommand = new RootCommand() { - RootCommand rootCommand = new RootCommand() - { - new Command("the-command") - { - new Option("-x"), - new Option("-y"), - new Option("-z") - } - }; - - CommandLineConfiguration configuration = new (rootCommand) + new Command("the-command") { - EnablePosixBundling = false - }; + new Option("-x"), + new Option("-y"), + new Option("-z") + } + }; - var result = rootCommand.Parse("the-command -xyz", configuration); + CommandLineConfiguration configuration = new (rootCommand) + { + EnablePosixBundling = false + }; - result.UnmatchedTokens - .Should() - .BeEquivalentTo("-xyz"); - } + var result = rootCommand.Parse("the-command -xyz", configuration); - [Fact] - public void Option_long_forms_do_not_get_unbundled() - { - Command command = - new Command("the-command") - { - new Option("--xyz"), - new Option("-x"), - new Option("-y"), - new Option("-z") - }; - - var result = command.Parse("the-command --xyz"); - - result.CommandResult - .Children - .OfType() - .Where(r => !r.Implicit) - .Select(o => o.Option.Name) - .Should() - .BeEquivalentTo("--xyz"); - } + result.UnmatchedTokens + .Should() + .BeEquivalentTo("-xyz"); + } - [Fact] - public void Options_do_not_get_unbundled_unless_all_resulting_options_would_be_valid_for_the_current_command() - { - var outer = new Command("outer"); - outer.Options.Add(new Option("-a")); - var inner = new Command("inner") + [Fact] + public void Option_long_forms_do_not_get_unbundled() + { + Command command = + new Command("the-command") { - new Argument("arg") + new Option("--xyz"), + new Option("-x"), + new Option("-y"), + new Option("-z") }; - inner.Options.Add(new Option("-b")); - inner.Options.Add(new Option("-c")); - outer.Subcommands.Add(inner); - ParseResult result = outer.Parse("outer inner -abc"); + var result = command.Parse("the-command --xyz"); - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("-abc"); - } + result.CommandResult + .Children + .OfType() + .Where(r => !r.Implicit) + .Select(o => o.Option.Name) + .Should() + .BeEquivalentTo("--xyz"); + } - [Fact] - public void Required_option_arguments_are_not_unbundled() - { - var optionA = new Option("-a"); - var optionB = new Option("-b"); - var optionC = new Option("-c"); + [Fact] + public void Options_do_not_get_unbundled_unless_all_resulting_options_would_be_valid_for_the_current_command() + { + var outer = new Command("outer"); + outer.Options.Add(new Option("-a")); + var inner = new Command("inner") + { + new Argument("arg") + }; + inner.Options.Add(new Option("-b")); + inner.Options.Add(new Option("-c")); + outer.Subcommands.Add(inner); + + ParseResult result = outer.Parse("outer inner -abc"); + + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("-abc"); + } - var command = new RootCommand - { - optionA, - optionB, - optionC - }; + [Fact] + public void Required_option_arguments_are_not_unbundled() + { + var optionA = new Option("-a"); + var optionB = new Option("-b"); + var optionC = new Option("-c"); - var result = command.Parse("-a -bc"); + var command = new RootCommand + { + optionA, + optionB, + optionC + }; - result.GetResult(optionA) - .Tokens - .Should() - .ContainSingle(t => t.Value == "-bc"); - } + var result = command.Parse("-a -bc"); + + result.GetResult(optionA) + .Tokens + .Should() + .ContainSingle(t => t.Value == "-bc"); + } - [Fact] - public void Last_bundled_option_can_accept_argument_with_no_separator() + [Fact] + public void Last_bundled_option_can_accept_argument_with_no_separator() + { + var optionA = new Option("-a"); + var optionB = new Option("-b") { Arity = ArgumentArity.ZeroOrOne }; + var optionC = new Option("-c") { Arity = ArgumentArity.ExactlyOne }; + + var command = new RootCommand { - var optionA = new Option("-a"); - var optionB = new Option("-b") { Arity = ArgumentArity.ZeroOrOne }; - var optionC = new Option("-c") { Arity = ArgumentArity.ExactlyOne }; + optionA, + optionB, + optionC + }; - var command = new RootCommand - { - optionA, - optionB, - optionC - }; + var result = command.Parse("-abcvalue"); + result.GetResult(optionA).Should().NotBeNull(); + result.GetResult(optionB).Should().NotBeNull(); - var result = command.Parse("-abcvalue"); - result.GetResult(optionA).Should().NotBeNull(); - result.GetResult(optionB).Should().NotBeNull(); + result.GetResult(optionC) + .Tokens + .Should() + .ContainSingle(t => t.Value == "value"); + } - result.GetResult(optionC) - .Tokens - .Should() - .ContainSingle(t => t.Value == "value"); - } + [Fact] + public void Last_bundled_option_can_accept_argument_with_equals_separator() + { + var optionA = new Option("-a"); + var optionB = new Option("-b") { Arity = ArgumentArity.ZeroOrOne }; + var optionC = new Option("-c") { Arity = ArgumentArity.ExactlyOne }; - [Fact] - public void Last_bundled_option_can_accept_argument_with_equals_separator() + var command = new RootCommand { - var optionA = new Option("-a"); - var optionB = new Option("-b") { Arity = ArgumentArity.ZeroOrOne }; - var optionC = new Option("-c") { Arity = ArgumentArity.ExactlyOne }; + optionA, + optionB, + optionC + }; - var command = new RootCommand - { - optionA, - optionB, - optionC - }; + var result = command.Parse("-abc=value"); + result.GetResult(optionA).Should().NotBeNull(); + result.GetResult(optionB).Should().NotBeNull(); - var result = command.Parse("-abc=value"); - result.GetResult(optionA).Should().NotBeNull(); - result.GetResult(optionB).Should().NotBeNull(); + result.GetResult(optionC) + .Tokens + .Should() + .ContainSingle(t => t.Value == "value"); + } - result.GetResult(optionC) - .Tokens - .Should() - .ContainSingle(t => t.Value == "value"); - } + [Fact] + public void Last_bundled_option_can_accept_argument_with_colon_separator() + { + var optionA = new Option("-a"); + var optionB = new Option("-b") { Arity = ArgumentArity.ZeroOrOne }; + var optionC = new Option("-c") { Arity = ArgumentArity.ExactlyOne }; - [Fact] - public void Last_bundled_option_can_accept_argument_with_colon_separator() + var command = new RootCommand { - var optionA = new Option("-a"); - var optionB = new Option("-b") { Arity = ArgumentArity.ZeroOrOne }; - var optionC = new Option("-c") { Arity = ArgumentArity.ExactlyOne }; + optionA, + optionB, + optionC + }; - var command = new RootCommand - { - optionA, - optionB, - optionC - }; + var result = command.Parse("-abc:value"); + result.GetResult(optionA).Should().NotBeNull(); + result.GetResult(optionB).Should().NotBeNull(); - var result = command.Parse("-abc:value"); - result.GetResult(optionA).Should().NotBeNull(); - result.GetResult(optionB).Should().NotBeNull(); + result.GetResult(optionC) + .Tokens + .Should() + .ContainSingle(t => t.Value == "value"); + } - result.GetResult(optionC) - .Tokens - .Should() - .ContainSingle(t => t.Value == "value"); - } + [Fact] + public void Invalid_char_in_bundle_causes_rest_to_be_interpreted_as_value() + { + var optionA = new Option("-a"); + var optionB = new Option("-b") { Arity = ArgumentArity.ZeroOrOne }; + var optionC = new Option("-c") { Arity = ArgumentArity.ExactlyOne }; - [Fact] - public void Invalid_char_in_bundle_causes_rest_to_be_interpreted_as_value() + var command = new RootCommand { - var optionA = new Option("-a"); - var optionB = new Option("-b") { Arity = ArgumentArity.ZeroOrOne }; - var optionC = new Option("-c") { Arity = ArgumentArity.ExactlyOne }; + optionA, + optionB, + optionC + }; - var command = new RootCommand - { - optionA, - optionB, - optionC - }; + var result = command.Parse("-abvcalue"); + result.GetResult(optionA).Should().NotBeNull(); + result.GetResult(optionB).Should().NotBeNull(); - var result = command.Parse("-abvcalue"); - result.GetResult(optionA).Should().NotBeNull(); - result.GetResult(optionB).Should().NotBeNull(); + result.GetResult(optionB) + .Tokens + .Should() + .ContainSingle(t => t.Value == "vcalue"); - result.GetResult(optionB) - .Tokens - .Should() - .ContainSingle(t => t.Value == "vcalue"); + result.GetResult(optionC).Should().BeNull(); + } - result.GetResult(optionC).Should().BeNull(); - } + [Fact] + public void Parser_root_Options_can_be_specified_multiple_times_and_their_arguments_are_collated() + { + var animalsOption = new Option("-a", "--animals"); + var vegetablesOption = new Option("-v", "--vegetables"); + var parser = new RootCommand + { + animalsOption, + vegetablesOption + }; + + var result = parser.Parse("-a cat -v carrot -a dog"); + + result.GetResult(animalsOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("cat", "dog"); + + result.GetResult(vegetablesOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("carrot"); + } - [Fact] - public void Parser_root_Options_can_be_specified_multiple_times_and_their_arguments_are_collated() - { - var animalsOption = new Option("-a", "--animals"); - var vegetablesOption = new Option("-v", "--vegetables"); - var parser = new RootCommand - { + [Fact] + public void Options_can_be_specified_multiple_times_and_their_arguments_are_collated() + { + var animalsOption = new Option("-a", "--animals"); + animalsOption.AcceptOnlyFromAmong("dog", "cat", "sheep"); + var vegetablesOption = new Option("-v", "--vegetables"); + Command command = + new Command("the-command") { animalsOption, vegetablesOption }; - var result = parser.Parse("-a cat -v carrot -a dog"); - - result.GetResult(animalsOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("cat", "dog"); + var result = command.Parse("the-command -a cat -v carrot -a dog"); - result.GetResult(vegetablesOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("carrot"); - } - - [Fact] - public void Options_can_be_specified_multiple_times_and_their_arguments_are_collated() - { - var animalsOption = new Option("-a", "--animals"); - animalsOption.AcceptOnlyFromAmong("dog", "cat", "sheep"); - var vegetablesOption = new Option("-v", "--vegetables"); - Command command = - new Command("the-command") { - animalsOption, - vegetablesOption - }; - - var result = command.Parse("the-command -a cat -v carrot -a dog"); - - result.GetResult(animalsOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("cat", "dog"); - - result.GetResult(vegetablesOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("carrot"); - } + result.GetResult(animalsOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("cat", "dog"); - [Fact] - public void When_an_option_is_not_respecified_but_limit_is_reached_then_the_following_token_is_considered_an_argument_to_the_parent_command() - { - var animalsOption = new Option("-a", "--animals"); + result.GetResult(vegetablesOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("carrot"); + } - var vegetablesOption = new Option("-v", "--vegetables"); + [Fact] + public void When_an_option_is_not_respecified_but_limit_is_reached_then_the_following_token_is_considered_an_argument_to_the_parent_command() + { + var animalsOption = new Option("-a", "--animals"); - Command command = - new Command("the-command") - { - animalsOption, - vegetablesOption, - new Argument("arg") - }; - - var result = command.Parse("the-command -a cat some-arg -v carrot"); - - result.GetResult(animalsOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("cat"); - - result.GetResult(vegetablesOption) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("carrot"); - - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("some-arg"); - } + var vegetablesOption = new Option("-v", "--vegetables"); - [Fact] - public void Command_with_multiple_options_is_parsed_correctly() - { - var command = new Command("outer") + Command command = + new Command("the-command") { - new Option("--inner1"), - new Option("--inner2") + animalsOption, + vegetablesOption, + new Argument("arg") }; - var result = command.Parse("outer --inner1 argument1 --inner2 argument2"); - - result.CommandResult - .Children - .Should() - .ContainSingle(o => - ((OptionResult)o).Option.Name == "--inner1" && - o.Tokens.Single().Value == "argument1"); - result.CommandResult - .Children - .Should() - .ContainSingle(o => - ((OptionResult)o).Option.Name == "--inner2" && - o.Tokens.Single().Value == "argument2"); - } + var result = command.Parse("the-command -a cat some-arg -v carrot"); - [Fact] - public void Relative_order_of_arguments_and_options_within_a_command_does_not_matter() - { - var command = new Command("move") - { - new Argument("arg"), - new Option("-X") - }; + result.GetResult(animalsOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("cat"); - // option before args - ParseResult result1 = command.Parse( - "move -X the-arg-for-option-x ARG1 ARG2"); - - // option between two args - ParseResult result2 = command.Parse( - "move ARG1 -X the-arg-for-option-x ARG2"); - - // option after args - ParseResult result3 = command.Parse( - "move ARG1 ARG2 -X the-arg-for-option-x"); - - // all should be equivalent - result1.Should() - .BeEquivalentTo( - result2, - x => x.IgnoringCyclicReferences() - .Excluding(y => y.WhichGetterHas(CSharpAccessModifier.Internal)) - .Excluding(y => y.WhichGetterHas(CSharpAccessModifier.PrivateProtected))); - result1.Should() - .BeEquivalentTo( - result3, - x => x.IgnoringCyclicReferences() - .Excluding(y => y.WhichGetterHas(CSharpAccessModifier.Internal)) - .Excluding(y => y.WhichGetterHas(CSharpAccessModifier.PrivateProtected))); - } + result.GetResult(vegetablesOption) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("carrot"); - [Theory] - [InlineData("--one 1 --many 1 --many 2")] - [InlineData("--one 1 --many 1 --many 2 arg1 arg2")] - [InlineData("--many 1 --one 1 --many 2")] - [InlineData("--many 2 --many 1 --one 1")] - [InlineData("[parse] --one 1 --many 1 --many 2")] - [InlineData("--one \"stuff in quotes\" this-is-arg1 \"this is arg2\"")] - [InlineData("not a valid command line --one 1")] - public void Original_order_of_tokens_is_preserved_in_ParseResult_Tokens(string commandLine) - { - var rawSplit = CommandLineParser.SplitCommandLine(commandLine); + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("some-arg"); + } - var command = new Command("the-command") - { - new Argument("arg"), - new Option("--one"), - new Option("--many") - }; + [Fact] + public void Command_with_multiple_options_is_parsed_correctly() + { + var command = new Command("outer") + { + new Option("--inner1"), + new Option("--inner2") + }; + + var result = command.Parse("outer --inner1 argument1 --inner2 argument2"); + + result.CommandResult + .Children + .Should() + .ContainSingle(o => + ((OptionResult)o).Option.Name == "--inner1" && + o.Tokens.Single().Value == "argument1"); + result.CommandResult + .Children + .Should() + .ContainSingle(o => + ((OptionResult)o).Option.Name == "--inner2" && + o.Tokens.Single().Value == "argument2"); + } - var result = command.Parse(commandLine); + [Fact] + public void Relative_order_of_arguments_and_options_within_a_command_does_not_matter() + { + var command = new Command("move") + { + new Argument("arg"), + new Option("-X") + }; + + // option before args + ParseResult result1 = command.Parse( + "move -X the-arg-for-option-x ARG1 ARG2"); + + // option between two args + ParseResult result2 = command.Parse( + "move ARG1 -X the-arg-for-option-x ARG2"); + + // option after args + ParseResult result3 = command.Parse( + "move ARG1 ARG2 -X the-arg-for-option-x"); + + // all should be equivalent + result1.Should() + .BeEquivalentTo( + result2, + x => x.IgnoringCyclicReferences() + .Excluding(y => y.WhichGetterHas(CSharpAccessModifier.Internal)) + .Excluding(y => y.WhichGetterHas(CSharpAccessModifier.PrivateProtected))); + result1.Should() + .BeEquivalentTo( + result3, + x => x.IgnoringCyclicReferences() + .Excluding(y => y.WhichGetterHas(CSharpAccessModifier.Internal)) + .Excluding(y => y.WhichGetterHas(CSharpAccessModifier.PrivateProtected))); + } - result.Tokens.Select(t => t.Value).Should().Equal(rawSplit); - } + [Theory] + [InlineData("--one 1 --many 1 --many 2")] + [InlineData("--one 1 --many 1 --many 2 arg1 arg2")] + [InlineData("--many 1 --one 1 --many 2")] + [InlineData("--many 2 --many 1 --one 1")] + [InlineData("[parse] --one 1 --many 1 --many 2")] + [InlineData("--one \"stuff in quotes\" this-is-arg1 \"this is arg2\"")] + [InlineData("not a valid command line --one 1")] + public void Original_order_of_tokens_is_preserved_in_ParseResult_Tokens(string commandLine) + { + var rawSplit = CommandLineParser.SplitCommandLine(commandLine); - [Fact] - public void An_outer_command_with_the_same_name_does_not_capture() + var command = new Command("the-command") { - var command = new Command("one") - { - new Command("two") - { - new Command("three") - }, - new Command("three") - }; + new Argument("arg"), + new Option("--one"), + new Option("--many") + }; - ParseResult result = command.Parse("one two three"); + var result = command.Parse(commandLine); - result.Diagram().Should().Be("[ one [ two [ three ] ] ]"); - } + result.Tokens.Select(t => t.Value).Should().Equal(rawSplit); + } - [Fact] - public void An_inner_command_with_the_same_name_does_not_capture() + [Fact] + public void An_outer_command_with_the_same_name_does_not_capture() + { + var command = new Command("one") { - var command = new Command("one") - { - new Command("two") - { - new Command("three") - }, - new Command("three") - }; + new Command("two") + { + new Command("three") + }, + new Command("three") + }; - ParseResult result = command.Parse("one three"); + ParseResult result = command.Parse("one two three"); - result.Diagram().Should().Be("[ one [ three ] ]"); - } + result.Diagram().Should().Be("[ one [ two [ three ] ] ]"); + } - [Fact] - public void When_nested_commands_all_accept_arguments_then_the_nearest_captures_the_arguments() + [Fact] + public void An_inner_command_with_the_same_name_does_not_capture() + { + var command = new Command("one") { - var command = new Command( - "outer") + new Command("two") { - new Argument("arg1"), - new Command("inner") - { - new Argument("arg2") - } - }; + new Command("three") + }, + new Command("three") + }; - var result = command.Parse("outer arg1 inner arg2"); + ParseResult result = command.Parse("one three"); - result.CommandResult - .Parent - .Tokens.Select(t => t.Value) - .Should() - .BeEquivalentTo("arg1"); + result.Diagram().Should().Be("[ one [ three ] ]"); + } - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("arg2"); - } + [Fact] + public void When_nested_commands_all_accept_arguments_then_the_nearest_captures_the_arguments() + { + var command = new Command( + "outer") + { + new Argument("arg1"), + new Command("inner") + { + new Argument("arg2") + } + }; + + var result = command.Parse("outer arg1 inner arg2"); - [Fact] - public void Nested_commands_with_colliding_names_cannot_both_be_applied() + result.CommandResult + .Parent + .Tokens.Select(t => t.Value) + .Should() + .BeEquivalentTo("arg1"); + + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("arg2"); + } + + [Fact] + public void Nested_commands_with_colliding_names_cannot_both_be_applied() + { + var command = new Command("outer") { - var command = new Command("outer") + new Argument("arg1"), + new Command("non-unique") { - new Argument("arg1"), + new Argument("arg2") + }, + new Command("inner") + { + new Argument("arg3"), new Command("non-unique") { - new Argument("arg2") - }, - new Command("inner") - { - new Argument("arg3"), - new Command("non-unique") - { - new Argument("arg4") - } + new Argument("arg4") } - }; + } + }; - ParseResult result = command.Parse("outer arg1 inner arg2 non-unique arg3 "); + ParseResult result = command.Parse("outer arg1 inner arg2 non-unique arg3 "); - result.Diagram().Should().Be("[ outer [ inner [ non-unique ] ] ]"); - } + result.Diagram().Should().Be("[ outer [ inner [ non-unique ] ] ]"); + } - [Fact] - public void When_child_option_will_not_accept_arg_then_parent_can() + [Fact] + public void When_child_option_will_not_accept_arg_then_parent_can() + { + var option = new Option("-x"); + var command = new Command("the-command") { - var option = new Option("-x"); - var command = new Command("the-command") - { - option, - new Argument("arg") - }; + option, + new Argument("arg") + }; - var result = command.Parse("the-command -x the-argument"); + var result = command.Parse("the-command -x the-argument"); - var optionResult = result.GetResult(option); - optionResult.Tokens.Should().BeEmpty(); - result.CommandResult.Tokens.Select(t => t.Value).Should().BeEquivalentTo("the-argument"); - } + var optionResult = result.GetResult(option); + optionResult.Tokens.Should().BeEmpty(); + result.CommandResult.Tokens.Select(t => t.Value).Should().BeEquivalentTo("the-argument"); + } - [Fact] - public void When_parent_option_will_not_accept_arg_then_child_can() + [Fact] + public void When_parent_option_will_not_accept_arg_then_child_can() + { + var option = new Option("-x"); + var command = new Command("the-command") { - var option = new Option("-x"); - var command = new Command("the-command") - { - option - }; + option + }; - var result = command.Parse("the-command -x the-argument"); + var result = command.Parse("the-command -x the-argument"); - result.GetResult(option).Tokens.Select(t => t.Value).Should().BeEquivalentTo("the-argument"); - result.CommandResult.Tokens.Should().BeEmpty(); - } + result.GetResult(option).Tokens.Select(t => t.Value).Should().BeEquivalentTo("the-argument"); + result.CommandResult.Tokens.Should().BeEmpty(); + } + + [Fact] + public void Required_arguments_on_parent_commands_do_not_create_parse_errors_when_an_inner_command_is_specified() + { + var child = new Command("child"); - [Fact] - public void Required_arguments_on_parent_commands_do_not_create_parse_errors_when_an_inner_command_is_specified() + var parent = new Command("parent") { - var child = new Command("child"); + new Argument("arg"), + child + }; - var parent = new Command("parent") - { - new Argument("arg"), - child - }; + var result = parent.Parse("child"); - var result = parent.Parse("child"); + result.Errors.Should().BeEmpty(); + } - result.Errors.Should().BeEmpty(); - } + [Fact] + public void Required_arguments_on_grandparent_commands_do_not_create_parse_errors_when_an_inner_command_is_specified() + { + var grandchild = new Command("grandchild"); - [Fact] - public void Required_arguments_on_grandparent_commands_do_not_create_parse_errors_when_an_inner_command_is_specified() + var grandparent = new Command("grandparent") { - var grandchild = new Command("grandchild"); - - var grandparent = new Command("grandparent") + new Argument("arg"), + new Command("parent") { - new Argument("arg"), - new Command("parent") - { - grandchild - } - }; - - var result = grandparent.Parse("parent grandchild"); - - result.Errors.Should().BeEmpty(); - } + grandchild + } + }; - [Fact] - public void When_options_with_the_same_name_are_defined_on_parent_and_child_commands_and_specified_at_the_end_then_it_attaches_to_the_inner_command() - { - var outer = new Command("outer") - { - new Command("inner") - { - new Option("-x") - }, - new Option("-x") - }; - - ParseResult result = outer.Parse("outer inner -x"); - - result.CommandResult - .Parent - .Should() - .BeOfType() - .Which - .Command - .Should() - .Be(outer); - result.CommandResult - .Children - .Should() - .ContainSingle(o => ((OptionResult)o).Option.Name == "-x"); - } + var result = grandparent.Parse("parent grandchild"); - [Fact] - public void When_options_with_the_same_name_are_defined_on_parent_and_child_commands_and_specified_in_between_then_it_attaches_to_the_outer_command() - { - var outer = new Command("outer"); - var outerOption = new Option("-x"); - outer.Options.Add(outerOption); - var inner = new Command("inner"); - var innerOption = new Option("-x"); - inner.Options.Add(innerOption); - outer.Subcommands.Add(inner); + result.Errors.Should().BeEmpty(); + } - var result = outer.Parse("outer -x inner"); + [Fact] + public void When_options_with_the_same_name_are_defined_on_parent_and_child_commands_and_specified_at_the_end_then_it_attaches_to_the_inner_command() + { + var outer = new Command("outer") + { + new Command("inner") + { + new Option("-x") + }, + new Option("-x") + }; + + ParseResult result = outer.Parse("outer inner -x"); + + result.CommandResult + .Parent + .Should() + .BeOfType() + .Which + .Command + .Should() + .Be(outer); + result.CommandResult + .Children + .Should() + .ContainSingle(o => ((OptionResult)o).Option.Name == "-x"); + } - result.GetValue(outerOption).Should().BeTrue(); - result.GetValue(innerOption).Should().BeFalse(); - } + [Fact] + public void When_options_with_the_same_name_are_defined_on_parent_and_child_commands_and_specified_in_between_then_it_attaches_to_the_outer_command() + { + var outer = new Command("outer"); + var outerOption = new Option("-x"); + outer.Options.Add(outerOption); + var inner = new Command("inner"); + var innerOption = new Option("-x"); + inner.Options.Add(innerOption); + outer.Subcommands.Add(inner); + + var result = outer.Parse("outer -x inner"); + + result.GetValue(outerOption).Should().BeTrue(); + result.GetValue(innerOption).Should().BeFalse(); + } - [Fact] - public void Arguments_only_apply_to_the_nearest_command() + [Fact] + public void Arguments_only_apply_to_the_nearest_command() + { + var outer = new Command("outer") { - var outer = new Command("outer") + new Argument("arg1"), + new Command("inner") { - new Argument("arg1"), - new Command("inner") - { - new Argument("arg2") - } - }; + new Argument("arg2") + } + }; + + ParseResult result = outer.Parse("outer inner arg1 arg2"); + + result.CommandResult + .Parent + .Tokens + .Should() + .BeEmpty(); + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("arg1"); + result.UnmatchedTokens + .Should() + .BeEquivalentTo("arg2"); + } - ParseResult result = outer.Parse("outer inner arg1 arg2"); - - result.CommandResult - .Parent - .Tokens - .Should() - .BeEmpty(); - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("arg1"); - result.UnmatchedTokens - .Should() - .BeEquivalentTo("arg2"); - } + [Fact] + public void Options_only_apply_to_the_nearest_command() + { + var outerOption = new Option("-x"); + var innerOption = new Option("-x"); - [Fact] - public void Options_only_apply_to_the_nearest_command() + var outer = new Command("outer") { - var outerOption = new Option("-x"); - var innerOption = new Option("-x"); - - var outer = new Command("outer") - { - new Command("inner") - { - innerOption - }, - outerOption - }; + new Command("inner") + { + innerOption + }, + outerOption + }; - var result = outer.Parse("outer inner -x one -x two"); + var result = outer.Parse("outer inner -x one -x two"); - result.RootCommandResult - .GetResult(outerOption) - .Should() - .BeNull(); - } + result.RootCommandResult + .GetResult(outerOption) + .Should() + .BeNull(); + } - [Fact] - public void Subsequent_occurrences_of_tokens_matching_command_names_are_parsed_as_arguments() + [Fact] + public void Subsequent_occurrences_of_tokens_matching_command_names_are_parsed_as_arguments() + { + var command = new Command("the-command") { - var command = new Command("the-command") + new Command("complete") { - new Command("complete") - { - new Argument("arg"), - new Option("--position") - } - }; + new Argument("arg"), + new Option("--position") + } + }; - ParseResult result = command.Parse(new[] { "the-command", - "complete", - "--position", - "7", - "the-command" }); + ParseResult result = command.Parse(new[] { "the-command", + "complete", + "--position", + "7", + "the-command" }); - CommandResult completeResult = result.CommandResult; + CommandResult completeResult = result.CommandResult; - completeResult.Tokens.Select(t => t.Value).Should().BeEquivalentTo("the-command"); - } + completeResult.Tokens.Select(t => t.Value).Should().BeEquivalentTo("the-command"); + } - [Fact] - public void Absolute_unix_style_paths_are_lexed_correctly() + [Fact] + public void Absolute_unix_style_paths_are_lexed_correctly() + { + const string commandText = + @"rm ""/temp/the file.txt"""; + + Command command = new ("rm") { - const string commandText = - @"rm ""/temp/the file.txt"""; + new Argument("arg") + }; - Command command = new ("rm") - { - new Argument("arg") - }; + var result = command.Parse(commandText); - var result = command.Parse(commandText); + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .OnlyContain(a => a == @"/temp/the file.txt"); + } - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .OnlyContain(a => a == @"/temp/the file.txt"); - } + [Fact] + public void Absolute_Windows_style_paths_are_lexed_correctly() + { + const string commandText = + @"rm ""c:\temp\the file.txt\"""; - [Fact] - public void Absolute_Windows_style_paths_are_lexed_correctly() + Command command = new("rm") { - const string commandText = - @"rm ""c:\temp\the file.txt\"""; + new Argument("arg") + }; - Command command = new("rm") - { - new Argument("arg") - }; + ParseResult result = command.Parse(commandText); - ParseResult result = command.Parse(commandText); + result.CommandResult + .Tokens + .Should() + .OnlyContain(a => a.Value == @"c:\temp\the file.txt\"); + } - result.CommandResult - .Tokens - .Should() - .OnlyContain(a => a.Value == @"c:\temp\the file.txt\"); - } + [Fact] + public void Commands_can_have_default_argument_values() + { + var argument = new Argument("the-arg") + { + DefaultValueFactory = (_) => "default" + }; - [Fact] - public void Commands_can_have_default_argument_values() + var command = new Command("command") { - var argument = new Argument("the-arg") - { - DefaultValueFactory = (_) => "default" - }; + argument + }; - var command = new Command("command") - { - argument - }; + ParseResult result = command.Parse("command"); - ParseResult result = command.Parse("command"); + GetValue(result, argument) + .Should() + .Be("default"); - GetValue(result, argument) - .Should() - .Be("default"); + result.GetRequiredValue(argument) + .Should() + .Be("default"); + } - result.GetRequiredValue(argument) - .Should() - .Be("default"); - } + [Fact] + public void GetRequiredValue_throws_when_argument_without_default_value_was_not_provided() + { + Argument argument = new("the-arg"); + Option option = new("--option"); - [Fact] - public void GetRequiredValue_throws_when_argument_without_default_value_was_not_provided() + Command command = new("command") { - Argument argument = new("the-arg"); - Option option = new("--option"); + argument, + option + }; - Command command = new("command") - { - argument, - option - }; - - ParseResult result = command.Parse("command --option"); + ParseResult result = command.Parse("command --option"); - result.Invoking(result => result.GetRequiredValue(argument)) - .Should() - .Throw(); + result.Invoking(result => result.GetRequiredValue(argument)) + .Should() + .Throw(); - result.Invoking(result => result.GetRequiredValue(argument.Name)) - .Should() - .Throw(); - } + result.Invoking(result => result.GetRequiredValue(argument.Name)) + .Should() + .Throw(); + } - [Fact] - public void When_an_option_with_a_default_value_is_not_matched_then_the_option_can_still_be_accessed_as_though_it_had_been_applied() + [Fact] + public void When_an_option_with_a_default_value_is_not_matched_then_the_option_can_still_be_accessed_as_though_it_had_been_applied() + { + var command = new Command("command"); + var option = new Option("-o", "--option") { - var command = new Command("command"); - var option = new Option("-o", "--option") - { - DefaultValueFactory = (_) => "the-default" - }; - command.Options.Add(option); + DefaultValueFactory = (_) => "the-default" + }; + command.Options.Add(option); - ParseResult result = command.Parse("command"); + ParseResult result = command.Parse("command"); - result.GetResult(option).Should().NotBeNull(); - GetValue(result, option).Should().Be("the-default"); - } + result.GetResult(option).Should().NotBeNull(); + GetValue(result, option).Should().Be("the-default"); + } - [Fact] - public void When_an_option_with_a_default_value_is_not_matched_then_the_option_result_is_implicit() + [Fact] + public void When_an_option_with_a_default_value_is_not_matched_then_the_option_result_is_implicit() + { + var option = new Option("-o", "--option") { - var option = new Option("-o", "--option") - { - DefaultValueFactory = (_) => "the-default" - }; + DefaultValueFactory = (_) => "the-default" + }; - var command = new Command("command") - { - option - }; + var command = new Command("command") + { + option + }; - var result = command.Parse("command"); + var result = command.Parse("command"); - result.GetResult(option) - .Implicit - .Should() - .BeTrue(); - } + result.GetResult(option) + .Implicit + .Should() + .BeTrue(); + } - [Fact] - public void When_an_option_with_a_default_value_is_not_matched_then_there_are_no_tokens() + [Fact] + public void When_an_option_with_a_default_value_is_not_matched_then_there_are_no_tokens() + { + var option = new Option("-o") { - var option = new Option("-o") - { - DefaultValueFactory = (_) => "the-default" - }; + DefaultValueFactory = (_) => "the-default" + }; - var command = new Command("command") - { - option - }; + var command = new Command("command") + { + option + }; - var result = command.Parse("command"); + var result = command.Parse("command"); - result.GetResult(option) - .IdentifierToken - .Should() - .BeEquivalentTo(default(Token)); - } + result.GetResult(option) + .IdentifierToken + .Should() + .BeEquivalentTo(default(Token)); + } - [Fact] - public void When_an_argument_with_a_default_value_is_not_matched_then_there_are_no_tokens() + [Fact] + public void When_an_argument_with_a_default_value_is_not_matched_then_there_are_no_tokens() + { + var argument = new Argument("o") { - var argument = new Argument("o") - { - DefaultValueFactory = (_) => "the-default" - }; + DefaultValueFactory = (_) => "the-default" + }; - var command = new Command("command") - { - argument - }; - var result = command.Parse("command"); + var command = new Command("command") + { + argument + }; + var result = command.Parse("command"); - result.GetResult(argument) - .Tokens - .Should() - .BeEmpty(); - } + result.GetResult(argument) + .Tokens + .Should() + .BeEmpty(); + } - [Fact] - public void Command_default_argument_value_does_not_override_parsed_value() + [Fact] + public void Command_default_argument_value_does_not_override_parsed_value() + { + var argument = new Argument("the-arg") { - var argument = new Argument("the-arg") - { - DefaultValueFactory = (_) => new DirectoryInfo(Directory.GetCurrentDirectory()) - }; + DefaultValueFactory = (_) => new DirectoryInfo(Directory.GetCurrentDirectory()) + }; - var command = new Command("inner") - { - argument - }; + var command = new Command("inner") + { + argument + }; - var result = command.Parse("the-directory"); + var result = command.Parse("the-directory"); - GetValue(result, argument) - .Name - .Should() - .Be("the-directory"); + GetValue(result, argument) + .Name + .Should() + .Be("the-directory"); - result.GetRequiredValue(argument) - .Name - .Should() - .Be("the-directory"); - } + result.GetRequiredValue(argument) + .Name + .Should() + .Be("the-directory"); + } - [Fact] - public void Unmatched_tokens_that_look_like_options_are_not_split_into_smaller_tokens() + [Fact] + public void Unmatched_tokens_that_look_like_options_are_not_split_into_smaller_tokens() + { + var outer = new Command("outer") { - var outer = new Command("outer") + new Command("inner") { - new Command("inner") + new Argument("arg") { - new Argument("arg") - { - Arity = ArgumentArity.OneOrMore - } + Arity = ArgumentArity.OneOrMore } - }; + } + }; - ParseResult result = outer.Parse("outer inner -p:RandomThing=random"); + ParseResult result = outer.Parse("outer inner -p:RandomThing=random"); - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("-p:RandomThing=random"); - } + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("-p:RandomThing=random"); + } - [Fact] - public void The_default_behavior_of_unmatched_tokens_resulting_in_errors_can_be_turned_off() + [Fact] + public void The_default_behavior_of_unmatched_tokens_resulting_in_errors_can_be_turned_off() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Argument("arg") - }; - command.TreatUnmatchedTokensAsErrors = false; - - ParseResult result = command.Parse("the-command arg1 arg2"); - - result.Errors.Should().BeEmpty(); + new Argument("arg") + }; + command.TreatUnmatchedTokensAsErrors = false; - result.UnmatchedTokens - .Should() - .BeEquivalentTo("arg2"); - } + ParseResult result = command.Parse("the-command arg1 arg2"); - [Fact] - public void Option_and_Command_can_have_the_same_alias() - { - var innerCommand = new Command("inner") - { - new Argument("arg1") - }; + result.Errors.Should().BeEmpty(); - var option = new Option("--inner"); - - var outerCommand = new Command("outer") - { - innerCommand, - option, - new Argument("arg2") - }; + result.UnmatchedTokens + .Should() + .BeEquivalentTo("arg2"); + } - outerCommand.Parse("outer inner") - .CommandResult - .Command - .Should() - .BeSameAs(innerCommand); - - outerCommand.Parse("outer --inner") - .CommandResult - .Command - .Should() - .BeSameAs(outerCommand); - - outerCommand.Parse("outer --inner inner") - .CommandResult - .Command - .Should() - .BeSameAs(innerCommand); - - outerCommand.Parse("outer --inner inner") - .CommandResult - .Parent - .Should() - .BeOfType() - .Which - .Children - .Should() - .Contain(o => ((OptionResult)o).Option == option); - } + [Fact] + public void Option_and_Command_can_have_the_same_alias() + { + var innerCommand = new Command("inner") + { + new Argument("arg1") + }; + + var option = new Option("--inner"); + + var outerCommand = new Command("outer") + { + innerCommand, + option, + new Argument("arg2") + }; + + outerCommand.Parse("outer inner") + .CommandResult + .Command + .Should() + .BeSameAs(innerCommand); + + outerCommand.Parse("outer --inner") + .CommandResult + .Command + .Should() + .BeSameAs(outerCommand); + + outerCommand.Parse("outer --inner inner") + .CommandResult + .Command + .Should() + .BeSameAs(innerCommand); + + outerCommand.Parse("outer --inner inner") + .CommandResult + .Parent + .Should() + .BeOfType() + .Which + .Children + .Should() + .Contain(o => ((OptionResult)o).Option == option); + } - [Fact] - public void Options_can_have_the_same_alias_differentiated_only_by_prefix() - { - var option1 = new Option("-a"); - var option2 = new Option("--a"); + [Fact] + public void Options_can_have_the_same_alias_differentiated_only_by_prefix() + { + var option1 = new Option("-a"); + var option2 = new Option("--a"); + + var parser = new RootCommand + { + option1, + option2 + }; + + parser.Parse("-a value").CommandResult + .Children + .Select(s => ((OptionResult)s).Option) + .Should() + .BeEquivalentTo([option1]); + parser.Parse("--a value").CommandResult + .Children + .Select(s => ((OptionResult)s).Option) + .Should() + .BeEquivalentTo([option2]); + } - var parser = new RootCommand - { - option1, - option2 - }; + [Theory] + [InlineData("-x", "\"hello\"")] + [InlineData("-x=", "\"hello\"")] + [InlineData("-x:", "\"hello\"")] + [InlineData("-x", "\"\"")] + [InlineData("-x=", "\"\"")] + [InlineData("-x:", "\"\"")] + public void When_an_option_argument_is_enclosed_in_double_quotes_its_value_retains_the_quotes( + string arg1, + string arg2) + { + var option = new Option("-x"); - parser.Parse("-a value").CommandResult - .Children - .Select(s => ((OptionResult)s).Option) - .Should() - .BeEquivalentTo([option1]); - parser.Parse("--a value").CommandResult - .Children - .Select(s => ((OptionResult)s).Option) - .Should() - .BeEquivalentTo([option2]); - } + var parseResult = new RootCommand { option }.Parse(new[] { arg1, arg2 }); - [Theory] - [InlineData("-x", "\"hello\"")] - [InlineData("-x=", "\"hello\"")] - [InlineData("-x:", "\"hello\"")] - [InlineData("-x", "\"\"")] - [InlineData("-x=", "\"\"")] - [InlineData("-x:", "\"\"")] - public void When_an_option_argument_is_enclosed_in_double_quotes_its_value_retains_the_quotes( - string arg1, - string arg2) - { - var option = new Option("-x"); - - var parseResult = new RootCommand { option }.Parse(new[] { arg1, arg2 }); - - parseResult - .GetResult(option) - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo(new[] { arg2 }); - } + parseResult + .GetResult(option) + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo(new[] { arg2 }); + } - [Fact] // https://github.com/dotnet/command-line-api/issues/1445 - public void Trailing_option_delimiters_are_ignored() + [Fact] // https://github.com/dotnet/command-line-api/issues/1445 + public void Trailing_option_delimiters_are_ignored() + { + var rootCommand = new RootCommand { - var rootCommand = new RootCommand + new Command("subcommand") { - new Command("subcommand") - { - new Option("--directory") - } - }; + new Option("--directory") + } + }; - var args = new[] { "subcommand", "--directory:", @"c:\" }; + var args = new[] { "subcommand", "--directory:", @"c:\" }; - var result = rootCommand.Parse(args); + var result = rootCommand.Parse(args); - result.Errors.Should().BeEmpty(); + result.Errors.Should().BeEmpty(); - result.Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentSequenceTo(new[] { "subcommand", "--directory", @"c:\" }); - } + result.Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentSequenceTo(new[] { "subcommand", "--directory", @"c:\" }); + } - [Theory] - [InlineData("-x -y")] - [InlineData("-x=-y")] - [InlineData("-x:-y")] - public void Option_arguments_can_start_with_prefixes_that_make_them_look_like_options(string input) - { - var optionX = new Option("-x"); + [Theory] + [InlineData("-x -y")] + [InlineData("-x=-y")] + [InlineData("-x:-y")] + public void Option_arguments_can_start_with_prefixes_that_make_them_look_like_options(string input) + { + var optionX = new Option("-x"); - var command = new Command("command") - { - optionX, - new Option("-z") - }; + var command = new Command("command") + { + optionX, + new Option("-z") + }; - var result = command.Parse(input); + var result = command.Parse(input); - GetValue(result, optionX).Should().Be("-y"); - } + GetValue(result, optionX).Should().Be("-y"); + } - [Fact] - public void Option_arguments_can_start_with_prefixes_that_make_them_look_like_bundled_options() - { - var optionA = new Option("-a"); - var optionB = new Option("-b"); - var optionC = new Option("-c"); + [Fact] + public void Option_arguments_can_start_with_prefixes_that_make_them_look_like_bundled_options() + { + var optionA = new Option("-a"); + var optionB = new Option("-b"); + var optionC = new Option("-c"); - var command = new RootCommand - { - optionA, - optionB, - optionC - }; + var command = new RootCommand + { + optionA, + optionB, + optionC + }; - var result = command.Parse("-a -bc"); + var result = command.Parse("-a -bc"); - GetValue(result, optionA).Should().Be("-bc"); - GetValue(result, optionB).Should().BeFalse(); - GetValue(result, optionC).Should().BeFalse(); - } + GetValue(result, optionA).Should().Be("-bc"); + GetValue(result, optionB).Should().BeFalse(); + GetValue(result, optionC).Should().BeFalse(); + } - [Fact] - public void Option_arguments_can_match_subcommands() + [Fact] + public void Option_arguments_can_match_subcommands() + { + var optionA = new Option("-a"); + var root = new RootCommand { - var optionA = new Option("-a"); - var root = new RootCommand - { - new Command("subcommand"), - optionA - }; + new Command("subcommand"), + optionA + }; - var result = root.Parse("-a subcommand"); + var result = root.Parse("-a subcommand"); - GetValue(result, optionA).Should().Be("subcommand"); - result.CommandResult.Command.Should().BeSameAs(root); - } + GetValue(result, optionA).Should().Be("subcommand"); + result.CommandResult.Command.Should().BeSameAs(root); + } - [Fact] - public void Arguments_can_match_subcommands() + [Fact] + public void Arguments_can_match_subcommands() + { + var argument = new Argument("arg"); + var subcommand = new Command("subcommand") { - var argument = new Argument("arg"); - var subcommand = new Command("subcommand") - { - argument - }; - var root = new RootCommand - { - subcommand - }; + argument + }; + var root = new RootCommand + { + subcommand + }; - var result = root.Parse("subcommand one two three subcommand four"); + var result = root.Parse("subcommand one two three subcommand four"); - result.CommandResult.Command.Should().BeSameAs(subcommand); + result.CommandResult.Command.Should().BeSameAs(subcommand); - GetValue(result, argument) - .Should() - .BeEquivalentSequenceTo("one", "two", "three", "subcommand", "four"); + GetValue(result, argument) + .Should() + .BeEquivalentSequenceTo("one", "two", "three", "subcommand", "four"); - result.GetRequiredValue(argument) - .Should() - .BeEquivalentSequenceTo("one", "two", "three", "subcommand", "four"); - } + result.GetRequiredValue(argument) + .Should() + .BeEquivalentSequenceTo("one", "two", "three", "subcommand", "four"); + } - [Theory] - [InlineData("-x=-y")] - [InlineData("-x:-y")] - public void Option_arguments_can_match_the_aliases_of_sibling_options_when_non_space_argument_delimiter_is_used(string input) + [Theory] + [InlineData("-x=-y")] + [InlineData("-x:-y")] + public void Option_arguments_can_match_the_aliases_of_sibling_options_when_non_space_argument_delimiter_is_used(string input) + { + var optionX = new Option("-x"); + + var command = new Command("command") { - var optionX = new Option("-x"); + optionX, + new Option("-y") + }; - var command = new Command("command") - { - optionX, - new Option("-y") - }; + var result = command.Parse(input); - var result = command.Parse(input); + result.Errors.Should().BeEmpty(); + GetValue(result, optionX).Should().Be("-y"); + } - result.Errors.Should().BeEmpty(); - GetValue(result, optionX).Should().Be("-y"); - } + [Fact] + public void Single_option_arguments_that_match_option_aliases_are_parsed_correctly() + { + var optionX = new Option("-x"); - [Fact] - public void Single_option_arguments_that_match_option_aliases_are_parsed_correctly() + var command = new RootCommand { - var optionX = new Option("-x"); + optionX + }; - var command = new RootCommand - { - optionX - }; + var result = command.Parse("-x -x"); - var result = command.Parse("-x -x"); + GetValue(result, optionX).Should().Be("-x"); + } - GetValue(result, optionX).Should().Be("-x"); - } + [Theory] + [InlineData("-x -y")] + [InlineData("-x true -y")] + [InlineData("-x:true -y")] + [InlineData("-x=true -y")] + [InlineData("-x -y true")] + [InlineData("-x true -y true")] + [InlineData("-x:true -y:true")] + [InlineData("-x=true -y:true")] + public void Boolean_options_are_not_greedy(string commandLine) + { + var optX = new Option("-x"); + var optY = new Option("-y"); - [Theory] - [InlineData("-x -y")] - [InlineData("-x true -y")] - [InlineData("-x:true -y")] - [InlineData("-x=true -y")] - [InlineData("-x -y true")] - [InlineData("-x true -y true")] - [InlineData("-x:true -y:true")] - [InlineData("-x=true -y:true")] - public void Boolean_options_are_not_greedy(string commandLine) - { - var optX = new Option("-x"); - var optY = new Option("-y"); - - var root = new RootCommand("parent") - { - optX, - optY, - }; + var root = new RootCommand("parent") + { + optX, + optY, + }; - var result = root.Parse(commandLine); + var result = root.Parse(commandLine); - result.Errors.Should().BeEmpty(); + result.Errors.Should().BeEmpty(); - GetValue(result, optX).Should().BeTrue(); - GetValue(result, optY).Should().BeTrue(); - } + GetValue(result, optX).Should().BeTrue(); + GetValue(result, optY).Should().BeTrue(); + } + + [Fact] + public void Multiple_option_arguments_that_match_multiple_arity_option_aliases_are_parsed_correctly() + { + var optionX = new Option("-x"); + var optionY = new Option("-y"); - [Fact] - public void Multiple_option_arguments_that_match_multiple_arity_option_aliases_are_parsed_correctly() + var command = new RootCommand { - var optionX = new Option("-x"); - var optionY = new Option("-y"); + optionX, + optionY + }; - var command = new RootCommand - { - optionX, - optionY - }; + var result = command.Parse("-x -x -x -y -y -x -y -y -y -x -x -y"); - var result = command.Parse("-x -x -x -y -y -x -y -y -y -x -x -y"); + GetValue(result, optionX).Should().BeEquivalentTo(new[] { "-x", "-y", "-y" }); + GetValue(result, optionY).Should().BeEquivalentTo(new[] { "-x", "-y", "-x" }); + } - GetValue(result, optionX).Should().BeEquivalentTo(new[] { "-x", "-y", "-y" }); - GetValue(result, optionY).Should().BeEquivalentTo(new[] { "-x", "-y", "-x" }); - } + [Fact] + public void Bundled_option_arguments_that_match_option_aliases_are_parsed_correctly() + { + var optionX = new Option("-x"); + var optionY = new Option("-y"); - [Fact] - public void Bundled_option_arguments_that_match_option_aliases_are_parsed_correctly() + var command = new RootCommand { - var optionX = new Option("-x"); - var optionY = new Option("-y"); + optionX, + optionY + }; - var command = new RootCommand - { - optionX, - optionY - }; + var result = command.Parse("-yxx"); - var result = command.Parse("-yxx"); + GetValue(result, optionX).Should().Be("x"); + } - GetValue(result, optionX).Should().Be("x"); - } + [Fact] + public void Argument_name_is_not_matched_as_a_token() + { + var nameArg = new Argument("name"); + var columnsArg = new Argument>("columns"); - [Fact] - public void Argument_name_is_not_matched_as_a_token() + var command = new Command("add", "Adds a new series") { - var nameArg = new Argument("name"); - var columnsArg = new Argument>("columns"); + nameArg, + columnsArg + }; - var command = new Command("add", "Adds a new series") - { - nameArg, - columnsArg - }; + var result = command.Parse("name one two three"); - var result = command.Parse("name one two three"); + GetValue(result, nameArg).Should().Be("name"); + GetValue(result, columnsArg).Should().BeEquivalentTo("one", "two", "three"); + } - GetValue(result, nameArg).Should().Be("name"); - GetValue(result, columnsArg).Should().BeEquivalentTo("one", "two", "three"); - } + [Fact] + public void Option_aliases_do_not_need_to_be_prefixed() + { + var option = new Option("noprefix"); - [Fact] - public void Option_aliases_do_not_need_to_be_prefixed() - { - var option = new Option("noprefix"); + var result = new RootCommand { option }.Parse("noprefix"); - var result = new RootCommand { option }.Parse("noprefix"); + result.GetResult(option).Should().NotBeNull(); + } - result.GetResult(option).Should().NotBeNull(); - } + [Fact] + public void Boolean_options_with_no_argument_specified_do_not_match_subsequent_arguments() + { + var option = new Option("-v"); - [Fact] - public void Boolean_options_with_no_argument_specified_do_not_match_subsequent_arguments() + var command = new Command("command") { - var option = new Option("-v"); - - var command = new Command("command") - { - option - }; + option + }; - var result = command.Parse("-v an-argument"); + var result = command.Parse("-v an-argument"); - GetValue(result, option).Should().BeTrue(); - } + GetValue(result, option).Should().BeTrue(); + } - [Fact] - public void When_a_command_line_has_unmatched_tokens_they_are_not_applied_to_subsequent_options() + [Fact] + public void When_a_command_line_has_unmatched_tokens_they_are_not_applied_to_subsequent_options() + { + var command = new Command("command") { - var command = new Command("command") - { - TreatUnmatchedTokensAsErrors = false - }; - var optionX = new Option("-x"); - command.Options.Add(optionX); - var optionY = new Option("-y"); - command.Options.Add(optionY); + TreatUnmatchedTokensAsErrors = false + }; + var optionX = new Option("-x"); + command.Options.Add(optionX); + var optionY = new Option("-y"); + command.Options.Add(optionY); - var result = command.Parse("-x 23 unmatched-token -y 42"); + var result = command.Parse("-x 23 unmatched-token -y 42"); - GetValue(result, optionX).Should().Be("23"); - GetValue(result, optionY).Should().Be("42"); - result.UnmatchedTokens.Should().BeEquivalentTo("unmatched-token"); - } + GetValue(result, optionX).Should().Be("23"); + GetValue(result, optionY).Should().Be("42"); + result.UnmatchedTokens.Should().BeEquivalentTo("unmatched-token"); + } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void When_a_command_line_has_unmatched_tokens_the_parse_result_action_should_depend_on_parsed_command_TreatUnmatchedTokensAsErrors(bool treatUnmatchedTokensAsErrors) + [Theory] + [InlineData(true)] + [InlineData(false)] + public void When_a_command_line_has_unmatched_tokens_the_parse_result_action_should_depend_on_parsed_command_TreatUnmatchedTokensAsErrors(bool treatUnmatchedTokensAsErrors) + { + RootCommand rootCommand = new(); + Command subcommand = new("vstest") { - RootCommand rootCommand = new(); - Command subcommand = new("vstest") - { - new Option("--Platform"), - new Option("--Framework"), - new Option("--logger") - }; - subcommand.TreatUnmatchedTokensAsErrors = treatUnmatchedTokensAsErrors; - rootCommand.Subcommands.Add(subcommand); + new Option("--Platform"), + new Option("--Framework"), + new Option("--logger") + }; + subcommand.TreatUnmatchedTokensAsErrors = treatUnmatchedTokensAsErrors; + rootCommand.Subcommands.Add(subcommand); - var result = rootCommand.Parse("vstest test1.dll test2.dll"); + var result = rootCommand.Parse("vstest test1.dll test2.dll"); - result.UnmatchedTokens.Should().BeEquivalentTo("test1.dll", "test2.dll"); + result.UnmatchedTokens.Should().BeEquivalentTo("test1.dll", "test2.dll"); - if (treatUnmatchedTokensAsErrors) - { - result.Errors.Should().NotBeEmpty(); - result.Action.Should().NotBeSameAs(result.CommandResult.Command.Action); - } - else - { - result.Errors.Should().BeEmpty(); - result.Action.Should().BeSameAs(result.CommandResult.Command.Action); - } + if (treatUnmatchedTokensAsErrors) + { + result.Errors.Should().NotBeEmpty(); + result.Action.Should().NotBeSameAs(result.CommandResult.Command.Action); } - - [Fact] - public void RootCommand_TreatUnmatchedTokensAsErrors_set_to_false_has_precedence_over_subcommands() + else { - RootCommand rootCommand = new(); - rootCommand.TreatUnmatchedTokensAsErrors = false; - Command subcommand = new("vstest") - { - new Option("--Platform"), - new Option("--Framework"), - new Option("--logger") - }; - subcommand.TreatUnmatchedTokensAsErrors = true; // the default, set to true to make it explicit - rootCommand.Subcommands.Add(subcommand); - - var result = rootCommand.Parse("vstest test1.dll test2.dll"); - - result.UnmatchedTokens.Should().BeEquivalentTo("test1.dll", "test2.dll"); - result.Errors.Should().BeEmpty(); result.Action.Should().BeSameAs(result.CommandResult.Command.Action); } + } - [Fact] - public void Parse_can_not_be_called_with_null_args() + [Fact] + public void RootCommand_TreatUnmatchedTokensAsErrors_set_to_false_has_precedence_over_subcommands() + { + RootCommand rootCommand = new(); + rootCommand.TreatUnmatchedTokensAsErrors = false; + Command subcommand = new("vstest") { - Action passNull = () => new RootCommand().Parse(args: null); + new Option("--Platform"), + new Option("--Framework"), + new Option("--logger") + }; + subcommand.TreatUnmatchedTokensAsErrors = true; // the default, set to true to make it explicit + rootCommand.Subcommands.Add(subcommand); - passNull.Should().Throw(); - } + var result = rootCommand.Parse("vstest test1.dll test2.dll"); - [Fact] - public void Command_argument_arity_can_be_a_fixed_value_greater_than_1() - { - var argument = new Argument("arg") - { - Arity = new ArgumentArity(3, 3) - }; - var command = new Command("the-command") - { - argument - }; - - command.Parse("1 2 3") - .CommandResult - .Tokens - .Should() - .BeEquivalentTo(new [] { - new Token("1", TokenType.Argument, argument), - new Token("2", TokenType.Argument, argument), - new Token("3", TokenType.Argument, argument)}); - } + result.UnmatchedTokens.Should().BeEquivalentTo("test1.dll", "test2.dll"); - [Fact] - public void Command_argument_arity_can_be_a_range_with_a_lower_bound_greater_than_1() - { - var argument = new Argument("arg") - { - Arity = new ArgumentArity(3, 5) - }; - var command = new Command("the-command") - { - argument - }; + result.Errors.Should().BeEmpty(); + result.Action.Should().BeSameAs(result.CommandResult.Command.Action); + } - command.Parse("1 2 3") - .CommandResult - .Tokens - .Should() - .BeEquivalentTo(new [] { - new Token("1", TokenType.Argument, argument), - new Token("2", TokenType.Argument, argument), - new Token("3", TokenType.Argument, argument)}); - command.Parse("1 2 3 4 5") - .CommandResult - .Tokens - .Should() - .BeEquivalentTo(new [] { - new Token("1", TokenType.Argument, argument), - new Token("2", TokenType.Argument, argument), - new Token("3", TokenType.Argument, argument), - new Token("4", TokenType.Argument, argument), - new Token("5", TokenType.Argument, argument)}); - } + [Fact] + public void Parse_can_not_be_called_with_null_args() + { + Action passNull = () => new RootCommand().Parse(args: null); - [Fact] - public void When_command_arguments_are_fewer_than_minimum_arity_then_an_error_is_returned() - { - var command = new Command("the-command") - { - new Argument("arg") - { - Arity = new ArgumentArity(2, 3) - } - }; + passNull.Should().Throw(); + } - var result = command.Parse("1"); + [Fact] + public void Command_argument_arity_can_be_a_fixed_value_greater_than_1() + { + var argument = new Argument("arg") + { + Arity = new ArgumentArity(3, 3) + }; + var command = new Command("the-command") + { + argument + }; + + command.Parse("1 2 3") + .CommandResult + .Tokens + .Should() + .BeEquivalentTo(new [] { + new Token("1", TokenType.Argument, argument), + new Token("2", TokenType.Argument, argument), + new Token("3", TokenType.Argument, argument)}); + } - result.Errors - .Select(e => e.Message) - .Should() - .Contain(LocalizationResources.RequiredArgumentMissing(result.GetResult(command.Arguments[0]))); - } + [Fact] + public void Command_argument_arity_can_be_a_range_with_a_lower_bound_greater_than_1() + { + var argument = new Argument("arg") + { + Arity = new ArgumentArity(3, 5) + }; + var command = new Command("the-command") + { + argument + }; + + command.Parse("1 2 3") + .CommandResult + .Tokens + .Should() + .BeEquivalentTo(new [] { + new Token("1", TokenType.Argument, argument), + new Token("2", TokenType.Argument, argument), + new Token("3", TokenType.Argument, argument)}); + command.Parse("1 2 3 4 5") + .CommandResult + .Tokens + .Should() + .BeEquivalentTo(new [] { + new Token("1", TokenType.Argument, argument), + new Token("2", TokenType.Argument, argument), + new Token("3", TokenType.Argument, argument), + new Token("4", TokenType.Argument, argument), + new Token("5", TokenType.Argument, argument)}); + } - [Fact] - public void When_command_arguments_are_greater_than_maximum_arity_then_an_error_is_returned() + [Fact] + public void When_command_arguments_are_fewer_than_minimum_arity_then_an_error_is_returned() + { + var command = new Command("the-command") { - var command = new Command("the-command") + new Argument("arg") { - new Argument("arg") - { - Arity = new ArgumentArity(2, 3) - } - }; + Arity = new ArgumentArity(2, 3) + } + }; - ParseResult parseResult = command.Parse("1 2 3 4"); + var result = command.Parse("1"); - parseResult - .Errors - .Select(e => e.Message) - .Should() - .Contain(LocalizationResources.UnrecognizedCommandOrArgument("4")); - } + result.Errors + .Select(e => e.Message) + .Should() + .Contain(LocalizationResources.RequiredArgumentMissing(result.GetResult(command.Arguments[0]))); + } - [Fact] - public void Option_argument_arity_can_be_a_fixed_value_greater_than_1() + [Fact] + public void When_command_arguments_are_greater_than_maximum_arity_then_an_error_is_returned() + { + var command = new Command("the-command") { - var option = new Option("-x") { Arity = new ArgumentArity(3, 3)}; - - var command = new Command("the-command") + new Argument("arg") { - option - }; + Arity = new ArgumentArity(2, 3) + } + }; - command.Parse("-x 1 -x 2 -x 3") - .GetResult(option) - .Tokens - .Should() - .BeEquivalentTo(new [] { - new Token("1", TokenType.Argument, default), - new Token("2", TokenType.Argument, default), - new Token("3", TokenType.Argument, default)}); - } + ParseResult parseResult = command.Parse("1 2 3 4"); + + parseResult + .Errors + .Select(e => e.Message) + .Should() + .Contain(LocalizationResources.UnrecognizedCommandOrArgument("4")); + } + + [Fact] + public void Option_argument_arity_can_be_a_fixed_value_greater_than_1() + { + var option = new Option("-x") { Arity = new ArgumentArity(3, 3)}; - [Fact] - public void Option_argument_arity_can_be_a_range_with_a_lower_bound_greater_than_1() + var command = new Command("the-command") { - var option = new Option("-x") { Arity = new ArgumentArity(3, 5) }; + option + }; - var command = new Command("the-command") - { - option - }; + command.Parse("-x 1 -x 2 -x 3") + .GetResult(option) + .Tokens + .Should() + .BeEquivalentTo(new [] { + new Token("1", TokenType.Argument, default), + new Token("2", TokenType.Argument, default), + new Token("3", TokenType.Argument, default)}); + } - command.Parse("-x 1 -x 2 -x 3") - .GetResult(option) - .Tokens - .Should() - .BeEquivalentTo(new [] { - new Token("1", TokenType.Argument, default), - new Token("2", TokenType.Argument, default), - new Token("3", TokenType.Argument, default)}); - command.Parse("-x 1 -x 2 -x 3 -x 4 -x 5") - .GetResult(option) - .Tokens - .Should() - .BeEquivalentTo(new [] { - new Token("1", TokenType.Argument, default), - new Token("2", TokenType.Argument, default), - new Token("3", TokenType.Argument, default), - new Token("4", TokenType.Argument, default), - new Token("5", TokenType.Argument, default)}); - } + [Fact] + public void Option_argument_arity_can_be_a_range_with_a_lower_bound_greater_than_1() + { + var option = new Option("-x") { Arity = new ArgumentArity(3, 5) }; + + var command = new Command("the-command") + { + option + }; + + command.Parse("-x 1 -x 2 -x 3") + .GetResult(option) + .Tokens + .Should() + .BeEquivalentTo(new [] { + new Token("1", TokenType.Argument, default), + new Token("2", TokenType.Argument, default), + new Token("3", TokenType.Argument, default)}); + command.Parse("-x 1 -x 2 -x 3 -x 4 -x 5") + .GetResult(option) + .Tokens + .Should() + .BeEquivalentTo(new [] { + new Token("1", TokenType.Argument, default), + new Token("2", TokenType.Argument, default), + new Token("3", TokenType.Argument, default), + new Token("4", TokenType.Argument, default), + new Token("5", TokenType.Argument, default)}); + } - [Fact] - public void When_option_arguments_are_fewer_than_minimum_arity_then_an_error_is_returned() - { - var option = new Option("-x") - { - Arity = new ArgumentArity(2, 3) - }; + [Fact] + public void When_option_arguments_are_fewer_than_minimum_arity_then_an_error_is_returned() + { + var option = new Option("-x") + { + Arity = new ArgumentArity(2, 3) + }; - var command = new Command("the-command") - { - option - }; + var command = new Command("the-command") + { + option + }; - var result = command.Parse("-x 1"); + var result = command.Parse("-x 1"); - result.Errors - .Select(e => e.Message) - .Should() - .Contain(LocalizationResources.RequiredArgumentMissing(result.GetResult(option))); - } + result.Errors + .Select(e => e.Message) + .Should() + .Contain(LocalizationResources.RequiredArgumentMissing(result.GetResult(option))); + } - [Fact] - public void When_option_arguments_are_greater_than_maximum_arity_then_an_error_is_returned() + [Fact] + public void When_option_arguments_are_greater_than_maximum_arity_then_an_error_is_returned() + { + var command = new Command("the-command") { - var command = new Command("the-command") - { - new Option("-x") { Arity = new ArgumentArity(2, 3)} - }; + new Option("-x") { Arity = new ArgumentArity(2, 3)} + }; - command.Parse("-x 1 2 3 4") - .Errors - .Select(e => e.Message) - .Should() - .Contain(LocalizationResources.UnrecognizedCommandOrArgument("4")); - } + command.Parse("-x 1 2 3 4") + .Errors + .Select(e => e.Message) + .Should() + .Contain(LocalizationResources.UnrecognizedCommandOrArgument("4")); + } - [Fact] - public void Tokens_are_not_split_if_the_part_before_the_delimiter_is_not_an_option() - { - var rootCommand = new Command("jdbc"); - rootCommand.Add(new Option("url")); - var result = rootCommand.Parse("jdbc url \"jdbc:sqlserver://10.0.0.2;databaseName=main\""); - - result.Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentTo("url", - "jdbc:sqlserver://10.0.0.2;databaseName=main"); - } + [Fact] + public void Tokens_are_not_split_if_the_part_before_the_delimiter_is_not_an_option() + { + var rootCommand = new Command("jdbc"); + rootCommand.Add(new Option("url")); + var result = rootCommand.Parse("jdbc url \"jdbc:sqlserver://10.0.0.2;databaseName=main\""); + + result.Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentTo("url", + "jdbc:sqlserver://10.0.0.2;databaseName=main"); + } - [Fact] - public void A_subcommand_wont_overflow_when_checking_maximum_argument_capacity() - { - // Tests bug identified in https://github.com/dotnet/command-line-api/issues/997 + [Fact] + public void A_subcommand_wont_overflow_when_checking_maximum_argument_capacity() + { + // Tests bug identified in https://github.com/dotnet/command-line-api/issues/997 - var argument1 = new Argument("arg1"); + var argument1 = new Argument("arg1"); - var argument2 = new Argument("arg2"); + var argument2 = new Argument("arg2"); - var command = new Command("subcommand") - { - argument1, - argument2 - }; + var command = new Command("subcommand") + { + argument1, + argument2 + }; - var rootCommand = new RootCommand - { - command - }; + var rootCommand = new RootCommand + { + command + }; - var parseResult = rootCommand.Parse("subcommand arg1 arg2"); + var parseResult = rootCommand.Parse("subcommand arg1 arg2"); - Action act = () => parseResult.GetCompletions(); - act.Should().NotThrow(); - } + Action act = () => parseResult.GetCompletions(); + act.Should().NotThrow(); + } - [Theory] // https://github.com/dotnet/command-line-api/issues/1551, https://github.com/dotnet/command-line-api/issues/1533 - [InlineData("--exec-prefix", "")] - [InlineData("--exec-prefix:", "")] - [InlineData("--exec-prefix=", "")] - public void Parsed_value_of_empty_string_arg_is_an_empty_string(string arg1, string arg2) + [Theory] // https://github.com/dotnet/command-line-api/issues/1551, https://github.com/dotnet/command-line-api/issues/1533 + [InlineData("--exec-prefix", "")] + [InlineData("--exec-prefix:", "")] + [InlineData("--exec-prefix=", "")] + public void Parsed_value_of_empty_string_arg_is_an_empty_string(string arg1, string arg2) + { + var option = new Option("--exec-prefix") { - var option = new Option("--exec-prefix") - { - DefaultValueFactory = _ => "/usr/local" - }; + DefaultValueFactory = _ => "/usr/local" + }; - var rootCommand = new RootCommand - { - option - }; + var rootCommand = new RootCommand + { + option + }; - var result = rootCommand.Parse(new[] { arg1, arg2 }); + var result = rootCommand.Parse(new[] { arg1, arg2 }); - GetValue(result, option).Should().BeEmpty(); - } + GetValue(result, option).Should().BeEmpty(); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs b/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs index 423abdab39..48d47adef4 100644 --- a/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs +++ b/src/System.CommandLine.Tests/Parsing/CommandLineStringSplitterTests.cs @@ -7,91 +7,90 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests.Parsing +namespace System.CommandLine.Tests.Parsing; + +public class CommandLineStringSplitterTests { - public class CommandLineStringSplitterTests + [Theory] + [InlineData("one two three four")] + [InlineData("one two\tthree four ")] + [InlineData(" one two three four")] + [InlineData(" one\ntwo\nthree\nfour\n")] + [InlineData(" one\r\ntwo\r\nthree\r\nfour\r\n")] + public void It_splits_strings_based_on_whitespace(string commandLine) + { + CommandLineParser.SplitCommandLine(commandLine) + .Should() + .BeEquivalentSequenceTo("one", "two", "three", "four"); + } + + [Fact] + public void It_does_not_break_up_double_quote_delimited_values() + { + var commandLine = @"rm -r ""c:\temp files\"""; + + CommandLineParser.SplitCommandLine(commandLine) + .Should() + .BeEquivalentSequenceTo("rm", "-r", @"c:\temp files\"); + } + + [Theory] + [InlineData("-", '=')] + [InlineData("-", ':')] + [InlineData("--", '=')] + [InlineData("--", ':')] + [InlineData("/", '=')] + [InlineData("/", ':')] + public void It_does_not_split_double_quote_delimited_values_when_a_non_whitespace_argument_delimiter_is_used( + string prefix, + char delimiter) + { + var optionAndArgument = $@"{prefix}the-option{delimiter}""c:\temp files\"""; + + var commandLine = $"the-command {optionAndArgument}"; + + CommandLineParser.SplitCommandLine(commandLine) + .Should() + .BeEquivalentSequenceTo("the-command", optionAndArgument.Replace("\"", "")); + } + + [Fact] + public void It_handles_multiple_options_with_quoted_arguments() + { + var source = Directory.GetCurrentDirectory(); + var destination = Path.Combine(Directory.GetCurrentDirectory(), ".trash"); + + var commandLine = $"move --from \"{source}\" --to \"{destination}\" --verbose"; + + var tokenized = CommandLineParser.SplitCommandLine(commandLine); + + tokenized.Should() + .BeEquivalentSequenceTo( + "move", + "--from", + source, + "--to", + destination, + "--verbose"); + } + + [Fact] + public void Internal_quotes_do_not_cause_string_to_be_split() + { + var commandLine = @"POST --raw='{""Id"":1,""Name"":""Alice""}'"; + + CommandLineParser.SplitCommandLine(commandLine) + .Should() + .BeEquivalentTo("POST", "--raw='{Id:1,Name:Alice}'"); + } + + [Fact] + public void Internal_whitespaces_are_preserved_and_do_not_cause_string_to_be_split() { - [Theory] - [InlineData("one two three four")] - [InlineData("one two\tthree four ")] - [InlineData(" one two three four")] - [InlineData(" one\ntwo\nthree\nfour\n")] - [InlineData(" one\r\ntwo\r\nthree\r\nfour\r\n")] - public void It_splits_strings_based_on_whitespace(string commandLine) - { - CommandLineParser.SplitCommandLine(commandLine) - .Should() - .BeEquivalentSequenceTo("one", "two", "three", "four"); - } - - [Fact] - public void It_does_not_break_up_double_quote_delimited_values() - { - var commandLine = @"rm -r ""c:\temp files\"""; - - CommandLineParser.SplitCommandLine(commandLine) - .Should() - .BeEquivalentSequenceTo("rm", "-r", @"c:\temp files\"); - } - - [Theory] - [InlineData("-", '=')] - [InlineData("-", ':')] - [InlineData("--", '=')] - [InlineData("--", ':')] - [InlineData("/", '=')] - [InlineData("/", ':')] - public void It_does_not_split_double_quote_delimited_values_when_a_non_whitespace_argument_delimiter_is_used( - string prefix, - char delimiter) - { - var optionAndArgument = $@"{prefix}the-option{delimiter}""c:\temp files\"""; - - var commandLine = $"the-command {optionAndArgument}"; - - CommandLineParser.SplitCommandLine(commandLine) - .Should() - .BeEquivalentSequenceTo("the-command", optionAndArgument.Replace("\"", "")); - } - - [Fact] - public void It_handles_multiple_options_with_quoted_arguments() - { - var source = Directory.GetCurrentDirectory(); - var destination = Path.Combine(Directory.GetCurrentDirectory(), ".trash"); - - var commandLine = $"move --from \"{source}\" --to \"{destination}\" --verbose"; - - var tokenized = CommandLineParser.SplitCommandLine(commandLine); - - tokenized.Should() - .BeEquivalentSequenceTo( - "move", - "--from", - source, - "--to", - destination, - "--verbose"); - } - - [Fact] - public void Internal_quotes_do_not_cause_string_to_be_split() - { - var commandLine = @"POST --raw='{""Id"":1,""Name"":""Alice""}'"; - - CommandLineParser.SplitCommandLine(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""}'"; - - CommandLineParser.SplitCommandLine(commandLine) - .Should() - .BeEquivalentTo("command", "--raw='{Id:1,Movie Name:The Three Musketeers}'"); - } + var commandLine = @"command --raw='{""Id"":1,""Movie Name"":""The Three Musketeers""}'"; + + CommandLineParser.SplitCommandLine(commandLine) + .Should() + .BeEquivalentTo("command", "--raw='{Id:1,Movie Name:The Three Musketeers}'"); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ParsingValidationTests.cs b/src/System.CommandLine.Tests/ParsingValidationTests.cs index 097f6094b1..8d2149fdcb 100644 --- a/src/System.CommandLine.Tests/ParsingValidationTests.cs +++ b/src/System.CommandLine.Tests/ParsingValidationTests.cs @@ -10,1299 +10,1298 @@ using Xunit; using Xunit.Abstractions; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class ParsingValidationTests { - public class ParsingValidationTests + private readonly ITestOutputHelper _output; + + public ParsingValidationTests(ITestOutputHelper output) { - private readonly ITestOutputHelper _output; + _output = output; + } - public ParsingValidationTests(ITestOutputHelper output) - { - _output = output; - } + [Fact] + public void When_an_option_accepts_only_specific_arguments_but_a_wrong_one_is_supplied_then_an_informative_error_is_returned() + { + var option = new Option("-x"); + option.AcceptOnlyFromAmong("this", "that", "the-other-thing"); - [Fact] - public void When_an_option_accepts_only_specific_arguments_but_a_wrong_one_is_supplied_then_an_informative_error_is_returned() - { - var option = new Option("-x"); - option.AcceptOnlyFromAmong("this", "that", "the-other-thing"); + var result = new RootCommand { option }.Parse("-x none-of-those"); - var result = new RootCommand { option }.Parse("-x none-of-those"); + result.Errors + .Select(e => e.Message) + .Should() + .HaveCount(1) + .And + .Contain("Argument 'none-of-those' not recognized. Must be one of:\n\t'this'\n\t'that'\n\t'the-other-thing'"); + } - result.Errors - .Select(e => e.Message) - .Should() - .HaveCount(1) - .And - .Contain("Argument 'none-of-those' not recognized. Must be one of:\n\t'this'\n\t'that'\n\t'the-other-thing'"); - } + [Fact] + public void When_an_option_has_en_error_then_the_error_has_a_reference_to_the_option() + { + var option = new Option("-x"); + option.AcceptOnlyFromAmong("this", "that"); - [Fact] - public void When_an_option_has_en_error_then_the_error_has_a_reference_to_the_option() - { - var option = new Option("-x"); - option.AcceptOnlyFromAmong("this", "that"); + var result = new RootCommand { option }.Parse("-x something_else"); - var result = new RootCommand { option }.Parse("-x something_else"); + result.Errors + .Where(e => e.SymbolResult != null) + .Should() + .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == option.Name); + } - result.Errors - .Where(e => e.SymbolResult != null) - .Should() - .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == option.Name); - } + [Fact] // https://github.com/dotnet/command-line-api/issues/1475 + public void When_FromAmong_is_used_then_the_OptionResult_ErrorMessage_is_set() + { + var option = new Option("--opt"); + option.AcceptOnlyFromAmong("a", "b"); + var command = new Command("test") { option }; - [Fact] // https://github.com/dotnet/command-line-api/issues/1475 - public void When_FromAmong_is_used_then_the_OptionResult_ErrorMessage_is_set() - { - var option = new Option("--opt"); - option.AcceptOnlyFromAmong("a", "b"); - var command = new Command("test") { option }; + var parseResult = command.Parse("test --opt c"); - var parseResult = command.Parse("test --opt c"); + var error = parseResult.Errors.Single(); - var error = parseResult.Errors.Single(); + error + .Message + .Should() + .Be(LocalizationResources.UnrecognizedArgument("c", new []{ "a", "b"})); + error + .SymbolResult + .Should() + .BeOfType(); - error - .Message - .Should() - .Be(LocalizationResources.UnrecognizedArgument("c", new []{ "a", "b"})); - error - .SymbolResult - .Should() - .BeOfType(); + } - } + [Fact] // https://github.com/dotnet/command-line-api/issues/1475 + public void When_FromAmong_is_used_then_the_ArgumentResult_ErrorMessage_is_set() + { + var argument = new Argument("arg"); + argument.AcceptOnlyFromAmong("a", "b"); + + var command = new Command("test") { argument }; - [Fact] // https://github.com/dotnet/command-line-api/issues/1475 - public void When_FromAmong_is_used_then_the_ArgumentResult_ErrorMessage_is_set() + var parseResult = command.Parse("test c"); + + var error = parseResult.Errors.Single(); + + error + .Message + .Should() + .Be(LocalizationResources.UnrecognizedArgument("c", new []{ "a", "b"})); + error + .SymbolResult + .Should() + .BeOfType(); + } + + [Fact] // https://github.com/dotnet/command-line-api/issues/1556 + public void When_FromAmong_is_used_for_multiple_arguments_and_valid_input_is_provided_then_there_are_no_errors() + { + var command = new Command("set") { - var argument = new Argument("arg"); - argument.AcceptOnlyFromAmong("a", "b"); + CreateArgumentWithAcceptOnlyFromAmong(name: "key", "key1", "key2"), + CreateArgumentWithAcceptOnlyFromAmong(name : "value", "value1", "value2") + }; - var command = new Command("test") { argument }; + var result = command.Parse("set key1 value1"); - var parseResult = command.Parse("test c"); + result.Errors.Should().BeEmpty(); + } - var error = parseResult.Errors.Single(); + [Fact] + public void When_FromAmong_is_used_for_multiple_arguments_and_invalid_input_is_provided_for_the_first_one_then_the_error_is_informative() + { + var command = new Command("set") + { + CreateArgumentWithAcceptOnlyFromAmong(name : "key", "key1", "key2"), + CreateArgumentWithAcceptOnlyFromAmong(name : "value", "value1", "value2") + }; + + var result = command.Parse("set not-key1 value1"); + + result.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be(LocalizationResources.UnrecognizedArgument("not-key1", new[] { "key1", "key2" })); + } - error - .Message - .Should() - .Be(LocalizationResources.UnrecognizedArgument("c", new []{ "a", "b"})); - error - .SymbolResult - .Should() - .BeOfType(); - } + [Fact] + public void When_FromAmong_is_used_multiple_times_only_the_most_recently_provided_values_are_taken_into_account() + { + Argument argument = new("key"); + argument.AcceptOnlyFromAmong("key1"); - [Fact] // https://github.com/dotnet/command-line-api/issues/1556 - public void When_FromAmong_is_used_for_multiple_arguments_and_valid_input_is_provided_then_there_are_no_errors() + var command = new Command("set") { - var command = new Command("set") - { - CreateArgumentWithAcceptOnlyFromAmong(name: "key", "key1", "key2"), - CreateArgumentWithAcceptOnlyFromAmong(name : "value", "value1", "value2") - }; + argument + }; - var result = command.Parse("set key1 value1"); + var result = command.Parse("set key2"); - result.Errors.Should().BeEmpty(); - } + result.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be(LocalizationResources.UnrecognizedArgument("key2", new[] { "key1" })); - [Fact] - public void When_FromAmong_is_used_for_multiple_arguments_and_invalid_input_is_provided_for_the_first_one_then_the_error_is_informative() - { - var command = new Command("set") - { - CreateArgumentWithAcceptOnlyFromAmong(name : "key", "key1", "key2"), - CreateArgumentWithAcceptOnlyFromAmong(name : "value", "value1", "value2") - }; + argument.AcceptOnlyFromAmong("key2"); - var result = command.Parse("set not-key1 value1"); + result = command.Parse("set key2"); - result.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be(LocalizationResources.UnrecognizedArgument("not-key1", new[] { "key1", "key2" })); - } + result.Errors.Should().BeEmpty(); + } - [Fact] - public void When_FromAmong_is_used_multiple_times_only_the_most_recently_provided_values_are_taken_into_account() + [Fact] + public void When_FromAmong_is_used_for_multiple_arguments_and_invalid_input_is_provided_for_the_second_one_then_the_error_is_informative() + { + var command = new Command("set") { - Argument argument = new("key"); - argument.AcceptOnlyFromAmong("key1"); + CreateArgumentWithAcceptOnlyFromAmong(name : "key", "key1", "key2"), + CreateArgumentWithAcceptOnlyFromAmong(name : "value", "value1", "value2") + }; + + var result = command.Parse("set key1 not-value1"); + + result.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be(LocalizationResources.UnrecognizedArgument("not-value1", new[] { "value1", "value2" })); + } - var command = new Command("set") - { - argument - }; + [Fact] + public void When_FromAmong_is_used_and_multiple_invalid_inputs_are_provided_the_errors_mention_all_invalid_arguments() + { + Option option = new("--columns"); + option.AcceptOnlyFromAmong("author", "language", "tags", "type"); + option.Arity = new ArgumentArity(1, 4); + option.AllowMultipleArgumentsPerToken = true; + + var command = new Command("list") + { + option + }; - var result = command.Parse("set key2"); + var result = command.Parse("list --columns c1 c2"); - result.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be(LocalizationResources.UnrecognizedArgument("key2", new[] { "key1" })); + result.Errors.Count.Should().Be(2); - argument.AcceptOnlyFromAmong("key2"); + result.Errors[0] + .Message + .Should() + .Be(LocalizationResources.UnrecognizedArgument("c1", new[] { "author", "language", "tags", "type" })); - result = command.Parse("set key2"); + result.Errors[1] + .Message + .Should() + .Be(LocalizationResources.UnrecognizedArgument("c2", new[] { "author", "language", "tags", "type" })); + } - result.Errors.Should().BeEmpty(); - } + [Fact] + public void When_a_required_argument_is_not_supplied_then_an_error_is_returned() + { + var option = new Option("-x"); - [Fact] - public void When_FromAmong_is_used_for_multiple_arguments_and_invalid_input_is_provided_for_the_second_one_then_the_error_is_informative() + var result = new RootCommand { option }.Parse("-x"); + + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => e.Message == "Required argument missing for option: '-x'."); + } + + [Fact] + public void When_a_required_option_is_not_supplied_then_an_error_is_returned() + { + var command = new Command("command") { - var command = new Command("set") + new Option("-x") { - CreateArgumentWithAcceptOnlyFromAmong(name : "key", "key1", "key2"), - CreateArgumentWithAcceptOnlyFromAmong(name : "value", "value1", "value2") - }; - - var result = command.Parse("set key1 not-value1"); + Required = true + } + }; + + var result = command.Parse(""); + + result.Errors + .Should() + .HaveCount(1) + .And + .ContainSingle() + .Which + .Message + .Should() + .Be("Option '-x' is required."); + } - result.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be(LocalizationResources.UnrecognizedArgument("not-value1", new[] { "value1", "value2" })); - } + [Fact] + public void When_a_required_option_has_multiple_aliases_the_error_message_uses_the_name() + { + var command = new Command("command") + { + new Option("--xray", "-x") + { + Required = true + } + }; + + var result = command.Parse(""); + + result.Errors + .Should() + .HaveCount(1) + .And + .ContainSingle() + .Which + .Message + .Should() + .Be("Option '--xray' is required."); + } - [Fact] - public void When_FromAmong_is_used_and_multiple_invalid_inputs_are_provided_the_errors_mention_all_invalid_arguments() + [Theory] + [InlineData("subcommand -x arg")] + [InlineData("-x arg subcommand")] + public void When_a_required_option_is_allowed_at_more_than_one_position_it_only_needs_to_be_satisfied_in_one(string commandLine) + { + var option = new Option("-x") { - Option option = new("--columns"); - option.AcceptOnlyFromAmong("author", "language", "tags", "type"); - option.Arity = new ArgumentArity(1, 4); - option.AllowMultipleArgumentsPerToken = true; + Required = true + }; - var command = new Command("list") + var command = new RootCommand + { + option, + new Command("subcommand") { option - }; - - var result = command.Parse("list --columns c1 c2"); + } + }; - result.Errors.Count.Should().Be(2); + var result = command.Parse(commandLine); - result.Errors[0] - .Message - .Should() - .Be(LocalizationResources.UnrecognizedArgument("c1", new[] { "author", "language", "tags", "type" })); + result.Errors.Should().BeEmpty(); + } - result.Errors[1] - .Message - .Should() - .Be(LocalizationResources.UnrecognizedArgument("c2", new[] { "author", "language", "tags", "type" })); - } + [Fact] + public void Required_options_on_parent_commands_do_not_create_parse_errors_when_an_inner_command_is_specified() + { + var child = new Command("child"); - [Fact] - public void When_a_required_argument_is_not_supplied_then_an_error_is_returned() + var parent = new Command("parent") { - var option = new Option("-x"); + new Option("-x") { Required = true }, + child + }; - var result = new RootCommand { option }.Parse("-x"); + var result = parent.Parse("child"); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => e.Message == "Required argument missing for option: '-x'."); - } + result.Errors.Should().BeEmpty(); + } - [Fact] - public void When_a_required_option_is_not_supplied_then_an_error_is_returned() - { - var command = new Command("command") + [Fact] + public void When_no_option_accepts_arguments_but_one_is_supplied_then_an_error_is_returned() + { + var command = + new Command("the-command") { - new Option("-x") + new Option("-x") { - Required = true + Arity = ArgumentArity.Zero } }; - var result = command.Parse(""); + var result = command.Parse("the-command -x some-arg"); - result.Errors - .Should() - .HaveCount(1) - .And - .ContainSingle() - .Which - .Message - .Should() - .Be("Option '-x' is required."); - } + _output.WriteLine(result.ToString()); - [Fact] - public void When_a_required_option_has_multiple_aliases_the_error_message_uses_the_name() + result.Errors + .Select(e => e.Message) + .Should() + .HaveCount(1) + .And + .Contain(e => e == "Unrecognized command or argument 'some-arg'."); + } + + [Fact] + public void A_custom_validator_can_be_added_to_a_command() + { + var command = new Command("the-command") { - var command = new Command("command") + new Option("--one"), + new Option("--two") + }; + + command.Validators.Add(commandResult => + { + if (commandResult.Children.Any(sr => ((OptionResult)sr).Option.Name == "--one" && + commandResult.Children.Any(sr => ((OptionResult)sr).Option.Name == "--two"))) { - new Option("--xray", "-x") - { - Required = true - } - }; + commandResult.AddError("Options '--one' and '--two' cannot be used together."); + } + }); - var result = command.Parse(""); + var result = command.Parse("the-command --one --two"); - result.Errors - .Should() - .HaveCount(1) - .And - .ContainSingle() - .Which - .Message - .Should() - .Be("Option '--xray' is required."); - } + result + .Errors + .Select(e => e.Message) + .Should() + .HaveCount(1) + .And + .Contain("Options '--one' and '--two' cannot be used together."); + } + + [Fact] + public void A_custom_validator_can_be_added_to_an_option() + { + var option = new Option("-x"); - [Theory] - [InlineData("subcommand -x arg")] - [InlineData("-x arg subcommand")] - public void When_a_required_option_is_allowed_at_more_than_one_position_it_only_needs_to_be_satisfied_in_one(string commandLine) + option.Validators.Add(r => { - var option = new Option("-x") - { - Required = true - }; + var value = r.GetValueOrDefault(); - var command = new RootCommand - { - option, - new Command("subcommand") - { - option - } - }; + r.AddError($"Option {r.IdentifierToken.Value} cannot be set to {value}"); + }); - var result = command.Parse(commandLine); + var command = new RootCommand { option }; - result.Errors.Should().BeEmpty(); - } + var result = command.Parse("-x 123"); - [Fact] - public void Required_options_on_parent_commands_do_not_create_parse_errors_when_an_inner_command_is_specified() + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((OptionResult)e.SymbolResult).Option == option) + .Which + .Message + .Should() + .Be("Option -x cannot be set to 123"); + } + + [Fact] + public void A_custom_validator_can_be_added_to_an_argument() + { + var argument = new Argument("x"); + + argument.Validators.Add(r => { - var child = new Command("child"); + var value = r.GetValueOrDefault(); - var parent = new Command("parent") - { - new Option("-x") { Required = true }, - child - }; + r.AddError($"Argument {r.Argument.Name} cannot be set to {value}"); + }); - var result = parent.Parse("child"); + var command = new RootCommand { argument }; - result.Errors.Should().BeEmpty(); - } + var result = command.Parse("123"); - [Fact] - public void When_no_option_accepts_arguments_but_one_is_supplied_then_an_error_is_returned() + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((ArgumentResult)e.SymbolResult).Argument == argument) + .Which + .Message + .Should() + .Be("Argument x cannot be set to 123"); + } + + [Theory] + [InlineData("-o=optionValue argValue")] + [InlineData("argValue -o=optionValue")] + public void All_custom_validators_are_called(string commandLine) + { + var commandValidatorWasCalled = false; + var optionValidatorWasCalled = false; + var argumentValidatorWasCalled = false; + + var option = new Option("-o"); + option.Validators.Add(_ => { - var command = - new Command("the-command") - { - new Option("-x") - { - Arity = ArgumentArity.Zero - } - }; + optionValidatorWasCalled = true; + }); - var result = command.Parse("the-command -x some-arg"); + var argument = new Argument("the-arg"); + argument.Validators.Add(_ => + { + argumentValidatorWasCalled = true; + }); - _output.WriteLine(result.ToString()); + var rootCommand = new RootCommand + { + option, + argument + }; + rootCommand.Validators.Add(_ => + { + commandValidatorWasCalled = true; + }); - result.Errors - .Select(e => e.Message) - .Should() - .HaveCount(1) - .And - .Contain(e => e == "Unrecognized command or argument 'some-arg'."); - } + rootCommand.Parse(commandLine).Invoke(); - [Fact] - public void A_custom_validator_can_be_added_to_a_command() + commandValidatorWasCalled.Should().BeTrue(); + optionValidatorWasCalled.Should().BeTrue(); + argumentValidatorWasCalled.Should().BeTrue(); + } + + [Theory] + [InlineData("--file \"Foo\" subcommand")] + [InlineData("subcommand --file \"Foo\"")] + public void Validators_on_global_options_are_executed_when_invoking_a_subcommand(string commandLine) + { + var option = new Option("--file") { Recursive = true }; + option.Validators.Add(r => { - var command = new Command("the-command") - { - new Option("--one"), - new Option("--two") - }; + r.AddError("Invoked validator"); + }); - command.Validators.Add(commandResult => - { - if (commandResult.Children.Any(sr => ((OptionResult)sr).Option.Name == "--one" && - commandResult.Children.Any(sr => ((OptionResult)sr).Option.Name == "--two"))) - { - commandResult.AddError("Options '--one' and '--two' cannot be used together."); - } - }); + var subCommand = new Command("subcommand"); + var rootCommand = new RootCommand + { + subCommand + }; + rootCommand.Options.Add(option); + + var result = rootCommand.Parse(commandLine); + + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((OptionResult)e.SymbolResult).Option == option) + .Which + .Message + .Should() + .Be("Invoked validator"); + } - var result = command.Parse("the-command --one --two"); + [Theory] + [InlineData("--value 123")] + [InlineData("--value=123 child")] + [InlineData("--value=123 child grandchild")] + [InlineData("child --value=123")] + [InlineData("child --value=123 grandchild")] + [InlineData("child grandchild --value=123 ")] + public async Task A_custom_validator_added_to_a_global_option_is_checked(string commandLine) + { + var handlerWasCalled = false; - result - .Errors - .Select(e => e.Message) - .Should() - .HaveCount(1) - .And - .Contain("Options '--one' and '--two' cannot be used together."); - } + var globalOption = new Option("--value") + { + Recursive = true + }; - [Fact] - public void A_custom_validator_can_be_added_to_an_option() + globalOption.Validators.Add(r => r.AddError("oops!")); + + var grandchildCommand = new Command("grandchild"); + + var childCommand = new Command("child") { - var option = new Option("-x"); + grandchildCommand + }; + var rootCommand = new RootCommand + { + childCommand + }; - option.Validators.Add(r => - { - var value = r.GetValueOrDefault(); + rootCommand.Options.Add(globalOption); - r.AddError($"Option {r.IdentifierToken.Value} cannot be set to {value}"); - }); + rootCommand.SetAction((ctx) => handlerWasCalled = true); + childCommand.SetAction((ctx) => handlerWasCalled = true); + grandchildCommand.SetAction((ctx) => handlerWasCalled = true); - var command = new RootCommand { option }; + var result = await rootCommand.Parse(commandLine).InvokeAsync(); - var result = command.Parse("-x 123"); + result.Should().Be(1); + handlerWasCalled.Should().BeFalse(); + } - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((OptionResult)e.SymbolResult).Option == option) - .Which - .Message - .Should() - .Be("Option -x cannot be set to 123"); - } + [Fact] + public void Custom_validator_error_messages_are_not_repeated() + { + var errorMessage = "that's not right..."; + var argument = new Argument("arg"); + argument.Validators.Add(r => r.AddError(errorMessage)); - [Fact] - public void A_custom_validator_can_be_added_to_an_argument() + var cmd = new Command("get") { - var argument = new Argument("x"); + argument + }; - argument.Validators.Add(r => - { - var value = r.GetValueOrDefault(); + var result = cmd.Parse("get something"); - r.AddError($"Argument {r.Argument.Name} cannot be set to {value}"); - }); + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => e.Message == errorMessage); + } + + [Fact] + public void The_parsed_value_of_an_argument_is_available_within_a_validator() + { + var argument = new Argument("arg"); + var errorMessage = "The value of option '-x' must be between 1 and 100."; + argument.Validators.Add(result => + { + var value = result.GetValue(argument); - var command = new RootCommand { argument }; + if (value < 0 || value > 100) + { + result.AddError(errorMessage); + } + }); - var result = command.Parse("123"); + var result = new RootCommand() { argument }.Parse("-1"); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((ArgumentResult)e.SymbolResult).Argument == argument) - .Which - .Message - .Should() - .Be("Argument x cannot be set to 123"); - } + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => e.Message == errorMessage); + } - [Theory] - [InlineData("-o=optionValue argValue")] - [InlineData("argValue -o=optionValue")] - public void All_custom_validators_are_called(string commandLine) + [Fact] + public void The_parsed_value_of_an_option_is_available_within_a_validator() + { + var option = new Option("-x"); + var errorMessage = "The value of option '-x' must be between 1 and 100."; + option.Validators.Add(result => { - var commandValidatorWasCalled = false; - var optionValidatorWasCalled = false; - var argumentValidatorWasCalled = false; + var value = result.GetValue(option); - var option = new Option("-o"); - option.Validators.Add(_ => + if (value < 0 || value > 100) { - optionValidatorWasCalled = true; - }); + result.AddError(errorMessage); + } + }); - var argument = new Argument("the-arg"); - argument.Validators.Add(_ => - { - argumentValidatorWasCalled = true; - }); + var result = new RootCommand { option }.Parse("-x -1"); - var rootCommand = new RootCommand + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => e.Message == errorMessage); + } + + public class PathValidity + { + [Fact] + public void LegalFilePathsOnly_rejects_command_arguments_containing_invalid_path_characters() + { + Argument argument = new("arg"); + argument.AcceptLegalFilePathsOnly(); + var command = new Command("the-command") { - option, argument }; - rootCommand.Validators.Add(_ => - { - commandValidatorWasCalled = true; - }); - rootCommand.Parse(commandLine).Invoke(); + var invalidCharacter = Path.GetInvalidPathChars().First(c => c != '"'); - commandValidatorWasCalled.Should().BeTrue(); - optionValidatorWasCalled.Should().BeTrue(); - argumentValidatorWasCalled.Should().BeTrue(); - } + var result = command.Parse($"the-command {invalidCharacter}"); - [Theory] - [InlineData("--file \"Foo\" subcommand")] - [InlineData("subcommand --file \"Foo\"")] - public void Validators_on_global_options_are_executed_when_invoking_a_subcommand(string commandLine) + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((ArgumentResult)e.SymbolResult).Argument == command.Arguments.First() && + e.Message == $"Character not allowed in a path: '{invalidCharacter}'."); + } + + [Fact] + public void LegalFilePathsOnly_rejects_option_arguments_containing_invalid_path_characters() { - var option = new Option("--file") { Recursive = true }; - option.Validators.Add(r => - { - r.AddError("Invoked validator"); - }); - - var subCommand = new Command("subcommand"); - var rootCommand = new RootCommand + Option option = new ("-x"); + option.AcceptLegalFilePathsOnly(); + var command = new Command("the-command") { - subCommand + option }; - rootCommand.Options.Add(option); - var result = rootCommand.Parse(commandLine); + var invalidCharacter = Path.GetInvalidPathChars().First(c => c != '"'); + + var result = command.Parse($"the-command -x {invalidCharacter}"); result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((OptionResult)e.SymbolResult).Option == option) - .Which - .Message - .Should() - .Be("Invoked validator"); + .Should() + .HaveCount(1) + .And + .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "-x" && + e.Message == $"Character not allowed in a path: '{invalidCharacter}'."); } - [Theory] - [InlineData("--value 123")] - [InlineData("--value=123 child")] - [InlineData("--value=123 child grandchild")] - [InlineData("child --value=123")] - [InlineData("child --value=123 grandchild")] - [InlineData("child grandchild --value=123 ")] - public async Task A_custom_validator_added_to_a_global_option_is_checked(string commandLine) + [Fact] + public void LegalFilePathsOnly_accepts_command_arguments_containing_valid_path_characters() { - var handlerWasCalled = false; - - var globalOption = new Option("--value") - { - Recursive = true + Argument argument = new ("arg"); + argument.AcceptLegalFilePathsOnly(); + var command = new Command("the-command") + { + argument }; - globalOption.Validators.Add(r => r.AddError("oops!")); + var validPathName = Directory.GetCurrentDirectory(); + var validNonExistingFileName = Path.Combine(validPathName, Guid.NewGuid().ToString()); - var grandchildCommand = new Command("grandchild"); + var result = command.Parse($"the-command {validPathName} {validNonExistingFileName}"); - var childCommand = new Command("child") - { - grandchildCommand - }; - var rootCommand = new RootCommand + result.Errors.Should().BeEmpty(); + } + + [Fact] + public void LegalFilePathsOnly_accepts_option_arguments_containing_valid_path_characters() + { + Option option = new ("-x"); + option.AcceptLegalFilePathsOnly(); + + var command = new Command("the-command") { - childCommand + option }; - rootCommand.Options.Add(globalOption); + var validPathName = Directory.GetCurrentDirectory(); + var validNonExistingFileName = Path.Combine(validPathName, Guid.NewGuid().ToString()); - rootCommand.SetAction((ctx) => handlerWasCalled = true); - childCommand.SetAction((ctx) => handlerWasCalled = true); - grandchildCommand.SetAction((ctx) => handlerWasCalled = true); + var result = command.Parse($"the-command -x {validPathName} -x {validNonExistingFileName}"); - var result = await rootCommand.Parse(commandLine).InvokeAsync(); - - result.Should().Be(1); - handlerWasCalled.Should().BeFalse(); + result.Errors.Should().BeEmpty(); } + } + public class FileNameValidity + { [Fact] - public void Custom_validator_error_messages_are_not_repeated() + public void LegalFileNamesOnly_rejects_command_arguments_containing_invalid_file_name_characters() { - var errorMessage = "that's not right..."; - var argument = new Argument("arg"); - argument.Validators.Add(r => r.AddError(errorMessage)); + Argument argument = new("arg"); + argument.AcceptLegalFileNamesOnly(); - var cmd = new Command("get") + var command = new Command("the-command") { argument }; - var result = cmd.Parse("get something"); + var invalidCharacter = Path.GetInvalidFileNameChars().First(c => c != '"'); + + var result = command.Parse($"the-command {invalidCharacter}"); result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => e.Message == errorMessage); + .Should() + .HaveCount(1) + .And + .Contain(e => ((ArgumentResult)e.SymbolResult).Argument == command.Arguments.First() && + e.Message == $"Character not allowed in a file name: '{invalidCharacter}'."); } [Fact] - public void The_parsed_value_of_an_argument_is_available_within_a_validator() + public void LegalFileNamesOnly_rejects_option_arguments_containing_invalid_file_name_characters() { - var argument = new Argument("arg"); - var errorMessage = "The value of option '-x' must be between 1 and 100."; - argument.Validators.Add(result => + Option option = new("-x"); + option.AcceptLegalFileNamesOnly(); + + var command = new Command("the-command") { - var value = result.GetValue(argument); + option + }; - if (value < 0 || value > 100) - { - result.AddError(errorMessage); - } - }); + var invalidCharacter = Path.GetInvalidFileNameChars().First(c => c != '"'); - var result = new RootCommand() { argument }.Parse("-1"); + var result = command.Parse($"the-command -x {invalidCharacter}"); result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => e.Message == errorMessage); + .Should() + .HaveCount(1) + .And + .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "-x" && + e.Message == $"Character not allowed in a file name: '{invalidCharacter}'."); } [Fact] - public void The_parsed_value_of_an_option_is_available_within_a_validator() + public void LegalFileNamesOnly_accepts_command_arguments_containing_valid_file_name_characters() { - var option = new Option("-x"); - var errorMessage = "The value of option '-x' must be between 1 and 100."; - option.Validators.Add(result => + Argument argument = new ("arg"); + argument.AcceptLegalFileNamesOnly(); + + var command = new Command("the-command") { - var value = result.GetValue(option); + argument + }; - if (value < 0 || value > 100) - { - result.AddError(errorMessage); - } - }); + var validFileName = Path.GetFileName(Directory.GetCurrentDirectory()); + var validNonExistingFileName = Guid.NewGuid().ToString(); - var result = new RootCommand { option }.Parse("-x -1"); + var result = command.Parse($"the-command {validFileName} {validNonExistingFileName}"); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => e.Message == errorMessage); + result.Errors.Should().BeEmpty(); } - public class PathValidity + [Fact] + public void LegalFileNamesOnly_accepts_option_arguments_containing_valid_file_name_characters() { - [Fact] - public void LegalFilePathsOnly_rejects_command_arguments_containing_invalid_path_characters() + Option option = new("-x"); + option.AcceptLegalFileNamesOnly(); + + var command = new Command("the-command") { - Argument argument = new("arg"); - argument.AcceptLegalFilePathsOnly(); - var command = new Command("the-command") - { - argument - }; + option + }; - var invalidCharacter = Path.GetInvalidPathChars().First(c => c != '"'); + var validFileName = Path.GetFileName(Directory.GetCurrentDirectory()); + var validNonExistingFileName = Guid.NewGuid().ToString(); - var result = command.Parse($"the-command {invalidCharacter}"); + var result = command.Parse($"the-command -x {validFileName} -x {validNonExistingFileName}"); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((ArgumentResult)e.SymbolResult).Argument == command.Arguments.First() && - e.Message == $"Character not allowed in a path: '{invalidCharacter}'."); - } - - [Fact] - public void LegalFilePathsOnly_rejects_option_arguments_containing_invalid_path_characters() - { - Option option = new ("-x"); - option.AcceptLegalFilePathsOnly(); - var command = new Command("the-command") - { - option - }; - - var invalidCharacter = Path.GetInvalidPathChars().First(c => c != '"'); - - var result = command.Parse($"the-command -x {invalidCharacter}"); - - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "-x" && - e.Message == $"Character not allowed in a path: '{invalidCharacter}'."); - } + result.Errors.Should().BeEmpty(); + } + } - [Fact] - public void LegalFilePathsOnly_accepts_command_arguments_containing_valid_path_characters() + public class FileExistence + { + [Fact] + public void A_command_argument_can_be_invalid_based_on_file_existence() + { + var command = new Command("move") { - Argument argument = new ("arg"); - argument.AcceptLegalFilePathsOnly(); - var command = new Command("the-command") - { - argument - }; + new Argument("to").AcceptExistingOnly() + }; - var validPathName = Directory.GetCurrentDirectory(); - var validNonExistingFileName = Path.Combine(validPathName, Guid.NewGuid().ToString()); + var path = NonexistentPath(); + var result = command.Parse($@"move ""{path}"""); - var result = command.Parse($"the-command {validPathName} {validNonExistingFileName}"); + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && + e.Message == $"File does not exist: '{path}'."); + } - result.Errors.Should().BeEmpty(); - } - - [Fact] - public void LegalFilePathsOnly_accepts_option_arguments_containing_valid_path_characters() + [Fact] + public void An_option_argument_can_be_invalid_based_on_file_existence() + { + var command = new Command("move") { - Option option = new ("-x"); - option.AcceptLegalFilePathsOnly(); - - var command = new Command("the-command") - { - option - }; - - var validPathName = Directory.GetCurrentDirectory(); - var validNonExistingFileName = Path.Combine(validPathName, Guid.NewGuid().ToString()); + new Option("--to").AcceptExistingOnly() + }; - var result = command.Parse($"the-command -x {validPathName} -x {validNonExistingFileName}"); + var path = NonexistentPath(); + var result = command.Parse($@"move --to ""{path}"""); - result.Errors.Should().BeEmpty(); - } + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + e.Message == $"File does not exist: '{path}'."); } - public class FileNameValidity + [Fact] + public void A_command_argument_can_be_invalid_based_on_directory_existence() { - [Fact] - public void LegalFileNamesOnly_rejects_command_arguments_containing_invalid_file_name_characters() + var command = new Command("move") { - Argument argument = new("arg"); - argument.AcceptLegalFileNamesOnly(); - - var command = new Command("the-command") - { - argument - }; - - var invalidCharacter = Path.GetInvalidFileNameChars().First(c => c != '"'); + new Argument("to").AcceptExistingOnly() + }; - var result = command.Parse($"the-command {invalidCharacter}"); + var path = NonexistentPath(); + var result = command.Parse($@"move ""{path}"""); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((ArgumentResult)e.SymbolResult).Argument == command.Arguments.First() && - e.Message == $"Character not allowed in a file name: '{invalidCharacter}'."); - } + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && + e.Message == $"Directory does not exist: '{path}'."); + } - [Fact] - public void LegalFileNamesOnly_rejects_option_arguments_containing_invalid_file_name_characters() + [Fact] + public void An_option_argument_can_be_invalid_based_on_directory_existence() + { + var command = new Command("move") { - Option option = new("-x"); - option.AcceptLegalFileNamesOnly(); - - var command = new Command("the-command") - { - option - }; - - var invalidCharacter = Path.GetInvalidFileNameChars().First(c => c != '"'); + new Option("--to").AcceptExistingOnly() + }; - var result = command.Parse($"the-command -x {invalidCharacter}"); + var path = NonexistentPath(); + var result = command.Parse($@"move --to ""{path}"""); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "-x" && - e.Message == $"Character not allowed in a file name: '{invalidCharacter}'."); - } + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + e.Message == $"Directory does not exist: '{path}'."); + } - [Fact] - public void LegalFileNamesOnly_accepts_command_arguments_containing_valid_file_name_characters() + [Fact] + public void A_command_argument_can_be_invalid_based_on_file_or_directory_existence() + { + var command = new Command("move") { - Argument argument = new ("arg"); - argument.AcceptLegalFileNamesOnly(); - - var command = new Command("the-command") - { - argument - }; - - var validFileName = Path.GetFileName(Directory.GetCurrentDirectory()); - var validNonExistingFileName = Guid.NewGuid().ToString(); + new Argument("arg").AcceptExistingOnly() + }; - var result = command.Parse($"the-command {validFileName} {validNonExistingFileName}"); + var path = NonexistentPath(); + var result = command.Parse($"move \"{path}\""); - result.Errors.Should().BeEmpty(); - } + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((ArgumentResult)e.SymbolResult).Argument == command.Arguments.First() && + e.Message == $"File or directory does not exist: '{path}'."); + } - [Fact] - public void LegalFileNamesOnly_accepts_option_arguments_containing_valid_file_name_characters() + [Fact] + public void An_option_argument_can_be_invalid_based_on_file_or_directory_existence() + { + var command = new Command("move") { - Option option = new("-x"); - option.AcceptLegalFileNamesOnly(); - - var command = new Command("the-command") - { - option - }; - - var validFileName = Path.GetFileName(Directory.GetCurrentDirectory()); - var validNonExistingFileName = Guid.NewGuid().ToString(); + new Option("--to").AcceptExistingOnly() + }; - var result = command.Parse($"the-command -x {validFileName} -x {validNonExistingFileName}"); + var path = NonexistentPath(); + var result = command.Parse($@"move --to ""{path}"""); - result.Errors.Should().BeEmpty(); - } + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + e.Message == $"File or directory does not exist: '{path}'."); } - public class FileExistence + [Fact] + public void A_command_argument_with_multiple_files_can_be_invalid_based_on_file_existence() { - [Fact] - public void A_command_argument_can_be_invalid_based_on_file_existence() + var command = new Command("move") { - var command = new Command("move") - { - new Argument("to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move ""{path}"""); - - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && - e.Message == $"File does not exist: '{path}'."); - } + new Argument>("to").AcceptExistingOnly() + }; - [Fact] - public void An_option_argument_can_be_invalid_based_on_file_existence() - { - var command = new Command("move") - { - new Option("--to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move --to ""{path}"""); - - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && - e.Message == $"File does not exist: '{path}'."); - } + var path = NonexistentPath(); + var result = command.Parse($@"move ""{path}"""); - [Fact] - public void A_command_argument_can_be_invalid_based_on_directory_existence() + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && + e.Message == $"File does not exist: '{path}'."); + } + + [Fact] + public void An_option_argument_with_multiple_files_can_be_invalid_based_on_file_existence() + { + var command = new Command("move") { - var command = new Command("move") - { - new Argument("to").AcceptExistingOnly() - }; + new Option>("--to").AcceptExistingOnly() + }; - var path = NonexistentPath(); - var result = command.Parse($@"move ""{path}"""); + var path = NonexistentPath(); + var result = command.Parse($@"move --to ""{path}"""); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && - e.Message == $"Directory does not exist: '{path}'."); - } + result.Errors + .Should() + .HaveCount(1) + .And + .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + e.Message == $"File does not exist: '{path}'."); + } - [Fact] - public void An_option_argument_can_be_invalid_based_on_directory_existence() + [Fact] + public void A_command_argument_with_multiple_directories_can_be_invalid_based_on_directory_existence() + { + var command = new Command("move") { - var command = new Command("move") - { - new Option("--to").AcceptExistingOnly() - }; + new Argument>("to").AcceptExistingOnly() + }; - var path = NonexistentPath(); - var result = command.Parse($@"move --to ""{path}"""); + var path = NonexistentPath(); + var result = command.Parse($@"move ""{path}"""); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + result.Errors + .Should() + .HaveCount(1) + .And + .ContainSingle(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && e.Message == $"Directory does not exist: '{path}'."); - } + } - [Fact] - public void A_command_argument_can_be_invalid_based_on_file_or_directory_existence() + [Fact] + public void An_option_argument_with_multiple_directories_can_be_invalid_based_on_directory_existence() + { + var command = new Command("move") { - var command = new Command("move") - { - new Argument("arg").AcceptExistingOnly() - }; + new Option("--to").AcceptExistingOnly() + }; - var path = NonexistentPath(); - var result = command.Parse($"move \"{path}\""); + var path = NonexistentPath(); + var result = command.Parse($@"move --to ""{path}"""); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((ArgumentResult)e.SymbolResult).Argument == command.Arguments.First() && - e.Message == $"File or directory does not exist: '{path}'."); - } + result.Errors + .Should() + .HaveCount(1) + .And + .ContainSingle(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + e.Message == $"Directory does not exist: '{path}'."); + } - [Fact] - public void An_option_argument_can_be_invalid_based_on_file_or_directory_existence() + [Fact] + public void A_command_argument_with_multiple_FileSystemInfos_can_be_invalid_based_on_file_existence() + { + var command = new Command("move") { - var command = new Command("move") + new Argument("to") { - new Option("--to").AcceptExistingOnly() - }; + Arity = ArgumentArity.ZeroOrMore + }.AcceptExistingOnly(), + new Option("--to") + }; - var path = NonexistentPath(); - var result = command.Parse($@"move --to ""{path}"""); + var path = NonexistentPath(); + var result = command.Parse($@"move ""{path}"""); - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + result.Errors + .Should() + .ContainSingle(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && e.Message == $"File or directory does not exist: '{path}'."); - } - - [Fact] - public void A_command_argument_with_multiple_files_can_be_invalid_based_on_file_existence() - { - var command = new Command("move") - { - new Argument>("to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move ""{path}"""); - - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && - e.Message == $"File does not exist: '{path}'."); - } - - [Fact] - public void An_option_argument_with_multiple_files_can_be_invalid_based_on_file_existence() - { - var command = new Command("move") - { - new Option>("--to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move --to ""{path}"""); - - result.Errors - .Should() - .HaveCount(1) - .And - .Contain(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && - e.Message == $"File does not exist: '{path}'."); - } - - [Fact] - public void A_command_argument_with_multiple_directories_can_be_invalid_based_on_directory_existence() - { - var command = new Command("move") - { - new Argument>("to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move ""{path}"""); - - result.Errors - .Should() - .HaveCount(1) - .And - .ContainSingle(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && - e.Message == $"Directory does not exist: '{path}'."); - } - - [Fact] - public void An_option_argument_with_multiple_directories_can_be_invalid_based_on_directory_existence() - { - var command = new Command("move") - { - new Option("--to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move --to ""{path}"""); - - result.Errors - .Should() - .HaveCount(1) - .And - .ContainSingle(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && - e.Message == $"Directory does not exist: '{path}'."); - } - - [Fact] - public void A_command_argument_with_multiple_FileSystemInfos_can_be_invalid_based_on_file_existence() - { - var command = new Command("move") - { - new Argument("to") - { - Arity = ArgumentArity.ZeroOrMore - }.AcceptExistingOnly(), - new Option("--to") - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move ""{path}"""); - - result.Errors - .Should() - .ContainSingle(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && - e.Message == $"File or directory does not exist: '{path}'."); - } - - [Fact] - public void An_option_argument_with_multiple_FileSystemInfos_can_be_invalid_based_on_file_existence() - { - var command = new Command("move") - { - new Option("--to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = - command.Parse( - $@"move --to ""{path}"""); - - result.Errors - .Should() - .ContainSingle(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && - e.Message == $"File or directory does not exist: '{path}'."); - } - - [Fact] - public void A_command_argument_with_multiple_FileSystemInfos_can_be_invalid_based_on_directory_existence() - { - var command = new Command("move") - { - new Argument("to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move ""{path}"""); - - result.Errors - .Should() - .HaveCount(1) - .And - .ContainSingle(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && - e.Message == $"File or directory does not exist: '{path}'."); - } - - [Fact] - public void An_option_argument_with_multiple_FileSystemInfos_can_be_invalid_based_on_directory_existence() - { - var command = new Command("move") - { - new Option("--to").AcceptExistingOnly() - }; - - var path = NonexistentPath(); - var result = command.Parse($@"move --to ""{path}"""); - - result.Errors - .Should() - .HaveCount(1) - .And - .ContainSingle(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && - e.Message == $"File or directory does not exist: '{path}'."); - } - - [Fact] - public void Command_argument_does_not_return_errors_when_file_exists() - { - var command = new Command("move") - { - new Argument("arg").AcceptExistingOnly() - }; - - var path = ExistingFile(); - var result = command.Parse($@"move ""{path}"""); - - result.Errors.Should().BeEmpty(); - } - - [Fact] - public void Option_argument_does_not_return_errors_when_file_exists() - { - var command = new Command("move") - { - new Option("--to").AcceptExistingOnly() - }; - - var path = ExistingFile(); - var result = command.Parse($@"move --to ""{path}"""); - - result.Errors.Should().BeEmpty(); - } - - [Fact] - public void Command_argument_does_not_return_errors_when_Directory_exists() - { - var command = new Command("move") - { - new Argument("arg").AcceptExistingOnly() - }; - - var path = ExistingDirectory(); - var result = command.Parse($@"move ""{path}"""); - - result.Errors.Should().BeEmpty(); - } + } - [Fact] - public void Option_argument_does_not_return_errors_when_Directory_exists() + [Fact] + public void An_option_argument_with_multiple_FileSystemInfos_can_be_invalid_based_on_file_existence() + { + var command = new Command("move") { - var command = new Command("move") - { - new Option("--to").AcceptExistingOnly() - }; - - var path = ExistingDirectory(); - var result = command.Parse($@"move --to ""{path}"""); - - result.Errors.Should().BeEmpty(); - } + new Option("--to").AcceptExistingOnly() + }; - private string NonexistentPath() - { - return Guid.NewGuid().ToString(); - } + var path = NonexistentPath(); + var result = + command.Parse( + $@"move --to ""{path}"""); - private string ExistingDirectory() - { - return Directory.GetCurrentDirectory(); - } - - private string ExistingFile() - { - return new DirectoryInfo(Directory.GetCurrentDirectory()).GetFiles()[0].FullName; - } + result.Errors + .Should() + .ContainSingle(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + e.Message == $"File or directory does not exist: '{path}'."); } [Fact] - public void A_command_with_subcommands_is_invalid_to_invoke_if_it_has_no_handler() + public void A_command_argument_with_multiple_FileSystemInfos_can_be_invalid_based_on_directory_existence() { - var outer = new Command("outer") + var command = new Command("move") { - new Command("inner") - { - new Command("inner-er") - } + new Argument("to").AcceptExistingOnly() }; - var result = outer.Parse("outer inner"); + var path = NonexistentPath(); + var result = command.Parse($@"move ""{path}"""); result.Errors - .Should() - .ContainSingle( - e => e.Message.Equals(LocalizationResources.RequiredCommandWasNotProvided()) && - ((CommandResult)e.SymbolResult).Command.Name.Equals("inner")); + .Should() + .HaveCount(1) + .And + .ContainSingle(e => ((ArgumentResult)e.SymbolResult).Argument.Name == "to" && + e.Message == $"File or directory does not exist: '{path}'."); } [Fact] - public void A_root_command_with_subcommands_is_invalid_to_invoke_if_it_has_no_handler() + public void An_option_argument_with_multiple_FileSystemInfos_can_be_invalid_based_on_directory_existence() { - var rootCommand = new RootCommand(); - var inner = new Command("inner"); - rootCommand.Add(inner); + var command = new Command("move") + { + new Option("--to").AcceptExistingOnly() + }; - var result = rootCommand.Parse(""); + var path = NonexistentPath(); + var result = command.Parse($@"move --to ""{path}"""); result.Errors - .Should() - .ContainSingle( - e => e.Message.Equals(LocalizationResources.RequiredCommandWasNotProvided()) && - ((CommandResult)e.SymbolResult).Command == rootCommand); + .Should() + .HaveCount(1) + .And + .ContainSingle(e => ((OptionResult)e.SymbolResult).Option.Name == "--to" && + e.Message == $"File or directory does not exist: '{path}'."); } [Fact] - public void A_command_with_subcommands_is_valid_to_invoke_if_it_has_a_handler() + public void Command_argument_does_not_return_errors_when_file_exists() { - var outer = new Command("outer"); - var inner = new Command("inner"); - inner.SetAction((_) => { }); - var innerer = new Command("inner-er"); - outer.Subcommands.Add(inner); - inner.Subcommands.Add(innerer); + var command = new Command("move") + { + new Argument("arg").AcceptExistingOnly() + }; - var result = outer.Parse("outer inner"); + var path = ExistingFile(); + var result = command.Parse($@"move ""{path}"""); result.Errors.Should().BeEmpty(); - result.CommandResult.Command.Should().BeSameAs(inner); } [Fact] - public void When_an_option_has_a_default_value_it_is_not_valid_to_specify_the_option_without_an_argument() + public void Option_argument_does_not_return_errors_when_file_exists() { - var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; + var command = new Command("move") + { + new Option("--to").AcceptExistingOnly() + }; - var result = new RootCommand { option }.Parse("-x"); + var path = ExistingFile(); + var result = command.Parse($@"move --to ""{path}"""); - result.Errors - .Select(e => e.Message) - .Should() - .Contain("Required argument missing for option: '-x'."); + result.Errors.Should().BeEmpty(); } [Fact] - public void When_an_option_has_a_default_value_then_the_default_should_apply_if_not_specified() + public void Command_argument_does_not_return_errors_when_Directory_exists() { - var optionX = new Option("-x") { DefaultValueFactory = (_) => 123 }; - var optionY = new Option("-y") { DefaultValueFactory = (_) => 456 }; - - var parser = new RootCommand + var command = new Command("move") { - optionX, - optionY + new Argument("arg").AcceptExistingOnly() }; - var result = parser.Parse(""); + var path = ExistingDirectory(); + var result = command.Parse($@"move ""{path}"""); result.Errors.Should().BeEmpty(); - result.GetValue(optionX).Should().Be(123); - result.GetValue(optionY).Should().Be(456); } - [Fact] // https://github.com/dotnet/command-line-api/issues/1505 - public void Arity_failures_are_not_reported_for_both_an_argument_and_its_parent_option() + [Fact] + public void Option_argument_does_not_return_errors_when_Directory_exists() { - var newCommand = new Command("test") + var command = new Command("move") { - new Option("--opt") + new Option("--to").AcceptExistingOnly() }; - var parseResult = newCommand.Parse("test --opt"); - - parseResult.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be("Required argument missing for option: '--opt'."); + var path = ExistingDirectory(); + var result = command.Parse($@"move --to ""{path}"""); + + result.Errors.Should().BeEmpty(); } - [Fact] // https://github.com/dotnet/command-line-api/issues/1573 - public void Multiple_validators_on_the_same_command_do_not_report_duplicate_errors() + private string NonexistentPath() { - var command = new RootCommand(); - command.Validators.Add(result => result.AddError("Wrong")); - command.Validators.Add(_ => { }); - - var parseResult = command.Parse(""); - - parseResult.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be("Wrong"); + return Guid.NewGuid().ToString(); } - [Fact] // https://github.com/dotnet/command-line-api/issues/1573 - public void Multiple_validators_on_the_same_option_do_not_report_duplicate_errors() + private string ExistingDirectory() + { + return Directory.GetCurrentDirectory(); + } + + private string ExistingFile() { - var option = new Option("-x"); - option.Validators.Add(result => result.AddError("Wrong")); - option.Validators.Add(_ => { }); + return new DirectoryInfo(Directory.GetCurrentDirectory()).GetFiles()[0].FullName; + } + } - var command = new RootCommand + [Fact] + public void A_command_with_subcommands_is_invalid_to_invoke_if_it_has_no_handler() + { + var outer = new Command("outer") + { + new Command("inner") { - option - }; + new Command("inner-er") + } + }; - var parseResult = command.Parse("-x b"); + var result = outer.Parse("outer inner"); - parseResult.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be("Wrong"); - } - - [Fact] // https://github.com/dotnet/command-line-api/issues/1573 - public void Multiple_validators_on_the_same_argument_do_not_report_duplicate_errors() - { - var argument = new Argument("arg"); - argument.Validators.Add(result => result.AddError("Wrong")); - argument.Validators.Add(_ => { }); + result.Errors + .Should() + .ContainSingle( + e => e.Message.Equals(LocalizationResources.RequiredCommandWasNotProvided()) && + ((CommandResult)e.SymbolResult).Command.Name.Equals("inner")); + } - var command = new RootCommand - { - argument - }; + [Fact] + public void A_root_command_with_subcommands_is_invalid_to_invoke_if_it_has_no_handler() + { + var rootCommand = new RootCommand(); + var inner = new Command("inner"); + rootCommand.Add(inner); - var parseResult = command.Parse("b"); + var result = rootCommand.Parse(""); - parseResult.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be("Wrong"); - } + result.Errors + .Should() + .ContainSingle( + e => e.Message.Equals(LocalizationResources.RequiredCommandWasNotProvided()) && + ((CommandResult)e.SymbolResult).Command == rootCommand); + } + + [Fact] + public void A_command_with_subcommands_is_valid_to_invoke_if_it_has_a_handler() + { + var outer = new Command("outer"); + var inner = new Command("inner"); + inner.SetAction((_) => { }); + var innerer = new Command("inner-er"); + outer.Subcommands.Add(inner); + inner.Subcommands.Add(innerer); + + var result = outer.Parse("outer inner"); + + result.Errors.Should().BeEmpty(); + result.CommandResult.Command.Should().BeSameAs(inner); + } + + [Fact] + public void When_an_option_has_a_default_value_it_is_not_valid_to_specify_the_option_without_an_argument() + { + var option = new Option("-x") { DefaultValueFactory = (_) => 123 }; - [Fact] // https://github.com/dotnet/command-line-api/issues/1609 - internal void When_there_is_an_arity_error_then_further_errors_are_not_reported() + var result = new RootCommand { option }.Parse("-x"); + + result.Errors + .Select(e => e.Message) + .Should() + .Contain("Required argument missing for option: '-x'."); + } + + [Fact] + public void When_an_option_has_a_default_value_then_the_default_should_apply_if_not_specified() + { + var optionX = new Option("-x") { DefaultValueFactory = (_) => 123 }; + var optionY = new Option("-y") { DefaultValueFactory = (_) => 456 }; + + var parser = new RootCommand { - var option = new Option("-o"); - option.Validators.Add(result => - { - result.AddError("OOPS"); - }); //all good; + optionX, + optionY + }; - var command = new Command("comm") - { - option - }; + var result = parser.Parse(""); - var parseResult = command.Parse("comm -o"); + result.Errors.Should().BeEmpty(); + result.GetValue(optionX).Should().Be(123); + result.GetValue(optionY).Should().Be(456); + } - parseResult.Errors - .Should() - .ContainSingle() - .Which - .Message - .Should() - .Be("Required argument missing for option: '-o'."); - } + [Fact] // https://github.com/dotnet/command-line-api/issues/1505 + public void Arity_failures_are_not_reported_for_both_an_argument_and_its_parent_option() + { + var newCommand = new Command("test") + { + new Option("--opt") + }; + + var parseResult = newCommand.Parse("test --opt"); + + parseResult.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be("Required argument missing for option: '--opt'."); + } + + [Fact] // https://github.com/dotnet/command-line-api/issues/1573 + public void Multiple_validators_on_the_same_command_do_not_report_duplicate_errors() + { + var command = new RootCommand(); + command.Validators.Add(result => result.AddError("Wrong")); + command.Validators.Add(_ => { }); + + var parseResult = command.Parse(""); + + parseResult.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be("Wrong"); + } + + [Fact] // https://github.com/dotnet/command-line-api/issues/1573 + public void Multiple_validators_on_the_same_option_do_not_report_duplicate_errors() + { + var option = new Option("-x"); + option.Validators.Add(result => result.AddError("Wrong")); + option.Validators.Add(_ => { }); + + var command = new RootCommand + { + option + }; + + var parseResult = command.Parse("-x b"); + + parseResult.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be("Wrong"); + } - private Argument CreateArgumentWithAcceptOnlyFromAmong(string name, params string[] values) + [Fact] // https://github.com/dotnet/command-line-api/issues/1573 + public void Multiple_validators_on_the_same_argument_do_not_report_duplicate_errors() + { + var argument = new Argument("arg"); + argument.Validators.Add(result => result.AddError("Wrong")); + argument.Validators.Add(_ => { }); + + var command = new RootCommand { - Argument argument = new(name); - argument.AcceptOnlyFromAmong(values); - return argument; - } + argument + }; + + var parseResult = command.Parse("b"); + + parseResult.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be("Wrong"); + } + + [Fact] // https://github.com/dotnet/command-line-api/issues/1609 + internal void When_there_is_an_arity_error_then_further_errors_are_not_reported() + { + var option = new Option("-o"); + option.Validators.Add(result => + { + result.AddError("OOPS"); + }); //all good; + + var command = new Command("comm") + { + option + }; + + var parseResult = command.Parse("comm -o"); + + parseResult.Errors + .Should() + .ContainSingle() + .Which + .Message + .Should() + .Be("Required argument missing for option: '-o'."); + } + + private Argument CreateArgumentWithAcceptOnlyFromAmong(string name, params string[] values) + { + Argument argument = new(name); + argument.AcceptOnlyFromAmong(values); + return argument; } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/ResponseFileTests.cs b/src/System.CommandLine.Tests/ResponseFileTests.cs index 5404fdee22..a14872f66d 100644 --- a/src/System.CommandLine.Tests/ResponseFileTests.cs +++ b/src/System.CommandLine.Tests/ResponseFileTests.cs @@ -9,379 +9,378 @@ using System.Linq; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class ResponseFileTests : IDisposable { - public class ResponseFileTests : IDisposable - { - private readonly List _responseFiles = new(); + private readonly List _responseFiles = new(); - public void Dispose() + public void Dispose() + { + foreach (var responseFile in _responseFiles) { - foreach (var responseFile in _responseFiles) - { - responseFile.Delete(); - } + responseFile.Delete(); } + } - private string CreateResponseFile(params string[] lines) - { - var responseFile = new FileInfo(Path.GetTempFileName()); + private string CreateResponseFile(params string[] lines) + { + var responseFile = new FileInfo(Path.GetTempFileName()); - using (var writer = new StreamWriter(responseFile.FullName)) + using (var writer = new StreamWriter(responseFile.FullName)) + { + foreach (var line in lines) { - foreach (var line in lines) - { - writer.WriteLine(line); - } + writer.WriteLine(line); } + } - _responseFiles.Add(responseFile); + _responseFiles.Add(responseFile); - return responseFile.FullName; - } + return responseFile.FullName; + } - [Fact] - public void When_response_file_specified_it_loads_options_from_response_file() - { - var option = new Option("--flag"); + [Fact] + public void When_response_file_specified_it_loads_options_from_response_file() + { + var option = new Option("--flag"); - var result = new RootCommand { option }.Parse($"@{CreateResponseFile("--flag")}"); + var result = new RootCommand { option }.Parse($"@{CreateResponseFile("--flag")}"); - result.GetResult(option).Should().NotBeNull(); - } + result.GetResult(option).Should().NotBeNull(); + } - [Fact] - public void When_response_file_is_specified_it_loads_options_with_arguments_from_response_file() - { - var responseFile = CreateResponseFile( - "--flag", - "--flag2", - "123"); + [Fact] + public void When_response_file_is_specified_it_loads_options_with_arguments_from_response_file() + { + var responseFile = CreateResponseFile( + "--flag", + "--flag2", + "123"); - var optionOne = new Option("--flag"); + var optionOne = new Option("--flag"); - var optionTwo = new Option("--flag2"); - var result = new RootCommand - { - optionOne, - optionTwo - } - .Parse($"@{responseFile}"); - - result.GetResult(optionOne).Should().NotBeNull(); - result.GetValue(optionTwo).Should().Be(123); - result.Errors.Should().BeEmpty(); - } + var optionTwo = new Option("--flag2"); + var result = new RootCommand + { + optionOne, + optionTwo + } + .Parse($"@{responseFile}"); - [Fact] - public void When_response_file_is_specified_it_loads_command_arguments_from_response_file() - { - var responseFile = CreateResponseFile( - "one", - "two", - "three"); + result.GetResult(optionOne).Should().NotBeNull(); + result.GetValue(optionTwo).Should().Be(123); + result.Errors.Should().BeEmpty(); + } - var result = new RootCommand + [Fact] + public void When_response_file_is_specified_it_loads_command_arguments_from_response_file() + { + var responseFile = CreateResponseFile( + "one", + "two", + "three"); + + var result = new RootCommand { new Argument("arg") } .Parse($"@{responseFile}"); - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentSequenceTo("one", "two", "three"); - } - - [Fact] - public void Response_file_can_provide_subcommand_arguments() - { - var responseFile = CreateResponseFile( - "one", - "two", - "three"); + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentSequenceTo("one", "two", "three"); + } - var result = new RootCommand - { - new Command("subcommand") - { - new Argument("arg") - } - } - .Parse($"subcommand @{responseFile}"); - - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentSequenceTo("one", "two", "three"); - } + [Fact] + public void Response_file_can_provide_subcommand_arguments() + { + var responseFile = CreateResponseFile( + "one", + "two", + "three"); - [Fact] - public void Response_file_can_provide_subcommand() - { - var responseFile = CreateResponseFile("subcommand"); + var result = new RootCommand + { + new Command("subcommand") + { + new Argument("arg") + } + } + .Parse($"subcommand @{responseFile}"); - var result = new RootCommand - { - new Command("subcommand") - { - new Argument("arg") - } - } - .Parse($"@{responseFile} one two three"); - - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentSequenceTo("one", "two", "three"); - } + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentSequenceTo("one", "two", "three"); + } - [Fact] - public void When_response_file_is_specified_it_loads_subcommand_arguments_from_response_file() - { - var responseFile = CreateResponseFile( - "one", - "two", - "three"); + [Fact] + public void Response_file_can_provide_subcommand() + { + var responseFile = CreateResponseFile("subcommand"); - var result = new RootCommand - { - new Command("subcommand") - { - new Argument("arg") - } - } - .Parse($"subcommand @{responseFile}"); - - result.CommandResult - .Tokens - .Select(t => t.Value) - .Should() - .BeEquivalentSequenceTo("one", "two", "three"); - } + var result = new RootCommand + { + new Command("subcommand") + { + new Argument("arg") + } + } + .Parse($"@{responseFile} one two three"); - [Fact] - public void Response_file_can_contain_blank_lines() - { - var responseFile = CreateResponseFile( - "--flag", - "", - "123"); + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentSequenceTo("one", "two", "three"); + } - var option = new Option("--flag"); + [Fact] + public void When_response_file_is_specified_it_loads_subcommand_arguments_from_response_file() + { + var responseFile = CreateResponseFile( + "one", + "two", + "three"); - var result = new RootCommand + var result = new RootCommand + { + new Command("subcommand") { - option + new Argument("arg") } - .Parse($"@{responseFile}"); + } + .Parse($"subcommand @{responseFile}"); - result.GetValue(option).Should().Be(123); - result.Errors.Should().BeEmpty(); - } + result.CommandResult + .Tokens + .Select(t => t.Value) + .Should() + .BeEquivalentSequenceTo("one", "two", "three"); + } - [Fact] - public void Response_file_can_contain_comments_which_are_ignored_when_loaded() - { - var optionOne = new Option("--flag"); - var optionTwo = new Option("--flag2"); + [Fact] + public void Response_file_can_contain_blank_lines() + { + var responseFile = CreateResponseFile( + "--flag", + "", + "123"); - var responseFile = CreateResponseFile( - "# comment one", - "--flag", - "# comment two", - "#", - " # comment two", - "--flag2"); + var option = new Option("--flag"); - var result = new RootCommand + var result = new RootCommand { - optionOne, - optionTwo - }.Parse($"@{responseFile}"); + option + } + .Parse($"@{responseFile}"); - result.GetResult(optionOne).Should().NotBeNull(); - result.GetResult(optionTwo).Should().NotBeNull(); - result.Errors.Should().BeEmpty(); - } + result.GetValue(option).Should().Be(123); + result.Errors.Should().BeEmpty(); + } - [Fact] - public void When_response_file_does_not_exist_then_error_is_returned() + [Fact] + public void Response_file_can_contain_comments_which_are_ignored_when_loaded() + { + var optionOne = new Option("--flag"); + var optionTwo = new Option("--flag2"); + + var responseFile = CreateResponseFile( + "# comment one", + "--flag", + "# comment two", + "#", + " # comment two", + "--flag2"); + + var result = new RootCommand { - var optionOne = new Option("-x"); - var optionTwo = new Option("-y"); + optionOne, + optionTwo + }.Parse($"@{responseFile}"); - var result = new RootCommand - { - optionOne, - optionTwo - }.Parse("@nonexistent.rsp"); + result.GetResult(optionOne).Should().NotBeNull(); + result.GetResult(optionTwo).Should().NotBeNull(); + result.Errors.Should().BeEmpty(); + } - result.GetResult(optionOne).Should().BeNull(); - result.GetResult(optionTwo).Should().BeNull(); - result.Errors.Should().HaveCount(1); - result.Errors.Single().Message.Should().Be("Response file not found 'nonexistent.rsp'."); - } + [Fact] + public void When_response_file_does_not_exist_then_error_is_returned() + { + var optionOne = new Option("-x"); + var optionTwo = new Option("-y"); - [Fact] - public void When_response_filepath_is_not_specified_then_error_is_returned() + var result = new RootCommand { - var optionOne = new Option("-x"); - var optionTwo = new Option("-y"); + optionOne, + optionTwo + }.Parse("@nonexistent.rsp"); + + result.GetResult(optionOne).Should().BeNull(); + result.GetResult(optionTwo).Should().BeNull(); + result.Errors.Should().HaveCount(1); + result.Errors.Single().Message.Should().Be("Response file not found 'nonexistent.rsp'."); + } + + [Fact] + public void When_response_filepath_is_not_specified_then_error_is_returned() + { + var optionOne = new Option("-x"); + var optionTwo = new Option("-y"); + + var result = new RootCommand + { + optionOne, + optionTwo + } + .Parse("@"); + + result.GetResult(optionOne).Should().BeNull(); + result.GetResult(optionTwo).Should().BeNull(); + result.Errors.Should().HaveCount(1); + result.Errors + .Single() + .Message + .Should() + .Be("Unrecognized command or argument '@'."); + } + + [Fact] + public void When_response_file_cannot_be_read_then_specified_error_is_returned() + { + var nonexistent = Path.GetTempFileName(); + var optionOne = new Option("--flag"); + var optionTwo = new Option("--flag2"); + using (File.Open(nonexistent, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) + { var result = new RootCommand - { - optionOne, - optionTwo - } - .Parse("@"); + { + optionOne, + optionTwo + }.Parse($"@{nonexistent}"); result.GetResult(optionOne).Should().BeNull(); result.GetResult(optionTwo).Should().BeNull(); result.Errors.Should().HaveCount(1); - result.Errors - .Single() - .Message - .Should() - .Be("Unrecognized command or argument '@'."); + result.Errors.Single().Message.Should().StartWith($"Error reading response file '{nonexistent}'"); } + } - [Fact] - public void When_response_file_cannot_be_read_then_specified_error_is_returned() - { - var nonexistent = Path.GetTempFileName(); - var optionOne = new Option("--flag"); - var optionTwo = new Option("--flag2"); + [Theory] + [InlineData("--flag \"first value\" --flag2 123")] + [InlineData("--flag:\"first value\" --flag2:123")] + [InlineData("--flag=\"first value\" --flag2=123")] + public void When_response_file_parse_as_space_separated_returns_expected_values(string input) + { + var responseFile = CreateResponseFile(input); - using (File.Open(nonexistent, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) - { - var result = new RootCommand - { - optionOne, - optionTwo - }.Parse($"@{nonexistent}"); - - result.GetResult(optionOne).Should().BeNull(); - result.GetResult(optionTwo).Should().BeNull(); - result.Errors.Should().HaveCount(1); - result.Errors.Single().Message.Should().StartWith($"Error reading response file '{nonexistent}'"); - } - } + var optionOne = new Option("--flag"); + var optionTwo = new Option("--flag2"); - [Theory] - [InlineData("--flag \"first value\" --flag2 123")] - [InlineData("--flag:\"first value\" --flag2:123")] - [InlineData("--flag=\"first value\" --flag2=123")] - public void When_response_file_parse_as_space_separated_returns_expected_values(string input) + var rootCommand = new RootCommand { - var responseFile = CreateResponseFile(input); + optionOne, + optionTwo + }; + CommandLineConfiguration config = new (rootCommand); - var optionOne = new Option("--flag"); - var optionTwo = new Option("--flag2"); + var result = rootCommand.Parse($"@{responseFile}", config); - var rootCommand = new RootCommand - { - optionOne, - optionTwo - }; - CommandLineConfiguration config = new (rootCommand); + result.GetValue(optionOne).Should().Be("first value"); + result.GetValue(optionTwo).Should().Be(123); + } - var result = rootCommand.Parse($"@{responseFile}", config); + [Fact] + public void When_response_file_processing_is_disabled_then_it_returns_response_file_name_as_argument() + { + RootCommand command = new () + { + new Argument>("arg") + }; + CommandLineConfiguration configuration = new(command) + { + ResponseFileTokenReplacer = null + }; - result.GetValue(optionOne).Should().Be("first value"); - result.GetValue(optionTwo).Should().Be(123); - } + var result = CommandLineParser.Parse(command, "@file.rsp", configuration); - [Fact] - public void When_response_file_processing_is_disabled_then_it_returns_response_file_name_as_argument() - { - RootCommand command = new () - { - new Argument>("arg") - }; - CommandLineConfiguration configuration = new(command) - { - ResponseFileTokenReplacer = null - }; + result.Tokens + .Should() + .Contain(t => t.Value == "@file.rsp" && + t.Type == TokenType.Argument); + result.Errors.Should().HaveCount(0); + } - var result = CommandLineParser.Parse(command, "@file.rsp", configuration); + [Fact] + public void Response_files_can_refer_to_other_response_files() + { + var file3 = CreateResponseFile("--three", "3"); + var file2 = CreateResponseFile($"@{file3}", "--two", "2"); + var file1 = CreateResponseFile("--one", "1", $"@{file2}"); - result.Tokens - .Should() - .Contain(t => t.Value == "@file.rsp" && - t.Type == TokenType.Argument); - result.Errors.Should().HaveCount(0); - } + var option1 = new Option("--one"); + var option2 = new Option("--two"); + var option3 = new Option("--three"); - [Fact] - public void Response_files_can_refer_to_other_response_files() + var command = new RootCommand { - var file3 = CreateResponseFile("--three", "3"); - var file2 = CreateResponseFile($"@{file3}", "--two", "2"); - var file1 = CreateResponseFile("--one", "1", $"@{file2}"); - - var option1 = new Option("--one"); - var option2 = new Option("--two"); - var option3 = new Option("--three"); - - var command = new RootCommand - { - option1, - option2, - option3 - }; - - var result = command.Parse($"@{file1}"); - - result.GetValue(option1).Should().Be(1); - result.GetValue(option1).Should().Be(1); - result.GetValue(option2).Should().Be(2); - result.GetValue(option3).Should().Be(3); - result.Errors.Should().BeEmpty(); - } + option1, + option2, + option3 + }; + + var result = command.Parse($"@{file1}"); + + result.GetValue(option1).Should().Be(1); + result.GetValue(option1).Should().Be(1); + result.GetValue(option2).Should().Be(2); + result.GetValue(option3).Should().Be(3); + result.Errors.Should().BeEmpty(); + } - [Fact] - public void When_response_file_options_or_arguments_contain_trailing_spaces_they_are_ignored() - { - var responseFile = CreateResponseFile("--option1 ", "value1 ", "--option2\t", "2\t"); + [Fact] + public void When_response_file_options_or_arguments_contain_trailing_spaces_they_are_ignored() + { + var responseFile = CreateResponseFile("--option1 ", "value1 ", "--option2\t", "2\t"); - var option1 = new Option("--option1"); - var option2 = new Option("--option2"); + var option1 = new Option("--option1"); + var option2 = new Option("--option2"); - var result = new RootCommand { option1, option2 }.Parse($"@{responseFile}"); - result.GetValue(option1).Should().Be("value1"); - result.GetValue(option2).Should().Be(2); - } + var result = new RootCommand { option1, option2 }.Parse($"@{responseFile}"); + result.GetValue(option1).Should().Be("value1"); + result.GetValue(option2).Should().Be(2); + } - [Fact] - public void When_response_file_options_or_arguments_contain_leading_spaces_they_are_ignored() - { - var responseFile = CreateResponseFile(" --option1", " value1", "\t--option2", "\t2"); + [Fact] + public void When_response_file_options_or_arguments_contain_leading_spaces_they_are_ignored() + { + var responseFile = CreateResponseFile(" --option1", " value1", "\t--option2", "\t2"); - var option1 = new Option("--option1"); - var option2 = new Option("--option2"); + var option1 = new Option("--option1"); + var option2 = new Option("--option2"); - var result = new RootCommand { option1, option2 }.Parse($"@{responseFile}"); - result.GetValue(option1).Should().Be("value1"); - result.GetValue(option2).Should().Be(2); - result.Errors.Should().BeEmpty(); - } + var result = new RootCommand { option1, option2 }.Parse($"@{responseFile}"); + result.GetValue(option1).Should().Be("value1"); + result.GetValue(option2).Should().Be(2); + result.Errors.Should().BeEmpty(); + } - [Fact] - public void When_response_file_options_or_arguments_contain_trailing_and_leading_spaces_they_are_ignored() - { - var responseFile = CreateResponseFile(" --option1 ", " value1 ", "\t--option2\t", "\t2\t"); + [Fact] + public void When_response_file_options_or_arguments_contain_trailing_and_leading_spaces_they_are_ignored() + { + var responseFile = CreateResponseFile(" --option1 ", " value1 ", "\t--option2\t", "\t2\t"); - var option1 = new Option("--option1"); - var option2 = new Option("--option2"); + var option1 = new Option("--option1"); + var option2 = new Option("--option2"); - var result = new RootCommand { option1, option2 }.Parse($"@{responseFile}"); - result.GetValue(option1).Should().Be("value1"); - result.GetValue(option2).Should().Be(2); - result.Errors.Should().BeEmpty(); - } + var result = new RootCommand { option1, option2 }.Parse($"@{responseFile}"); + result.GetValue(option1).Should().Be("value1"); + result.GetValue(option2).Should().Be(2); + result.Errors.Should().BeEmpty(); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/RootCommandTests.cs b/src/System.CommandLine.Tests/RootCommandTests.cs index 664d976106..ef60fcec29 100644 --- a/src/System.CommandLine.Tests/RootCommandTests.cs +++ b/src/System.CommandLine.Tests/RootCommandTests.cs @@ -4,16 +4,15 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class RootCommandTests { - public class RootCommandTests + [Fact] + public void Root_command_name_defaults_to_executable_name() { - [Fact] - public void Root_command_name_defaults_to_executable_name() - { - var rootCommand = new RootCommand(); + var rootCommand = new RootCommand(); - rootCommand.Name.Should().Be(RootCommand.ExecutableName); - } + rootCommand.Name.Should().Be(RootCommand.ExecutableName); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/SplitCommandLineTests.cs b/src/System.CommandLine.Tests/SplitCommandLineTests.cs index d454d48d8a..261c31637e 100644 --- a/src/System.CommandLine.Tests/SplitCommandLineTests.cs +++ b/src/System.CommandLine.Tests/SplitCommandLineTests.cs @@ -8,79 +8,78 @@ using Xunit; using Xunit.Abstractions; -namespace System.CommandLine.Tests -{ - public class SplitCommandLineTests - { - private readonly ITestOutputHelper _output; +namespace System.CommandLine.Tests; - public SplitCommandLineTests(ITestOutputHelper output) - { - _output = output; - } +public class SplitCommandLineTests +{ + private readonly ITestOutputHelper _output; - [Fact] - public void It_splits_strings_based_on_whitespace() - { - var commandLine = "one two\tthree four "; + public SplitCommandLineTests(ITestOutputHelper output) + { + _output = output; + } - CommandLineParser - .SplitCommandLine(commandLine) - .Should() - .BeEquivalentSequenceTo("one", "two", "three", "four"); - } + [Fact] + public void It_splits_strings_based_on_whitespace() + { + var commandLine = "one two\tthree four "; - [Fact] - public void It_does_not_break_up_double_quote_delimited_values() - { - var commandLine = @"rm -r ""c:\temp files\"""; + CommandLineParser + .SplitCommandLine(commandLine) + .Should() + .BeEquivalentSequenceTo("one", "two", "three", "four"); + } - CommandLineParser - .SplitCommandLine(commandLine) - .Should() - .BeEquivalentSequenceTo("rm", "-r", @"c:\temp files\"); - } + [Fact] + public void It_does_not_break_up_double_quote_delimited_values() + { + var commandLine = @"rm -r ""c:\temp files\"""; - [Theory] - [InlineData("-", '=')] - [InlineData("-", ':')] - [InlineData("--", '=')] - [InlineData("--", ':')] - [InlineData("/", '=')] - [InlineData("/", ':')] - public void It_does_not_split_double_quote_delimited_values_when_a_non_whitespace_argument_delimiter_is_used( - string prefix, - char delimiter) - { - var optionAndArgument = $@"{prefix}the-option{delimiter}""c:\temp files\"""; + CommandLineParser + .SplitCommandLine(commandLine) + .Should() + .BeEquivalentSequenceTo("rm", "-r", @"c:\temp files\"); + } - var commandLine = $"the-command {optionAndArgument}"; + [Theory] + [InlineData("-", '=')] + [InlineData("-", ':')] + [InlineData("--", '=')] + [InlineData("--", ':')] + [InlineData("/", '=')] + [InlineData("/", ':')] + public void It_does_not_split_double_quote_delimited_values_when_a_non_whitespace_argument_delimiter_is_used( + string prefix, + char delimiter) + { + var optionAndArgument = $@"{prefix}the-option{delimiter}""c:\temp files\"""; - CommandLineParser - .SplitCommandLine(commandLine) - .Should() - .BeEquivalentSequenceTo("the-command", optionAndArgument.Replace("\"", "")); - } + var commandLine = $"the-command {optionAndArgument}"; - [Fact] - public void It_handles_multiple_options_with_quoted_arguments() - { - var source = Directory.GetCurrentDirectory(); - var destination = Path.Combine(Directory.GetCurrentDirectory(), ".trash"); + CommandLineParser + .SplitCommandLine(commandLine) + .Should() + .BeEquivalentSequenceTo("the-command", optionAndArgument.Replace("\"", "")); + } - var commandLine = $"move --from \"{source}\" --to \"{destination}\""; + [Fact] + public void It_handles_multiple_options_with_quoted_arguments() + { + var source = Directory.GetCurrentDirectory(); + var destination = Path.Combine(Directory.GetCurrentDirectory(), ".trash"); - var tokenized = CommandLineParser.SplitCommandLine(commandLine); + var commandLine = $"move --from \"{source}\" --to \"{destination}\""; - _output.WriteLine(commandLine); + var tokenized = CommandLineParser.SplitCommandLine(commandLine); - foreach (var token in tokenized) - { - _output.WriteLine(" " + token); - } + _output.WriteLine(commandLine); - tokenized.Should() - .BeEquivalentSequenceTo("move", "--from", source, "--to", destination); + foreach (var token in tokenized) + { + _output.WriteLine(" " + token); } + + tokenized.Should() + .BeEquivalentSequenceTo("move", "--from", source, "--to", destination); } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/SuggestDirectiveTests.cs b/src/System.CommandLine.Tests/SuggestDirectiveTests.cs index ec1bb7209e..c17c9effca 100644 --- a/src/System.CommandLine.Tests/SuggestDirectiveTests.cs +++ b/src/System.CommandLine.Tests/SuggestDirectiveTests.cs @@ -9,259 +9,258 @@ using Xunit; using static System.Environment; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class SuggestDirectiveTests { - public class SuggestDirectiveTests + protected Option _fruitOption; + + protected Option _vegetableOption; + + private readonly Command _eatCommand; + + public SuggestDirectiveTests() { - protected Option _fruitOption; + _fruitOption = new Option("--fruit"); + _fruitOption.CompletionSources.Add("apple", "banana", "cherry"); - protected Option _vegetableOption; + _vegetableOption = new Option("--vegetable"); + _vegetableOption.CompletionSources.Add(_ => new[] { "asparagus", "broccoli", "carrot" }); - private readonly Command _eatCommand; + _eatCommand = new Command("eat") + { + _fruitOption, + _vegetableOption + }; + } - public SuggestDirectiveTests() + [Fact] + public async Task It_writes_suggestions_for_option_arguments_when_under_subcommand() + { + RootCommand rootCommand = new() { - _fruitOption = new Option("--fruit"); - _fruitOption.CompletionSources.Add("apple", "banana", "cherry"); + _eatCommand, + new SuggestDirective() + }; + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter() + }; - _vegetableOption = new Option("--vegetable"); - _vegetableOption.CompletionSources.Add(_ => new[] { "asparagus", "broccoli", "carrot" }); + var result = rootCommand.Parse("[suggest:13] \"eat --fruit\"", config); - _eatCommand = new Command("eat") - { - _fruitOption, - _vegetableOption - }; - } + await result.InvokeAsync(); - [Fact] - public async Task It_writes_suggestions_for_option_arguments_when_under_subcommand() + config.Output + .ToString() + .Should() + .Be($"apple{NewLine}banana{NewLine}cherry{NewLine}"); + } + + [Fact] + public async Task It_writes_suggestions_for_option_arguments_when_under_root_command() + { + RootCommand rootCommand = new () { - RootCommand rootCommand = new() - { - _eatCommand, - new SuggestDirective() - }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; - - var result = rootCommand.Parse("[suggest:13] \"eat --fruit\"", config); - - await result.InvokeAsync(); - - config.Output - .ToString() - .Should() - .Be($"apple{NewLine}banana{NewLine}cherry{NewLine}"); - } - - [Fact] - public async Task It_writes_suggestions_for_option_arguments_when_under_root_command() + _fruitOption, + _vegetableOption + }; + CommandLineConfiguration config = new (rootCommand) { - RootCommand rootCommand = new () - { - _fruitOption, - _vegetableOption - }; - CommandLineConfiguration config = new (rootCommand) - { - Output = new StringWriter() - }; - - var result = rootCommand.Parse($"[suggest:8] \"--fruit\"", config); - - await result.InvokeAsync(); - - config.Output - .ToString() - .Should() - .Be($"apple{NewLine}banana{NewLine}cherry{NewLine}"); - } - - [Theory] - [InlineData("[suggest:4] \"eat\"", new[] { "--fruit", "--help", "--vegetable", "-?", "-h", "/?", "/h" })] - [InlineData("[suggest:6] \"eat --\"", new[] { "--fruit", "--help", "--vegetable" })] - public async Task It_writes_suggestions_for_option_aliases_under_subcommand(string commandLine, string[] expectedCompletions) + Output = new StringWriter() + }; + + var result = rootCommand.Parse($"[suggest:8] \"--fruit\"", config); + + await result.InvokeAsync(); + + config.Output + .ToString() + .Should() + .Be($"apple{NewLine}banana{NewLine}cherry{NewLine}"); + } + + [Theory] + [InlineData("[suggest:4] \"eat\"", new[] { "--fruit", "--help", "--vegetable", "-?", "-h", "/?", "/h" })] + [InlineData("[suggest:6] \"eat --\"", new[] { "--fruit", "--help", "--vegetable" })] + public async Task It_writes_suggestions_for_option_aliases_under_subcommand(string commandLine, string[] expectedCompletions) + { + RootCommand rootCommand = new() { _eatCommand }; + CommandLineConfiguration config = new(rootCommand) { - RootCommand rootCommand = new() { _eatCommand }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; - - var result = rootCommand.Parse(commandLine, config); - - await result.InvokeAsync(); - - string expected = string.Join(NewLine, expectedCompletions) + NewLine; - - config.Output - .ToString() - .Should() - .Be(expected); - } - - [Theory] - [InlineData("[suggest]")] - [InlineData("[suggest:0]")] - [InlineData("[suggest] ")] - [InlineData("[suggest:0] ")] - public async Task It_writes_suggestions_for_option_aliases_under_root_command(string input) + Output = new StringWriter() + }; + + var result = rootCommand.Parse(commandLine, config); + + await result.InvokeAsync(); + + string expected = string.Join(NewLine, expectedCompletions) + NewLine; + + config.Output + .ToString() + .Should() + .Be(expected); + } + + [Theory] + [InlineData("[suggest]")] + [InlineData("[suggest:0]")] + [InlineData("[suggest] ")] + [InlineData("[suggest:0] ")] + public async Task It_writes_suggestions_for_option_aliases_under_root_command(string input) + { + RootCommand rootCommand = new() { - RootCommand rootCommand = new() - { - _vegetableOption, - _fruitOption - }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; - - var result = rootCommand.Parse(input, config); - await result.InvokeAsync(); - - config.Output - .ToString() - .Should() - .Be($"--fruit{NewLine}--help{NewLine}--vegetable{NewLine}--version{NewLine}-?{NewLine}-h{NewLine}/?{NewLine}/h{NewLine}"); - } - - [Fact] - public async Task It_writes_suggestions_for_subcommand_aliases_under_root_command() + _vegetableOption, + _fruitOption + }; + CommandLineConfiguration config = new(rootCommand) { - RootCommand rootCommand = new() - { - _eatCommand - }; - CommandLineConfiguration config = new(rootCommand) - { - Output = new StringWriter() - }; - - var result = rootCommand.Parse("[suggest]", config); - await result.InvokeAsync(); - - config.Output - .ToString() - .Should() - .Be($"--help{NewLine}--version{NewLine}-?{NewLine}-h{NewLine}/?{NewLine}/h{NewLine}eat{NewLine}"); - } - - [Fact] - public async Task It_writes_suggestions_for_partial_option_aliases_under_root_command() + Output = new StringWriter() + }; + + var result = rootCommand.Parse(input, config); + await result.InvokeAsync(); + + config.Output + .ToString() + .Should() + .Be($"--fruit{NewLine}--help{NewLine}--vegetable{NewLine}--version{NewLine}-?{NewLine}-h{NewLine}/?{NewLine}/h{NewLine}"); + } + + [Fact] + public async Task It_writes_suggestions_for_subcommand_aliases_under_root_command() + { + RootCommand rootCommand = new() { - RootCommand rootCommand = new() - { - _fruitOption, - _vegetableOption - }; - CommandLineConfiguration config = new (rootCommand) - { - Output = new StringWriter(), - }; - - var result = rootCommand.Parse("[suggest:1] \"f\"", config); - - await result.InvokeAsync(); - - config.Output - .ToString() - .Should() - .Be($"--fruit{NewLine}"); - } - - [Fact] - public async Task It_writes_suggestions_for_partial_subcommand_aliases_under_root_command() + _eatCommand + }; + CommandLineConfiguration config = new(rootCommand) { - RootCommand rootCommand = new () - { - _eatCommand, - new Command("wash-dishes") - }; - CommandLineConfiguration config = new (rootCommand) - { - Output = new StringWriter() - }; - - var result = rootCommand.Parse("[suggest:1] \"d\"", config); - - await result.InvokeAsync(); - - config.Output - .ToString() - .Should() - .Be($"wash-dishes{NewLine}"); - } - - [Fact] - public async Task It_writes_suggestions_for_partial_option_and_subcommand_aliases_under_root_command() + Output = new StringWriter() + }; + + var result = rootCommand.Parse("[suggest]", config); + await result.InvokeAsync(); + + config.Output + .ToString() + .Should() + .Be($"--help{NewLine}--version{NewLine}-?{NewLine}-h{NewLine}/?{NewLine}/h{NewLine}eat{NewLine}"); + } + + [Fact] + public async Task It_writes_suggestions_for_partial_option_aliases_under_root_command() + { + RootCommand rootCommand = new() { - RootCommand rootCommand = new () - { - _eatCommand, - new Command("wash-dishes"), - }; - CommandLineConfiguration config = new (rootCommand) - { - Output = new StringWriter() - }; - - var result = rootCommand.Parse("[suggest:5] \"--ver\"", config); - - await result.InvokeAsync(); - - config.Output - .ToString() - .Should() - .Be($"--version{NewLine}"); - } - - [Fact] - public async Task It_writes_suggestions_for_partial_option_and_subcommand_aliases_under_root_command_with_an_argument() + _fruitOption, + _vegetableOption + }; + CommandLineConfiguration config = new (rootCommand) { - RootCommand command = new("parent") - { - new Command("child"), - new Option("--option1"), - new Option("--option2"), - new Argument("arg") - }; - CommandLineConfiguration config = new (command) - { - Output = new StringWriter() - }; - - await config.InvokeAsync("[suggest:3] \"opt\""); - - config.Output - .ToString() - .Should() - .Be($"--option1{NewLine}--option2{NewLine}"); - } - - [Fact] - public async Task It_does_not_repeat_suggestion_for_already_specified_bool_option() + Output = new StringWriter(), + }; + + var result = rootCommand.Parse("[suggest:1] \"f\"", config); + + await result.InvokeAsync(); + + config.Output + .ToString() + .Should() + .Be($"--fruit{NewLine}"); + } + + [Fact] + public async Task It_writes_suggestions_for_partial_subcommand_aliases_under_root_command() + { + RootCommand rootCommand = new () + { + _eatCommand, + new Command("wash-dishes") + }; + CommandLineConfiguration config = new (rootCommand) { - var command = new RootCommand - { - new Option("--bool-option") - }; - CommandLineConfiguration config = new (command) - { - Output = new StringWriter() - }; - - var commandLine = "--bool-option false"; - - await command.Parse($"[suggest:{commandLine.Length + 1}] \"{commandLine}\"", config).InvokeAsync(); - - config.Output - .ToString() - .Should() - .NotContain("--bool-option"); - } + Output = new StringWriter() + }; + + var result = rootCommand.Parse("[suggest:1] \"d\"", config); + + await result.InvokeAsync(); + + config.Output + .ToString() + .Should() + .Be($"wash-dishes{NewLine}"); + } + + [Fact] + public async Task It_writes_suggestions_for_partial_option_and_subcommand_aliases_under_root_command() + { + RootCommand rootCommand = new () + { + _eatCommand, + new Command("wash-dishes"), + }; + CommandLineConfiguration config = new (rootCommand) + { + Output = new StringWriter() + }; + + var result = rootCommand.Parse("[suggest:5] \"--ver\"", config); + + await result.InvokeAsync(); + + config.Output + .ToString() + .Should() + .Be($"--version{NewLine}"); + } + + [Fact] + public async Task It_writes_suggestions_for_partial_option_and_subcommand_aliases_under_root_command_with_an_argument() + { + RootCommand command = new("parent") + { + new Command("child"), + new Option("--option1"), + new Option("--option2"), + new Argument("arg") + }; + CommandLineConfiguration config = new (command) + { + Output = new StringWriter() + }; + + await config.InvokeAsync("[suggest:3] \"opt\""); + + config.Output + .ToString() + .Should() + .Be($"--option1{NewLine}--option2{NewLine}"); + } + + [Fact] + public async Task It_does_not_repeat_suggestion_for_already_specified_bool_option() + { + var command = new RootCommand + { + new Option("--bool-option") + }; + CommandLineConfiguration config = new (command) + { + Output = new StringWriter() + }; + + var commandLine = "--bool-option false"; + + await command.Parse($"[suggest:{commandLine.Length + 1}] \"{commandLine}\"", config).InvokeAsync(); + + config.Output + .ToString() + .Should() + .NotContain("--bool-option"); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/UseExceptionHandlerTests.cs b/src/System.CommandLine.Tests/UseExceptionHandlerTests.cs index 5acf4b59b5..58082c2346 100644 --- a/src/System.CommandLine.Tests/UseExceptionHandlerTests.cs +++ b/src/System.CommandLine.Tests/UseExceptionHandlerTests.cs @@ -6,93 +6,92 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class UseExceptionHandlerTests { - public class UseExceptionHandlerTests + [Fact] + public async Task UseExceptionHandler_catches_command_handler_exceptions_and_sets_result_code_to_1() { - [Fact] - public async Task UseExceptionHandler_catches_command_handler_exceptions_and_sets_result_code_to_1() + var command = new Command("the-command"); + command.SetAction((_, __) => Task.FromException(new Exception("oops!"))); + + CommandLineConfiguration config = new(command) { - var command = new Command("the-command"); - command.SetAction((_, __) => Task.FromException(new Exception("oops!"))); + Error = new StringWriter(), + }; - CommandLineConfiguration config = new(command) - { - Error = new StringWriter(), - }; + var resultCode = await config.InvokeAsync("the-command"); - var resultCode = await config.InvokeAsync("the-command"); + resultCode.Should().Be(1); + } - resultCode.Should().Be(1); - } + [Fact] + public async Task UseExceptionHandler_catches_command_handler_exceptions_and_writes_details_to_standard_error() + { + var command = new Command("the-command"); + command.SetAction((_, __) => Task.FromException(new Exception("oops!"))); - [Fact] - public async Task UseExceptionHandler_catches_command_handler_exceptions_and_writes_details_to_standard_error() + CommandLineConfiguration config = new(command) { - var command = new Command("the-command"); - command.SetAction((_, __) => Task.FromException(new Exception("oops!"))); + Error = new StringWriter(), + }; - CommandLineConfiguration config = new(command) - { - Error = new StringWriter(), - }; + await config.InvokeAsync("the-command"); - await config.InvokeAsync("the-command"); + config.Error.ToString().Should().Contain("System.Exception: oops!"); + } - config.Error.ToString().Should().Contain("System.Exception: oops!"); - } + [Fact] + public async Task When_thrown_exception_is_from_cancelation_no_output_is_generated() + { + Command command = new("the-command"); + command.SetAction((_, __) => throw new OperationCanceledException()); + + CommandLineConfiguration config = new(command) + { + Output = new StringWriter(), + Error = new StringWriter() + }; + + int resultCode = await config + .InvokeAsync("the-command"); - [Fact] - public async Task When_thrown_exception_is_from_cancelation_no_output_is_generated() + config.Output.ToString().Should().BeEmpty(); + resultCode.Should().NotBe(0); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Exception_output_can_be_customized(bool async) + { + Exception expectedException = new ("oops!"); + Command command = new("the-command"); + command.SetAction((_, __) => throw expectedException); + + CommandLineConfiguration config = new(command) { - Command command = new("the-command"); - command.SetAction((_, __) => throw new OperationCanceledException()); + Error = new StringWriter(), + EnableDefaultExceptionHandler = false + }; - CommandLineConfiguration config = new(command) - { - Output = new StringWriter(), - Error = new StringWriter() - }; + ParseResult parseResult = command.Parse("the-command", config); - int resultCode = await config - .InvokeAsync("the-command"); + int resultCode = 0; - config.Output.ToString().Should().BeEmpty(); - resultCode.Should().NotBe(0); + try + { + resultCode = async ? await parseResult.InvokeAsync() : parseResult.Invoke(); } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task Exception_output_can_be_customized(bool async) + catch (Exception ex) { - Exception expectedException = new ("oops!"); - Command command = new("the-command"); - command.SetAction((_, __) => throw expectedException); - - CommandLineConfiguration config = new(command) - { - Error = new StringWriter(), - EnableDefaultExceptionHandler = false - }; - - ParseResult parseResult = command.Parse("the-command", config); - - int resultCode = 0; - - try - { - resultCode = async ? await parseResult.InvokeAsync() : parseResult.Invoke(); - } - catch (Exception ex) - { - ex.Should().Be(expectedException); - parseResult.Configuration.Error.Write("Well that's awkward."); - resultCode = 22; - } - - config.Error.ToString().Should().Be("Well that's awkward."); - resultCode.Should().Be(22); + ex.Should().Be(expectedException); + parseResult.Configuration.Error.Write("Well that's awkward."); + resultCode = 22; } + + config.Error.ToString().Should().Be("Well that's awkward."); + resultCode.Should().Be(22); } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Utility/LinuxOnlyTheory.cs b/src/System.CommandLine.Tests/Utility/LinuxOnlyTheory.cs index a7a8890cb9..5f614954c9 100644 --- a/src/System.CommandLine.Tests/Utility/LinuxOnlyTheory.cs +++ b/src/System.CommandLine.Tests/Utility/LinuxOnlyTheory.cs @@ -4,16 +4,15 @@ using System.Runtime.InteropServices; using Xunit; -namespace System.CommandLine.Tests.Utility +namespace System.CommandLine.Tests.Utility; + +public class LinuxOnlyTheory : TheoryAttribute { - public class LinuxOnlyTheory : TheoryAttribute + public LinuxOnlyTheory() { - public LinuxOnlyTheory() + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - Skip = "This test requires Linux to run"; - } + Skip = "This test requires Linux to run"; } } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Utility/NonWindowsOnlyFactAttribute.cs b/src/System.CommandLine.Tests/Utility/NonWindowsOnlyFactAttribute.cs index 4b1e99d3e5..efd3b2858c 100644 --- a/src/System.CommandLine.Tests/Utility/NonWindowsOnlyFactAttribute.cs +++ b/src/System.CommandLine.Tests/Utility/NonWindowsOnlyFactAttribute.cs @@ -4,16 +4,15 @@ using Microsoft.DotNet.PlatformAbstractions; using Xunit; -namespace System.CommandLine.Tests.Utility +namespace System.CommandLine.Tests.Utility; + +public class NonWindowsOnlyFactAttribute : FactAttribute { - public class NonWindowsOnlyFactAttribute : FactAttribute + public NonWindowsOnlyFactAttribute() { - public NonWindowsOnlyFactAttribute() + if (RuntimeEnvironment.OperatingSystemPlatform == Platform.Windows) { - if (RuntimeEnvironment.OperatingSystemPlatform == Platform.Windows) - { - Skip = "This test requires non-Windows to run"; - } + Skip = "This test requires non-Windows to run"; } } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Utility/ParseResultExtensions.cs b/src/System.CommandLine.Tests/Utility/ParseResultExtensions.cs index 60e46a2599..362729fb49 100644 --- a/src/System.CommandLine.Tests/Utility/ParseResultExtensions.cs +++ b/src/System.CommandLine.Tests/Utility/ParseResultExtensions.cs @@ -1,26 +1,25 @@ using System.CommandLine.Invocation; using System.IO; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +internal static class ParseResultExtensions { - internal static class ParseResultExtensions + internal static string Diagram(this ParseResult parseResult) { - internal static string Diagram(this ParseResult parseResult) - { - TextWriter outputBefore = parseResult.Configuration.Output; + TextWriter outputBefore = parseResult.Configuration.Output; - try - { - parseResult.Configuration.Output = new StringWriter(); - ((SynchronousCommandLineAction)new DiagramDirective().Action!).Invoke(parseResult); - return parseResult.Configuration.Output.ToString() - .TrimEnd(); // the directive adds a new line, tests that used to rely on Diagram extension method don't expect it - } - finally - { - // some of the tests check the Output after getting the Diagram - parseResult.Configuration.Output = outputBefore; - } + try + { + parseResult.Configuration.Output = new StringWriter(); + ((SynchronousCommandLineAction)new DiagramDirective().Action!).Invoke(parseResult); + return parseResult.Configuration.Output.ToString() + .TrimEnd(); // the directive adds a new line, tests that used to rely on Diagram extension method don't expect it + } + finally + { + // some of the tests check the Output after getting the Diagram + parseResult.Configuration.Output = outputBefore; } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Utility/ReleaseBuildOnlyFactAttribute.cs b/src/System.CommandLine.Tests/Utility/ReleaseBuildOnlyFactAttribute.cs index e6cdb53f34..27bb326a12 100644 --- a/src/System.CommandLine.Tests/Utility/ReleaseBuildOnlyFactAttribute.cs +++ b/src/System.CommandLine.Tests/Utility/ReleaseBuildOnlyFactAttribute.cs @@ -3,15 +3,14 @@ using Xunit; -namespace System.CommandLine.Tests.Utility +namespace System.CommandLine.Tests.Utility; + +public class ReleaseBuildOnlyFactAttribute : FactAttribute { - public class ReleaseBuildOnlyFactAttribute : FactAttribute + public ReleaseBuildOnlyFactAttribute() { - public ReleaseBuildOnlyFactAttribute() - { #if DEBUG - Skip = "This test runs only on Release builds."; + Skip = "This test runs only on Release builds."; #endif - } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Utility/RemoteExecution.cs b/src/System.CommandLine.Tests/Utility/RemoteExecution.cs index e4a759df4c..5f172f5f65 100644 --- a/src/System.CommandLine.Tests/Utility/RemoteExecution.cs +++ b/src/System.CommandLine.Tests/Utility/RemoteExecution.cs @@ -4,77 +4,76 @@ using System.IO; using Xunit; -namespace System.CommandLine.Tests.Utility +namespace System.CommandLine.Tests.Utility; + +public class RemoteExecution : IDisposable { - public class RemoteExecution : IDisposable + private const int FailWaitTimeoutMilliseconds = 60 * 1000; + private readonly string _exceptionFile; + + public RemoteExecution(Diagnostics.Process process, string className, string methodName, string exceptionFile) { - private const int FailWaitTimeoutMilliseconds = 60 * 1000; - private readonly string _exceptionFile; + Process = process; + ClassName = className; + MethodName = methodName; + _exceptionFile = exceptionFile; + } - public RemoteExecution(Diagnostics.Process process, string className, string methodName, string exceptionFile) - { - Process = process; - ClassName = className; - MethodName = methodName; - _exceptionFile = exceptionFile; - } + public Diagnostics.Process Process { get; private set; } + public string ClassName { get; } + public string MethodName { get; } - public Diagnostics.Process Process { get; private set; } - public string ClassName { get; } - public string MethodName { get; } + public void Dispose() + { + GC.SuppressFinalize(this); // before Dispose(true) in case the Dispose call throws + Dispose(disposing: true); + } - public void Dispose() - { - GC.SuppressFinalize(this); // before Dispose(true) in case the Dispose call throws - Dispose(disposing: true); - } + private void Dispose(bool disposing) + { + Assert.True(disposing, $"A test {ClassName}.{MethodName} forgot to Dispose() the result of RemoteInvoke()"); - private void Dispose(bool disposing) + if (Process != null) { - Assert.True(disposing, $"A test {ClassName}.{MethodName} forgot to Dispose() the result of RemoteInvoke()"); + Assert.True(Process.WaitForExit(FailWaitTimeoutMilliseconds), + $"Timed out after {FailWaitTimeoutMilliseconds}ms waiting for remote process {Process.Id}"); - if (Process != null) + // A bit unorthodox to do throwing operations in a Dispose, but by doing it here we avoid + // needing to do this in every derived test and keep each test much simpler. + try { - Assert.True(Process.WaitForExit(FailWaitTimeoutMilliseconds), - $"Timed out after {FailWaitTimeoutMilliseconds}ms waiting for remote process {Process.Id}"); - - // A bit unorthodox to do throwing operations in a Dispose, but by doing it here we avoid - // needing to do this in every derived test and keep each test much simpler. - try + if (File.Exists(_exceptionFile)) { - if (File.Exists(_exceptionFile)) - { - throw new RemoteExecutionException(File.ReadAllText(_exceptionFile)); - } + throw new RemoteExecutionException(File.ReadAllText(_exceptionFile)); } - finally + } + finally + { + if (File.Exists(_exceptionFile)) { - if (File.Exists(_exceptionFile)) - { - File.Delete(_exceptionFile); - } - - // Cleanup - try { Process.Kill(); } - catch { } // ignore all cleanup errors + File.Delete(_exceptionFile); } - Process.Dispose(); - Process = null; + // Cleanup + try { Process.Kill(); } + catch { } // ignore all cleanup errors } - } - private sealed class RemoteExecutionException : Exception - { - private readonly string _stackTrace; + Process.Dispose(); + Process = null; + } + } - internal RemoteExecutionException(string stackTrace) - : base("Remote process failed with an unhandled exception.") - { - _stackTrace = stackTrace; - } + private sealed class RemoteExecutionException : Exception + { + private readonly string _stackTrace; - public override string StackTrace => _stackTrace ?? base.StackTrace; + internal RemoteExecutionException(string stackTrace) + : base("Remote process failed with an unhandled exception.") + { + _stackTrace = stackTrace; } + + public override string StackTrace => _stackTrace ?? base.StackTrace; } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Utility/RemoteExecutor.cs b/src/System.CommandLine.Tests/Utility/RemoteExecutor.cs index 23f7500f27..a0a155ba21 100644 --- a/src/System.CommandLine.Tests/Utility/RemoteExecutor.cs +++ b/src/System.CommandLine.Tests/Utility/RemoteExecutor.cs @@ -11,197 +11,196 @@ using System.Text; using System.Threading.Tasks; -namespace System.CommandLine.Tests.Utility +namespace System.CommandLine.Tests.Utility; + +public class RemoteExecutor { - public class RemoteExecutor + public static int Main(string[] args) { - public static int Main(string[] args) + if (args.Length < 3) { - if (args.Length < 3) - { - Console.Error.WriteLine("This is not the program you are looking for. Run 'dotnet test' instead."); - return -1; - } + Console.Error.WriteLine("This is not the program you are looking for. Run 'dotnet test' instead."); + return -1; + } - string typeName = args[0]; - string methodName = args[1]; - string exceptionFile = args[2]; - string[] methodArguments = args.Skip(3).ToArray(); + string typeName = args[0]; + string methodName = args[1]; + string exceptionFile = args[2]; + string[] methodArguments = args.Skip(3).ToArray(); - Type type = null; - MethodInfo methodInfo = null; - object instance = null; - int exitCode = 0; - try + Type type = null; + MethodInfo methodInfo = null; + object instance = null; + int exitCode = 0; + try + { + type = typeof(RemoteExecutor).Assembly.GetType(typeName); + methodInfo = type.GetTypeInfo().GetDeclaredMethod(methodName); + instance = null; + + if (!methodInfo.IsStatic) { - type = typeof(RemoteExecutor).Assembly.GetType(typeName); - methodInfo = type.GetTypeInfo().GetDeclaredMethod(methodName); - instance = null; - - if (!methodInfo.IsStatic) - { - instance = Activator.CreateInstance(type); - } - - object result = methodInfo.Invoke(instance, new object[] { methodArguments }); - if (result is Task task) - { - exitCode = task.GetAwaiter().GetResult(); - } - else if (result is int exit) - { - exitCode = exit; - } + instance = Activator.CreateInstance(type); } - catch (Exception exc) + + object result = methodInfo.Invoke(instance, new object[] { methodArguments }); + if (result is Task task) { - if (exc is TargetInvocationException && exc.InnerException != null) - exc = exc.InnerException; - - var output = new StringBuilder(); - output.AppendLine(); - output.AppendLine("Child exception:"); - output.AppendLine(" " + exc); - output.AppendLine(); - output.AppendLine("Child process:"); - output.AppendLine($" {type} {methodInfo}"); - output.AppendLine(); - - if (methodArguments.Length > 0) - { - output.AppendLine("Child arguments:"); - output.AppendLine(" " + string.Join(", ", methodArguments)); - } - - File.WriteAllText(exceptionFile, output.ToString()); + exitCode = task.GetAwaiter().GetResult(); } - finally + else if (result is int exit) { - (instance as IDisposable)?.Dispose(); + exitCode = exit; } - - return exitCode; } - - public static RemoteExecution Execute(Func mainMethod, string[] args = null, ProcessStartInfo psi = null) - => Execute(mainMethod.GetMethodInfo(), args, psi); - - public static RemoteExecution Execute(Func> mainMethod, string[] args = null, ProcessStartInfo psi = null) - => Execute(mainMethod.GetMethodInfo(), args, psi); - - private static RemoteExecution Execute(MethodInfo methodInfo, string[] args, ProcessStartInfo psi) + catch (Exception exc) { - Type declaringType = methodInfo.DeclaringType; - string className = declaringType.FullName; - string methodName = methodInfo.Name; - string exceptionFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - - string dotnetExecutable = Diagnostics.Process.GetCurrentProcess().MainModule.FileName; - string thisAssembly = typeof(RemoteExecutor).Assembly.Location; - var assembly = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()); - string entryAssemblyWithoutExtension = Path.Combine(Path.GetDirectoryName(assembly.Location), - Path.GetFileNameWithoutExtension(assembly.Location)); - string runtimeConfig = GetApplicationArgument("--runtimeconfig"); - if (runtimeConfig == null) - { - runtimeConfig = entryAssemblyWithoutExtension + ".runtimeconfig.json"; - } - string depsFile = GetApplicationArgument("--depsfile"); - if (depsFile == null) + if (exc is TargetInvocationException && exc.InnerException != null) + exc = exc.InnerException; + + var output = new StringBuilder(); + output.AppendLine(); + output.AppendLine("Child exception:"); + output.AppendLine(" " + exc); + output.AppendLine(); + output.AppendLine("Child process:"); + output.AppendLine($" {type} {methodInfo}"); + output.AppendLine(); + + if (methodArguments.Length > 0) { - depsFile = entryAssemblyWithoutExtension + ".deps.json"; + output.AppendLine("Child arguments:"); + output.AppendLine(" " + string.Join(", ", methodArguments)); } - if (psi == null) - { - psi = new ProcessStartInfo(); - } - psi.FileName = dotnetExecutable; + File.WriteAllText(exceptionFile, output.ToString()); + } + finally + { + (instance as IDisposable)?.Dispose(); + } - var argumentList = new List(); - argumentList.AddRange(new[] { "exec", "--runtimeconfig", runtimeConfig, "--depsfile", depsFile, thisAssembly, - className, methodName, exceptionFile }); - if (args != null) - { - argumentList.AddRange(args); - } + return exitCode; + } - psi.Arguments = string.Join(" ", argumentList); - Diagnostics.Process process = Diagnostics.Process.Start(psi); + public static RemoteExecution Execute(Func mainMethod, string[] args = null, ProcessStartInfo psi = null) + => Execute(mainMethod.GetMethodInfo(), args, psi); - return new RemoteExecution(process, className, methodName, exceptionFile); - } + public static RemoteExecution Execute(Func> mainMethod, string[] args = null, ProcessStartInfo psi = null) + => Execute(mainMethod.GetMethodInfo(), args, psi); - private static string GetApplicationArgument(string name) + private static RemoteExecution Execute(MethodInfo methodInfo, string[] args, ProcessStartInfo psi) + { + Type declaringType = methodInfo.DeclaringType; + string className = declaringType.FullName; + string methodName = methodInfo.Name; + string exceptionFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + string dotnetExecutable = Diagnostics.Process.GetCurrentProcess().MainModule.FileName; + string thisAssembly = typeof(RemoteExecutor).Assembly.Location; + var assembly = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()); + string entryAssemblyWithoutExtension = Path.Combine(Path.GetDirectoryName(assembly.Location), + Path.GetFileNameWithoutExtension(assembly.Location)); + string runtimeConfig = GetApplicationArgument("--runtimeconfig"); + if (runtimeConfig == null) { - string[] arguments = GetApplicationArguments(); - for (int i = 0; i < arguments.Length - 1; i++) - { - if (arguments[i] == name) - { - return arguments[i + 1]; - } - } - return null; + runtimeConfig = entryAssemblyWithoutExtension + ".runtimeconfig.json"; + } + string depsFile = GetApplicationArgument("--depsfile"); + if (depsFile == null) + { + depsFile = entryAssemblyWithoutExtension + ".deps.json"; } - private static string[] s_arguments; + if (psi == null) + { + psi = new ProcessStartInfo(); + } + psi.FileName = dotnetExecutable; - private static string[] GetApplicationArguments() + var argumentList = new List(); + argumentList.AddRange(new[] { "exec", "--runtimeconfig", runtimeConfig, "--depsfile", depsFile, thisAssembly, + className, methodName, exceptionFile }); + if (args != null) { - // Environment.GetCommandLineArgs doesn't include arguments passed to the runtime. - // We use a native API to get all arguments. + argumentList.AddRange(args); + } - if (s_arguments != null) - { - return s_arguments; - } + psi.Arguments = string.Join(" ", argumentList); + Diagnostics.Process process = Diagnostics.Process.Start(psi); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - s_arguments = File.ReadAllText($"/proc/{Diagnostics.Process.GetCurrentProcess().Id}/cmdline").Split(new[] { '\0' }); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - System.IntPtr ptr = GetCommandLine(); - string commandLine = Marshal.PtrToStringAuto(ptr); - s_arguments = CommandLineToArgs(commandLine); - } - else + return new RemoteExecution(process, className, methodName, exceptionFile); + } + + private static string GetApplicationArgument(string name) + { + string[] arguments = GetApplicationArguments(); + for (int i = 0; i < arguments.Length - 1; i++) + { + if (arguments[i] == name) { - throw new PlatformNotSupportedException($"{nameof(GetApplicationArguments)} is not supported on this platform."); + return arguments[i + 1]; } + } + return null; + } + + private static string[] s_arguments; + private static string[] GetApplicationArguments() + { + // Environment.GetCommandLineArgs doesn't include arguments passed to the runtime. + // We use a native API to get all arguments. + + if (s_arguments != null) + { return s_arguments; } - [DllImport("kernel32.dll", CharSet = CharSet.Auto)] - private static extern IntPtr GetCommandLine(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + s_arguments = File.ReadAllText($"/proc/{Diagnostics.Process.GetCurrentProcess().Id}/cmdline").Split(new[] { '\0' }); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + System.IntPtr ptr = GetCommandLine(); + string commandLine = Marshal.PtrToStringAuto(ptr); + s_arguments = CommandLineToArgs(commandLine); + } + else + { + throw new PlatformNotSupportedException($"{nameof(GetApplicationArguments)} is not supported on this platform."); + } + + return s_arguments; + } - [DllImport("shell32.dll", SetLastError = true)] - private static extern IntPtr CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + private static extern IntPtr GetCommandLine(); - public static string[] CommandLineToArgs(string commandLine) + [DllImport("shell32.dll", SetLastError = true)] + private static extern IntPtr CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); + + public static string[] CommandLineToArgs(string commandLine) + { + int argc; + var argv = CommandLineToArgvW(commandLine, out argc); + if (argv == IntPtr.Zero) + throw new Win32Exception(); + try { - int argc; - var argv = CommandLineToArgvW(commandLine, out argc); - if (argv == IntPtr.Zero) - throw new Win32Exception(); - try - { - var args = new string[argc]; - for (var i = 0; i < args.Length; i++) - { - var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size); - args[i] = Marshal.PtrToStringUni(p); - } - - return args; - } - finally + var args = new string[argc]; + for (var i = 0; i < args.Length; i++) { - Marshal.FreeHGlobal(argv); + var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size); + args[i] = Marshal.PtrToStringUni(p); } + + return args; + } + finally + { + Marshal.FreeHGlobal(argv); } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/Utility/WindowsOnlyFactAttribute.cs b/src/System.CommandLine.Tests/Utility/WindowsOnlyFactAttribute.cs index ab389624a9..268ebf4e61 100644 --- a/src/System.CommandLine.Tests/Utility/WindowsOnlyFactAttribute.cs +++ b/src/System.CommandLine.Tests/Utility/WindowsOnlyFactAttribute.cs @@ -4,16 +4,15 @@ using Microsoft.DotNet.PlatformAbstractions; using Xunit; -namespace System.CommandLine.Tests.Utility +namespace System.CommandLine.Tests.Utility; + +public class WindowsOnlyFactAttribute : FactAttribute { - public class WindowsOnlyFactAttribute : FactAttribute + public WindowsOnlyFactAttribute() { - public WindowsOnlyFactAttribute() + if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Windows) { - if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Windows) - { - Skip = "This test requires Windows to run"; - } + Skip = "This test requires Windows to run"; } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine.Tests/VersionOptionTests.cs b/src/System.CommandLine.Tests/VersionOptionTests.cs index b796349136..f01b270337 100644 --- a/src/System.CommandLine.Tests/VersionOptionTests.cs +++ b/src/System.CommandLine.Tests/VersionOptionTests.cs @@ -10,197 +10,196 @@ using Xunit; using static System.Environment; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests; + +public class VersionOptionTests { - public class VersionOptionTests - { - private static readonly string version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()) - .GetCustomAttribute() - .InformationalVersion; + private static readonly string version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()) + .GetCustomAttribute() + .InformationalVersion; - [Fact] - public async Task When_the_version_option_is_specified_then_the_version_is_written_to_standard_out() + [Fact] + public async Task When_the_version_option_is_specified_then_the_version_is_written_to_standard_out() + { + CommandLineConfiguration configuration = new(new RootCommand()) { - CommandLineConfiguration configuration = new(new RootCommand()) - { - Output = new StringWriter() - }; + Output = new StringWriter() + }; - await configuration.InvokeAsync("--version"); + await configuration.InvokeAsync("--version"); - configuration.Output.ToString().Should().Be($"{version}{NewLine}"); - } + configuration.Output.ToString().Should().Be($"{version}{NewLine}"); + } - [Fact] - public async Task When_the_version_option_is_specified_then_invocation_is_short_circuited() - { - var wasCalled = false; - var rootCommand = new RootCommand(); - rootCommand.SetAction((_) => wasCalled = true); + [Fact] + public async Task When_the_version_option_is_specified_then_invocation_is_short_circuited() + { + var wasCalled = false; + var rootCommand = new RootCommand(); + rootCommand.SetAction((_) => wasCalled = true); - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - await configuration.InvokeAsync("--version"); + await configuration.InvokeAsync("--version"); - wasCalled.Should().BeFalse(); - } + wasCalled.Should().BeFalse(); + } - [Fact] - public async Task Version_option_appears_in_help() + [Fact] + public async Task Version_option_appears_in_help() + { + CommandLineConfiguration configuration = new(new RootCommand()) { - CommandLineConfiguration configuration = new(new RootCommand()) - { - Output = new StringWriter() - }; + Output = new StringWriter() + }; - await configuration.InvokeAsync("--help"); + await configuration.InvokeAsync("--help"); - configuration.Output - .ToString() - .Should() - .Match("*Options:*--version*Show version information*"); - } + configuration.Output + .ToString() + .Should() + .Match("*Options:*--version*Show version information*"); + } - [Fact] - public async Task When_the_version_option_is_specified_and_there_are_default_options_then_the_version_is_written_to_standard_out() + [Fact] + public async Task When_the_version_option_is_specified_and_there_are_default_options_then_the_version_is_written_to_standard_out() + { + var rootCommand = new RootCommand { - var rootCommand = new RootCommand + new Option("-x") { - new Option("-x") - { - DefaultValueFactory = (_) => true - }, - }; - rootCommand.SetAction((_) => { }); - - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + DefaultValueFactory = (_) => true + }, + }; + rootCommand.SetAction((_) => { }); - await configuration.InvokeAsync("--version"); + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - configuration.Output.ToString().Should().Be($"{version}{NewLine}"); - } + await configuration.InvokeAsync("--version"); - [Fact] - public async Task When_the_version_option_is_specified_and_there_are_default_arguments_then_the_version_is_written_to_standard_out() + configuration.Output.ToString().Should().Be($"{version}{NewLine}"); + } + + [Fact] + public async Task When_the_version_option_is_specified_and_there_are_default_arguments_then_the_version_is_written_to_standard_out() + { + RootCommand rootCommand = new () { - RootCommand rootCommand = new () - { - new Argument("x") { DefaultValueFactory =(_) => true }, - }; - rootCommand.SetAction((_) => { }); + new Argument("x") { DefaultValueFactory =(_) => true }, + }; + rootCommand.SetAction((_) => { }); - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - await configuration.InvokeAsync("--version"); + await configuration.InvokeAsync("--version"); - configuration.Output.ToString().Should().Be($"{version}{NewLine}"); - } + configuration.Output.ToString().Should().Be($"{version}{NewLine}"); + } - [Theory] - [InlineData("--version -x")] - [InlineData("--version subcommand")] - public void Version_is_not_valid_with_other_tokens(string commandLine) + [Theory] + [InlineData("--version -x")] + [InlineData("--version subcommand")] + public void Version_is_not_valid_with_other_tokens(string commandLine) + { + var subcommand = new Command("subcommand"); + subcommand.SetAction(_ => { }); + var rootCommand = new RootCommand { - var subcommand = new Command("subcommand"); - subcommand.SetAction(_ => { }); - var rootCommand = new RootCommand - { - subcommand, - new Option("-x") - }; - rootCommand.SetAction(_ => { }); + subcommand, + new Option("-x") + }; + rootCommand.SetAction(_ => { }); - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - var result = rootCommand.Parse(commandLine, configuration); + var result = rootCommand.Parse(commandLine, configuration); - result.Errors.Should().Contain(e => e.Message == "--version option cannot be combined with other arguments."); - } + result.Errors.Should().Contain(e => e.Message == "--version option cannot be combined with other arguments."); + } - [Fact] - public void Version_option_is_not_added_to_subcommands() - { - var childCommand = new Command("subcommand"); - childCommand.SetAction(_ => { }); + [Fact] + public void Version_option_is_not_added_to_subcommands() + { + var childCommand = new Command("subcommand"); + childCommand.SetAction(_ => { }); - var rootCommand = new RootCommand - { - childCommand - }; - rootCommand.SetAction(_ => { }); + var rootCommand = new RootCommand + { + childCommand + }; + rootCommand.SetAction(_ => { }); - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; - - configuration - .RootCommand - .Subcommands - .Single(c => c.Name == "subcommand") - .Options - .Should() - .BeEmpty(); - } - - [Fact] - public async Task Version_can_specify_additional_alias() + CommandLineConfiguration configuration = new(rootCommand) { - RootCommand rootCommand = new(); + Output = new StringWriter() + }; + + configuration + .RootCommand + .Subcommands + .Single(c => c.Name == "subcommand") + .Options + .Should() + .BeEmpty(); + } - rootCommand.Options.Clear(); - rootCommand.Add(new VersionOption("-v", "-version")); + [Fact] + public async Task Version_can_specify_additional_alias() + { + RootCommand rootCommand = new(); - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + rootCommand.Options.Clear(); + rootCommand.Add(new VersionOption("-v", "-version")); + + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - using var _ = new AssertionScope(); + using var _ = new AssertionScope(); - await configuration.InvokeAsync("-v"); - configuration.Output.ToString().Should().Be($"{version}{NewLine}"); + await configuration.InvokeAsync("-v"); + configuration.Output.ToString().Should().Be($"{version}{NewLine}"); - configuration.Output = new StringWriter(); - await configuration.InvokeAsync("-version"); - configuration.Output.ToString().Should().Be($"{version}{NewLine}"); - } + configuration.Output = new StringWriter(); + await configuration.InvokeAsync("-version"); + configuration.Output.ToString().Should().Be($"{version}{NewLine}"); + } - [Fact] - public void Version_is_not_valid_with_other_tokens_when_it_uses_custom_alias() + [Fact] + public void Version_is_not_valid_with_other_tokens_when_it_uses_custom_alias() + { + var childCommand = new Command("subcommand"); + childCommand.SetAction(_ => { }); + var rootCommand = new RootCommand { - var childCommand = new Command("subcommand"); - childCommand.SetAction(_ => { }); - var rootCommand = new RootCommand - { - childCommand - }; + childCommand + }; - rootCommand.Options.Clear(); - rootCommand.Add(new VersionOption("-v")); + rootCommand.Options.Clear(); + rootCommand.Add(new VersionOption("-v")); - rootCommand.SetAction(_ => { }); + rootCommand.SetAction(_ => { }); - CommandLineConfiguration configuration = new(rootCommand) - { - Output = new StringWriter() - }; + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - var result = rootCommand.Parse("-v subcommand", configuration); + var result = rootCommand.Parse("-v subcommand", configuration); - result.Errors.Should().ContainSingle(e => e.Message == "-v option cannot be combined with other arguments."); - } + result.Errors.Should().ContainSingle(e => e.Message == "-v option cannot be combined with other arguments."); } -} +} \ No newline at end of file diff --git a/src/System.CommandLine/AliasSet.cs b/src/System.CommandLine/AliasSet.cs index 0fed8a5d75..d2ce3b9814 100644 --- a/src/System.CommandLine/AliasSet.cs +++ b/src/System.CommandLine/AliasSet.cs @@ -1,47 +1,46 @@ using System.Collections; using System.Collections.Generic; -namespace System.CommandLine +namespace System.CommandLine; + +// this types exists only because we need to validate the added aliases ;) +internal sealed class AliasSet : ICollection { - // this types exists only because we need to validate the added aliases ;) - internal sealed class AliasSet : ICollection - { - private readonly HashSet _aliases; + private readonly HashSet _aliases; - internal AliasSet() => _aliases = new(StringComparer.Ordinal); + internal AliasSet() => _aliases = new(StringComparer.Ordinal); - internal AliasSet(string[] aliases) + internal AliasSet(string[] aliases) + { + foreach (string alias in aliases) { - foreach (string alias in aliases) - { - Symbol.ThrowIfEmptyOrWithWhitespaces(alias, nameof(alias)); - } - - _aliases = new(aliases, StringComparer.Ordinal); + Symbol.ThrowIfEmptyOrWithWhitespaces(alias, nameof(alias)); } - public int Count => _aliases.Count; + _aliases = new(aliases, StringComparer.Ordinal); + } - public bool IsReadOnly => false; + public int Count => _aliases.Count; - public void Add(string item) - => _aliases.Add(Symbol.ThrowIfEmptyOrWithWhitespaces(item, nameof(item))); + public bool IsReadOnly => false; - internal bool Overlaps(AliasSet other) => _aliases.Overlaps(other._aliases); + public void Add(string item) + => _aliases.Add(Symbol.ThrowIfEmptyOrWithWhitespaces(item, nameof(item))); - // a struct based enumerator for avoiding allocations - public HashSet.Enumerator GetEnumerator() => _aliases.GetEnumerator(); + internal bool Overlaps(AliasSet other) => _aliases.Overlaps(other._aliases); - public void Clear() => _aliases.Clear(); + // a struct based enumerator for avoiding allocations + public HashSet.Enumerator GetEnumerator() => _aliases.GetEnumerator(); - public bool Contains(string item) => _aliases.Contains(item); + public void Clear() => _aliases.Clear(); - public void CopyTo(string[] array, int arrayIndex) => _aliases.CopyTo(array, arrayIndex); + public bool Contains(string item) => _aliases.Contains(item); - public bool Remove(string item) => _aliases.Remove(item); + public void CopyTo(string[] array, int arrayIndex) => _aliases.CopyTo(array, arrayIndex); - IEnumerator IEnumerable.GetEnumerator() => _aliases.GetEnumerator(); + public bool Remove(string item) => _aliases.Remove(item); - IEnumerator IEnumerable.GetEnumerator() => _aliases.GetEnumerator(); - } -} + IEnumerator IEnumerable.GetEnumerator() => _aliases.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _aliases.GetEnumerator(); +} \ No newline at end of file diff --git a/src/System.CommandLine/Argument.cs b/src/System.CommandLine/Argument.cs index f278477afe..3fd529d0ad 100644 --- a/src/System.CommandLine/Argument.cs +++ b/src/System.CommandLine/Argument.cs @@ -7,144 +7,143 @@ using System.CommandLine.Completions; using System.Linq; -namespace System.CommandLine +namespace System.CommandLine; + +/// +/// A symbol defining a value that can be passed on the command line to a command or option. +/// +public abstract class Argument : Symbol { + private ArgumentArity _arity; + private TryConvertArgument? _convertArguments; + private List>>? _completionSources = null; + private List>? _validators = null; + + private protected Argument(string name) : base(name, allowWhitespace: true) + { + } + /// - /// A symbol defining a value that can be passed on the command line to a command or option. + /// Gets or sets the arity of the argument. /// - public abstract class Argument : Symbol + public ArgumentArity Arity { - private ArgumentArity _arity; - private TryConvertArgument? _convertArguments; - private List>>? _completionSources = null; - private List>? _validators = null; - - private protected Argument(string name) : base(name, allowWhitespace: true) - { - } - - /// - /// Gets or sets the arity of the argument. - /// - public ArgumentArity Arity + get { - get + if (!_arity.IsNonDefault) { - if (!_arity.IsNonDefault) - { - _arity = ArgumentArity.Default(this, FirstParent); - } - - return _arity; + _arity = ArgumentArity.Default(this, FirstParent); } - set => _arity = value; + + return _arity; } + set => _arity = value; + } - /// - /// The name used in help output to describe the argument. - /// - public string? HelpName { get; set; } + /// + /// The name used in help output to describe the argument. + /// + public string? HelpName { get; set; } - internal TryConvertArgument? ConvertArguments - { - get => _convertArguments ??= ArgumentConverter.GetConverter(this); - set => _convertArguments = value; - } + internal TryConvertArgument? ConvertArguments + { + get => _convertArguments ??= ArgumentConverter.GetConverter(this); + set => _convertArguments = value; + } - /// - /// Gets the list of completion sources for the argument. - /// - public List>> CompletionSources + /// + /// Gets the list of completion sources for the argument. + /// + public List>> CompletionSources + { + get { - get + if (_completionSources is null) { - if (_completionSources is null) + Type? valueType = ValueType; + if (IsBoolean()) { - Type? valueType = ValueType; - if (IsBoolean()) - { - _completionSources = new () - { - static _ => new CompletionItem[] - { - new(bool.TrueString), - new(bool.FalseString) - } - }; - } - else if (!valueType.IsPrimitive && (valueType.IsEnum || (valueType.TryGetNullableType(out valueType) && valueType.IsEnum))) + _completionSources = new () { - _completionSources = new() + static _ => new CompletionItem[] { - _ => Enum.GetNames(valueType).Select(n => new CompletionItem(n)) - }; - } - else + new(bool.TrueString), + new(bool.FalseString) + } + }; + } + else if (!valueType.IsPrimitive && (valueType.IsEnum || (valueType.TryGetNullableType(out valueType) && valueType.IsEnum))) + { + _completionSources = new() { - _completionSources = new(); - } + _ => Enum.GetNames(valueType).Select(n => new CompletionItem(n)) + }; + } + else + { + _completionSources = new(); } - - return _completionSources; } + + return _completionSources; } + } - /// - /// Gets or sets the that the argument's parsed tokens will be converted to. - /// - public abstract Type ValueType { get; } + /// + /// Gets or sets the that the argument's parsed tokens will be converted to. + /// + public abstract Type ValueType { get; } - /// - /// Provides a list of argument validators. Validators can be used - /// to provide custom errors based on user input. - /// - public List> Validators => _validators ??= new (); + /// + /// Provides a list of argument validators. Validators can be used + /// to provide custom errors based on user input. + /// + public List> Validators => _validators ??= new (); - internal bool HasValidators => (_validators?.Count ?? 0) > 0; + internal bool HasValidators => (_validators?.Count ?? 0) > 0; - /// - /// Gets the default value for the argument. - /// - /// Returns the default value for the argument, if defined. Null otherwise. - public object? GetDefaultValue() - { - return GetDefaultValue(new ArgumentResult(this, null!, null)); - } + /// + /// Gets the default value for the argument. + /// + /// Returns the default value for the argument, if defined. Null otherwise. + public object? GetDefaultValue() + { + return GetDefaultValue(new ArgumentResult(this, null!, null)); + } - internal abstract object? GetDefaultValue(ArgumentResult argumentResult); + internal abstract object? GetDefaultValue(ArgumentResult argumentResult); - /// - /// Specifies if a default value is defined for the argument. - /// - public abstract bool HasDefaultValue { get; } + /// + /// Specifies if a default value is defined for the argument. + /// + public abstract bool HasDefaultValue { get; } - /// - public override IEnumerable GetCompletions(CompletionContext context) - { - return CompletionSources - .SelectMany(source => source.Invoke(context)) - .Distinct() - .OrderBy(c => c.SortText, StringComparer.OrdinalIgnoreCase); - } + /// + public override IEnumerable GetCompletions(CompletionContext context) + { + return CompletionSources + .SelectMany(source => source.Invoke(context)) + .Distinct() + .OrderBy(c => c.SortText, StringComparer.OrdinalIgnoreCase); + } - /// - public override string ToString() => $"{nameof(Argument)}: {Name}"; + /// + public override string ToString() => $"{nameof(Argument)}: {Name}"; - internal bool IsBoolean() => ValueType == typeof(bool) || ValueType == typeof(bool?); + internal bool IsBoolean() => ValueType == typeof(bool) || ValueType == typeof(bool?); - internal static Argument None { get; } = new NoArgument(); + internal static Argument None { get; } = new NoArgument(); - private sealed class NoArgument : Argument + private sealed class NoArgument : Argument + { + internal NoArgument() : base("@none") { - internal NoArgument() : base("@none") - { - } + } - public override Type ValueType { get; } = typeof(void); + public override Type ValueType { get; } = typeof(void); - internal override object? GetDefaultValue(ArgumentResult argumentResult) => null; + internal override object? GetDefaultValue(ArgumentResult argumentResult) => null; - public override bool HasDefaultValue => false; - } + public override bool HasDefaultValue => false; } -} +} \ No newline at end of file diff --git a/src/System.CommandLine/ArgumentArity.cs b/src/System.CommandLine/ArgumentArity.cs index 2237ec3369..9998af8af2 100644 --- a/src/System.CommandLine/ArgumentArity.cs +++ b/src/System.CommandLine/ArgumentArity.cs @@ -7,161 +7,160 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -namespace System.CommandLine +namespace System.CommandLine; + +/// +/// Defines the arity of an option or argument. +/// +/// The arity refers to the number of values that can be passed on the command line. +/// +[DebuggerDisplay("\\{{" + nameof(MinimumNumberOfValues) + "},{" + nameof(MaximumNumberOfValues) + "}\\}")] +public readonly struct ArgumentArity : IEquatable { + private const int MaximumArity = 100_000; + /// - /// Defines the arity of an option or argument. + /// Initializes a new instance of the ArgumentArity class. /// - /// The arity refers to the number of values that can be passed on the command line. - /// - [DebuggerDisplay("\\{{" + nameof(MinimumNumberOfValues) + "},{" + nameof(MaximumNumberOfValues) + "}\\}")] - public readonly struct ArgumentArity : IEquatable + /// The minimum number of values required for the argument. + /// The maximum number of values allowed for the argument. + /// Thrown when is negative. + /// Thrown when the maximum number is less than the minimum number or the maximum number is greater than MaximumArity. + public ArgumentArity(int minimumNumberOfValues, int maximumNumberOfValues) { - private const int MaximumArity = 100_000; - - /// - /// Initializes a new instance of the ArgumentArity class. - /// - /// The minimum number of values required for the argument. - /// The maximum number of values allowed for the argument. - /// Thrown when is negative. - /// Thrown when the maximum number is less than the minimum number or the maximum number is greater than MaximumArity. - public ArgumentArity(int minimumNumberOfValues, int maximumNumberOfValues) + if (minimumNumberOfValues < 0) { - if (minimumNumberOfValues < 0) - { - throw new ArgumentOutOfRangeException(nameof(minimumNumberOfValues)); - } - - if (maximumNumberOfValues < minimumNumberOfValues) - { - throw new ArgumentException($"{nameof(maximumNumberOfValues)} must be greater than or equal to {nameof(minimumNumberOfValues)}"); - } + throw new ArgumentOutOfRangeException(nameof(minimumNumberOfValues)); + } - if (maximumNumberOfValues > MaximumArity) - { - throw new ArgumentException($"{nameof(maximumNumberOfValues)} must be less than or equal to {nameof(MaximumArity)}"); - } + if (maximumNumberOfValues < minimumNumberOfValues) + { + throw new ArgumentException($"{nameof(maximumNumberOfValues)} must be greater than or equal to {nameof(minimumNumberOfValues)}"); + } - MinimumNumberOfValues = minimumNumberOfValues; - MaximumNumberOfValues = maximumNumberOfValues; - IsNonDefault = true; + if (maximumNumberOfValues > MaximumArity) + { + throw new ArgumentException($"{nameof(maximumNumberOfValues)} must be less than or equal to {nameof(MaximumArity)}"); } - /// - /// Gets the minimum number of values required for an argument. - /// - public int MinimumNumberOfValues { get; } + MinimumNumberOfValues = minimumNumberOfValues; + MaximumNumberOfValues = maximumNumberOfValues; + IsNonDefault = true; + } - /// - /// Gets the maximum number of values allowed for an argument. - /// - public int MaximumNumberOfValues { get; } + /// + /// Gets the minimum number of values required for an argument. + /// + public int MinimumNumberOfValues { get; } - internal bool IsNonDefault { get; } + /// + /// Gets the maximum number of values allowed for an argument. + /// + public int MaximumNumberOfValues { get; } - /// - public bool Equals(ArgumentArity other) => - other.MaximumNumberOfValues == MaximumNumberOfValues && - other.MinimumNumberOfValues == MinimumNumberOfValues && - other.IsNonDefault == IsNonDefault; + internal bool IsNonDefault { get; } - /// - public override bool Equals(object? obj) => obj is ArgumentArity arity && Equals(arity); + /// + public bool Equals(ArgumentArity other) => + other.MaximumNumberOfValues == MaximumNumberOfValues && + other.MinimumNumberOfValues == MinimumNumberOfValues && + other.IsNonDefault == IsNonDefault; - /// - public override int GetHashCode() - => MaximumNumberOfValues ^ MinimumNumberOfValues ^ IsNonDefault.GetHashCode(); + /// + public override bool Equals(object? obj) => obj is ArgumentArity arity && Equals(arity); - internal static bool Validate(ArgumentResult argumentResult, [NotNullWhen(false)] out ArgumentConversionResult? error) - { - error = null; + /// + public override int GetHashCode() + => MaximumNumberOfValues ^ MinimumNumberOfValues ^ IsNonDefault.GetHashCode(); - if (argumentResult.Parent is null or OptionResult { Implicit: true }) - { - return true; - } + internal static bool Validate(ArgumentResult argumentResult, [NotNullWhen(false)] out ArgumentConversionResult? error) + { + error = null; - int tokenCount = argumentResult.Tokens.Count; - if (tokenCount < argumentResult.Argument.Arity.MinimumNumberOfValues) - { - error = ArgumentConversionResult.Failure( - argumentResult, - LocalizationResources.RequiredArgumentMissing(argumentResult), - ArgumentConversionResultType.FailedMissingArgument); + if (argumentResult.Parent is null or OptionResult { Implicit: true }) + { + return true; + } - return false; - } + int tokenCount = argumentResult.Tokens.Count; + if (tokenCount < argumentResult.Argument.Arity.MinimumNumberOfValues) + { + error = ArgumentConversionResult.Failure( + argumentResult, + LocalizationResources.RequiredArgumentMissing(argumentResult), + ArgumentConversionResultType.FailedMissingArgument); + + return false; + } - if (tokenCount > argumentResult.Argument.Arity.MaximumNumberOfValues) + if (tokenCount > argumentResult.Argument.Arity.MaximumNumberOfValues) + { + if (argumentResult.Parent is OptionResult optionResult) { - if (argumentResult.Parent is OptionResult optionResult) + if (!optionResult.Option.AllowMultipleArgumentsPerToken) { - if (!optionResult.Option.AllowMultipleArgumentsPerToken) - { - error = ArgumentConversionResult.Failure( - argumentResult, - LocalizationResources.ExpectsOneArgument(optionResult), - ArgumentConversionResultType.FailedTooManyArguments); - - return false; - } + error = ArgumentConversionResult.Failure( + argumentResult, + LocalizationResources.ExpectsOneArgument(optionResult), + ArgumentConversionResultType.FailedTooManyArguments); + + return false; } } - - return true; } - /// - /// An arity that does not allow any values. - /// - public static ArgumentArity Zero => new(0, 0); + return true; + } - /// - /// An arity that may have one value, but no more than one. - /// - public static ArgumentArity ZeroOrOne => new(0, 1); + /// + /// An arity that does not allow any values. + /// + public static ArgumentArity Zero => new(0, 0); - /// - /// An arity that must have exactly one value. - /// - public static ArgumentArity ExactlyOne => new(1, 1); + /// + /// An arity that may have one value, but no more than one. + /// + public static ArgumentArity ZeroOrOne => new(0, 1); - /// - /// An arity that may have multiple values. - /// - public static ArgumentArity ZeroOrMore => new(0, MaximumArity); + /// + /// An arity that must have exactly one value. + /// + public static ArgumentArity ExactlyOne => new(1, 1); - /// - /// An arity that must have at least one value. - /// - public static ArgumentArity OneOrMore => new(1, MaximumArity); + /// + /// An arity that may have multiple values. + /// + public static ArgumentArity ZeroOrMore => new(0, MaximumArity); - internal static ArgumentArity Default(Argument argument, SymbolNode? firstParent) - { - if (argument.IsBoolean()) - { - return ZeroOrOne; - } + /// + /// An arity that must have at least one value. + /// + public static ArgumentArity OneOrMore => new(1, MaximumArity); - var parent = firstParent?.Symbol; - Type type = argument.ValueType; + internal static ArgumentArity Default(Argument argument, SymbolNode? firstParent) + { + if (argument.IsBoolean()) + { + return ZeroOrOne; + } - if (type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type)) - { - return parent is Command - ? ZeroOrMore - : OneOrMore; - } + var parent = firstParent?.Symbol; + Type type = argument.ValueType; - if (parent is Command && - (argument.HasDefaultValue || - type.IsNullable())) - { - return ZeroOrOne; - } + if (type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type)) + { + return parent is Command + ? ZeroOrMore + : OneOrMore; + } - return ExactlyOne; + if (parent is Command && + (argument.HasDefaultValue || + type.IsNullable())) + { + return ZeroOrOne; } + + return ExactlyOne; } -} +} \ No newline at end of file diff --git a/src/System.CommandLine/ArgumentValidation.cs b/src/System.CommandLine/ArgumentValidation.cs index 80a94a7bb5..80875dd744 100644 --- a/src/System.CommandLine/ArgumentValidation.cs +++ b/src/System.CommandLine/ArgumentValidation.cs @@ -5,190 +5,189 @@ using System.CommandLine.Parsing; using System.IO; -namespace System.CommandLine +namespace System.CommandLine; + +/// +/// Provides extension methods for . +/// +public static class ArgumentValidation { /// - /// Provides extension methods for . + /// Configures an argument to accept only values corresponding to an existing file. + /// + /// The argument to configure. + /// The configured argument. + public static Argument AcceptExistingOnly(this Argument argument) + { + argument.Validators.Add(FileOrDirectoryExists); + return argument; + } + + /// + /// Configures an argument to accept only values corresponding to an existing directory. + /// + /// The argument to configure. + /// The configured argument. + public static Argument AcceptExistingOnly(this Argument argument) + { + argument.Validators.Add(FileOrDirectoryExists); + return argument; + } + + /// + /// Configures an argument to accept only values corresponding to an existing file or directory. + /// + /// The argument to configure. + /// The configured argument. + public static Argument AcceptExistingOnly(this Argument argument) + { + argument.Validators.Add(FileOrDirectoryExists); + return argument; + } + + /// + /// Configures an argument to accept only values corresponding to a existing files or directories. /// - public static class ArgumentValidation + /// The argument to configure. + /// The configured argument. + public static Argument AcceptExistingOnly(this Argument argument) + where T : IEnumerable { - /// - /// Configures an argument to accept only values corresponding to an existing file. - /// - /// The argument to configure. - /// The configured argument. - public static Argument AcceptExistingOnly(this Argument argument) + if (typeof(IEnumerable).IsAssignableFrom(typeof(T))) { argument.Validators.Add(FileOrDirectoryExists); - return argument; } - - /// - /// Configures an argument to accept only values corresponding to an existing directory. - /// - /// The argument to configure. - /// The configured argument. - public static Argument AcceptExistingOnly(this Argument argument) + else if (typeof(IEnumerable).IsAssignableFrom(typeof(T))) { argument.Validators.Add(FileOrDirectoryExists); - return argument; } - - /// - /// Configures an argument to accept only values corresponding to an existing file or directory. - /// - /// The argument to configure. - /// The configured argument. - public static Argument AcceptExistingOnly(this Argument argument) + else { argument.Validators.Add(FileOrDirectoryExists); - return argument; } - /// - /// Configures an argument to accept only values corresponding to a existing files or directories. - /// - /// The argument to configure. - /// The configured argument. - public static Argument AcceptExistingOnly(this Argument argument) - where T : IEnumerable - { - if (typeof(IEnumerable).IsAssignableFrom(typeof(T))) - { - argument.Validators.Add(FileOrDirectoryExists); - } - else if (typeof(IEnumerable).IsAssignableFrom(typeof(T))) - { - argument.Validators.Add(FileOrDirectoryExists); - } - else - { - argument.Validators.Add(FileOrDirectoryExists); - } - - return argument; - } + return argument; + } - /// - /// Configures the argument to accept only values representing legal file names. - /// - /// A parse error will result, for example, if file path separators are found in the parsed value. - public static Argument AcceptLegalFileNamesOnly(this Argument argument) + /// + /// Configures the argument to accept only values representing legal file names. + /// + /// A parse error will result, for example, if file path separators are found in the parsed value. + public static Argument AcceptLegalFileNamesOnly(this Argument argument) + { + argument.Validators.Add(static result => { - argument.Validators.Add(static result => + var invalidFileNameChars = Path.GetInvalidFileNameChars(); + + for (var i = 0; i < result.Tokens.Count; i++) { - var invalidFileNameChars = Path.GetInvalidFileNameChars(); + var token = result.Tokens[i]; + var invalidCharactersIndex = token.Value.IndexOfAny(invalidFileNameChars); - for (var i = 0; i < result.Tokens.Count; i++) + if (invalidCharactersIndex >= 0) { - var token = result.Tokens[i]; - var invalidCharactersIndex = token.Value.IndexOfAny(invalidFileNameChars); - - if (invalidCharactersIndex >= 0) - { - result.AddError(LocalizationResources.InvalidCharactersInFileName(token.Value[invalidCharactersIndex])); - } + result.AddError(LocalizationResources.InvalidCharactersInFileName(token.Value[invalidCharactersIndex])); } - }); + } + }); - return argument; - } + return argument; + } - /// - /// Configures the argument to accept only values representing legal file paths. - /// - public static Argument AcceptLegalFilePathsOnly(this Argument argument) + /// + /// Configures the argument to accept only values representing legal file paths. + /// + public static Argument AcceptLegalFilePathsOnly(this Argument argument) + { + argument.Validators.Add(static result => { - argument.Validators.Add(static result => - { - var invalidPathChars = Path.GetInvalidPathChars(); + var invalidPathChars = Path.GetInvalidPathChars(); - for (var i = 0; i < result.Tokens.Count; i++) - { - var token = result.Tokens[i]; + for (var i = 0; i < result.Tokens.Count; i++) + { + var token = result.Tokens[i]; - // File class no longer check invalid character - // https://blogs.msdn.microsoft.com/jeremykuhne/2018/03/09/custom-directory-enumeration-in-net-core-2-1/ - var invalidCharactersIndex = token.Value.IndexOfAny(invalidPathChars); + // File class no longer check invalid character + // https://blogs.msdn.microsoft.com/jeremykuhne/2018/03/09/custom-directory-enumeration-in-net-core-2-1/ + var invalidCharactersIndex = token.Value.IndexOfAny(invalidPathChars); - if (invalidCharactersIndex >= 0) - { - result.AddError(LocalizationResources.InvalidCharactersInPath(token.Value[invalidCharactersIndex])); - } + if (invalidCharactersIndex >= 0) + { + result.AddError(LocalizationResources.InvalidCharactersInPath(token.Value[invalidCharactersIndex])); } - }); + } + }); - return argument; - } + return argument; + } - /// - /// Configures the argument to accept only the specified values, and to suggest them as command line completions. - /// - /// The argument to configure. - /// The values that are allowed for the argument. - public static Argument AcceptOnlyFromAmong( - this Argument argument, - params string[] values) + /// + /// Configures the argument to accept only the specified values, and to suggest them as command line completions. + /// + /// The argument to configure. + /// The values that are allowed for the argument. + public static Argument AcceptOnlyFromAmong( + this Argument argument, + params string[] values) + { + if (values is not null && values.Length > 0) { - if (values is not null && values.Length > 0) - { - argument.Validators.Clear(); - argument.Validators.Add(UnrecognizedArgumentError); - argument.CompletionSources.Clear(); - argument.CompletionSources.Add(values); - } + argument.Validators.Clear(); + argument.Validators.Add(UnrecognizedArgumentError); + argument.CompletionSources.Clear(); + argument.CompletionSources.Add(values); + } - return argument; + return argument; - void UnrecognizedArgumentError(ArgumentResult argumentResult) + void UnrecognizedArgumentError(ArgumentResult argumentResult) + { + for (var i = 0; i < argumentResult.Tokens.Count; i++) { - for (var i = 0; i < argumentResult.Tokens.Count; i++) - { - var token = argumentResult.Tokens[i]; + var token = argumentResult.Tokens[i]; - if (token.Symbol is null || token.Symbol == argument) + if (token.Symbol is null || token.Symbol == argument) + { + if (Array.IndexOf(values, token.Value) < 0) { - if (Array.IndexOf(values, token.Value) < 0) - { - argumentResult.AddError(LocalizationResources.UnrecognizedArgument(token.Value, values)); - } + argumentResult.AddError(LocalizationResources.UnrecognizedArgument(token.Value, values)); } } } } + } + + private static void FileOrDirectoryExists(ArgumentResult result) + where T : FileSystemInfo + { + // both FileInfo and DirectoryInfo are sealed so following checks are enough + bool checkFile = typeof(T) != typeof(DirectoryInfo); + bool checkDirectory = typeof(T) != typeof(FileInfo); - private static void FileOrDirectoryExists(ArgumentResult result) - where T : FileSystemInfo + for (var i = 0; i < result.Tokens.Count; i++) { - // both FileInfo and DirectoryInfo are sealed so following checks are enough - bool checkFile = typeof(T) != typeof(DirectoryInfo); - bool checkDirectory = typeof(T) != typeof(FileInfo); + var token = result.Tokens[i]; - for (var i = 0; i < result.Tokens.Count; i++) + if (checkFile && checkDirectory) { - var token = result.Tokens[i]; - - if (checkFile && checkDirectory) - { #if NET7_0_OR_GREATER - if (!Path.Exists(token.Value)) + if (!Path.Exists(token.Value)) #else if (!Directory.Exists(token.Value) && !File.Exists(token.Value)) #endif - { - result.AddError(LocalizationResources.FileOrDirectoryDoesNotExist(token.Value)); - } - } - else if (checkDirectory && !Directory.Exists(token.Value)) - { - result.AddError(LocalizationResources.DirectoryDoesNotExist(token.Value)); - } - else if (checkFile && !Directory.Exists(token.Value) && !File.Exists(token.Value)) { - result.AddError(LocalizationResources.FileDoesNotExist(token.Value)); + result.AddError(LocalizationResources.FileOrDirectoryDoesNotExist(token.Value)); } } + else if (checkDirectory && !Directory.Exists(token.Value)) + { + result.AddError(LocalizationResources.DirectoryDoesNotExist(token.Value)); + } + else if (checkFile && !Directory.Exists(token.Value) && !File.Exists(token.Value)) + { + result.AddError(LocalizationResources.FileDoesNotExist(token.Value)); + } } } -} +} \ No newline at end of file diff --git a/src/System.CommandLine/Argument{T}.cs b/src/System.CommandLine/Argument{T}.cs index 1100b70c13..20f3955949 100644 --- a/src/System.CommandLine/Argument{T}.cs +++ b/src/System.CommandLine/Argument{T}.cs @@ -4,132 +4,131 @@ using System.Collections.Generic; using System.CommandLine.Parsing; using System.Diagnostics.CodeAnalysis; -namespace System.CommandLine +namespace System.CommandLine; + +/// +public class Argument : Argument { - /// - public class Argument : Argument - { - private Func? _customParser; - private Func? _defaultValueFactory; + private Func? _customParser; + private Func? _defaultValueFactory; - /// - /// Initializes a new instance of the Argument class. - /// - /// The name of the argument. It's not used for parsing, only when displaying Help or creating parse errors.> - public Argument(string name) : base(name) - { - } + /// + /// Initializes a new instance of the Argument class. + /// + /// The name of the argument. It's not used for parsing, only when displaying Help or creating parse errors.> + public Argument(string name) : base(name) + { + } - /// - /// The delegate to invoke to create the default value. - /// - /// - /// It's invoked when there was no parse input provided for given Argument. - /// The same instance can be set as , in such case - /// the delegate is also invoked when an input was provided. - /// - public Func? DefaultValueFactory + /// + /// The delegate to invoke to create the default value. + /// + /// + /// It's invoked when there was no parse input provided for given Argument. + /// The same instance can be set as , in such case + /// the delegate is also invoked when an input was provided. + /// + public Func? DefaultValueFactory + { + get { - get + if (_defaultValueFactory is null) { - if (_defaultValueFactory is null) + if (this is Argument boolArgument) { - if (this is Argument boolArgument) - { - boolArgument.DefaultValueFactory = _ => false; - } + boolArgument.DefaultValueFactory = _ => false; } - return _defaultValueFactory; } - set => _defaultValueFactory = value; + return _defaultValueFactory; } + set => _defaultValueFactory = value; + } - /// - /// A custom argument parser. - /// - /// - /// It's invoked when there was parse input provided for given Argument. - /// The same instance can be set as , in such case - /// the delegate is also invoked when no input was provided. - /// - public Func? CustomParser + /// + /// A custom argument parser. + /// + /// + /// It's invoked when there was parse input provided for given Argument. + /// The same instance can be set as , in such case + /// the delegate is also invoked when no input was provided. + /// + public Func? CustomParser + { + get => _customParser; + set { - get => _customParser; - set - { - _customParser = value; + _customParser = value; - if (value is not null) + if (value is not null) + { + ConvertArguments = (ArgumentResult argumentResult, out object? parsedValue) => { - ConvertArguments = (ArgumentResult argumentResult, out object? parsedValue) => - { - int errorsBefore = argumentResult.SymbolResultTree.ErrorCount; - var result = value(argumentResult); + int errorsBefore = argumentResult.SymbolResultTree.ErrorCount; + var result = value(argumentResult); - if (errorsBefore == argumentResult.SymbolResultTree.ErrorCount) - { - parsedValue = result; - return true; - } - else - { - parsedValue = default(T)!; - return false; - } - }; - } + if (errorsBefore == argumentResult.SymbolResultTree.ErrorCount) + { + parsedValue = result; + return true; + } + else + { + parsedValue = default(T)!; + return false; + } + }; } } + } - /// - public override Type ValueType => typeof(T); + /// + public override Type ValueType => typeof(T); - /// - public override bool HasDefaultValue => DefaultValueFactory is not null; + /// + public override bool HasDefaultValue => DefaultValueFactory is not null; - internal override object? GetDefaultValue(ArgumentResult argumentResult) + internal override object? GetDefaultValue(ArgumentResult argumentResult) + { + if (DefaultValueFactory is null) { - if (DefaultValueFactory is null) - { - throw new InvalidOperationException($"Argument \"{Name}\" does not have a default value"); - } - - return DefaultValueFactory.Invoke(argumentResult); + throw new InvalidOperationException($"Argument \"{Name}\" does not have a default value"); } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050", Justification = "https://github.com/dotnet/command-line-api/issues/1638")] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2091", Justification = "https://github.com/dotnet/command-line-api/issues/1638")] - internal static T? CreateDefaultValue() + return DefaultValueFactory.Invoke(argumentResult); + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050", Justification = "https://github.com/dotnet/command-line-api/issues/1638")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2091", Justification = "https://github.com/dotnet/command-line-api/issues/1638")] + internal static T? CreateDefaultValue() + { + if (default(T) is null && typeof(T) != typeof(string)) { - if (default(T) is null && typeof(T) != typeof(string)) - { #if NET7_0_OR_GREATER - if (typeof(T).IsSZArray) + if (typeof(T).IsSZArray) #else if (typeof(T).IsArray && typeof(T).GetArrayRank() == 1) #endif + { + return (T?)(object)Array.CreateInstance(typeof(T).GetElementType()!, 0); + } + else if (typeof(T).IsConstructedGenericType) + { + var genericTypeDefinition = typeof(T).GetGenericTypeDefinition(); + + if (genericTypeDefinition == typeof(IEnumerable<>) || + genericTypeDefinition == typeof(IList<>) || + genericTypeDefinition == typeof(ICollection<>)) { - return (T?)(object)Array.CreateInstance(typeof(T).GetElementType()!, 0); + return (T?)(object)Array.CreateInstance(typeof(T).GenericTypeArguments[0], 0); } - else if (typeof(T).IsConstructedGenericType) - { - var genericTypeDefinition = typeof(T).GetGenericTypeDefinition(); - - if (genericTypeDefinition == typeof(IEnumerable<>) || - genericTypeDefinition == typeof(IList<>) || - genericTypeDefinition == typeof(ICollection<>)) - { - return (T?)(object)Array.CreateInstance(typeof(T).GenericTypeArguments[0], 0); - } - if (genericTypeDefinition == typeof(List<>)) - { - return Activator.CreateInstance(); - } + if (genericTypeDefinition == typeof(List<>)) + { + return Activator.CreateInstance(); } } - - return default; } + + return default; } -} +} \ No newline at end of file diff --git a/src/System.CommandLine/Binding/ArgumentConversionResult.cs b/src/System.CommandLine/Binding/ArgumentConversionResult.cs index 03bd2f85f0..09568711bc 100644 --- a/src/System.CommandLine/Binding/ArgumentConversionResult.cs +++ b/src/System.CommandLine/Binding/ArgumentConversionResult.cs @@ -5,79 +5,78 @@ using System.CommandLine.Parsing; using System.Linq; -namespace System.CommandLine.Binding +namespace System.CommandLine.Binding; + +internal sealed class ArgumentConversionResult { - internal sealed class ArgumentConversionResult - { - internal readonly ArgumentResult ArgumentResult; - internal readonly object? Value; - internal readonly string? ErrorMessage; - internal ArgumentConversionResultType Result; + internal readonly ArgumentResult ArgumentResult; + internal readonly object? Value; + internal readonly string? ErrorMessage; + internal ArgumentConversionResultType Result; - private ArgumentConversionResult(ArgumentResult argumentResult, string error, ArgumentConversionResultType failure) - { - ArgumentResult = argumentResult; - ErrorMessage = error; - Result = failure; - } + private ArgumentConversionResult(ArgumentResult argumentResult, string error, ArgumentConversionResultType failure) + { + ArgumentResult = argumentResult; + ErrorMessage = error; + Result = failure; + } - private ArgumentConversionResult(ArgumentResult argumentResult, object? value, ArgumentConversionResultType result) - { - ArgumentResult = argumentResult; - Value = value; - Result = result; - } + private ArgumentConversionResult(ArgumentResult argumentResult, object? value, ArgumentConversionResultType result) + { + ArgumentResult = argumentResult; + Value = value; + Result = result; + } - internal static ArgumentConversionResult Failure(ArgumentResult argumentResult, string error, ArgumentConversionResultType reason) - => new(argumentResult, error, reason); + internal static ArgumentConversionResult Failure(ArgumentResult argumentResult, string error, ArgumentConversionResultType reason) + => new(argumentResult, error, reason); - internal static ArgumentConversionResult ArgumentConversionCannotParse(ArgumentResult argumentResult, Type expectedType, string value) - => new(argumentResult, FormatErrorMessage(argumentResult, expectedType, value), ArgumentConversionResultType.FailedType); + internal static ArgumentConversionResult ArgumentConversionCannotParse(ArgumentResult argumentResult, Type expectedType, string value) + => new(argumentResult, FormatErrorMessage(argumentResult, expectedType, value), ArgumentConversionResultType.FailedType); - public static ArgumentConversionResult Success(ArgumentResult argumentResult, object? value) - => new(argumentResult, value, ArgumentConversionResultType.Successful); + public static ArgumentConversionResult Success(ArgumentResult argumentResult, object? value) + => new(argumentResult, value, ArgumentConversionResultType.Successful); - internal static ArgumentConversionResult None(ArgumentResult argumentResult) - => new(argumentResult, value: null, ArgumentConversionResultType.NoArgument); + internal static ArgumentConversionResult None(ArgumentResult argumentResult) + => new(argumentResult, value: null, ArgumentConversionResultType.NoArgument); - private static string FormatErrorMessage( - ArgumentResult argumentResult, - Type expectedType, - string value) + private static string FormatErrorMessage( + ArgumentResult argumentResult, + Type expectedType, + string value) + { + if (argumentResult.Parent is CommandResult commandResult) { - if (argumentResult.Parent is CommandResult commandResult) - { - string alias = commandResult.Command.Name; - CompletionItem[] completionItems = argumentResult.Argument.GetCompletions(CompletionContext.Empty).ToArray(); + string alias = commandResult.Command.Name; + CompletionItem[] completionItems = argumentResult.Argument.GetCompletions(CompletionContext.Empty).ToArray(); - if (completionItems.Length > 0) - { - return LocalizationResources.ArgumentConversionCannotParseForCommand( - value, alias, expectedType, completionItems.Select(ci => ci.Label)); - } - else - { - return LocalizationResources.ArgumentConversionCannotParseForCommand(value, alias, expectedType); - } + if (completionItems.Length > 0) + { + return LocalizationResources.ArgumentConversionCannotParseForCommand( + value, alias, expectedType, completionItems.Select(ci => ci.Label)); } - else if (argumentResult.Parent is OptionResult optionResult) + else { - string alias = optionResult.Option.Name; - CompletionItem[] completionItems = optionResult.Option.GetCompletions(CompletionContext.Empty).ToArray(); - - if (completionItems.Length > 0) - { - return LocalizationResources.ArgumentConversionCannotParseForOption( - value, alias, expectedType, completionItems.Select(ci => ci.Label)); - } - else - { - return LocalizationResources.ArgumentConversionCannotParseForOption(value, alias, expectedType); - } + return LocalizationResources.ArgumentConversionCannotParseForCommand(value, alias, expectedType); } + } + else if (argumentResult.Parent is OptionResult optionResult) + { + string alias = optionResult.Option.Name; + CompletionItem[] completionItems = optionResult.Option.GetCompletions(CompletionContext.Empty).ToArray(); - // fake ArgumentResults with no Parent - return LocalizationResources.ArgumentConversionCannotParse(value, expectedType); + if (completionItems.Length > 0) + { + return LocalizationResources.ArgumentConversionCannotParseForOption( + value, alias, expectedType, completionItems.Select(ci => ci.Label)); + } + else + { + return LocalizationResources.ArgumentConversionCannotParseForOption(value, alias, expectedType); + } } + + // fake ArgumentResults with no Parent + return LocalizationResources.ArgumentConversionCannotParse(value, expectedType); } } \ No newline at end of file diff --git a/src/System.CommandLine/Binding/ArgumentConversionResultType.cs b/src/System.CommandLine/Binding/ArgumentConversionResultType.cs index 8915793850..d7da0f7886 100644 --- a/src/System.CommandLine/Binding/ArgumentConversionResultType.cs +++ b/src/System.CommandLine/Binding/ArgumentConversionResultType.cs @@ -1,16 +1,15 @@ // 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. -namespace System.CommandLine.Binding +namespace System.CommandLine.Binding; + +internal enum ArgumentConversionResultType { - internal enum ArgumentConversionResultType - { - NoArgument, // NoArgumentConversionResult - Successful, // SuccessfulArgumentConversionResult - Failed, // FailedArgumentConversionResult - FailedArity, // FailedArgumentConversionArityResult - FailedType, // FailedArgumentTypeConversionResult - FailedTooManyArguments, // TooManyArgumentsConversionResult - FailedMissingArgument, // MissingArgumentConversionResult - } + NoArgument, // NoArgumentConversionResult + Successful, // SuccessfulArgumentConversionResult + Failed, // FailedArgumentConversionResult + FailedArity, // FailedArgumentConversionArityResult + FailedType, // FailedArgumentTypeConversionResult + FailedTooManyArguments, // TooManyArgumentsConversionResult + FailedMissingArgument, // MissingArgumentConversionResult } \ No newline at end of file diff --git a/src/System.CommandLine/Binding/ArgumentConverter.cs b/src/System.CommandLine/Binding/ArgumentConverter.cs index 6f494bdd08..15295fda1a 100644 --- a/src/System.CommandLine/Binding/ArgumentConverter.cs +++ b/src/System.CommandLine/Binding/ArgumentConverter.cs @@ -5,67 +5,67 @@ using System.CommandLine.Parsing; using static System.CommandLine.Binding.ArgumentConversionResult; -namespace System.CommandLine.Binding +namespace System.CommandLine.Binding; + +internal static partial class ArgumentConverter { - internal static partial class ArgumentConverter + internal static ArgumentConversionResult ConvertObject( + ArgumentResult argumentResult, + Type type, + object? value) { - internal static ArgumentConversionResult ConvertObject( - ArgumentResult argumentResult, - Type type, - object? value) + switch (value) { - switch (value) - { - case Token singleValue: - return ConvertToken(argumentResult, type, singleValue); + case Token singleValue: + return ConvertToken(argumentResult, type, singleValue); - case IReadOnlyList manyValues: - return ConvertTokens(argumentResult, type, manyValues); + case IReadOnlyList manyValues: + return ConvertTokens(argumentResult, type, manyValues); - default: + default: - if (argumentResult.Tokens.Count == 0) - { - return None(argumentResult); - } - else - { - throw new InvalidCastException(); - } - } + if (argumentResult.Tokens.Count == 0) + { + return None(argumentResult); + } + else + { + throw new InvalidCastException(); + } } + } + + private static ArgumentConversionResult ConvertToken( + ArgumentResult argumentResult, + Type type, + Token token) + { + var value = token.Value; - private static ArgumentConversionResult ConvertToken( - ArgumentResult argumentResult, - Type type, - Token token) + if (type.TryGetNullableType(out var nullableType)) { - var value = token.Value; + return ConvertToken(argumentResult, nullableType, token); + } - if (type.TryGetNullableType(out var nullableType)) + if (StringConverters.TryGetValue(type, out var tryConvert)) + { + if (tryConvert(value, out var converted)) { - return ConvertToken(argumentResult, nullableType, token); + return Success(argumentResult, converted); } - - if (StringConverters.TryGetValue(type, out var tryConvert)) + else { - if (tryConvert(value, out var converted)) - { - return Success(argumentResult, converted); - } - else - { - return ArgumentConversionCannotParse(argumentResult, type, value); - } + return ArgumentConversionCannotParse(argumentResult, type, value); } + } - if (type.IsEnum) - { + if (type.IsEnum) + { #if NET7_0_OR_GREATER - if (Enum.TryParse(type, value, ignoreCase: true, out var converted)) - { - return Success(argumentResult, converted); - } + if (Enum.TryParse(type, value, ignoreCase: true, out var converted)) + { + return Success(argumentResult, converted); + } #else try { @@ -75,161 +75,160 @@ private static ArgumentConversionResult ConvertToken( { } #endif - } - - return ArgumentConversionCannotParse(argumentResult, type, value); } - private static ArgumentConversionResult ConvertTokens( - ArgumentResult argumentResult, - Type type, - IReadOnlyList tokens) + return ArgumentConversionCannotParse(argumentResult, type, value); + } + + private static ArgumentConversionResult ConvertTokens( + ArgumentResult argumentResult, + Type type, + IReadOnlyList tokens) + { + var itemType = type.GetElementTypeIfEnumerable() ?? typeof(string); + var values = CreateEnumerable(type, itemType, tokens.Count); + var isArray = values is Array; + + for (var i = 0; i < tokens.Count; i++) { - var itemType = type.GetElementTypeIfEnumerable() ?? typeof(string); - var values = CreateEnumerable(type, itemType, tokens.Count); - var isArray = values is Array; + var token = tokens[i]; + + var result = ConvertToken(argumentResult, itemType, token); - for (var i = 0; i < tokens.Count; i++) + switch (result.Result) { - var token = tokens[i]; + case ArgumentConversionResultType.Successful: + if (isArray) + { + values[i] = result.Value; + } + else + { + values.Add(result.Value); + } - var result = ConvertToken(argumentResult, itemType, token); + break; - switch (result.Result) - { - case ArgumentConversionResultType.Successful: - if (isArray) - { - values[i] = result.Value; - } - else - { - values.Add(result.Value); - } + default: // failures + if (argumentResult.Parent is CommandResult) + { + argumentResult.OnlyTake(i); + i = tokens.Count; break; + } - default: // failures - if (argumentResult.Parent is CommandResult) - { - argumentResult.OnlyTake(i); - - i = tokens.Count; - break; - } - - return result; - } + return result; } - - return Success(argumentResult, values); } - internal static TryConvertArgument? GetConverter(Argument argument) + return Success(argumentResult, values); + } + + internal static TryConvertArgument? GetConverter(Argument argument) + { + if (argument.Arity is { MaximumNumberOfValues: 1, MinimumNumberOfValues: 1 }) { - if (argument.Arity is { MaximumNumberOfValues: 1, MinimumNumberOfValues: 1 }) + if (argument.ValueType.TryGetNullableType(out var nullableType) && + StringConverters.TryGetValue(nullableType, out var convertNullable)) { - if (argument.ValueType.TryGetNullableType(out var nullableType) && - StringConverters.TryGetValue(nullableType, out var convertNullable)) - { - return (ArgumentResult result, out object? value) => ConvertSingleString(result, convertNullable, out value); - } - - if (StringConverters.TryGetValue(argument.ValueType, out var convert1)) - { - return (ArgumentResult result, out object? value) => ConvertSingleString(result, convert1, out value); - } - - static bool ConvertSingleString(ArgumentResult result, TryConvertString convert, out object? value) => - convert(result.Tokens[result.Tokens.Count - 1].Value, out value); + return (ArgumentResult result, out object? value) => ConvertSingleString(result, convertNullable, out value); } - if (argument.ValueType.CanBeBoundFromScalarValue()) + if (StringConverters.TryGetValue(argument.ValueType, out var convert1)) { - return TryConvertArgument; + return (ArgumentResult result, out object? value) => ConvertSingleString(result, convert1, out value); } - return default; + static bool ConvertSingleString(ArgumentResult result, TryConvertString convert, out object? value) => + convert(result.Tokens[result.Tokens.Count - 1].Value, out value); } - private static bool CanBeBoundFromScalarValue(this Type type) + if (argument.ValueType.CanBeBoundFromScalarValue()) { - while (true) - { - if (type.IsPrimitive || type.IsEnum) - { - return true; - } - - if (type == typeof(string)) - { - return true; - } + return TryConvertArgument; + } - if (type.GetElementTypeIfEnumerable() is { } itemType) - { - type = itemType; - continue; - } + return default; + } - if (type.TryGetNullableType(out Type? nullableType)) - { - type = nullableType; - continue; - } + private static bool CanBeBoundFromScalarValue(this Type type) + { + while (true) + { + if (type.IsPrimitive || type.IsEnum) + { + return true; + } - return false; + if (type == typeof(string)) + { + return true; } - } - internal static ArgumentConversionResult ConvertIfNeeded( - this ArgumentConversionResult conversionResult, - Type toType) - { - return conversionResult.Result switch + if (type.GetElementTypeIfEnumerable() is { } itemType) { - ArgumentConversionResultType.Successful when !toType.IsInstanceOfType(conversionResult.Value) => - ConvertObject(conversionResult.ArgumentResult, - toType, - conversionResult.Value), + type = itemType; + continue; + } - ArgumentConversionResultType.NoArgument when conversionResult.ArgumentResult.Argument.IsBoolean() => - Success(conversionResult.ArgumentResult, true), + if (type.TryGetNullableType(out Type? nullableType)) + { + type = nullableType; + continue; + } - _ => conversionResult - }; + return false; } + } - internal static T GetValueOrDefault(this ArgumentConversionResult result) + internal static ArgumentConversionResult ConvertIfNeeded( + this ArgumentConversionResult conversionResult, + Type toType) + { + return conversionResult.Result switch { - return result.Result switch - { - ArgumentConversionResultType.Successful => (T)result.Value!, - ArgumentConversionResultType.NoArgument => default!, - _ => throw new InvalidOperationException(result.ErrorMessage), - }; - } + ArgumentConversionResultType.Successful when !toType.IsInstanceOfType(conversionResult.Value) => + ConvertObject(conversionResult.ArgumentResult, + toType, + conversionResult.Value), + + ArgumentConversionResultType.NoArgument when conversionResult.ArgumentResult.Argument.IsBoolean() => + Success(conversionResult.ArgumentResult, true), + + _ => conversionResult + }; + } - public static bool TryConvertArgument(ArgumentResult argumentResult, out object? value) + internal static T GetValueOrDefault(this ArgumentConversionResult result) + { + return result.Result switch { - var argument = argumentResult.Argument; + ArgumentConversionResultType.Successful => (T)result.Value!, + ArgumentConversionResultType.NoArgument => default!, + _ => throw new InvalidOperationException(result.ErrorMessage), + }; + } - ArgumentConversionResult result = argument.Arity.MaximumNumberOfValues switch - { - // 0 is an implicit bool, i.e. a "flag" - 0 => Success(argumentResult, true), - 1 => ConvertObject(argumentResult, - argument.ValueType, - argumentResult.Tokens.Count > 0 - ? argumentResult.Tokens[argumentResult.Tokens.Count - 1] - : null), - _ => ConvertTokens(argumentResult, - argument.ValueType, - argumentResult.Tokens) - }; - - value = result; - return result.Result == ArgumentConversionResultType.Successful; - } + public static bool TryConvertArgument(ArgumentResult argumentResult, out object? value) + { + var argument = argumentResult.Argument; + + ArgumentConversionResult result = argument.Arity.MaximumNumberOfValues switch + { + // 0 is an implicit bool, i.e. a "flag" + 0 => Success(argumentResult, true), + 1 => ConvertObject(argumentResult, + argument.ValueType, + argumentResult.Tokens.Count > 0 + ? argumentResult.Tokens[argumentResult.Tokens.Count - 1] + : null), + _ => ConvertTokens(argumentResult, + argument.ValueType, + argumentResult.Tokens) + }; + + value = result; + return result.Result == ArgumentConversionResultType.Successful; } } \ No newline at end of file diff --git a/src/System.CommandLine/Binding/TryConvertArgument.cs b/src/System.CommandLine/Binding/TryConvertArgument.cs index 44d777e939..3bd19429e9 100644 --- a/src/System.CommandLine/Binding/TryConvertArgument.cs +++ b/src/System.CommandLine/Binding/TryConvertArgument.cs @@ -3,9 +3,8 @@ using System.CommandLine.Parsing; -namespace System.CommandLine.Binding -{ - internal delegate bool TryConvertArgument( - ArgumentResult argumentResult, - out object? value); -} \ No newline at end of file +namespace System.CommandLine.Binding; + +internal delegate bool TryConvertArgument( + ArgumentResult argumentResult, + out object? value); \ No newline at end of file diff --git a/src/System.CommandLine/Binding/TypeExtensions.cs b/src/System.CommandLine/Binding/TypeExtensions.cs index b0b680a698..80eb24a027 100644 --- a/src/System.CommandLine/Binding/TypeExtensions.cs +++ b/src/System.CommandLine/Binding/TypeExtensions.cs @@ -4,57 +4,56 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; -namespace System.CommandLine.Binding +namespace System.CommandLine.Binding; + +internal static class TypeExtensions { - internal static class TypeExtensions + internal static Type? GetElementTypeIfEnumerable(this Type type) { - internal static Type? GetElementTypeIfEnumerable(this Type type) + if (type.IsArray) { - if (type.IsArray) - { - return type.GetElementType(); - } - - if (type == typeof(string)) - { - return null; - } - - Type? enumerableInterface = null; - - if (type.IsEnumerable()) - { - enumerableInterface = type; - } - - return enumerableInterface?.GenericTypeArguments switch - { - { Length: 1 } genericTypeArguments => genericTypeArguments[0], - _ => null - }; + return type.GetElementType(); } - internal static bool IsEnumerable(this Type type) + if (type == typeof(string)) { - if (type == typeof(string)) - { - return false; - } - - return - type.IsArray - || - typeof(IEnumerable).IsAssignableFrom(type); + return null; } - internal static bool IsNullable(this Type t) => Nullable.GetUnderlyingType(t) is not null; + Type? enumerableInterface = null; - internal static bool TryGetNullableType( - this Type type, - [NotNullWhen(true)] out Type? nullableType) + if (type.IsEnumerable()) { - nullableType = Nullable.GetUnderlyingType(type); - return nullableType is not null; + enumerableInterface = type; } + + return enumerableInterface?.GenericTypeArguments switch + { + { Length: 1 } genericTypeArguments => genericTypeArguments[0], + _ => null + }; + } + + internal static bool IsEnumerable(this Type type) + { + if (type == typeof(string)) + { + return false; + } + + return + type.IsArray + || + typeof(IEnumerable).IsAssignableFrom(type); + } + + internal static bool IsNullable(this Type t) => Nullable.GetUnderlyingType(t) is not null; + + internal static bool TryGetNullableType( + this Type type, + [NotNullWhen(true)] out Type? nullableType) + { + nullableType = Nullable.GetUnderlyingType(type); + return nullableType is not null; } } \ No newline at end of file diff --git a/src/System.CommandLine/ChildSymbolList{T}.cs b/src/System.CommandLine/ChildSymbolList{T}.cs index a68c402090..157d2f23c9 100644 --- a/src/System.CommandLine/ChildSymbolList{T}.cs +++ b/src/System.CommandLine/ChildSymbolList{T}.cs @@ -4,62 +4,61 @@ using System.Collections; using System.Collections.Generic; -namespace System.CommandLine +namespace System.CommandLine; + +/// +/// a wrapper of List that sets parent for every added element +/// +internal sealed class ChildSymbolList : IList where T : Symbol { - /// - /// a wrapper of List that sets parent for every added element - /// - internal sealed class ChildSymbolList : IList where T : Symbol - { - private readonly List _children; - private readonly Command _parent; + private readonly List _children; + private readonly Command _parent; - public ChildSymbolList(Command parent) - { - _parent = parent; - _children = new(); - } + public ChildSymbolList(Command parent) + { + _parent = parent; + _children = new(); + } - public T this[int index] + public T this[int index] + { + get => _children[index]; + set { - get => _children[index]; - set - { - _children[index] = value; - value.AddParent(_parent); - } + _children[index] = value; + value.AddParent(_parent); } + } - public int Count => _children.Count; + public int Count => _children.Count; - public bool IsReadOnly => false; + public bool IsReadOnly => false; - public void Add(T item) - { - item.AddParent(_parent); - _children.Add(item); - } + public void Add(T item) + { + item.AddParent(_parent); + _children.Add(item); + } - public void Clear() => _children.Clear(); + public void Clear() => _children.Clear(); - public bool Contains(T item) => _children.Contains(item); + public bool Contains(T item) => _children.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => _children.CopyTo(array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => _children.CopyTo(array, arrayIndex); - public IEnumerator GetEnumerator() => _children.GetEnumerator(); + public IEnumerator GetEnumerator() => _children.GetEnumerator(); - public int IndexOf(T item) => _children.IndexOf(item); + public int IndexOf(T item) => _children.IndexOf(item); - public void Insert(int index, T item) - { - item.AddParent(_parent); - _children.Insert(index, item); - } + public void Insert(int index, T item) + { + item.AddParent(_parent); + _children.Insert(index, item); + } - public bool Remove(T item) => _children.Remove(item); + public bool Remove(T item) => _children.Remove(item); - public void RemoveAt(int index) => _children.RemoveAt(index); + public void RemoveAt(int index) => _children.RemoveAt(index); - IEnumerator IEnumerable.GetEnumerator() => _children.GetEnumerator(); - } -} + IEnumerator IEnumerable.GetEnumerator() => _children.GetEnumerator(); +} \ No newline at end of file diff --git a/src/System.CommandLine/Command.cs b/src/System.CommandLine/Command.cs index 7a1bc15941..c54920b646 100644 --- a/src/System.CommandLine/Command.cs +++ b/src/System.CommandLine/Command.cs @@ -12,326 +12,325 @@ using System.Threading.Tasks; using System.Threading; -namespace System.CommandLine +namespace System.CommandLine; + +/// +/// Represents a specific action that the application performs. +/// +/// +/// Use the Command object for actions that correspond to a specific string (the command name). See +/// for simple applications that only have one action. For example, dotnet run +/// uses run as the command. +/// +public class Command : Symbol, IEnumerable { + internal AliasSet? _aliases; + private ChildSymbolList? _arguments; + private ChildSymbolList