Add support for fake input in asciicast recordings

* Fixes a bug with `SelectionPrompt` and page size.
* Allow `IAnsiConsoleInput` to return `null`.
This commit is contained in:
Patrik Svensson
2021-05-24 02:36:14 +02:00
committed by Phil Scott
parent 46abadaccb
commit 450d87f5d3
56 changed files with 1314 additions and 1166 deletions

View File

@ -0,0 +1,33 @@
using System;
using Spectre.Console;
using Generator.Commands;
using System.Threading;
namespace DocExampleGenerator
{
internal static class AnsiConsoleExtensions
{
/// <summary>
/// Displays something via AnsiConsole, waits a bit and then simulates typing based on the input. If the console
/// doesn't have the focus this will just type into whatever window does so watch the alt-tab.
/// </summary>
/// <param name="console"></param>
/// <param name="action">The display action.</param>
/// <param name="input">The characters to type. ↑ for an up arrow, ↓ for down arrow, ↲ for a return and ¦ for a pause.</param>
/// <param name="initialDelayMs">How long to delay before typing. This should be at least 100ms because we won't check if the prompt has displayed before simulating typing.</param>
/// <param name="keypressDelayMs">Delay between keypresses. There will be a bit of randomness between each keypress +/- 20% of this value.</param>
public static void DisplayThenType(this IAnsiConsole console, Action<IAnsiConsole> action, string input, int initialDelayMs = 500, int keypressDelayMs = 200)
{
if (console is not AsciiCastConsole asciiConsole)
{
throw new InvalidOperationException("Not an ASCII cast console");
}
asciiConsole.Input.PushText(input, keypressDelayMs);
Thread.Sleep(initialDelayMs);
action(console);
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using Spectre.Console;
using Spectre.Console.Rendering;
namespace Generator.Commands
{
public sealed class AsciiCastConsole : IAnsiConsole
{
private readonly IAnsiConsole _console;
private readonly AsciiCastInput _input;
public Profile Profile => _console.Profile;
public IAnsiConsoleCursor Cursor => _console.Cursor;
IAnsiConsoleInput IAnsiConsole.Input => _input;
public AsciiCastInput Input => _input;
public IExclusivityMode ExclusivityMode => _console.ExclusivityMode;
public RenderPipeline Pipeline => _console.Pipeline;
public AsciiCastConsole(IAnsiConsole console)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_input = new AsciiCastInput();
}
public void Clear(bool home)
{
_console.Clear(home);
}
public void Write(IRenderable renderable)
{
_console.Write(renderable);
}
}
}

View File

@ -0,0 +1,15 @@
using Spectre.Console;
namespace Generator.Commands
{
public static class AsciiCastExtensions
{
public static AsciiCastOut WrapWithAsciiCastRecorder(this IAnsiConsole ansiConsole)
{
AsciiCastOut castRecorder = new(ansiConsole.Profile.Out);
ansiConsole.Profile.Out = castRecorder;
return castRecorder;
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Spectre.Console;
namespace Generator.Commands
{
public sealed class AsciiCastInput : IAnsiConsoleInput
{
private readonly Queue<(ConsoleKeyInfo?, int)> _input;
private readonly Random _random = new Random();
public AsciiCastInput()
{
_input = new Queue<(ConsoleKeyInfo?, int)>();
}
public void PushText(string input, int keypressDelayMs)
{
if (input is null)
{
throw new ArgumentNullException(nameof(input));
}
foreach (var character in input)
{
PushCharacter(character, keypressDelayMs);
}
}
public void PushTextWithEnter(string input, int keypressDelayMs)
{
PushText(input, keypressDelayMs);
PushKey(ConsoleKey.Enter, keypressDelayMs);
}
public void PushCharacter(char input, int keypressDelayMs)
{
var delay = keypressDelayMs + _random.Next((int)(keypressDelayMs * -.2), (int)(keypressDelayMs * .2));
switch (input)
{
case '↑':
PushKey(ConsoleKey.UpArrow, keypressDelayMs);
break;
case '↓':
PushKey(ConsoleKey.DownArrow, keypressDelayMs);
break;
case '↲':
PushKey(ConsoleKey.Enter, keypressDelayMs);
break;
case '¦':
_input.Enqueue((null, delay));
break;
default:
var control = char.IsUpper(input);
_input.Enqueue((new ConsoleKeyInfo(input, (ConsoleKey)input, false, false, control), delay));
break;
}
}
public void PushKey(ConsoleKey input, int keypressDelayMs)
{
var delay = keypressDelayMs + _random.Next((int)(keypressDelayMs * -.2), (int)(keypressDelayMs * .2));
_input.Enqueue((new ConsoleKeyInfo((char)input, input, false, false, false), delay));
}
public ConsoleKeyInfo? ReadKey(bool intercept)
{
if (_input.Count == 0)
{
throw new InvalidOperationException("No input available.");
}
var result = _input.Dequeue();
Thread.Sleep(result.Item2);
return result.Item1;
}
}
}

View File

@ -0,0 +1,95 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.Json;
using Spectre.Console;
namespace Generator.Commands
{
public class AsciiCastOut : IAnsiConsoleOutput
{
private sealed class AsciiCastWriter : TextWriter
{
private readonly TextWriter _wrappedTextWriter;
private readonly StringBuilder _builder = new StringBuilder();
private int? _firstTick;
public AsciiCastWriter(TextWriter wrappedTextWriter)
{
_wrappedTextWriter = wrappedTextWriter;
}
public override void Write(string value)
{
if (value == null)
{
return;
}
Append(value);
_wrappedTextWriter.Write(value);
base.Write(value);
}
public override Encoding Encoding => _wrappedTextWriter.Encoding;
private void Append(string value)
{
var tick = 0m;
if (_firstTick.HasValue)
{
tick = Environment.TickCount - _firstTick.Value;
}
else
{
_firstTick = Environment.TickCount;
}
tick /= 1000m;
_builder.Append('[')
.AppendFormat(CultureInfo.InvariantCulture, "{0}", tick)
.Append(", \"o\", \"").Append(JsonEncodedText.Encode(value)).AppendLine("\"]");
}
public string GetJsonAndClearBuffer()
{
var json = _builder.ToString();
// reset the buffer and also reset the first tick count
_builder.Clear();
_firstTick = null;
return json;
}
}
private readonly IAnsiConsoleOutput _wrappedAnsiConsole;
private readonly AsciiCastWriter _asciiCastWriter;
public AsciiCastOut(IAnsiConsoleOutput wrappedAnsiConsole)
{
_wrappedAnsiConsole = wrappedAnsiConsole ?? throw new ArgumentNullException(nameof(wrappedAnsiConsole));
_asciiCastWriter = new AsciiCastWriter(_wrappedAnsiConsole.Writer);
}
public TextWriter Writer => _asciiCastWriter;
public bool IsTerminal => _wrappedAnsiConsole.IsTerminal;
public int Width => _wrappedAnsiConsole.Width;
public int Height => _wrappedAnsiConsole.Height;
public void SetEncoding(Encoding encoding)
{
_wrappedAnsiConsole.SetEncoding(encoding);
}
public string GetCastJson(string title, int? width = null, int? height = null)
{
var header = $"{{\"version\": 2, \"width\": {width ?? _wrappedAnsiConsole.Width}, \"height\": {height ?? _wrappedAnsiConsole.Height}, \"title\": \"{JsonEncodedText.Encode(title)}\", \"env\": {{\"TERM\": \"Spectre.Console\"}}}}";
return $"{header}{Environment.NewLine}{_asciiCastWriter.GetJsonAndClearBuffer()}{Environment.NewLine}";
}
}
}

View File

@ -0,0 +1,20 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class BarChartSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 5);
public override void Run(IAnsiConsole console)
{
console.Write(new BarChart()
.Width(60)
.Label("[green bold underline]Number of fruits[/]")
.CenterLabel()
.AddItem("Apple", 12, Color.Yellow)
.AddItem("Orange", 54, Color.Green)
.AddItem("Banana", 33, Color.Red));
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Text;
using Spectre.Console;
namespace Generator.Commands.Samples
{
public abstract class BaseSample
{
public abstract void Run(IAnsiConsole console);
public virtual string Name() => PascalToKebab(GetType().Name.Replace("Sample",""));
public virtual (int Cols, int Rows) ConsoleSize => (82, 24);
public virtual IEnumerable<(string Name, Action<Capabilities> CapabilitiesAction)> GetCapabilities()
{
return new (string Name, Action<Capabilities> CapabilitiesAction)[]
{
("plain", capabilities =>
{
capabilities.Unicode = false;
capabilities.Ansi = true;
capabilities.Interactive = true;
capabilities.Legacy = false;
capabilities.Links = false;
capabilities.ColorSystem = ColorSystem.Legacy;
}),
("rich", capabilities =>
{
capabilities.Unicode = true;
capabilities.Ansi = true;
capabilities.Interactive = true;
capabilities.Legacy = false;
capabilities.Links = false;
capabilities.ColorSystem = ColorSystem.TrueColor;
}),
};
}
private string PascalToKebab(ReadOnlySpan<char> input)
{
var sb = new StringBuilder();
var previousUpper = true;
foreach (var chr in input)
{
if (char.IsUpper(chr) && previousUpper == false)
{
sb.Append('-');
previousUpper = true;
}
else
{
previousUpper = false;
}
sb.Append(char.ToLower(chr));
}
return sb.ToString();
}
}
}

View File

@ -0,0 +1,40 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal abstract class BaseCalendarSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 12);
}
internal class CalendarSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console) => console.Write(new Calendar(2020,10));
}
internal class CalendarCultureSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console) => console.Write(new Calendar(2020,10).Culture("sv-SE"));
}
internal class CalendarHeader : BaseCalendarSample
{
public override void Run(IAnsiConsole console)
{
var calendar = new Calendar(2020,10);
calendar.HeaderStyle(Style.Parse("blue bold"));
console.Write(calendar);
}
}
internal class CalendarHighlightSample : BaseCalendarSample
{
public override void Run(IAnsiConsole console)
{
var calendar = new Calendar(2020, 10).HighlightStyle(Style.Parse("yellow bold"));
calendar.AddCalendarEvent(2020, 10, 11);
console.Write(calendar);
}
}
}

