mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-24 04:02:50 +08:00
Add text prompt support
This commit is contained in:
parent
380c6aca45
commit
0d209d8f18
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace InfoExample
|
namespace InfoExample
|
||||||
@ -12,6 +13,7 @@ namespace InfoExample
|
|||||||
.AddRow("[b]Color system[/]", $"{AnsiConsole.Capabilities.ColorSystem}")
|
.AddRow("[b]Color system[/]", $"{AnsiConsole.Capabilities.ColorSystem}")
|
||||||
.AddRow("[b]Supports ansi?[/]", $"{YesNo(AnsiConsole.Capabilities.SupportsAnsi)}")
|
.AddRow("[b]Supports ansi?[/]", $"{YesNo(AnsiConsole.Capabilities.SupportsAnsi)}")
|
||||||
.AddRow("[b]Legacy console?[/]", $"{YesNo(AnsiConsole.Capabilities.LegacyConsole)}")
|
.AddRow("[b]Legacy console?[/]", $"{YesNo(AnsiConsole.Capabilities.LegacyConsole)}")
|
||||||
|
.AddRow("[b]Interactive?[/]", $"{YesNo(Environment.UserInteractive)}")
|
||||||
.AddRow("[b]Buffer width[/]", $"{AnsiConsole.Console.Width}")
|
.AddRow("[b]Buffer width[/]", $"{AnsiConsole.Console.Width}")
|
||||||
.AddRow("[b]Buffer height[/]", $"{AnsiConsole.Console.Height}");
|
.AddRow("[b]Buffer height[/]", $"{AnsiConsole.Console.Height}");
|
||||||
|
|
||||||
|
77
examples/Prompt/Program.cs
Normal file
77
examples/Prompt/Program.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace Cursor
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// Confirmation
|
||||||
|
if (!AnsiConsole.Confirm("Run prompt example?"))
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("Ok... :(");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// String
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftAligned());
|
||||||
|
var name = AnsiConsole.Ask<string>("What's your [green]name[/]?");
|
||||||
|
|
||||||
|
// String with choices
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftAligned());
|
||||||
|
var fruit = AnsiConsole.Prompt(
|
||||||
|
new TextPrompt<string>("What's your [green]favorite fruit[/]?")
|
||||||
|
.InvalidChoiceMessage("[red]That's not a valid fruit[/]")
|
||||||
|
.DefaultValue("Orange")
|
||||||
|
.AddChoice("Apple")
|
||||||
|
.AddChoice("Banana")
|
||||||
|
.AddChoice("Orange"));
|
||||||
|
|
||||||
|
// Integer
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftAligned());
|
||||||
|
var age = AnsiConsole.Prompt(
|
||||||
|
new TextPrompt<int>("How [green]old[/] are you?")
|
||||||
|
.PromptStyle("green")
|
||||||
|
.ValidationErrorMessage("[red]That's not a valid age[/]")
|
||||||
|
.Validate(age =>
|
||||||
|
{
|
||||||
|
return age switch
|
||||||
|
{
|
||||||
|
<= 0 => ValidationResult.Error("[red]You must at least be 1 years old[/]"),
|
||||||
|
>= 123 => ValidationResult.Error("[red]You must be younger than the oldest person alive[/]"),
|
||||||
|
_ => ValidationResult.Success(),
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Secret
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftAligned());
|
||||||
|
var password = AnsiConsole.Prompt(
|
||||||
|
new TextPrompt<string>("Enter [green]password[/]?")
|
||||||
|
.PromptStyle("red")
|
||||||
|
.Secret());
|
||||||
|
|
||||||
|
// Optional
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftAligned());
|
||||||
|
var color = AnsiConsole.Prompt(
|
||||||
|
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
|
||||||
|
.AllowEmpty());
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftAligned());
|
||||||
|
AnsiConsole.Render(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
|
||||||
|
.RoundedBorder()
|
||||||
|
.BorderColor(Color.Grey)
|
||||||
|
.AddRow("[grey]Name[/]", name)
|
||||||
|
.AddRow("[grey]Favorite fruit[/]", fruit)
|
||||||
|
.AddRow("[grey]Age[/]", age.ToString())
|
||||||
|
.AddRow("[grey]Password[/]", password)
|
||||||
|
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
examples/Prompt/Prompt.csproj
Normal file
16
examples/Prompt/Prompt.csproj
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
<LangVersion>9</LangVersion>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<Title>Prompt</Title>
|
||||||
|
<Description>Demonstrates how to get input from a user.</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="Settings">
|
<PropertyGroup Label="Settings">
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>9.0</LangVersion>
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
|
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using Spectre.Console.Rendering;
|
|
||||||
|
|
||||||
namespace Spectre.Console.Tests.Tools
|
|
||||||
{
|
|
||||||
public sealed class MarkupConsoleFixture : IDisposable, IAnsiConsole
|
|
||||||
{
|
|
||||||
private readonly StringWriter _writer;
|
|
||||||
private readonly IAnsiConsole _console;
|
|
||||||
|
|
||||||
public string Output => _writer.ToString().TrimEnd('\n');
|
|
||||||
|
|
||||||
public Capabilities Capabilities => _console.Capabilities;
|
|
||||||
public Encoding Encoding => _console.Encoding;
|
|
||||||
public IAnsiConsoleCursor Cursor => _console.Cursor;
|
|
||||||
public int Width { get; }
|
|
||||||
public int Height => _console.Height;
|
|
||||||
|
|
||||||
public MarkupConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
|
|
||||||
{
|
|
||||||
_writer = new StringWriter();
|
|
||||||
_console = AnsiConsole.Create(new AnsiConsoleSettings
|
|
||||||
{
|
|
||||||
Ansi = ansi,
|
|
||||||
ColorSystem = (ColorSystemSupport)system,
|
|
||||||
Out = _writer,
|
|
||||||
LinkIdentityGenerator = new TestLinkIdentityGenerator(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Width = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_writer?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear(bool home)
|
|
||||||
{
|
|
||||||
_console.Clear(home);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write(Segment segment)
|
|
||||||
{
|
|
||||||
_console.Write(segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,10 +12,13 @@ namespace Spectre.Console.Tests
|
|||||||
public Capabilities Capabilities { get; }
|
public Capabilities Capabilities { get; }
|
||||||
public Encoding Encoding { get; }
|
public Encoding Encoding { get; }
|
||||||
public IAnsiConsoleCursor Cursor => throw new NotSupportedException();
|
public IAnsiConsoleCursor Cursor => throw new NotSupportedException();
|
||||||
|
public TestableConsoleInput Input { get; }
|
||||||
|
|
||||||
public int Width { get; }
|
public int Width { get; }
|
||||||
public int Height { get; }
|
public int Height { get; }
|
||||||
|
|
||||||
|
IAnsiConsoleInput IAnsiConsole.Input => Input;
|
||||||
|
|
||||||
public Decoration Decoration { get; set; }
|
public Decoration Decoration { get; set; }
|
||||||
public Color Foreground { get; set; }
|
public Color Foreground { get; set; }
|
||||||
public Color Background { get; set; }
|
public Color Background { get; set; }
|
||||||
@ -36,6 +39,7 @@ namespace Spectre.Console.Tests
|
|||||||
Width = width;
|
Width = width;
|
||||||
Height = height;
|
Height = height;
|
||||||
Writer = new StringWriter();
|
Writer = new StringWriter();
|
||||||
|
Input = new TestableConsoleInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -18,6 +18,9 @@ namespace Spectre.Console.Tests
|
|||||||
public int Width { get; }
|
public int Width { get; }
|
||||||
public int Height => _console.Height;
|
public int Height => _console.Height;
|
||||||
public IAnsiConsoleCursor Cursor => _console.Cursor;
|
public IAnsiConsoleCursor Cursor => _console.Cursor;
|
||||||
|
public TestableConsoleInput Input { get; }
|
||||||
|
|
||||||
|
IAnsiConsoleInput IAnsiConsole.Input => Input;
|
||||||
|
|
||||||
public TestableAnsiConsole(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
|
public TestableAnsiConsole(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
|
||||||
{
|
{
|
||||||
@ -31,6 +34,7 @@ namespace Spectre.Console.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
Width = width;
|
Width = width;
|
||||||
|
Input = new TestableConsoleInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
51
src/Spectre.Console.Tests/Tools/TestableConsoleInput.cs
Normal file
51
src/Spectre.Console.Tests/Tools/TestableConsoleInput.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests
|
||||||
|
{
|
||||||
|
public sealed class TestableConsoleInput : IAnsiConsoleInput
|
||||||
|
{
|
||||||
|
private readonly Queue<ConsoleKeyInfo> _input;
|
||||||
|
|
||||||
|
public TestableConsoleInput()
|
||||||
|
{
|
||||||
|
_input = new Queue<ConsoleKeyInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushText(string input)
|
||||||
|
{
|
||||||
|
if (input is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var character in input)
|
||||||
|
{
|
||||||
|
PushCharacter(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
PushKey(ConsoleKey.Enter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushCharacter(char character)
|
||||||
|
{
|
||||||
|
var control = char.IsUpper(character);
|
||||||
|
_input.Enqueue(new ConsoleKeyInfo(character, (ConsoleKey)character, false, false, control));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushKey(ConsoleKey key)
|
||||||
|
{
|
||||||
|
_input.Enqueue(new ConsoleKeyInfo((char)key, key, false, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleKeyInfo ReadKey(bool intercept)
|
||||||
|
{
|
||||||
|
if (_input.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No input available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _input.Dequeue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
src/Spectre.Console.Tests/Unit/PromptTests.cs
Normal file
126
src/Spectre.Console.Tests/Unit/PromptTests.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Unit
|
||||||
|
{
|
||||||
|
public sealed class PromptTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Return_Validation_Error_If_Value_Cannot_Be_Converted()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole();
|
||||||
|
console.Input.PushText("ninety-nine");
|
||||||
|
console.Input.PushText("99");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Prompt(new TextPrompt<int>("Age?"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
console.Lines[0].ShouldBe("Age? ninety-nine");
|
||||||
|
console.Lines[1].ShouldBe("Invalid input");
|
||||||
|
console.Lines[2].ShouldBe("Age? 99");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Chose_Default_Value_If_Nothing_Is_Entered()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole();
|
||||||
|
console.Input.PushKey(ConsoleKey.Enter);
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Prompt(
|
||||||
|
new TextPrompt<string>("Favorite fruit?")
|
||||||
|
.AddChoice("Banana")
|
||||||
|
.AddChoice("Orange")
|
||||||
|
.DefaultValue("Banana"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(1);
|
||||||
|
console.Lines[0].ShouldBe("Favorite fruit? [Banana/Orange] (Banana): Banana");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Return_Error_If_An_Invalid_Choice_Is_Made()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole();
|
||||||
|
console.Input.PushText("Apple");
|
||||||
|
console.Input.PushText("Banana");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Prompt(
|
||||||
|
new TextPrompt<string>("Favorite fruit?")
|
||||||
|
.AddChoice("Banana")
|
||||||
|
.AddChoice("Orange")
|
||||||
|
.DefaultValue("Banana"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
console.Lines[0].ShouldBe("Favorite fruit? [Banana/Orange] (Banana): Apple");
|
||||||
|
console.Lines[1].ShouldBe("Please select one of the available options");
|
||||||
|
console.Lines[2].ShouldBe("Favorite fruit? [Banana/Orange] (Banana): Banana");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Accept_Choice_In_List()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole();
|
||||||
|
console.Input.PushText("Orange");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Prompt(
|
||||||
|
new TextPrompt<string>("Favorite fruit?")
|
||||||
|
.AddChoice("Banana")
|
||||||
|
.AddChoice("Orange")
|
||||||
|
.DefaultValue("Banana"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(1);
|
||||||
|
console.Lines[0].ShouldBe("Favorite fruit? [Banana/Orange] (Banana): Orange");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Return_Error_If_Custom_Validation_Fails()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole();
|
||||||
|
console.Input.PushText("22");
|
||||||
|
console.Input.PushText("102");
|
||||||
|
console.Input.PushText("ABC");
|
||||||
|
console.Input.PushText("99");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Prompt(
|
||||||
|
new TextPrompt<int>("Guess number:")
|
||||||
|
.ValidationErrorMessage("Invalid input")
|
||||||
|
.Validate(age =>
|
||||||
|
{
|
||||||
|
if (age < 99)
|
||||||
|
{
|
||||||
|
return ValidationResult.Error("Too low");
|
||||||
|
}
|
||||||
|
else if (age > 99)
|
||||||
|
{
|
||||||
|
return ValidationResult.Error("Too high");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult.Success();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(7);
|
||||||
|
console.Lines[0].ShouldBe("Guess number: 22");
|
||||||
|
console.Lines[1].ShouldBe("Too low");
|
||||||
|
console.Lines[2].ShouldBe("Guess number: 102");
|
||||||
|
console.Lines[3].ShouldBe("Too high");
|
||||||
|
console.Lines[4].ShouldBe("Guess number: ABC");
|
||||||
|
console.Lines[5].ShouldBe("Invalid input");
|
||||||
|
console.Lines[6].ShouldBe("Guess number: 99");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules", "..\examples\Rules\
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cursor", "..\examples\Cursor\Cursor.csproj", "{75C608C3-ABB4-4168-A229-7F8250B946D1}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cursor", "..\examples\Cursor\Cursor.csproj", "{75C608C3-ABB4-4168-A229-7F8250B946D1}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Prompt", "..\examples\Prompt\Prompt.csproj", "{6351C70F-F368-46DB-BAED-9B87CCD69353}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -240,6 +242,18 @@ Global
|
|||||||
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x64.Build.0 = Release|Any CPU
|
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x86.ActiveCfg = Release|Any CPU
|
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x86.Build.0 = Release|Any CPU
|
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -259,6 +273,7 @@ Global
|
|||||||
{57691C7D-683D-46E6-AA4F-57A8C5F65D25} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
{57691C7D-683D-46E6-AA4F-57A8C5F65D25} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
{8622A261-02C6-40CA-9797-E3F01ED87D6B} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
{8622A261-02C6-40CA-9797-E3F01ED87D6B} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
{75C608C3-ABB4-4168-A229-7F8250B946D1} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
{75C608C3-ABB4-4168-A229-7F8250B946D1} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
|
{6351C70F-F368-46DB-BAED-9B87CCD69353} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
||||||
|
47
src/Spectre.Console/AnsiConsole.Prompt.cs
Normal file
47
src/Spectre.Console/AnsiConsole.Prompt.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A console capable of writing ANSI escape sequences.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class AnsiConsole
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a prompt to the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="prompt">The prompt to display.</param>
|
||||||
|
/// <returns>The prompt input result.</returns>
|
||||||
|
public static T Prompt<T>(IPrompt<T> prompt)
|
||||||
|
{
|
||||||
|
if (prompt is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(prompt));
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompt.Show(Console);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a prompt to the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="prompt">The prompt markup text.</param>
|
||||||
|
/// <returns>The prompt input result.</returns>
|
||||||
|
public static T Ask<T>(string prompt)
|
||||||
|
{
|
||||||
|
return new TextPrompt<T>(prompt).Show(Console);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a prompt with two choices, yes or no.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prompt">The prompt markup text.</param>
|
||||||
|
/// <returns><c>true</c> if the user selected "yes", otherwise <c>false</c>.</returns>
|
||||||
|
public static bool Confirm(string prompt)
|
||||||
|
{
|
||||||
|
return new ConfirmationPrompt(prompt).Show(Console);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
src/Spectre.Console/ConfirmationPrompt.cs
Normal file
62
src/Spectre.Console/ConfirmationPrompt.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A prompt that is answered with a yes or no.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ConfirmationPrompt : IPrompt<bool>
|
||||||
|
{
|
||||||
|
private readonly string _prompt;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the character that represents "yes".
|
||||||
|
/// </summary>
|
||||||
|
public char Yes { get; set; } = 'y';
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the character that represents "no".
|
||||||
|
/// </summary>
|
||||||
|
public char No { get; set; } = 'n';
|
||||||
|
|
||||||
|
/// <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 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>
|
||||||
|
/// Initializes a new instance of the <see cref="ConfirmationPrompt"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prompt">The prompt markup text.</param>
|
||||||
|
public ConfirmationPrompt(string prompt)
|
||||||
|
{
|
||||||
|
_prompt = prompt ?? throw new System.ArgumentNullException(nameof(prompt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Show(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
var prompt = new TextPrompt<char>(_prompt)
|
||||||
|
.InvalidChoiceMessage(InvalidChoiceMessage)
|
||||||
|
.ValidationErrorMessage(InvalidChoiceMessage)
|
||||||
|
.ShowChoices(ShowChoices)
|
||||||
|
.ShowDefaultValue(ShowDefaultValue)
|
||||||
|
.DefaultValue(Yes)
|
||||||
|
.AddChoice(Yes)
|
||||||
|
.AddChoice(No);
|
||||||
|
|
||||||
|
var result = prompt.Show(console);
|
||||||
|
return result == Yes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="IAnsiConsole"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class AnsiConsoleExtensions
|
||||||
|
{
|
||||||
|
internal static string ReadLine(this IAnsiConsole console, Style? style, bool secret)
|
||||||
|
{
|
||||||
|
if (console is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(console));
|
||||||
|
}
|
||||||
|
|
||||||
|
style ??= Style.Plain;
|
||||||
|
|
||||||
|
var result = string.Empty;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var key = console.Input.ReadKey(true);
|
||||||
|
|
||||||
|
if (key.Key == ConsoleKey.Enter)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.Key == ConsoleKey.Backspace)
|
||||||
|
{
|
||||||
|
if (result.Length > 0)
|
||||||
|
{
|
||||||
|
result = result.Substring(0, result.Length - 1);
|
||||||
|
console.Write("\b \b");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += key.KeyChar.ToString();
|
||||||
|
|
||||||
|
if (!char.IsControl(key.KeyChar))
|
||||||
|
{
|
||||||
|
console.Write(secret ? "*" : key.KeyChar.ToString(), style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="IAnsiConsole"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class AnsiConsoleExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a prompt to the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="console">The console.</param>
|
||||||
|
/// <param name="prompt">The prompt to display.</param>
|
||||||
|
/// <returns>The prompt input result.</returns>
|
||||||
|
public static T Prompt<T>(this IAnsiConsole console, IPrompt<T> prompt)
|
||||||
|
{
|
||||||
|
if (prompt is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(prompt));
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompt.Show(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a prompt to the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="console">The console.</param>
|
||||||
|
/// <param name="prompt">The prompt markup text.</param>
|
||||||
|
/// <returns>The prompt input result.</returns>
|
||||||
|
public static T Ask<T>(this IAnsiConsole console, string prompt)
|
||||||
|
{
|
||||||
|
return new TextPrompt<T>(prompt).Show(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a prompt with two choices, yes or no.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="console">The console.</param>
|
||||||
|
/// <param name="prompt">The prompt markup text.</param>
|
||||||
|
/// <returns><c>true</c> if the user selected "yes", otherwise <c>false</c>.</returns>
|
||||||
|
public static bool Confirm(this IAnsiConsole console, string prompt)
|
||||||
|
{
|
||||||
|
return new ConfirmationPrompt(prompt).Show(console);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,16 @@ namespace Spectre.Console
|
|||||||
return new Recorder(console);
|
return new Recorder(console);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified string value to the console.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="console">The console to write to.</param>
|
||||||
|
/// <param name="text">The text to write.</param>
|
||||||
|
public static void Write(this IAnsiConsole console, string text)
|
||||||
|
{
|
||||||
|
Write(console, text, Style.Plain);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the specified string value to the console.
|
/// Writes the specified string value to the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -31,6 +41,11 @@ namespace Spectre.Console
|
|||||||
throw new ArgumentNullException(nameof(console));
|
throw new ArgumentNullException(nameof(console));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(text));
|
||||||
|
}
|
||||||
|
|
||||||
console.Write(new Segment(text, style));
|
console.Write(new Segment(text, style));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +63,16 @@ namespace Spectre.Console
|
|||||||
console.Write(Environment.NewLine, Style.Plain);
|
console.Write(Environment.NewLine, Style.Plain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified string value, followed by the current line terminator, to the console.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="console">The console to write to.</param>
|
||||||
|
/// <param name="text">The text to write.</param>
|
||||||
|
public static void WriteLine(this IAnsiConsole console, string text)
|
||||||
|
{
|
||||||
|
WriteLine(console, text, Style.Plain);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the specified string value, followed by the current line terminator, to the console.
|
/// Writes the specified string value, followed by the current line terminator, to the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -61,6 +86,11 @@ namespace Spectre.Console
|
|||||||
throw new ArgumentNullException(nameof(console));
|
throw new ArgumentNullException(nameof(console));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(text));
|
||||||
|
}
|
||||||
|
|
||||||
console.Write(new Segment(text, style));
|
console.Write(new Segment(text, style));
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
}
|
}
|
||||||
|
135
src/Spectre.Console/Extensions/ConfirmationPromptExtensions.cs
Normal file
135
src/Spectre.Console/Extensions/ConfirmationPromptExtensions.cs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="ConfirmationPrompt"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ConfirmationPromptExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Show or hide choices.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="show">Whether or not the choices should be visible.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static ConfirmationPrompt ShowChoices(this ConfirmationPrompt obj, bool show)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.ShowChoices = show;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows choices.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static ConfirmationPrompt ShowChoices(this ConfirmationPrompt obj)
|
||||||
|
{
|
||||||
|
return ShowChoices(obj, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides choices.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static ConfirmationPrompt HideChoices(this ConfirmationPrompt obj)
|
||||||
|
{
|
||||||
|
return ShowChoices(obj, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show or hide the default value.
|
||||||
|
/// </summary>
|
||||||
|
/// <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 ConfirmationPrompt ShowDefaultValue(this ConfirmationPrompt obj, bool show)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.ShowDefaultValue = show;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows the default value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static ConfirmationPrompt ShowDefaultValue(this ConfirmationPrompt obj)
|
||||||
|
{
|
||||||
|
return ShowDefaultValue(obj, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the default value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static ConfirmationPrompt HideDefaultValue(this ConfirmationPrompt obj)
|
||||||
|
{
|
||||||
|
return ShowDefaultValue(obj, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the "invalid choice" message for the prompt.
|
||||||
|
/// </summary>
|
||||||
|
/// <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 ConfirmationPrompt InvalidChoiceMessage(this ConfirmationPrompt obj, string message)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.InvalidChoiceMessage = message;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the character to interpret as "yes".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The confirmation prompt.</param>
|
||||||
|
/// <param name="character">The character to interpret as "yes".</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static ConfirmationPrompt Yes(this ConfirmationPrompt obj, char character)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Yes = character;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the character to interpret as "no".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The confirmation prompt.</param>
|
||||||
|
/// <param name="character">The character to interpret as "no".</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static ConfirmationPrompt No(this ConfirmationPrompt obj, char character)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.No = character;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
266
src/Spectre.Console/Extensions/TextPromptExtensions.cs
Normal file
266
src/Spectre.Console/Extensions/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 TextPrompt<T>.DefaultValueContainer(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,11 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
IAnsiConsoleCursor Cursor { get; }
|
IAnsiConsoleCursor Cursor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the console input.
|
||||||
|
/// </summary>
|
||||||
|
IAnsiConsoleInput Input { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the buffer width of the console.
|
/// Gets the buffer width of the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
17
src/Spectre.Console/IAnsiConsoleInput.cs
Normal file
17
src/Spectre.Console/IAnsiConsoleInput.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the console's input mechanism.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAnsiConsoleInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a key from the console.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="intercept">Whether or not to intercept the key.</param>
|
||||||
|
/// <returns>The key that was read.</returns>
|
||||||
|
ConsoleKeyInfo ReadKey(bool intercept);
|
||||||
|
}
|
||||||
|
}
|
16
src/Spectre.Console/IPrompt.cs
Normal file
16
src/Spectre.Console/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);
|
||||||
|
}
|
||||||
|
}
|
@ -10,10 +10,12 @@ namespace Spectre.Console.Internal
|
|||||||
private readonly TextWriter _out;
|
private readonly TextWriter _out;
|
||||||
private readonly AnsiBuilder _ansiBuilder;
|
private readonly AnsiBuilder _ansiBuilder;
|
||||||
private readonly AnsiCursor _cursor;
|
private readonly AnsiCursor _cursor;
|
||||||
|
private readonly ConsoleInput _input;
|
||||||
|
|
||||||
public Capabilities Capabilities { get; }
|
public Capabilities Capabilities { get; }
|
||||||
public Encoding Encoding { get; }
|
public Encoding Encoding { get; }
|
||||||
public IAnsiConsoleCursor Cursor => _cursor;
|
public IAnsiConsoleCursor Cursor => _cursor;
|
||||||
|
public IAnsiConsoleInput Input => _input;
|
||||||
|
|
||||||
public int Width
|
public int Width
|
||||||
{
|
{
|
||||||
@ -50,6 +52,7 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
_ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
|
_ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
|
||||||
_cursor = new AnsiCursor(this);
|
_cursor = new AnsiCursor(this);
|
||||||
|
_input = new ConsoleInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear(bool home)
|
public void Clear(bool home)
|
||||||
|
@ -9,11 +9,13 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
private readonly ColorSystem _system;
|
private readonly ColorSystem _system;
|
||||||
private readonly FallbackCursor _cursor;
|
private readonly FallbackCursor _cursor;
|
||||||
|
private readonly ConsoleInput _input;
|
||||||
private Style? _lastStyle;
|
private Style? _lastStyle;
|
||||||
|
|
||||||
public Capabilities Capabilities { get; }
|
public Capabilities Capabilities { get; }
|
||||||
public Encoding Encoding { get; }
|
public Encoding Encoding { get; }
|
||||||
public IAnsiConsoleCursor Cursor => _cursor;
|
public IAnsiConsoleCursor Cursor => _cursor;
|
||||||
|
public IAnsiConsoleInput Input => _input;
|
||||||
|
|
||||||
public int Width
|
public int Width
|
||||||
{
|
{
|
||||||
@ -34,6 +36,7 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
_system = capabilities.ColorSystem;
|
_system = capabilities.ColorSystem;
|
||||||
_cursor = new FallbackCursor();
|
_cursor = new FallbackCursor();
|
||||||
|
_input = new ConsoleInput();
|
||||||
|
|
||||||
if (@out != System.Console.Out)
|
if (@out != System.Console.Out)
|
||||||
{
|
{
|
||||||
|
17
src/Spectre.Console/Internal/ConsoleInput.cs
Normal file
17
src/Spectre.Console/Internal/ConsoleInput.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Internal
|
||||||
|
{
|
||||||
|
internal sealed class ConsoleInput : IAnsiConsoleInput
|
||||||
|
{
|
||||||
|
public ConsoleKeyInfo ReadKey(bool intercept)
|
||||||
|
{
|
||||||
|
if (!Environment.UserInteractive)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to read input in non-interactive mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return System.Console.ReadKey(intercept);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,9 @@ namespace Spectre.Console
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IAnsiConsoleCursor Cursor => _console.Cursor;
|
public IAnsiConsoleCursor Cursor => _console.Cursor;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IAnsiConsoleInput Input => _console.Input;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int Width => _console.Width;
|
public int Width => _console.Width;
|
||||||
|
|
||||||
|
@ -10,27 +10,6 @@
|
|||||||
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Update="AnsiConsole.*.cs">
|
|
||||||
<DependentUpon>AnsiConsole.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Border.*.cs">
|
|
||||||
<DependentUpon>Border.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="BoxBorder.*.cs">
|
|
||||||
<DependentUpon>BoxBorder.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Color.*.cs">
|
|
||||||
<DependentUpon>Color.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Emoji.*.cs">
|
|
||||||
<DependentUpon>Emoji.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Extensions/AnsiConsoleExtensions.*.cs">
|
|
||||||
<DependentUpon>Extensions/AnsiConsoleExtensions.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" />
|
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" />
|
||||||
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" />
|
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" />
|
||||||
|
284
src/Spectre.Console/TextPrompt.cs
Normal file
284
src/Spectre.Console/TextPrompt.cs
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
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 DefaultValueContainer? DefaultValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A nullable container for a default value.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class DefaultValueContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the default value.
|
||||||
|
/// </summary>
|
||||||
|
public T Value { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DefaultValueContainer"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The default value.</param>
|
||||||
|
public DefaultValueContainer(T value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
WritePrompt(console);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var input = console.ReadLine(promptStyle, IsSecret);
|
||||||
|
|
||||||
|
// Nothing entered?
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
if (DefaultValue != null)
|
||||||
|
{
|
||||||
|
console.Write(TextPrompt<T>.GetTypeConverter().ConvertToInvariantString(DefaultValue.Value), promptStyle);
|
||||||
|
console.WriteLine();
|
||||||
|
return DefaultValue.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AllowEmpty)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.WriteLine();
|
||||||
|
|
||||||
|
// Try convert the value to the expected type.
|
||||||
|
if (!TextPrompt<T>.TryConvert(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 => TextPrompt<T>.GetTypeConverter().ConvertToInvariantString(choice)));
|
||||||
|
builder.AppendFormat(CultureInfo.InvariantCulture, " [blue][[{0}]][/]", choices);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShowDefaultValue && DefaultValue != null)
|
||||||
|
{
|
||||||
|
builder.AppendFormat(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
" [green]({0})[/]",
|
||||||
|
TextPrompt<T>.GetTypeConverter().ConvertToInvariantString(DefaultValue.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
var markup = builder.ToString().Trim();
|
||||||
|
if (!markup.EndsWith("?", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!markup.EndsWith(":", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
markup += ":";
|
||||||
|
}
|
||||||
|
|
||||||
|
console.Markup(markup + " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to convert the input string to <typeparamref name="T"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The input to convert.</param>
|
||||||
|
/// <param name="result">The result.</param>
|
||||||
|
/// <returns><c>true</c> if the conversion succeeded, otherwise <c>false</c>.</returns>
|
||||||
|
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||||
|
private static bool TryConvert(string input, [MaybeNull] out T result)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = (T)TextPrompt<T>.GetTypeConverter().ConvertFromInvariantString(input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
result = default;
|
||||||
|
#pragma warning restore CS8601 // Possible null reference assignment.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type converter that's used to convert values.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The type converter that's used to convert values.</returns>
|
||||||
|
private static TypeConverter GetTypeConverter()
|
||||||
|
{
|
||||||
|
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||||||
|
if (converter != null)
|
||||||
|
{
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attribute = typeof(T).GetCustomAttribute<TypeConverterAttribute>();
|
||||||
|
if (attribute != null)
|
||||||
|
{
|
||||||
|
var type = Type.GetType(attribute.ConverterTypeName, false, false);
|
||||||
|
if (type != null)
|
||||||
|
{
|
||||||
|
converter = Activator.CreateInstance(type) as TypeConverter;
|
||||||
|
if (converter != null)
|
||||||
|
{
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException("Could not find type converter");
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/Spectre.Console/ValidationResult.cs
Normal file
43
src/Spectre.Console/ValidationResult.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user