89 lines
3.1 KiB
C#
89 lines
3.1 KiB
C#
namespace BotPages.Core.Routing;
|
||
|
||
using System.Text.RegularExpressions;
|
||
|
||
|
||
/// <summary>
|
||
/// Реестр команд, доступных из любого места.
|
||
/// </summary>
|
||
internal sealed class CommandsRegistry
|
||
{
|
||
private readonly List<Command> _commands = new();
|
||
|
||
public List<Command> Commands => _commands;
|
||
|
||
/// <summary>
|
||
/// Зарегистрировать команду, ведущую на страницу.
|
||
/// </summary>
|
||
public CommandsRegistry Map<TPage>(string commandTemplate, bool publish = false, string? description = null) where TPage : Page
|
||
{
|
||
var pattern = ToRegex(commandTemplate);
|
||
|
||
return Map(commandTemplate, (ctx, args, ct) => ctx.Navigation.GoToAsync<TPage>(ctx, ct), publish, description);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Зарегистрировать команду с кастомным обработчиком.
|
||
/// </summary>
|
||
public CommandsRegistry Map(string commandTemplate, CommandHandler handler, bool publish = false, string? description = null)
|
||
{
|
||
var pattern = ToRegex(commandTemplate);
|
||
_commands.Add(new Command()
|
||
{
|
||
Name = ToCommandName(commandTemplate),
|
||
Pattern = pattern,
|
||
Handler = handler,
|
||
Publish = publish,
|
||
Description = string.IsNullOrWhiteSpace(description) ? null : description
|
||
});
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Попробовать выполнить команду.
|
||
/// </summary>
|
||
public bool TryDispatch(PageContext ctx, string command, CancellationToken ct, out Task? task)
|
||
{
|
||
foreach (var cmd in _commands)
|
||
{
|
||
var match = cmd.Pattern.Match(command);
|
||
if (match.Success)
|
||
{
|
||
// Собираем именованные группы (без числовых)
|
||
var args = cmd.Pattern.GetGroupNames()
|
||
.Where(n => !int.TryParse(n, out _))
|
||
.ToDictionary(n => n, n => match.Groups[n].Value);
|
||
|
||
task = cmd.Handler(ctx, args, ct);
|
||
return true;
|
||
}
|
||
}
|
||
task = null;
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Универсальный парсер шаблонов: /cmd {a} {b?} {c}
|
||
/// </summary>
|
||
private static Regex ToRegex(string template)
|
||
{
|
||
var escaped = Regex.Escape(template)
|
||
.Replace("\\{", "{").Replace("\\}", "}");
|
||
|
||
var pattern = "^" + Regex.Replace(escaped, @"\{(\w+)(\?)?\}", m =>
|
||
{
|
||
var name = m.Groups[1].Value;
|
||
var optional = m.Groups[2].Success;
|
||
return optional ? $"(?<{name}>\\S+)?" : $"(?<{name}>\\S+)";
|
||
}) + "$";
|
||
|
||
return new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||
}
|
||
|
||
private static string ToCommandName(string template)
|
||
{
|
||
// Простейшее преобразование шаблона: "/open {page} {id?}" -> "/open"
|
||
return template.Split(" ", StringSplitOptions.RemoveEmptyEntries).First().ToLowerInvariant();
|
||
}
|
||
}
|