View File

@ -0,0 +1,27 @@
using SixLabors.ImageSharp.Processing;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class CanvasImageSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var image = new CanvasImage("../../../examples/Console/Canvas/cake.png");
image.MaxWidth(16);
console.Write(image);
}
}
internal class CanvasImageManipulationSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var image = new CanvasImage("../../../examples/Console/Canvas/cake.png");
image.MaxWidth(24);
image.BilinearResampler();
image.Mutate(ctx => ctx.Grayscale().Rotate(-45).EntropyCrop());
console.Write(image);
}
}
}

View File

@ -0,0 +1,29 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class CanvasSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var canvas = new Canvas(16, 16);
// Draw some shapes
for(var i = 0; i < canvas.Width; i++)
{
// Cross
canvas.SetPixel(i, i, Color.White);
canvas.SetPixel(canvas.Width - i - 1, i, Color.White);
// Border
canvas.SetPixel(i, 0, Color.Red);
canvas.SetPixel(0, i, Color.Green);
canvas.SetPixel(i, canvas.Height - 1, Color.Blue);
canvas.SetPixel(canvas.Width - 1, i, Color.Yellow);
}
// Render the canvas
console.Write(canvas);
}
}
}

View File

@ -0,0 +1,78 @@
using System;
using System.Security.Authentication;
using Spectre.Console;
namespace Generator.Commands.Samples
{
public class Exceptions
{
internal abstract class BaseExceptionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 12);
protected readonly Exception Exception = null!;
protected BaseExceptionSample()
{
try
{
DoMagic(42, null);
}
catch (Exception ex)
{
Exception = ex;
}
}
}
internal class DefaultExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console) => console.WriteException(Exception);
}
internal class ShortenedExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console) => console.WriteException(Exception, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks);
}
internal class CustomColorsExceptionSample : BaseExceptionSample
{
public override void Run(IAnsiConsole console)
{
console.WriteException(Exception, new ExceptionSettings
{
Format = ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks,
Style = new ExceptionStyle
{
Exception = new Style().Foreground(Color.Grey),
Message = new Style().Foreground(Color.White),
NonEmphasized = new Style().Foreground(Color.Cornsilk1),
Parenthesis = new Style().Foreground(Color.Cornsilk1),
Method = new Style().Foreground(Color.Red),
ParameterName = new Style().Foreground(Color.Cornsilk1),
ParameterType = new Style().Foreground(Color.Red),
Path = new Style().Foreground(Color.Red),
LineNumber = new Style().Foreground(Color.Cornsilk1),
}
});
}
}
private static void DoMagic(int foo, string[,] bar)
{
try
{
CheckCredentials(foo, bar);
}
catch(Exception ex)
{
throw new InvalidOperationException("Whaaat?", ex);
}
}
private static void CheckCredentials(int qux, string[,] corgi)
{
throw new InvalidCredentialException("The credentials are invalid.");
}
}
}

