Improve command system and add help command

This commit is contained in:
Stone_Red
2024-02-01 18:14:28 +01:00
parent c22478bc7f
commit 0aef65fadc
10 changed files with 231 additions and 79 deletions
+19
View File
@@ -0,0 +1,19 @@
using TextLore.Models;
namespace TextLore.Commands;
public class ClearCommand : Command
{
public override string Name => "clear";
public override string Description => "Clears the console.";
public override string[] Aliases => ["cls"];
public override bool NoHistoryIfNoOutput => true;
public override Task<CommandResult> Execute(ConsoleWriter consoleOutput, string args)
{
consoleOutput.Clear();
return Task.FromResult(CommandResult.Success("Console cleared!"));
}
}
+51
View File
@@ -0,0 +1,51 @@
using TextLore.Models;
namespace TextLore.Commands;
public class HelpCommand(IEnumerable<Command> commands) : Command
{
public override string Name => "help";
public override string Description => "Displays a list of commands and their descriptions.";
public override string Usage => "help <command>";
public override string[] Aliases => new[] { "h", "?" };
public override Task<CommandResult> Execute(ConsoleWriter consoleOutput, string args)
{
if (string.IsNullOrWhiteSpace(args))
{
consoleOutput.WriteLine("Available commands:");
foreach (Command command in commands)
{
consoleOutput.WriteLine($" {command.Name} - {command.Description}");
}
return Task.FromResult(CommandResult.Success());
}
else
{
Command? command = commands.FirstOrDefault(c => c.Name.Equals(args, StringComparison.OrdinalIgnoreCase) || c.Aliases.Contains(args, StringComparer.OrdinalIgnoreCase));
if (command is null)
{
consoleOutput.WriteLine($"Command \"{args[0]}\" not found.");
return Task.FromResult(CommandResult.Failure());
}
else
{
consoleOutput.WriteLine($"Command: {command.Name}");
if (command.Aliases.Length > 0)
{
consoleOutput.WriteLine($"Aliases: {string.Join(", ", command.Aliases)}");
}
consoleOutput.WriteLine($"Description: {command.Description}");
if (!string.IsNullOrWhiteSpace(command.Usage))
{
consoleOutput.WriteLine($"Usage: {command.Usage}");
}
return Task.FromResult(CommandResult.Success());
}
}
}
}
+4 -2
View File
@@ -7,9 +7,11 @@ public class TestCommand : Command
public override string Name => "test";
public override string Description => "A test command.";
public override Task<bool> Execute(ConsoleOutput consoleOutput, string[] args)
public override string[] Aliases => ["tst"];
public override Task<CommandResult> Execute(ConsoleWriter consoleOutput, string args)
{
consoleOutput.WriteLine("This is a test command.");
return Task.FromResult(true);
return Task.FromResult(CommandResult.Success("test"));
}
}
+12 -5
View File
@@ -1,12 +1,19 @@
@page "/"
@using TextLore.Commands
@using TextLore.Models
<PageTitle>Home</PageTitle>
<TextLore.Components.Shared.Console Commands="commands" />
<PageTitle>TextLore</PageTitle>
<Console Name="Main Menu" Commands="commands" HelpCommand="helpCommand" />
@code
{
Command[] commands = [new TestCommand()];
List<Command> commands = [new TestCommand(), new ClearCommand()];
HelpCommand? helpCommand;
protected override void OnInitialized()
{
helpCommand = new HelpCommand(commands);
commands.Add(helpCommand);
}
}
+63 -30
View File
@@ -18,7 +18,7 @@
<EditForm OnSubmit="Execute" autocomplete="off" Model="ConsoleInput" novalidate>
<div id="input-line" class="input-line">
<div class="prompt">
Command >
Command >
</div>
<div>
<InputText @ref="inputText" id="commandline" autocomplete="off" class="cmdline" disabled="@disabled" placeholder="@Placeholder" @bind-Value="@ConsoleInput.Text" />
@@ -26,8 +26,16 @@
</div>
</EditForm>
<pre>
<code>@((MarkupString)running)</code>
<code>@((MarkupString)output)</code>
<code>@currentOutput?.Text</code>
<code>
@foreach (ConsoleOutput output in consoleOutputs.Reverse<ConsoleOutput>())
{
<p>
<span class='header'>@output.Time.ToString("HH:mm") > </span><span class='command'>@output.Command</span>
@output.Text
</p>
}
</code>
</pre>
</div>
</div>
@@ -43,20 +51,16 @@
[Parameter]
public bool ShowDate { get; set; } = true;
public string Placeholder => $"Enter a command{(HelpCommand is null ? "." : ", type 'help' for avaliable commands.")}";
public ConsoleInput ConsoleInput { get; set; } = new();
public ConsoleOutput ConsoleOutput { get; set; } = new();
[Parameter, EditorRequired]
public IEnumerable<Command> Commands { get; set; } = [];
[Parameter]
public Command? HelpCommand { get; set; }
private string output { get; set; } = "";
private string running { get; set; } = "";
private string Placeholder => $"Enter a command{(HelpCommand is null ? "." : ", type 'help' for avaliable commands.")}";
private ConsoleInput ConsoleInput { get; set; } = new();
private List<ConsoleOutput> consoleOutputs = new();
private ConsoleOutput? currentOutput;
private bool disabled { get; set; } = false;
private InputText? inputText;
@@ -68,45 +72,74 @@
}
}
protected override void OnInitialized()
{
ConsoleOutput.OnOutput += WriteOutput;
}
public async Task Execute(EditContext context)
{
Command? command = Commands.FirstOrDefault(c=>c.Name.Equals(ConsoleInput.Text));
command ??= HelpCommand;
string commandName = ConsoleInput.Text.Split(' ')[0];
string commandArgs = new string(ConsoleInput.Text.Skip(commandName.Length).ToArray()).TrimStart(' ');
if(command is null)
Command? command = Commands.FirstOrDefault(c => c.Name.Equals(commandName) || c.Aliases.Contains(commandName));
command ??= HelpCommand?.Name.Equals(commandName) == true || HelpCommand?.Aliases.Contains(commandName) == true ? HelpCommand : null;
if (command is null)
{
running = $"[Failed] Command '{ConsoleInput.Text}' not found!";
ConsoleInput.Text = string.Empty;
await CommandNotFound(commandName);
return;
}
disabled = true;
running = $"<p>";
running += $"<span class='header'>{ConsoleInput.Time.ToString("HH:mm")} > </span><span class='command'>{ConsoleInput.Text}{Environment.NewLine}</span>";
ConsoleWriter consoleWriter = new ConsoleWriter();
ConsoleOutput consoleOutput = new ConsoleOutput();
bool success = await command.Execute(ConsoleOutput, []);
consoleWriter.OnOutput += (_, e) => WriteOutput(consoleOutput, e);
consoleWriter.OnClear += (_, _) => consoleOutputs.Clear();
consoleOutput.Command = ConsoleInput.Text;
currentOutput = consoleOutput;
running += "</p>";
CommandResult commandResult = await command.Execute(consoleWriter, commandArgs);
output = running + output;
currentOutput = new()
{
Text = $"{(commandResult.IsSuccess ? "[Success]" : "[Failed]")} {commandResult.Message}"
};
running = success ? $"[Success]" : $"[Failed]";
if (!command.NoHistoryIfNoOutput || !string.IsNullOrWhiteSpace(consoleOutput.Text))
{
consoleOutputs.Add(consoleOutput);
}
ConsoleInput.Text = string.Empty;
disabled = false;
StateHasChanged();
}
public void WriteOutput(object? sender, ConsoleOutputEventArgs e)
public void WriteOutput(ConsoleOutput consoleOutput, ConsoleOutputEventArgs e)
{
string newOutput = e.Message;
output = newOutput + output;
consoleOutput.Text += e.Message;
StateHasChanged();
}
private async Task CommandNotFound(string commandName)
{
currentOutput = new()
{
Text = $"[Failed] Command '{commandName}' not found!"
};
if(HelpCommand is null)
{
return;
}
ConsoleWriter consoleWriter = new ConsoleWriter();
ConsoleOutput consoleOutput = new ConsoleOutput();
consoleWriter.OnOutput += (_, e) => WriteOutput(consoleOutput, e);
consoleOutput.Command = ConsoleInput.Text;
consoleOutputs.Add(consoleOutput);
CommandResult commandResult = await HelpCommand.Execute(consoleWriter, string.Empty);
ConsoleInput.Text = string.Empty;
}
}
+3
View File
@@ -8,3 +8,6 @@
@using Microsoft.JSInterop
@using TextLore
@using TextLore.Components
@using TextLore.Components.Shared
@using TextLore.Commands
@using TextLore.Models
+2 -1
View File
@@ -6,6 +6,7 @@ public abstract class Command
public abstract string Description { get; }
public virtual string Usage { get; } = string.Empty;
public virtual string[] Aliases { get; } = [];
public virtual bool NoHistoryIfNoOutput { get; } = false;
public abstract Task<bool> Execute(ConsoleOutput consoleOutput, string[] args);
public abstract Task<CommandResult> Execute(ConsoleWriter consoleOutput, string args);
}
+22
View File
@@ -0,0 +1,22 @@
namespace TextLore.Models;
public class CommandResult(string message, bool success)
{
public string Message { get; } = message;
public bool IsSuccess { get; } = success;
public static CommandResult Success(string message = "")
{
return new(message, true);
}
public static CommandResult Failure(string message = "")
{
return new(message, false);
}
public static implicit operator CommandResult(string message)
{
return Success(message);
}
}
+3 -41
View File
@@ -2,45 +2,7 @@
public class ConsoleOutput
{
public event EventHandler<ConsoleOutputEventArgs>? OnOutput;
public void Write(string message, ConsoleMessageType consoleMessageType = ConsoleMessageType.Default)
{
OnOutput?.Invoke(this, new(message, consoleMessageType));
}
public void WriteLine(string message)
{
Write(message + Environment.NewLine);
}
public void WriteError(string message)
{
Write($"[ERROR] {message}", ConsoleMessageType.Error);
}
public void WriteErrorLine(string message)
{
WriteError(message + Environment.NewLine);
}
public void WriteWarning(string message)
{
Write($"[WARNING] {message}", ConsoleMessageType.Warning);
}
public void WriteWarningLine(string message)
{
WriteWarning(message + Environment.NewLine);
}
public void WriteInfo(string message)
{
Write($"[INFO] {message}", ConsoleMessageType.Info);
}
public void WriteInfoLine(string message)
{
WriteInfo(message + Environment.NewLine);
}
public string Text { get; set; } = string.Empty;
public string Command { get; set; } = string.Empty;
public DateTime Time { get; } = DateTime.UtcNow;
}
+52
View File
@@ -0,0 +1,52 @@
namespace TextLore.Models;
public class ConsoleWriter
{
public event EventHandler<ConsoleOutputEventArgs>? OnOutput;
public event EventHandler<EventArgs>? OnClear;
public void Write(string message, ConsoleMessageType consoleMessageType = ConsoleMessageType.Default)
{
OnOutput?.Invoke(this, new(message, consoleMessageType));
}
public void WriteLine(string message)
{
Write(message + Environment.NewLine);
}
public void WriteError(string message)
{
Write($"[ERROR] {message}", ConsoleMessageType.Error);
}
public void WriteErrorLine(string message)
{
WriteError(message + Environment.NewLine);
}
public void WriteWarning(string message)
{
Write($"[WARNING] {message}", ConsoleMessageType.Warning);
}
public void WriteWarningLine(string message)
{
WriteWarning(message + Environment.NewLine);
}
public void WriteInfo(string message)
{
Write($"[INFO] {message}", ConsoleMessageType.Info);
}
public void WriteInfoLine(string message)
{
WriteInfo(message + Environment.NewLine);
}
public void Clear()
{
OnClear?.Invoke(this, EventArgs.Empty);
}
}