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 { var pattern = ToRegex(commandTemplate); 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(); } }