View File

@ -0,0 +1,16 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
public class FigletSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (120, 24);
public override void Run(IAnsiConsole console)
{
console.Write(new FigletText("Left aligned").LeftAligned().Color(Color.Red));
console.Write(new FigletText("Centered").Centered().Color(Color.Green));
console.Write(new FigletText("Right aligned").RightAligned().Color(Color.Blue));
}
}
}

View File

@ -0,0 +1,97 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class InputSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var age = 0;
var name = string.Empty;
var sport = string.Empty;
var password = string.Empty;
var color = string.Empty;
console.DisplayThenType(c => name = AskName(c), "Peter F↲");
console.DisplayThenType(c => sport = AskSport(c), "football↲¦¦¦¦Hockey↲");
console.DisplayThenType(c => age = AskAge(c), "Forty↲¦¦¦¦40↲");
console.DisplayThenType(c => password = AskPassword(c), "hunter2↲");
console.DisplayThenType(c => color = AskColor(c), "↲");
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 sport[/]", sport)
.AddRow("[grey]Age[/]", age.ToString())
.AddRow("[grey]Password[/]", password)
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
}
private static string AskName(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftAligned());
var name = console.Ask<string>("What's your [green]name[/]?");
return name;
}
private static string AskSport(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftAligned());
return console.Prompt(
new TextPrompt<string>("What's your [green]favorite sport[/]?")
.InvalidChoiceMessage("[red]That's not a sport![/]")
.DefaultValue("Sport?")
.AddChoice("Soccer")
.AddChoice("Hockey")
.AddChoice("Basketball"));
}
private static int AskAge(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftAligned());
return console.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(),
};
}));
}
private static string AskPassword(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftAligned());
return console.Prompt(
new TextPrompt<string>("Enter [green]password[/]?")
.PromptStyle("red")
.Secret());
}
private static string AskColor(IAnsiConsole console)
{
console.WriteLine();
console.Write(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftAligned());
return console.Prompt(
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
.AllowEmpty());
}
}
}

