namespace BotPages.Core.Routing;
using System.Text.RegularExpressions;
///
/// Реестр команд, доступных из любого места.
///
internal sealed class CommandsRegistry
{
private readonly List _commands = new();
public List Commands => _commands;
///
/// Зарегистрировать команду, ведущую на страницу.
///
public CommandsRegistry Map(string commandTemplate, bool publish = false, string? description = null) where TPage : Page
{
return Map(commandTemplate, (ctx, args, ct) => ctx.Navigation.GoToAsync(ctx, ct), publish, description);
}
///
/// Зарегистрировать команду с кастомным обработчиком.
///
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;
}
///
/// Попробовать выполнить команду.
///
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;
}
///
/// Универсальный парсер шаблонов: /cmd {a} {b?} {c}
///
private static Regex ToRegex(string template)
{
// Заменяем все {name} и {name?} на регулярные группы
var pattern = "^" + Regex.Replace(template, @"\s*\{(\w+)(\?)?\}", m =>
{
var name = m.Groups[1].Value;
var optional = m.Groups[2].Success;
var argPattern = $"(?:\"(?<{name}>[^\"]+)\"|(?<{name}>\\S+))";
if (optional)
{
// необязательный параметр: пробел + значение целиком необязательны
return $"(?:\\s+{argPattern})?";
}
else
{
// обязательный параметр: пробел обязателен
return $"\\s+{argPattern}";
}
}) + "\\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();
}
}