using Microsoft.Extensions.Options; namespace MeetingAssistant.Transcription; public sealed class MarkdownDictationWordStore : IDictationWordStore { private readonly MeetingAssistantOptions options; public MarkdownDictationWordStore(IOptions options) { this.options = options.Value; } public async Task> ReadWordsAsync(CancellationToken cancellationToken) { return await ReadWordsAsync(options, cancellationToken); } public async Task> ReadWordsAsync( MeetingAssistantOptions options, CancellationToken cancellationToken) { var path = GetPath(options); if (!File.Exists(path)) { return []; } var lines = await File.ReadAllLinesAsync(path, cancellationToken); return Normalize(lines); } public async Task> AddWordAsync(string word, CancellationToken cancellationToken) { return await AddWordAsync(word, options, cancellationToken); } public async Task> AddWordAsync( string word, MeetingAssistantOptions options, CancellationToken cancellationToken) { var path = GetPath(options); Directory.CreateDirectory(Path.GetDirectoryName(path)!); var existing = File.Exists(path) ? await File.ReadAllLinesAsync(path, cancellationToken) : []; var words = Normalize(existing.Append(word)); await File.WriteAllTextAsync( path, string.Join(Environment.NewLine, words.Select(value => $"- {value}")) + Environment.NewLine, cancellationToken); return words; } private static string GetPath(MeetingAssistantOptions options) { return VaultPath.Resolve(options.Vault, options.Vault.DictationWordsPath); } internal static IReadOnlyList Normalize(IEnumerable lines) { return lines .Select(ParseLine) .Where(word => !string.IsNullOrWhiteSpace(word)) .Distinct(StringComparer.OrdinalIgnoreCase) .Order(StringComparer.OrdinalIgnoreCase) .ToList(); } private static string ParseLine(string line) { var trimmed = line.Trim(); if (trimmed.StartsWith("- ", StringComparison.Ordinal) || trimmed.StartsWith("* ", StringComparison.Ordinal)) { trimmed = trimmed[2..].Trim(); } return trimmed.Trim('`', '"', '\'').Trim(); } }