View File

@ -0,0 +1,84 @@
using System;
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class LiveSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (120, 20);
public override void Run(IAnsiConsole console)
{
var table = new Table();
// Animate
console.Live(table)
.AutoClear(false)
.Overflow(VerticalOverflow.Ellipsis)
.Cropping(VerticalOverflowCropping.Top)
.Start(ctx =>
{
void Update(int delay, Action action)
{
action();
ctx.Refresh();
Thread.Sleep(delay);
}
// Columns
Update(230, () => table.AddColumn("Release date"));
Update(230, () => table.AddColumn("Title"));
Update(230, () => table.AddColumn("Budget"));
Update(230, () => table.AddColumn("Opening Weekend"));
Update(230, () => table.AddColumn("Box office"));
// Rows
Update(70, () => table.AddRow("May 25, 1977", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IV[/]", "$11,000,000", "$1,554,475", "$775,398,007"));
Update(70, () => table.AddRow("May 21, 1980", "[yellow]Star Wars[/] [grey]Ep.[/] [u]V[/]", "$18,000,000", "$4,910,483", "$547,969,004"));
Update(70, () => table.AddRow("May 25, 1983", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VI[/]", "$32,500,000", "$23,019,618", "$475,106,177"));
Update(70, () => table.AddRow("May 19, 1999", "[yellow]Star Wars[/] [grey]Ep.[/] [u]I[/]", "$115,000,000", "$64,810,870", "$1,027,044,677"));
Update(70, () => table.AddRow("May 16, 2002", "[yellow]Star Wars[/] [grey]Ep.[/] [u]II[/]", "$115,000,000", "$80,027,814", "$649,436,358"));
Update(70, () => table.AddRow("May 19, 2005", "[yellow]Star Wars[/] [grey]Ep.[/] [u]III[/]", "$113,000,000", "$108,435,841", "$850,035,635"));
Update(70, () => table.AddRow("Dec 18, 2015", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VII[/]", "$245,000,000", "$247,966,675", "$2,068,223,624"));
Update(70, () => table.AddRow("Dec 15, 2017", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VIII[/]", "$317,000,000", "$220,009,584", "$1,333,539,889"));
Update(70, () => table.AddRow("Dec 20, 2019", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IX[/]", "$245,000,000", "$177,383,864", "$1,074,114,248"));
// Column footer
Update(230, () => table.Columns[2].Footer("$1,633,000,000"));
Update(230, () => table.Columns[3].Footer("$928,119,224"));
Update(400, () => table.Columns[4].Footer("$10,318,030,576"));
// Column alignment
Update(230, () => table.Columns[2].RightAligned());
Update(230, () => table.Columns[3].RightAligned());
Update(400, () => table.Columns[4].RightAligned());
// Column titles
Update(70, () => table.Columns[0].Header("[bold]Release date[/]"));
Update(70, () => table.Columns[1].Header("[bold]Title[/]"));
Update(70, () => table.Columns[2].Header("[red bold]Budget[/]"));
Update(70, () => table.Columns[3].Header("[green bold]Opening Weekend[/]"));
Update(400, () => table.Columns[4].Header("[blue bold]Box office[/]"));
// Footers
Update(70, () => table.Columns[2].Footer("[red bold]$1,633,000,000[/]"));
Update(70, () => table.Columns[3].Footer("[green bold]$928,119,224[/]"));
Update(400, () => table.Columns[4].Footer("[blue bold]$10,318,030,576[/]"));
// Title
Update(500, () => table.Title("Star Wars Movies"));
Update(400, () => table.Title("[[ [yellow]Star Wars Movies[/] ]]"));
// Borders
Update(230, () => table.BorderColor(Color.Yellow));
Update(230, () => table.MinimalBorder());
Update(230, () => table.SimpleBorder());
Update(230, () => table.SimpleHeavyBorder());
// Caption
Update(400, () => table.Caption("[[ [blue]THE END[/] ]]"));
});
}
}
}

View File

@ -0,0 +1,40 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class MultiSelectionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 14);
public override void Run(IAnsiConsole console)
{
console.DisplayThenType(AskFruit, "↓↓ ¦¦↑↑ ¦¦ ¦¦↓ ↓↓↓↓↓ ↓↓↓↓ ¦¦↲");
}
private static void AskFruit(IAnsiConsole console)
{
var favorites = console.Prompt(
new MultiSelectionPrompt<string>()
.PageSize(10)
.Title("What are your [green]favorite fruits[/]?")
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.InstructionsText("[grey](Press [blue]<space>[/] to toggle a fruit, [green]<enter>[/] to accept)[/]")
.AddChoiceGroup("Berries", new[]
{
"Blackcurrant", "Blueberry", "Cloudberry",
"Elderberry", "Honeyberry", "Mulberry"
})
.AddChoices(new[]
{
"Apple", "Apricot", "Avocado", "Banana",
"Cherry", "Cocunut", "Date", "Dragonfruit", "Durian",
"Egg plant", "Fig", "Grape", "Guava",
"Jackfruit", "Jambul", "Kiwano", "Kiwifruit", "Lime", "Lylo",
"Lychee", "Melon", "Nectarine", "Orange", "Olive"
}));
console.MarkupLine("Your selected: [yellow]{0}[/]", string.Join(',', favorites));
}
}
}

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class ProgressSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 10);
public override IEnumerable<(string Name, Action<Capabilities> CapabilitiesAction)> GetCapabilities()
{
yield return ("non-interactive", capabilities =>
{
capabilities.Ansi = false;
capabilities.Interactive = false;
capabilities.Legacy = false;
capabilities.Unicode = true;
capabilities.ColorSystem = ColorSystem.TrueColor;
});
foreach (var capability in base.GetCapabilities())
{
yield return capability;
}
}
public override void Run(IAnsiConsole console)
{
// Show progress
console.Progress()
.AutoClear(false)
.Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn(), new RemainingTimeColumn(), new SpinnerColumn())
.Start(ctx =>
{
var random = new Random(122978);
// Create some tasks
var tasks = CreateTasks(ctx, random);
var warpTask = ctx.AddTask("Going to warp", autoStart: false).IsIndeterminate();
// Wait for all tasks (except the indeterminate one) to complete
while (!ctx.IsFinished)
{
// Increment progress
foreach (var (task, increment) in tasks)
{
task.Increment(random.NextDouble() * increment);
}
// Simulate some delay
Thread.Sleep(100);
}
// Now start the "warp" task
warpTask.StartTask();
warpTask.IsIndeterminate(false);
while (!ctx.IsFinished)
{
warpTask.Increment(12 * random.NextDouble());
// Simulate some delay
Thread.Sleep(100);
}
});
}
private static List<(ProgressTask Task, int Delay)> CreateTasks(ProgressContext progress, Random random)
{
var tasks = new List<(ProgressTask, int)>();
var names = new[]
{
"Retriculating algorithms", "Colliding splines", "Solving quarks", "Folding data structures",
"Rerouting capacitators "
};
for (var i = 0; i < 5; i++)
{
tasks.Add((progress.AddTask(names[i]), random.Next(2, 10)));
}
return tasks;
}
}
}

View File

@ -0,0 +1,21 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class RuleSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (82, 10);
public override void Run(IAnsiConsole console)
{
console.Write(new Rule());
console.WriteLine();
console.Write(new Rule("[blue]Left aligned[/]").LeftAligned().RuleStyle("red"));
console.WriteLine();
console.Write(new Rule("[green]Centered[/]").Centered().RuleStyle("green"));
console.WriteLine();
console.Write(new Rule("[red]Right aligned[/]").RightAligned().RuleStyle("blue"));
console.WriteLine();
}
}
}

