using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace MeetingAssistant.Workflow; public interface IMeetingWorkflowRulesProvider { Task> GetRulesAsync( MeetingAssistantOptions options, CancellationToken cancellationToken); } public sealed class FileMeetingWorkflowRulesProvider : IMeetingWorkflowRulesProvider { private readonly ILogger logger; private readonly IDeserializer yamlDeserializer = new DeserializerBuilder() .WithNamingConvention(UnderscoredNamingConvention.Instance) .IgnoreUnmatchedProperties() .Build(); public FileMeetingWorkflowRulesProvider(ILogger logger) { this.logger = logger; } public async Task> GetRulesAsync( MeetingAssistantOptions options, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(options.Automation.RulesPath)) { return []; } var path = ResolvePath(options.Automation.RulesPath); if (!File.Exists(path)) { logger.LogDebug("Meeting workflow rules file {RulesPath} does not exist", path); return []; } var yaml = await File.ReadAllTextAsync(path, cancellationToken); if (string.IsNullOrWhiteSpace(yaml)) { return []; } var rulesFile = yamlDeserializer.Deserialize(yaml) ?? new MeetingWorkflowRulesFile(); return rulesFile.Rules; } private static string ResolvePath(string configuredPath) { var expanded = Environment.ExpandEnvironmentVariables(configuredPath); return Path.IsPathRooted(expanded) ? Path.GetFullPath(expanded) : Path.GetFullPath(expanded); } }