From f342561afc094afe1596734d8766a5e7aa1dbff6 Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Tue, 22 Nov 2022 20:01:56 +0100 Subject: [PATCH 1/2] implement nullable prompt --- .../Prompts/INullablePrompt.cs | 23 ++++++++++++++ src/Spectre.Console/Prompts/TextPrompt.cs | 31 ++++++++++++++++--- .../Prompts/TextPromptExtensions.cs | 4 +-- 3 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/Spectre.Console/Prompts/INullablePrompt.cs diff --git a/src/Spectre.Console/Prompts/INullablePrompt.cs b/src/Spectre.Console/Prompts/INullablePrompt.cs new file mode 100644 index 000000000..088e9cdcc --- /dev/null +++ b/src/Spectre.Console/Prompts/INullablePrompt.cs @@ -0,0 +1,23 @@ +namespace Spectre.Console; + +/// +/// Represents a prompt. +/// +/// The prompt result type. +public interface INullablePrompt +{ + /// + /// Shows the prompt. + /// + /// The console. + /// The prompt input result. + T? ShowNullable(IAnsiConsole console); + + /// + /// Shows the prompt asynchronously. + /// + /// The console. + /// The token to monitor for cancellation requests. + /// The prompt input result. + Task ShowNullableAsync(IAnsiConsole console, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Spectre.Console/Prompts/TextPrompt.cs b/src/Spectre.Console/Prompts/TextPrompt.cs index e48ed60ef..c2de017c0 100644 --- a/src/Spectre.Console/Prompts/TextPrompt.cs +++ b/src/Spectre.Console/Prompts/TextPrompt.cs @@ -4,7 +4,7 @@ namespace Spectre.Console; /// Represents a prompt. /// /// The prompt result type. -public sealed class TextPrompt : IPrompt, IHasCulture +public sealed class TextPrompt : IPrompt, INullablePrompt, IHasCulture { private readonly string _prompt; private readonly StringComparer? _comparer; @@ -72,7 +72,7 @@ public sealed class TextPrompt : IPrompt, IHasCulture /// /// Gets or sets the validator. /// - public Func? Validator { get; set; } + public Func? Validator { get; set; } /// /// Gets or sets the style in which the default value is displayed. @@ -111,8 +111,31 @@ public T Show(IAnsiConsole console) return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult(); } + /// + /// Shows the prompt and requests input from the user. + /// + /// The console to show the prompt in. + /// The user input converted to the expected type. + /// + public T? ShowNullable(IAnsiConsole console) + { + return ShowNullableAsync(console, CancellationToken.None).GetAwaiter().GetResult(); + } + /// public async Task ShowAsync(IAnsiConsole console, CancellationToken cancellationToken) + { + return (await ShowNullableAsync(console, false, cancellationToken)) + ?? throw new Exception("This method do not allow for null"); + } + + /// + public Task ShowNullableAsync(IAnsiConsole console, CancellationToken cancellationToken) + { + return ShowNullableAsync(console, true, cancellationToken); + } + + private async Task ShowNullableAsync(IAnsiConsole console, bool allowNull, CancellationToken cancellationToken) { if (console is null) { @@ -165,7 +188,7 @@ public async Task ShowAsync(IAnsiConsole console, CancellationToken cancellat continue; } } - else if (!TypeConverterHelper.TryConvertFromStringWithCulture(input, Culture, out result) || result == null) + else if (!TypeConverterHelper.TryConvertFromStringWithCulture(input, Culture, out result) || (!allowNull && result == null)) { console.MarkupLine(ValidationErrorMessage); WritePrompt(console); @@ -232,7 +255,7 @@ private void WritePrompt(IAnsiConsole console) console.Markup(markup + " "); } - private bool ValidateResult(T value, [NotNullWhen(false)] out string? message) + private bool ValidateResult(T? value, [NotNullWhen(false)] out string? message) { if (Validator != null) { diff --git a/src/Spectre.Console/Prompts/TextPromptExtensions.cs b/src/Spectre.Console/Prompts/TextPromptExtensions.cs index ea023dd8d..d3b784e11 100644 --- a/src/Spectre.Console/Prompts/TextPromptExtensions.cs +++ b/src/Spectre.Console/Prompts/TextPromptExtensions.cs @@ -187,7 +187,7 @@ public static TextPrompt DefaultValue(this TextPrompt obj, T value) /// The validation criteria. /// The validation error message. /// The same instance so that multiple calls can be chained. - public static TextPrompt Validate(this TextPrompt obj, Func validator, string? message = null) + public static TextPrompt Validate(this TextPrompt obj, Func validator, string? message = null) { if (obj is null) { @@ -214,7 +214,7 @@ public static TextPrompt Validate(this TextPrompt obj, Func va /// The prompt. /// The validation criteria. /// The same instance so that multiple calls can be chained. - public static TextPrompt Validate(this TextPrompt obj, Func validator) + public static TextPrompt Validate(this TextPrompt obj, Func validator) { if (obj is null) { From 17629ac13c64b43b0192b5786c5b6c278af59d95 Mon Sep 17 00:00:00 2001 From: Daniel Klecha Date: Wed, 23 Nov 2022 12:52:43 +0100 Subject: [PATCH 2/2] add console static methods and test for nullable prompt --- .../AnsiConsoleExtensions.Prompt.cs | 47 +++++++++++++++++++ .../Unit/AnsiConsoleTests.Prompt.cs | 14 ++++++ 2 files changed, 61 insertions(+) diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Prompt.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Prompt.cs index 382503f8b..6c123e638 100644 --- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Prompt.cs +++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Prompt.cs @@ -22,6 +22,23 @@ public static T Prompt(this IAnsiConsole console, IPrompt prompt) return prompt.Show(console); } + /// + /// Displays a prompt to the user. + /// + /// The prompt result type. + /// The console. + /// The prompt to display. + /// The prompt input result. + public static T? PromptNullable(this IAnsiConsole console, INullablePrompt prompt) + { + if (prompt is null) + { + throw new ArgumentNullException(nameof(prompt)); + } + + return prompt.ShowNullable(console); + } + /// /// Displays a prompt to the user. /// @@ -34,6 +51,20 @@ public static T Ask(this IAnsiConsole console, string prompt) return new TextPrompt(prompt).Show(console); } + /// + /// Displays a prompt to the user. + /// + /// The prompt result type. + /// The console. + /// The prompt markup text. + /// The prompt input result. + public static T? AskNullable(this IAnsiConsole console, string prompt) + { + return new TextPrompt(prompt) + .AllowEmpty() + .ShowNullable(console); + } + /// /// Displays a prompt to the user. /// @@ -49,6 +80,22 @@ public static T Ask(this IAnsiConsole console, string prompt, CultureInfo? cu return textPrompt.Show(console); } + /// + /// Displays a prompt to the user. + /// + /// The prompt result type. + /// The console. + /// The prompt markup text. + /// Specific CultureInfo to use when converting input. + /// The prompt input result. + public static T? AskNullable(this IAnsiConsole console, string prompt, CultureInfo? culture) + { + var textPrompt = new TextPrompt(prompt); + textPrompt.AllowEmpty(); + textPrompt.Culture = culture; + return textPrompt.ShowNullable(console); + } + /// /// Displays a prompt with two choices, yes or no. /// diff --git a/test/Spectre.Console.Tests/Unit/AnsiConsoleTests.Prompt.cs b/test/Spectre.Console.Tests/Unit/AnsiConsoleTests.Prompt.cs index e6fa30c5d..1e7827a5f 100644 --- a/test/Spectre.Console.Tests/Unit/AnsiConsoleTests.Prompt.cs +++ b/test/Spectre.Console.Tests/Unit/AnsiConsoleTests.Prompt.cs @@ -50,5 +50,19 @@ public void Should_Return_Correct_DateTime_When_Asked_US_Culture() // Then dateTime.ShouldBe(new DateTime(1998, 2, 1)); } + + [Fact] + public void Should_Return_Null_Value_When_Asked_Nullable_US_Culture() + { + // Given + var console = new TestConsole().EmitAnsiSequences(); + console.Input.PushKey(ConsoleKey.Enter); + + // When + var dateTime = console.AskNullable(string.Empty, CultureInfo.GetCultureInfo("en-US")); + + // Then + dateTime.ShouldBe(null); + } } }