View File

@ -0,0 +1,29 @@
using DocExampleGenerator;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class SelectionSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 14);
public override void Run(IAnsiConsole console)
{
console.DisplayThenType(AskFruit, "↓↓↓¦¦¦¦ ");
}
private static void AskFruit(IAnsiConsole console)
{
// Ask for the user's favorite fruit
var fruit = console.Prompt(
new SelectionPrompt<string>()
.Title("What's your [green]favorite fruit[/]?")
.PageSize(10)
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.AddChoices(new [] {"Apple", "Apricot", "Avocado", "Banana", "Blackcurrant", "Blueberry", "Cherry", "Cloudberry", "Cocunut"}));
// Echo the fruit back to the terminal
console.WriteLine($"I agree. {fruit} is tasty!");
}
}
}

View File

@ -0,0 +1,71 @@
using System.Threading;
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class StatusSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (base.ConsoleSize.Cols, 10);
private static void WriteLogMessage(string message)
{
AnsiConsole.MarkupLine($"[grey]LOG:[/] {message}[grey]...[/]");
}
public override void Run(IAnsiConsole console)
{
console.Status()
.AutoRefresh(true)
.Spinner(Spinner.Known.Default)
.Start("[yellow]Initializing warp drive[/]", ctx =>
{
// Initialize
Thread.Sleep(3000);
WriteLogMessage("Starting gravimetric field displacement manifold");
Thread.Sleep(1000);
WriteLogMessage("Warming up deuterium chamber");
Thread.Sleep(2000);
WriteLogMessage("Generating antideuterium");
// Warp nacelles
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.BouncingBar);
ctx.Status("[bold blue]Unfolding warp nacelles[/]");
WriteLogMessage("Unfolding left warp nacelle");
Thread.Sleep(2000);
WriteLogMessage("Left warp nacelle [green]online[/]");
WriteLogMessage("Unfolding right warp nacelle");
Thread.Sleep(1000);
WriteLogMessage("Right warp nacelle [green]online[/]");
// Warp bubble
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Star2);
ctx.Status("[bold blue]Generating warp bubble[/]");
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Star);
ctx.Status("[bold blue]Stabilizing warp bubble[/]");
// Safety
ctx.Spinner(Spinner.Known.Monkey);
ctx.Status("[bold blue]Performing safety checks[/]");
WriteLogMessage("Enabling interior dampening");
Thread.Sleep(2000);
WriteLogMessage("Interior dampening [green]enabled[/]");
// Warp!
Thread.Sleep(3000);
ctx.Spinner(Spinner.Known.Moon);
WriteLogMessage("Preparing for warp");
Thread.Sleep(1000);
for (var warp = 1; warp < 10; warp++)
{
ctx.Status($"[bold blue]Warp {warp}[/]");
Thread.Sleep(500);
}
});
// Done
AnsiConsole.MarkupLine("[bold green]Crusing at Warp 9.8[/]"); }
}
}

