mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-20 05:48:14 +08:00

committed by
Patrik Svensson

parent
e280b82679
commit
1cf30f62fc
12
src/Spectre.Console/Widgets/Prompt/DefaultPromptValue.cs
Normal file
12
src/Spectre.Console/Widgets/Prompt/DefaultPromptValue.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class DefaultPromptValue<T>
|
||||
{
|
||||
public T Value { get; }
|
||||
|
||||
public DefaultPromptValue(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
16
src/Spectre.Console/Widgets/Prompt/IPrompt.cs
Normal file
16
src/Spectre.Console/Widgets/Prompt/IPrompt.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
public interface IPrompt<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows the prompt.
|
||||
/// </summary>
|
||||
/// <param name="console">The console.</param>
|
||||
/// <returns>The prompt input result.</returns>
|
||||
T Show(IAnsiConsole console);
|
||||
}
|
||||
}
|
212
src/Spectre.Console/Widgets/Prompt/TextPrompt.cs
Normal file
212
src/Spectre.Console/Widgets/Prompt/TextPrompt.cs
Normal file
@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
public sealed class TextPrompt<T> : IPrompt<T>
|
||||
{
|
||||
private readonly string _prompt;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the prompt style.
|
||||
/// </summary>
|
||||
public Style? PromptStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of choices.
|
||||
/// </summary>
|
||||
public HashSet<T> Choices { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message for invalid choices.
|
||||
/// </summary>
|
||||
public string InvalidChoiceMessage { get; set; } = "[red]Please select one of the available options[/]";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether input should
|
||||
/// be hidden in the console.
|
||||
/// </summary>
|
||||
public bool IsSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the validation error message.
|
||||
/// </summary>
|
||||
public string ValidationErrorMessage { get; set; } = "[red]Invalid input[/]";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not
|
||||
/// choices should be shown.
|
||||
/// </summary>
|
||||
public bool ShowChoices { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not
|
||||
/// default values should be shown.
|
||||
/// </summary>
|
||||
public bool ShowDefaultValue { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not an empty result is valid.
|
||||
/// </summary>
|
||||
public bool AllowEmpty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the validator.
|
||||
/// </summary>
|
||||
public Func<T, ValidationResult>? Validator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default value.
|
||||
/// </summary>
|
||||
internal DefaultPromptValue<T>? DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextPrompt{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="prompt">The prompt markup text.</param>
|
||||
/// <param name="comparer">The comparer used for choices.</param>
|
||||
public TextPrompt(string prompt, IEqualityComparer<T>? comparer = null)
|
||||
{
|
||||
_prompt = prompt;
|
||||
|
||||
Choices = new HashSet<T>(comparer ?? EqualityComparer<T>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the prompt and requests input from the user.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to show the prompt in.</param>
|
||||
/// <returns>The user input converted to the expected type.</returns>
|
||||
/// <inheritdoc/>
|
||||
public T Show(IAnsiConsole console)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
var promptStyle = PromptStyle ?? Style.Plain;
|
||||
var choices = Choices.Select(choice => TypeConverterHelper.ConvertToString(choice));
|
||||
|
||||
WritePrompt(console);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var input = console.ReadLine(promptStyle, IsSecret, choices);
|
||||
|
||||
// Nothing entered?
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
if (DefaultValue != null)
|
||||
{
|
||||
console.Write(TypeConverterHelper.ConvertToString(DefaultValue.Value), promptStyle);
|
||||
console.WriteLine();
|
||||
return DefaultValue.Value;
|
||||
}
|
||||
|
||||
if (!AllowEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.WriteLine();
|
||||
|
||||
// Try convert the value to the expected type.
|
||||
if (!TypeConverterHelper.TryConvertFromString<T>(input, out var result) || result == null)
|
||||
{
|
||||
console.MarkupLine(ValidationErrorMessage);
|
||||
WritePrompt(console);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Choices.Count > 0)
|
||||
{
|
||||
if (Choices.Contains(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
console.MarkupLine(InvalidChoiceMessage);
|
||||
WritePrompt(console);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Run all validators
|
||||
if (!ValidateResult(result, out var validationMessage))
|
||||
{
|
||||
console.MarkupLine(validationMessage);
|
||||
WritePrompt(console);
|
||||
continue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the prompt to the console.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to write the prompt to.</param>
|
||||
private void WritePrompt(IAnsiConsole console)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(_prompt.TrimEnd());
|
||||
|
||||
if (ShowChoices && Choices.Count > 0)
|
||||
{
|
||||
var choices = string.Join("/", Choices.Select(choice => TypeConverterHelper.ConvertToString(choice)));
|
||||
builder.AppendFormat(CultureInfo.InvariantCulture, " [blue][[{0}]][/]", choices);
|
||||
}
|
||||
|
||||
if (ShowDefaultValue && DefaultValue != null)
|
||||
{
|
||||
builder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
" [green]({0})[/]",
|
||||
TypeConverterHelper.ConvertToString(DefaultValue.Value));
|
||||
}
|
||||
|
||||
var markup = builder.ToString().Trim();
|
||||
if (!markup.EndsWith("?", StringComparison.OrdinalIgnoreCase) &&
|
||||
!markup.EndsWith(":", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
markup += ":";
|
||||
}
|
||||
|
||||
console.Markup(markup + " ");
|
||||
}
|
||||
|
||||
private bool ValidateResult(T value, [NotNullWhen(false)] out string? message)
|
||||
{
|
||||
if (Validator != null)
|
||||
{
|
||||
var result = Validator(value);
|
||||
if (!result.Successful)
|
||||
{
|
||||
message = result.Message ?? ValidationErrorMessage;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
message = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
266
src/Spectre.Console/Widgets/Prompt/TextPromptExtensions.cs
Normal file
266
src/Spectre.Console/Widgets/Prompt/TextPromptExtensions.cs
Normal file
@ -0,0 +1,266 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="TextPrompt{T}"/>.
|
||||
/// </summary>
|
||||
public static class TextPromptExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Allow empty input.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> AllowEmpty<T>(this TextPrompt<T> obj)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.AllowEmpty = true;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the prompt style.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="style">The prompt style.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> PromptStyle<T>(this TextPrompt<T> obj, Style style)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
if (style is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(style));
|
||||
}
|
||||
|
||||
obj.PromptStyle = style;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show or hide choices.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="show">Whether or not choices should be visible.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> ShowChoices<T>(this TextPrompt<T> obj, bool show)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.ShowChoices = show;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows choices.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> ShowChoices<T>(this TextPrompt<T> obj)
|
||||
{
|
||||
return ShowChoices(obj, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides choices.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> HideChoices<T>(this TextPrompt<T> obj)
|
||||
{
|
||||
return ShowChoices(obj, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show or hide the default value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="show">Whether or not the default value should be visible.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> ShowDefaultValue<T>(this TextPrompt<T> obj, bool show)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.ShowDefaultValue = show;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the default value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> ShowDefaultValue<T>(this TextPrompt<T> obj)
|
||||
{
|
||||
return ShowDefaultValue(obj, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the default value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> HideDefaultValue<T>(this TextPrompt<T> obj)
|
||||
{
|
||||
return ShowDefaultValue(obj, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the validation error message for the prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="message">The validation error message.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> ValidationErrorMessage<T>(this TextPrompt<T> obj, string message)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.ValidationErrorMessage = message;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the "invalid choice" message for the prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="message">The "invalid choice" message.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> InvalidChoiceMessage<T>(this TextPrompt<T> obj, string message)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.InvalidChoiceMessage = message;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default value of the prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="value">The default value.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> DefaultValue<T>(this TextPrompt<T> obj, T value)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.DefaultValue = new DefaultPromptValue<T>(value);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the validation criteria for the prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="validator">The validation criteria.</param>
|
||||
/// <param name="message">The validation error message.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> Validate<T>(this TextPrompt<T> obj, Func<T, bool> validator, string? message = null)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.Validator = result =>
|
||||
{
|
||||
if (validator(result))
|
||||
{
|
||||
return ValidationResult.Success();
|
||||
}
|
||||
|
||||
return ValidationResult.Error(message);
|
||||
};
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the validation criteria for the prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="validator">The validation criteria.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> Validate<T>(this TextPrompt<T> obj, Func<T, ValidationResult> validator)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.Validator = validator;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a choice to the prompt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="choice">The choice to add.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> AddChoice<T>(this TextPrompt<T> obj, T choice)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.Choices.Add(choice);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces prompt user input with asterixes in the console.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static TextPrompt<T> Secret<T>(this TextPrompt<T> obj)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.IsSecret = true;
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
43
src/Spectre.Console/Widgets/Prompt/ValidationResult.cs
Normal file
43
src/Spectre.Console/Widgets/Prompt/ValidationResult.cs
Normal file
@ -0,0 +1,43 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a prompt validation result.
|
||||
/// </summary>
|
||||
public sealed class ValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not validation was successful.
|
||||
/// </summary>
|
||||
public bool Successful { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the validation error message.
|
||||
/// </summary>
|
||||
public string? Message { get; }
|
||||
|
||||
private ValidationResult(bool successful, string? message)
|
||||
{
|
||||
Successful = successful;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ValidationResult"/> representing successful validation.
|
||||
/// </summary>
|
||||
/// <returns>The validation result.</returns>
|
||||
public static ValidationResult Success()
|
||||
{
|
||||
return new ValidationResult(true, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ValidationResult"/> representing a validation error.
|
||||
/// </summary>
|
||||
/// <param name="message">The validation error message, or <c>null</c> to show the default validation error message.</param>
|
||||
/// <returns>The validation result.</returns>
|
||||
public static ValidationResult Error(string? message = null)
|
||||
{
|
||||
return new ValidationResult(false, message);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user