View File

@ -0,0 +1,46 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class TableSample : BaseSample
{
public override (int Cols, int Rows) ConsoleSize => (100, 30);
public override void Run(IAnsiConsole console)
{
var simple = new Table()
.Border(TableBorder.Square)
.BorderColor(Color.Red)
.AddColumn(new TableColumn("[u]CDE[/]").Footer("EDC").Centered())
.AddColumn(new TableColumn("[u]FED[/]").Footer("DEF"))
.AddColumn(new TableColumn("[u]IHG[/]").Footer("GHI"))
.AddRow("Hello", "[red]World![/]", "")
.AddRow("[blue]Bonjour[/]", "[white]le[/]", "[red]monde![/]")
.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var second = new Table()
.Border(TableBorder.Rounded)
.BorderColor(Color.Green)
.AddColumn(new TableColumn("[u]Foo[/]"))
.AddColumn(new TableColumn("[u]Bar[/]"))
.AddColumn(new TableColumn("[u]Baz[/]"))
.AddRow("Hello", "[red]World![/]", "")
.AddRow(simple, new Text("Whaaat"), new Text("Lolz"))
.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var table = new Table()
.Centered()
.Border(TableBorder.DoubleEdge)
.Title("TABLE [yellow]TITLE[/]")
.Caption("TABLE [yellow]CAPTION[/]")
.AddColumn(new TableColumn(new Panel("[u]ABC[/]").BorderColor(Color.Red)).Footer("[u]FOOTER 1[/]"))
.AddColumn(new TableColumn(new Panel("[u]DEF[/]").BorderColor(Color.Green)).Footer("[u]FOOTER 2[/]"))
.AddColumn(new TableColumn(new Panel("[u]GHI[/]").BorderColor(Color.Blue)).Footer("[u]FOOTER 3[/]"))
.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/]"), Text.Empty)
.AddRow(second, new Text("Whaaat"), new Text("Lol"))
.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty);
console.Write(table);
}
}
}

View File

@ -0,0 +1,39 @@
using Spectre.Console;
namespace Generator.Commands.Samples
{
internal class TreeSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
// Create the tree
var tree = new Tree("Root")
.Style(Style.Parse("red"))
.Guide(TreeGuide.Line);
// Add some nodes
var foo = tree.AddNode("[yellow]Nest objects like tables[/]");
var table = foo.AddNode(new Table()
.RoundedBorder()
.AddColumn("First")
.AddColumn("Second")
.AddRow("1", "2")
.AddRow("3", "4")
.AddRow("5", "6"));
table.AddNode("[blue]with[/]");
table.AddNode("[blue]multiple[/]");
table.AddNode("[blue]children too[/]");
var bar = tree.AddNode("Any IRenderable can be nested, such as [yellow]calendars[/]");
bar.AddNode(new Calendar(2020, 12)
.Border(TableBorder.Rounded)
.BorderStyle(new Style(Color.Green3_1))
.AddCalendarEvent(2020, 12, 12)
.HideHeader());
console.Write(tree);
}
}
}