diff --git a/.vscode/HelperMarkdown.cs b/.vscode/HelperMarkdown.cs deleted file mode 100644 index b73db84..0000000 --- a/.vscode/HelperMarkdown.cs +++ /dev/null @@ -1,1186 +0,0 @@ -using File_Folder_Helper.Models; -using Microsoft.Extensions.Logging; -using System.Collections.ObjectModel; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace File_Folder_Helper.Helpers; - -internal static partial class HelperMarkdown -{ - - private record Record(string Source, string? StartAt, string? Destination); - - private record MarkdownFileAndLines(MarkdownFile MarkdownFile, string[] Lines); - - private record MarkdownFileH1AndRelativePath(MarkdownFile? MarkdownFile, string[]? Lines, string? H1, string? RelativePath); - - [JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] - [JsonSerializable(typeof(Dictionary))] - internal partial class DictionaryStringAndJsonElementSourceGenerationContext : JsonSerializerContext - { - } - - private static Record GetRecord(List args) - { - Record result; - string? startAt = null; - string? destination = null; - for (int i = 1; i < args.Count; i++) - { - if (args[i].Length == 2 && i + 1 < args.Count) - { - if (args[i][1] == 's') - startAt = Path.GetFullPath(args[i + 1]); - else if (args[i][1] == 'd') - destination = Path.GetFullPath(args[i + 1]); - i++; - } - } - if (startAt is not null && !Directory.Exists(startAt)) - throw new Exception($"Start at directory <{startAt}> doesn't exist!"); - if (destination is not null) - { - string? root = Path.GetPathRoot(destination); - if (root is null || !Directory.Exists(root)) - throw new NotSupportedException($"This method requires frontMatterYamlLines valid -d path <{root}>!"); - if (!Directory.Exists(destination)) - _ = Directory.CreateDirectory(destination); - } - result = new(Path.GetFullPath(args[0]), startAt, destination); - return result; - } - - /// - /// Determines files text file's encoding by analyzing its byte order mark (BOM). - /// Defaults to ASCII when detection of the text file's endianness fails. - /// - /// The text file to analyze. - /// The detected encoding. - internal static Encoding? GetEncoding(string filename) - { - Encoding? result; - byte[] bom = new byte[4]; - using FileStream file = new(filename, FileMode.Open, FileAccess.Read); - _ = file.Read(bom, 0, 4); - if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) -#pragma warning disable SYSLIB0001 - result = Encoding.UTF7; -#pragma warning restore SYSLIB0001 - if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) - result = Encoding.UTF8; - if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0) - result = Encoding.UTF32; //UTF-32LE - if (bom[0] == 0xff && bom[1] == 0xfe) - result = Encoding.Unicode; //UTF-16LE - if (bom[0] == 0xfe && bom[1] == 0xff) - result = Encoding.BigEndianUnicode; //UTF-16BE - if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) - result = new UTF32Encoding(true, true); //UTF-32BE - else - result = null; - return result; - } - - internal static string[] GetFiles(AppSettings appSettings, string directory) - { - string[] results = Directory.GetFiles(directory, "*.md", SearchOption.AllDirectories). - Where(l => !appSettings.ExcludeDirectoryNames.Any(m => l.Contains(m))).ToArray(); - return results; - } - - private static (string type, string h1) GetTypeAndH1(AppSettings appSettings, string h1, List lines, LineNumber lineNumber) - { - string type = lineNumber.Type is null ? appSettings.DefaultNoteType : lines[lineNumber.Type.Value].Replace("type: ", string.Empty); - string h1FromFile = lineNumber.H1 is null ? h1 : lines[lineNumber.H1.Value][2..]; - return (type, h1FromFile); - } - - internal static (List, LineNumber) GetStatusAndFrontMatterYamlEndLineNumbers(FileInfo fileInfo) - { - string line; - int? h1LineNumber = null; - int? typeLineNumber = null; - int? statusLineNumber = null; - int? createdLineNumber = null; - int? updatedLineNumber = null; - int? frontMatterYamlEndLineNumber = null; - Encoding? encoding = GetEncoding(fileInfo.FullName) ?? Encoding.Default; - string[] lines = File.ReadAllLines(fileInfo.FullName, encoding); - for (int i = 1; i < lines.Length; i++) - { - line = lines[i]; - if (line.Length < 3) - continue; - if (line[..3] == "---") - { - frontMatterYamlEndLineNumber = i; - continue; - } - if (line.Length > 6 && line[..6] == "type: ") - { - typeLineNumber = i; - continue; - } - if (line.Length > 8 && line[..8] == "status: ") - { - statusLineNumber = i; - continue; - } - if (line.Length > 9 && line[..9] == "created: ") - { - createdLineNumber = i; - continue; - } - if (line.Length > 9 && line[..9] == "updated: ") - { - updatedLineNumber = i; - continue; - } - if (h1LineNumber is null && line.Length > 2 && line[..2] == "# ") - { - h1LineNumber = i; - continue; - } - } - LineNumber lineNumber = new(createdLineNumber, - h1LineNumber, - frontMatterYamlEndLineNumber, - statusLineNumber, - typeLineNumber, - updatedLineNumber); - return (lines.ToList(), lineNumber); - } - - private static ReadOnlyDictionary GetRelativeToCollection(AppSettings appSettings, Record record, string[] files, bool force) - { - Dictionary results = new(); - string h1; - string key; - string type; - FileInfo fileInfo; - List lines; - LineNumber lineNumber; - MarkdownFile markdownFile; - string fileNameWithoutExtension; - foreach (string file in files) - { - fileInfo = new(file); - if (fileInfo.DirectoryName is null) - continue; - key = Path.GetRelativePath(record.Source, file); - (lines, lineNumber) = GetStatusAndFrontMatterYamlEndLineNumbers(fileInfo); - fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName); - h1 = fileNameWithoutExtension.ToLower().Replace("%20", "-").Replace(' ', '-'); - if (lines.Any()) - (type, h1) = GetTypeAndH1(appSettings, h1, lines, lineNumber); - else - { - type = appSettings.DefaultNoteType; - File.WriteAllLines(file, new string[] { "---", $"type: \"{type}\"", "---", string.Empty, $"# {h1}" }); - lines = File.ReadAllLines(file).ToList(); - } - markdownFile = new(file, fileInfo.DirectoryName, fileInfo.Name, fileNameWithoutExtension, fileInfo.Extension, fileInfo.CreationTime, fileInfo.LastWriteTime, lineNumber, type, h1); - if (force || record.StartAt is null || file.StartsWith(record.StartAt)) - results.Add(key, new(markdownFile, lines.ToArray())); - else - results.Add(key, new(markdownFile, Array.Empty())); - } - return new(results); - } - - private static ReadOnlyDictionary GetRelativeToCollection(AppSettings appSettings, Record record, bool force = false) - { - ReadOnlyDictionary results; - string[] files = GetFiles(appSettings, record.Source); - results = GetRelativeToCollection(appSettings, record, files, force); - return new(results); - } - - private static int SetFrontMatterAndH1(AppSettings appSettings, ReadOnlyDictionary relativeToCollection) - { - int result = 0; - List results = new(); - string h1Line; - string[] lines; - string typeLine; - string createdLine; - string updatedLine; - DateTime creationDateTime; - MarkdownFile markdownFile; - string createdLineCompare; - string updatedLineCompare; - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - results.Clear(); - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - results.AddRange(lines); - creationDateTime = markdownFile.CreationDateTime > markdownFile.LastWriteDateTime ? markdownFile.LastWriteDateTime : markdownFile.CreationDateTime; - typeLine = $"type: \"{appSettings.DefaultNoteType}\""; - h1Line = $"# {markdownFile.FileNameWithoutExtension}"; - createdLineCompare = $"created: \"{creationDateTime.ToUniversalTime():yyyy-MM-dd}T"; - createdLine = $"created: \"{creationDateTime.ToUniversalTime():yyyy-MM-ddTHH:mm:ss.fffZ}\""; - updatedLineCompare = $"updated: \"{markdownFile.LastWriteDateTime.ToUniversalTime():yyyy-MM-dd}T"; - updatedLine = $"updated: \"{markdownFile.LastWriteDateTime.ToUniversalTime():yyyy-MM-ddTHH:mm:ss.fffZ}\""; - if (markdownFile.LineNumber.FrontMatterYamlEnd is null) - { - if (markdownFile.LineNumber.H1 is null) - { - results.Insert(0, string.Empty); - results.Insert(0, h1Line); - results.Insert(0, string.Empty); - } - results.Insert(0, "---"); - results.Insert(0, updatedLine); - results.Insert(0, createdLine); - results.Insert(0, typeLine); - results.Insert(0, "---"); - } - else - { - if (markdownFile.LineNumber.H1 is null) - { - results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value + 1, string.Empty); - results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value + 1, h1Line); - results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value + 1, string.Empty); - } - if (markdownFile.LineNumber.Type is null) - results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value, typeLine); - if (markdownFile.LineNumber.Updated is null) - results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value, updatedLine); - else - { - if (results[markdownFile.LineNumber.Updated.Value].Contains('$')) - continue; - if (results[markdownFile.LineNumber.Updated.Value][..updatedLineCompare.Length] == updatedLineCompare) - continue; - results[markdownFile.LineNumber.Updated.Value] = updatedLine; - } - if (markdownFile.LineNumber.Created is null) - results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value, createdLine); - else if (results[markdownFile.LineNumber.Created.Value][..createdLineCompare.Length] != createdLineCompare) - results[markdownFile.LineNumber.Created.Value] = createdLine; - } - File.WriteAllLines(markdownFile.File, results); - File.SetLastWriteTime(markdownFile.File, markdownFile.LastWriteDateTime); - result += 1; - } - return result; - } - - private static List GetFrontMatterLines(string[] parsedLines) - { - List results = new(); - string afterTrim; - string[] segments; - StringBuilder stringBuilder = new(); - for (int i = 0; i < parsedLines.Length; i++) - { - afterTrim = parsedLines[i].Trim(); - if (string.IsNullOrEmpty(afterTrim) || afterTrim[0] is '{' or '}') - continue; - segments = afterTrim.Split(": "); - if (segments.Length != 2) - { - if (results[^1][^1] == '[') - { - _ = stringBuilder.Clear(); - _ = stringBuilder.Append(results[^1]); - results.RemoveAt(results.Count - 1); - for (int j = i; j < parsedLines.Length; j++) - { - i = j; - afterTrim = parsedLines[j].Trim(); - if (afterTrim == "],") - _ = stringBuilder.Append(afterTrim[..^1]); - else if (afterTrim[^1] == ',') - _ = stringBuilder.Append(afterTrim).Append(' '); - else - _ = stringBuilder.Append(afterTrim); - if (afterTrim is "]" or "],") - { - results.Add(stringBuilder.ToString()); - break; - } - } - continue; - } - results.Clear(); - break; - } - if (afterTrim[^1] != ',') - results.Add(afterTrim[1..].Replace("\": ", ": ")); - else - results.Add(afterTrim[1..^1].Replace("\": ", ": ")); - } - return results; - } - - private static (string?, Dictionary?, List) Get(List jsonLines) - { - string? result; - List results; - Dictionary? keyValuePairs; - string jsonLinesLast = jsonLines[^1]; - jsonLines.RemoveAt(jsonLines.Count - 1); - jsonLines.Add(jsonLinesLast[..^1]); - jsonLines.Insert(0, "{"); - jsonLines.Add("}"); - result = string.Join(Environment.NewLine, jsonLines); - keyValuePairs = JsonSerializer.Deserialize(result, DictionaryStringAndJsonElementSourceGenerationContext.Default.DictionaryStringJsonElement); - if (keyValuePairs is null) - throw new NullReferenceException(nameof(keyValuePairs)); - result = JsonSerializer.Serialize(keyValuePairs, DictionaryStringAndJsonElementSourceGenerationContext.Default.DictionaryStringJsonElement); - string[] parsedLines = result.Split(Environment.NewLine).ToArray(); - results = GetFrontMatterLines(parsedLines); - if (!results.Any()) - { - result = null; - keyValuePairs = null; - } - return (result, keyValuePairs, results); - } - - private static (string?, Dictionary?, string[]) Get(int frontMatterYamlEnd, string[] lines) - { - string? result; - List results; - Dictionary? keyValuePairs; - string[] segments; - string[] segmentsB; - string segmentsLast; - string segmentsFirst; - List jsonLines = new(); - for (int i = 0; i < frontMatterYamlEnd; i++) - { - if (lines[i] == "---") - continue; - segments = lines[i].Split(": "); - if (segments.Length != 2) - { - jsonLines.Clear(); - break; - } - segmentsLast = segments[^1].Trim(); - segmentsFirst = segments[0].Trim(); - if (string.IsNullOrEmpty(segmentsLast)) - continue; - if (segmentsFirst[0] == '"' && segmentsFirst[^1] == '"') - jsonLines.Add($"{segmentsFirst}: "); - else if (segmentsFirst[0] == '\'' && segmentsFirst[^1] == '\'') - jsonLines.Add($"\"{segmentsFirst[1..^1]}\": "); - else - jsonLines.Add($"\"{segmentsFirst}\": "); - if (segmentsLast == "[]") - jsonLines.RemoveAt(jsonLines.Count - 1); - else if (segmentsLast.Length > 4 && segmentsLast[0] == '[' && segmentsLast[^1] == ']' && segmentsLast[1] == '"' && segmentsLast[^2] == '"') - jsonLines.Add($"{segmentsLast},"); - else if (segmentsLast[0] == '"' && segmentsLast[^1] == '"') - jsonLines.Add($"{segmentsLast},"); - else if (segmentsLast[0] == '"' && segmentsLast[^1] == '"') - jsonLines.Add($"\"{segmentsLast[1..^1]}\""); - else if (!segmentsLast.Contains('"') && !segmentsLast.Contains('\'')) - { - if (segmentsLast is "true" or "false") - jsonLines.Add($"{segmentsLast},"); - else if (DateTime.TryParse(segmentsLast, out DateTime dateTime)) - jsonLines.Add($"\"{segmentsLast}\","); - else if (segmentsLast.All(l => char.IsNumber(l))) - jsonLines.Add($"{segmentsLast},"); - else - { - segmentsB = segmentsLast.Split('.'); - if (segmentsB.Length == 2 && segmentsB[0].Length < 7 && segmentsB[^1].Length < 7 && segmentsB[0].All(l => char.IsNumber(l)) && segmentsB[^1].All(l => char.IsNumber(l))) - jsonLines.Add($"{segmentsLast},"); - else if (!segmentsLast.Contains('[') && !segmentsLast.Contains('{')) - jsonLines.Add($"\"{segmentsLast}\","); - else - { - jsonLines.Clear(); - break; - } - } - } - else - { - jsonLines.Clear(); - break; - } - } - if (jsonLines.Any()) - (result, keyValuePairs, results) = Get(jsonLines); - else - (result, keyValuePairs, results) = (null, null, new()); - return (result, keyValuePairs, results.ToArray()); - } - - private static int ConvertFrontMatterToJsonFriendly(ReadOnlyDictionary relativeToCollection) - { - int result = 0; - List results = new(); - bool write; - string[] lines; - MarkdownFile markdownFile; - string[] frontMatterYamlLines; - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - results.Clear(); - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - if (markdownFile.LineNumber.FrontMatterYamlEnd is null) - continue; - (_, _, frontMatterYamlLines) = Get(markdownFile.LineNumber.FrontMatterYamlEnd.Value, lines); - if (!frontMatterYamlLines.Any()) - continue; - results.Add("---"); - results.AddRange(frontMatterYamlLines); - results.Add("---"); - for (int i = markdownFile.LineNumber.FrontMatterYamlEnd.Value + 1; i < lines.Length; i++) - results.Add(lines[i]); - if (results.Count == lines.Length) - { - write = false; - for (int i = 0; i < lines.Length; i++) - { - if (results[i] == lines[i]) - continue; - write = true; - break; - } - if (!write) - continue; - } - File.WriteAllLines(markdownFile.File, results); - File.SetLastWriteTime(markdownFile.File, markdownFile.LastWriteDateTime); - result += 1; - } - return result; - } - - private static int CircularReference(ILogger logger, ReadOnlyDictionary relativeToCollection) - { - int result = 0; - string line; - string check; - string[] lines; - bool circularReference; - MarkdownFile markdownFile; - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - circularReference = false; - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - for (int i = 0; i < lines.Length; i++) - { - check = $"[[{markdownFile.FileNameWithoutExtension}]]"; - if (!lines[i].Contains(check)) - continue; - line = lines[i].Replace(check, $"~~{markdownFile.FileName}~~"); - if (lines[i] == line) - continue; - lines[i] = line; - if (!circularReference) - circularReference = true; - } - for (int i = 0; i < lines.Length; i++) - { - check = $"{markdownFile.FileNameWithoutExtension}|{markdownFile.FileNameWithoutExtension}]]"; - if (!lines[i].Contains(check)) - continue; - line = lines[i].Replace(check, $"~~{markdownFile.FileName}~~"); - if (lines[i] == line) - continue; - lines[i] = line; - if (!circularReference) - circularReference = true; - } - for (int i = 0; i < lines.Length; i++) - { - check = $"[{markdownFile.FileNameWithoutExtension}]({markdownFile.FileName})"; - if (!lines[i].Contains(check)) - continue; - line = lines[i].Replace(check, $"~~{markdownFile.FileName}~~"); - if (lines[i] == line) - continue; - lines[i] = line; - logger.LogInformation("circular reference for <{file}>", markdownFile.FileName); - if (!circularReference) - circularReference = true; - } - if (circularReference) - { - File.WriteAllLines(markdownFile.File, lines); - result += 1; - } - } - return result; - } - - private static int FindReplace(ReadOnlyDictionary relativeToCollection) - { - int result = 0; - bool found; - string line; - string check; - string[] lines; - MarkdownFile markdownFile; - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - found = false; - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - for (int i = 0; i < lines.Length; i++) - { - check = $"[[K-A/"; - if (!lines[i].Contains(check)) - continue; - line = lines[i].Replace(check, "[[.kanbn/Archive/"); - if (lines[i] == line) - continue; - lines[i] = line; - if (!found) - found = true; - } - for (int i = 0; i < lines.Length; i++) - { - check = $"[[K-T/"; - if (!lines[i].Contains(check)) - continue; - line = lines[i].Replace(check, "[[.kanbn/Tasks/"); - if (lines[i] == line) - continue; - lines[i] = line; - if (!found) - found = true; - } - if (found) - { - File.WriteAllLines(markdownFile.File, lines); - result += 1; - } - } - return result; - } - - private static ReadOnlyDictionary> GetKeyValuePairs(ReadOnlyDictionary relativeToCollection) - { - Dictionary> results = new(); - MarkdownFile markdownFile; - string fileNameWithoutExtension; - string fileNameWithoutExtensionB; - List? markdownFiles; - foreach (KeyValuePair relativeTo in relativeToCollection) - { - markdownFile = relativeTo.Value.MarkdownFile; - if (!results.TryGetValue(relativeTo.Key, out markdownFiles)) - { - results.Add(relativeTo.Key, new()); - if (!results.TryGetValue(relativeTo.Key, out markdownFiles)) - throw new NotSupportedException(); - } - markdownFiles.Add(relativeTo.Value); - } - foreach (KeyValuePair relativeTo in relativeToCollection) - { - markdownFile = relativeTo.Value.MarkdownFile; - fileNameWithoutExtension = markdownFile.FileNameWithoutExtension.ToLower(); - fileNameWithoutExtensionB = fileNameWithoutExtension.Replace("%20", "-").Replace(' ', '-'); - if (!results.TryGetValue(markdownFile.FileNameWithoutExtension, out markdownFiles)) - { - results.Add(markdownFile.FileNameWithoutExtension, new()); - if (!results.TryGetValue(markdownFile.FileNameWithoutExtension, out markdownFiles)) - throw new NotSupportedException(); - } - markdownFiles.Add(relativeTo.Value); - if (fileNameWithoutExtension == markdownFile.FileNameWithoutExtension) - continue; - if (!results.TryGetValue(fileNameWithoutExtension, out markdownFiles)) - { - results.Add(fileNameWithoutExtension, new()); - if (!results.TryGetValue(fileNameWithoutExtension, out markdownFiles)) - throw new NotSupportedException(); - } - if (fileNameWithoutExtensionB == markdownFile.FileNameWithoutExtension) - continue; - if (!results.TryGetValue(fileNameWithoutExtensionB, out markdownFiles)) - { - results.Add(fileNameWithoutExtensionB, new()); - if (!results.TryGetValue(fileNameWithoutExtensionB, out markdownFiles)) - throw new NotSupportedException(); - } - markdownFiles.Add(relativeTo.Value); - } - foreach (KeyValuePair relativeTo in relativeToCollection) - { - markdownFile = relativeTo.Value.MarkdownFile; - if (!results.TryGetValue(markdownFile.H1, out markdownFiles)) - { - results.Add(markdownFile.H1, new()); - if (!results.TryGetValue(markdownFile.H1, out markdownFiles)) - throw new NotSupportedException(); - } - markdownFiles.Add(relativeTo.Value); - } - return new(results); - } - - private static List GetMarkdownFileAndLines(string file, List markdownFiles) - { - List results = new(); - List distinct = new(); - string? directory = Path.GetDirectoryName(file); - foreach (MarkdownFileAndLines markdownFileAndLines in markdownFiles) - { - if (string.IsNullOrEmpty(directory) || markdownFileAndLines.MarkdownFile.Directory != directory) - continue; - if (distinct.Contains(markdownFileAndLines.MarkdownFile.File)) - continue; - distinct.Add(markdownFileAndLines.MarkdownFile.File); - results.Add(markdownFileAndLines); - } - return results; - } - - private static List GetMarkdownFileAndLines(ReadOnlyDictionary> keyValuePairs) - { - List results = new(); - foreach (KeyValuePair> keyValuePair in keyValuePairs) - { - foreach (MarkdownFileAndLines markdownFileAndLines in keyValuePair.Value) - results.Add(markdownFileAndLines); - } - return results; - } - - private static List Distinct(IEnumerable? markdownFileAndLinesCollection) - { - List results = new(); - if (markdownFileAndLinesCollection is not null) - { - List distinct = new(); - foreach (MarkdownFileAndLines markdownFileAndLines in markdownFileAndLinesCollection) - { - if (distinct.Contains(markdownFileAndLines.MarkdownFile.File)) - continue; - distinct.Add(markdownFileAndLines.MarkdownFile.File); - results.Add(markdownFileAndLines); - } - } - return results; - } - - private static MarkdownFileAndLines? GetMarkdownFile(ReadOnlyDictionary> keyValuePairs, MarkdownFile markdownFile, string file) - { - MarkdownFileAndLines? result; - List? markdownFileAndLinesCollection; - string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); - if (!keyValuePairs.TryGetValue(fileNameWithoutExtension, out markdownFileAndLinesCollection)) - _ = keyValuePairs.TryGetValue(fileNameWithoutExtension.ToLower(), out markdownFileAndLinesCollection); - markdownFileAndLinesCollection = Distinct(markdownFileAndLinesCollection); - if (markdownFileAndLinesCollection is not null && markdownFileAndLinesCollection.Count == 1) - result = markdownFileAndLinesCollection[0]; - else - { - List matches; - matches = markdownFileAndLinesCollection is null ? new() : GetMarkdownFileAndLines(file, markdownFileAndLinesCollection); - if (matches.Count == 1) - result = matches[0]; - else - { - markdownFileAndLinesCollection = GetMarkdownFileAndLines(keyValuePairs); - matches = Distinct(markdownFileAndLinesCollection.Where(l => l.MarkdownFile.FileNameWithoutExtension.Length == fileNameWithoutExtension.Length && l.MarkdownFile.FileNameWithoutExtension.Contains(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))); - if (matches.Count == 1) - result = matches[0]; - else - { - string checkName = fileNameWithoutExtension.ToLower().Replace("%20", "-").Replace(' ', '-'); - matches = Distinct(markdownFileAndLinesCollection.Where(l => l.MarkdownFile.FileNameWithoutExtension.Length == checkName.Length && l.MarkdownFile.FileNameWithoutExtension.Contains(checkName, StringComparison.OrdinalIgnoreCase))); - if (matches.Count == 1) - result = matches[0]; - else if (matches.Count == 0) - result = null; - else - { - checkName = matches[0].MarkdownFile.FileNameWithoutExtension; - matches = Distinct(markdownFileAndLinesCollection.Where(l => l.MarkdownFile.File.Contains(checkName, StringComparison.OrdinalIgnoreCase))); - if (matches.Count == 1) - result = matches[0]; - else - { - checkName = $"{checkName}{markdownFile.Extension}"; - matches = Distinct(markdownFileAndLinesCollection.Where(l => l.MarkdownFile.File.EndsWith(checkName, StringComparison.OrdinalIgnoreCase))); - if (matches.Count == 1) - result = matches[0]; - else - { - checkName = $"\\{checkName}"; - matches = Distinct(markdownFileAndLinesCollection.Where(l => l.MarkdownFile.File.EndsWith(checkName, StringComparison.OrdinalIgnoreCase))); - if (matches.Count == 1) - result = matches[0]; - else - result = null; - } - } - } - } - } - } - return result; - } - - private static MarkdownFileH1AndRelativePath GetRelativePath(ReadOnlyDictionary> keyValuePairs, MarkdownFile markdownFile, string file) - { - MarkdownFileAndLines? result = GetMarkdownFile(keyValuePairs, markdownFile, file); - return new(result?.MarkdownFile, result?.Lines, result?.MarkdownFile.H1, result is null ? null : Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(result.MarkdownFile.File))); - } - - private static int ConvertToRelativePath(ILogger logger, ReadOnlyDictionary relativeToCollection) - { - int result = 0; - bool write; - string line; - string[] lines; - string[] segmentsA; - string[] segmentsB; - string[] segmentsC; - MarkdownFile markdownFile; - MarkdownFileH1AndRelativePath markdownFileH1AndRelativePath; - ReadOnlyDictionary> keyValuePairs = GetKeyValuePairs(relativeToCollection); - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - write = false; - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - for (int i = 0; i < lines.Length; i++) - { - segmentsA = lines[i].Split("]]"); - if (segmentsA.Length is not 2 or 3) - continue; - segmentsB = segmentsA[0].Split("[["); - if (segmentsB.Length is not 2 or 3) - continue; - segmentsC = segmentsB[^1].Split('|'); - markdownFileH1AndRelativePath = GetRelativePath(keyValuePairs, markdownFile, segmentsC[0]); - if (markdownFileH1AndRelativePath.MarkdownFile is null || markdownFileH1AndRelativePath.H1 is null || markdownFileH1AndRelativePath.RelativePath is null) - { - logger.LogInformation("Didn't find {line} in <{file}>", lines[i], markdownFile.FileNameWithoutExtension); - continue; - } - line = $"{segmentsB[0]}[{markdownFileH1AndRelativePath.H1}]({markdownFileH1AndRelativePath.RelativePath.Replace('\\', '/')}){segmentsA[^1]}"; - if (lines[i] == line) - continue; - lines[i] = line; - if (!write) - write = true; - } - if (write) - { - File.WriteAllLines(markdownFile.File, lines); - result += 1; - } - } - return result; - } - - private static int ConvertFileToSlugName(ReadOnlyDictionary relativeToCollection) - { - int result = 0; - string h1; - string h1Check; - string[] lines; - string checkName; - string checkFileName; - MarkdownFile markdownFile; - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - if (markdownFile.LineNumber.H1 is not null) - { - h1 = lines[markdownFile.LineNumber.H1.Value]; - if (h1.Length > 2) - { - h1Check = $"# {h1[2..]}"; - if (h1Check.Length == h1.Length && h1Check != h1) - { - lines[markdownFile.LineNumber.H1.Value] = h1Check; - File.WriteAllLines(markdownFile.File, lines); - result += 1; - } - } - } - checkFileName = markdownFile.FileName.ToLower().Replace("%20", "-").Replace(' ', '-'); - if (checkFileName == markdownFile.FileName) - continue; - if (!File.Exists(markdownFile.File)) - continue; - checkName = Path.Combine(markdownFile.Directory, checkFileName); - if (checkName == markdownFile.File) - continue; - File.Move(markdownFile.File, checkName); - result += 1; - } - return result; - } - - private static int ConvertFileToSlugName(AppSettings appSettings, ILogger logger, ReadOnlyDictionary relativeToCollection) - { - int result = 0; - bool write; - string file; - string line; - string[] lines; - string fileName; - string checkName; - string? directory; - string[] segmentsA; - string[] segmentsB; - string[] segmentsC; - string checkFileName; - string segmentsALast; - string segmentsBFirst; - MarkdownFile markdownFile; - MarkdownFileH1AndRelativePath markdownFileH1AndRelativePath; - ReadOnlyDictionary> keyValuePairs = GetKeyValuePairs(relativeToCollection); - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - if (markdownFile.FileNameWithoutExtension == "index" && markdownFile.Directory.EndsWith(".kanbn")) - continue; - if (!File.Exists(markdownFile.File)) - continue; - write = false; - for (int i = 0; i < lines.Length; i++) - { - segmentsA = lines[i].Split("]("); - if (segmentsA.Length != 2) - continue; - segmentsALast = segmentsA[^1]; - if (appSettings.ExcludeSchemes.Any(l => segmentsALast.StartsWith(l))) - continue; - segmentsB = segmentsALast.Split(")"); - if (segmentsB.Length != 2) - continue; - segmentsBFirst = segmentsB[0]; - file = Path.GetFullPath(Path.Combine(markdownFile.Directory, segmentsBFirst)); - fileName = Path.GetFileName(file); - directory = Path.GetDirectoryName(file); - if (string.IsNullOrEmpty(directory)) - continue; - checkFileName = fileName.ToLower().Replace("%20", "-").Replace(' ', '-'); - checkName = Path.Combine(directory, checkFileName); - segmentsC = segmentsA[0].Split('['); - markdownFileH1AndRelativePath = GetRelativePath(keyValuePairs, markdownFile, file); - if (markdownFileH1AndRelativePath.MarkdownFile is null || markdownFileH1AndRelativePath.H1 is null || markdownFileH1AndRelativePath.RelativePath is null) - { - logger.LogInformation("Didn't find {line} in <{file}>", lines[i], markdownFile.FileNameWithoutExtension); - continue; - } - line = $"{string.Join('[', segmentsC, 0, segmentsC.Length - 1)}[{markdownFileH1AndRelativePath.H1}]({markdownFileH1AndRelativePath.RelativePath.Replace('\\', '/')}){segmentsB[^1]}"; - if (lines[i] == line) - continue; - if (fileName.Contains(' ') || fileName.Contains("%20")) - { - if (!File.Exists(file)) - { - logger.LogInformation("Didn't find <{file}>", file); - continue; - } - if (File.Exists(checkName)) - continue; - File.Move(file, checkName); - } - else if (fileName != fileName.ToLower()) - { - if (file != checkName) - { - if (!File.Exists(file)) - { - logger.LogInformation("Didn't find <{file}>", file); - continue; - } - File.Move(file, checkName); - } - } - lines[i] = line; - if (!write) - write = true; - } - if (write) - { - File.WriteAllLines(markdownFile.File, lines); - result += 1; - } - } - if (result == 0) - result = ConvertFileToSlugName(relativeToCollection); - return result; - } - - private static void SetRecursiveLines(AppSettings appSettings, ILogger logger, ReadOnlyDictionary> keyValuePairs, string linkTitle, MarkdownFile markdownFile, string[] lines, List indentations, List recursiveLines) - { - if (recursiveLines is null) - throw new Exception(); - string file; - string[] segmentsA; - string[] segmentsB; - string segmentsALast; - bool fencedCodeBlock = false; - string indentation = new(indentations.ToArray()); - MarkdownFileH1AndRelativePath markdownFileH1AndRelativePath; - for (int i = 0; i < lines.Length; i++) - { - if (indentations.Count > 15) - { - recursiveLines.Add("```Error```"); - break; - } - if (lines[i].Length < 1) - continue; - if (lines[i].Length > 4 && lines[i][..3] == "```") - fencedCodeBlock = !fencedCodeBlock; - if (fencedCodeBlock) - continue; - if (lines[i][0] == '#') - { - if (lines[i] == $"# {linkTitle}") - continue; - recursiveLines.Add($"{indentation}{lines[i]}"); - continue; - } - segmentsA = lines[i].Split("]("); - if (segmentsA.Length != 2) - continue; - segmentsALast = segmentsA[^1]; - if (appSettings.ExcludeSchemes.Any(l => segmentsALast.StartsWith(l))) - continue; - segmentsB = segmentsALast.Split(")"); - if (segmentsB.Length != 2) - continue; - file = Path.GetFullPath(Path.Combine(markdownFile.Directory, segmentsB[0])); - markdownFileH1AndRelativePath = GetRelativePath(keyValuePairs, markdownFile, file); - if (markdownFileH1AndRelativePath.MarkdownFile is null || markdownFileH1AndRelativePath.H1 is null || markdownFileH1AndRelativePath.RelativePath is null) - { - recursiveLines.Add($"???{indentation}{lines[i]}"); - logger.LogInformation("Didn't find {line} in <{file}>", lines[i], markdownFile.FileNameWithoutExtension); - continue; - } - if (markdownFileH1AndRelativePath.Lines is null) - continue; - indentations.Add('\t'); - recursiveLines.Add($"{indentation}{lines[i]}"); - SetRecursiveLines(appSettings, logger, keyValuePairs, segmentsA[0].Split('[')[^1], markdownFileH1AndRelativePath.MarkdownFile, markdownFileH1AndRelativePath.Lines, indentations, recursiveLines); - } - if (indentations.Count > 0) - indentations.RemoveAt(0); - } - - private static List GetRecursiveLines(AppSettings appSettings, Record record, ILogger logger, ReadOnlyDictionary relativeToCollection) - { - List results = new(); - string[] lines; - List indentations; - MarkdownFile markdownFile; - List recursiveLines; - ReadOnlyDictionary> keyValuePairs = GetKeyValuePairs(relativeToCollection); - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - if (record.StartAt is null || !relativeTo.Value.MarkdownFile.File.StartsWith(record.StartAt) || Path.GetFileName(relativeTo.Value.MarkdownFile.Directory) != Path.GetFileName(record.StartAt)) - continue; - indentations = new(); - recursiveLines = new(); - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - SetRecursiveLines(appSettings, logger, keyValuePairs, markdownFile.FileNameWithoutExtension, markdownFile, lines, indentations, recursiveLines); - results.Add(new(relativeTo.Value.MarkdownFile, recursiveLines.ToArray())); - } - return results; - } - - private static void Write(Record record, List markdownFileAndLinesCollection) - { - foreach (MarkdownFileAndLines markdownFileAndLines in markdownFileAndLinesCollection) - { - if (record.Destination is null) - continue; - File.WriteAllLines(Path.Combine(record.Destination, markdownFileAndLines.MarkdownFile.FileName), markdownFileAndLines.Lines); - } - } - - internal static void MarkdownWikiLinkVerification(AppSettings appSettings, ILogger logger, List args) - { - int updated; - Record record = GetRecord(args); - ReadOnlyDictionary relativeToCollection; - relativeToCollection = GetRelativeToCollection(appSettings, record); - updated = SetFrontMatterAndH1(appSettings, relativeToCollection); - if (updated != 0) - { - relativeToCollection = GetRelativeToCollection(appSettings, record); - logger.LogInformation("{updated} Markdown file(s) were updated", updated); - } - updated = ConvertFrontMatterToJsonFriendly(relativeToCollection); - if (updated != 0) - { - relativeToCollection = GetRelativeToCollection(appSettings, record); - logger.LogInformation("{updated} Markdown file(s) were updated", updated); - } - updated = CircularReference(logger, relativeToCollection); - if (updated != 0) - { - relativeToCollection = GetRelativeToCollection(appSettings, record); - logger.LogInformation("{updated} Markdown file(s) were updated", updated); - } - updated = FindReplace(relativeToCollection); - if (updated != 0) - { - relativeToCollection = GetRelativeToCollection(appSettings, record); - logger.LogInformation("{updated} Markdown file(s) were updated", updated); - } - updated = ConvertToRelativePath(logger, relativeToCollection); - if (updated != 0) - { - relativeToCollection = GetRelativeToCollection(appSettings, record); - logger.LogInformation("{updated} Markdown file(s) were updated", updated); - } - updated = ConvertFileToSlugName(appSettings, logger, relativeToCollection); - if (updated != 0) - { - relativeToCollection = GetRelativeToCollection(appSettings, record); - logger.LogInformation("{updated} Markdown file(s) were updated", updated); - } - if (!string.IsNullOrEmpty(record.StartAt)) - { - relativeToCollection = GetRelativeToCollection(appSettings, record, force: true); - List markdownFileAndLinesCollection = GetRecursiveLines(appSettings, record, logger, relativeToCollection); - if (!string.IsNullOrEmpty(record.Destination)) - Write(record, markdownFileAndLinesCollection); - } - string directory = Path.Combine(Environment.CurrentDirectory, ".vscode"); - if (!Directory.Exists(directory)) - { - string json; - MarkdownFile markdownFile = relativeToCollection.ElementAt(0).Value.MarkdownFile; - json = JsonSerializer.Serialize(markdownFile, MarkdownFileSourceGenerationContext.Default.MarkdownFile); - if (json != "{}") - { - json = JsonSerializer.Serialize(relativeToCollection.Select(l => l.Value.MarkdownFile).ToArray(), MarkdownFileCollectionSourceGenerationContext.Default.MarkdownFileArray); - File.WriteAllText($"{DateTime.Now.Ticks}.json", json); - } - } - } - - private static List<(string, string, string[])> GetWithLinksForHugo(AppSettings appSettings, Record record) - { - List<(string, string, string[])> results = new(); - string file; - string line; - string[] lines; - string fileName; - string? directory; - string[] segmentsA; - string[] segmentsB; - string relativeFile; - string segmentsALast; - string segmentsBFirst; - MarkdownFile markdownFile; - int sourceDirectoryLength = record.Source.Length; - ReadOnlyDictionary relativeToCollection = GetRelativeToCollection(appSettings, record); - foreach (KeyValuePair relativeTo in relativeToCollection) - { - if (!relativeTo.Value.Lines.Any()) - continue; - lines = relativeTo.Value.Lines; - markdownFile = relativeTo.Value.MarkdownFile; - if (record.Destination is null) - continue; - if (markdownFile.File.Length < sourceDirectoryLength) - continue; - if (!File.Exists(markdownFile.File)) - continue; - fileName = $"{record.Destination}{markdownFile.File[sourceDirectoryLength..]}"; - directory = Path.GetDirectoryName(fileName); - if (string.IsNullOrEmpty(directory)) - continue; - for (int i = 0; i < lines.Length; i++) - { - segmentsA = lines[i].Split("]("); - if (segmentsA.Length != 2) - continue; - segmentsALast = segmentsA[^1]; - if (appSettings.ExcludeSchemes.Any(l => segmentsALast.StartsWith(l))) - continue; - segmentsB = segmentsALast.Split(")"); - if (segmentsB.Length != 2) - continue; - segmentsBFirst = segmentsB[0]; - if (!segmentsBFirst.EndsWith(".md")) - file = Path.GetFullPath(Path.Combine(markdownFile.Directory, segmentsBFirst)); - else - file = Path.GetFullPath(Path.Combine(markdownFile.Directory, segmentsBFirst[..^3])); - relativeFile = Path.GetRelativePath(record.Source, file).Replace('\\', '/'); - line = $"{segmentsA[0]}]({relativeFile}){segmentsB[^1]}"; - if (lines[i] == line) - throw new NotSupportedException($"Line {i} shouldn't match with {line}"); - lines[i] = line; - } - results.Add((directory, fileName, lines)); - } - return results; - } - - private static List GetDistinct(List<(string, string, string[])> collection) - { - List results = new(); - foreach ((string directory, _, _) in collection) - { - if (results.Contains(directory)) - continue; - results.Add(directory); - } - return results; - } - - private static void CreateMissingDirectories(List directories) - { - foreach (string directory in directories) - { - if (!Directory.Exists(directory)) - _ = Directory.CreateDirectory(directory); - } - } - - internal static void MarkdownConvertLinksForHugo(AppSettings appSettings, ILogger logger, List args) - { - Record record = GetRecord(args); - if (string.IsNullOrEmpty(record.Destination)) - throw new NotSupportedException("This method requires frontMatterYamlLines -d path!"); - List<(string, string, string[])> collection = GetWithLinksForHugo(appSettings, record); - if (!collection.Any()) - logger.LogInformation("No files?"); - List distinct = GetDistinct(collection); - CreateMissingDirectories(distinct); - foreach ((_, string file, string[] lines) in collection) - File.WriteAllLines(file, lines); - } - -} \ No newline at end of file diff --git a/Helpers/HelperGenealogicalDataCommunication.cs b/Helpers/HelperGenealogicalDataCommunication.cs index 1484f7b..49f1093 100644 --- a/Helpers/HelperGenealogicalDataCommunication.cs +++ b/Helpers/HelperGenealogicalDataCommunication.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; using System.Globalization; using System.Text.Json; +using System.Text.Json.Serialization; using System.Text.RegularExpressions; namespace File_Folder_Helper.Helpers; @@ -44,6 +45,18 @@ internal static partial class HelperGenealogicalDataCommunication long PersonKey, string? LineTwo); + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(ReadOnlyCollection>))] + internal partial class CollectionSourceGenerationContext : JsonSerializerContext + { + } + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(ReadOnlyDictionary>>))] + internal partial class DictionarySourceGenerationContext : JsonSerializerContext + { + } + private static string? GetFaceBook(Person person) => person.Birth?.Continue.Where(l => !l.Contains("profile.php?id=") && l.StartsWith("https://www.facebook.com/")).Select(l => l[25..].Split('/')[0]).FirstOrDefault(); @@ -58,24 +71,41 @@ internal static partial class HelperGenealogicalDataCommunication return new(results); } - private static Dictionary>> Convert(Dictionary>> keyValuePairs) + private static Dictionary>> Convert(Dictionary>> keyValuePairs) { - Dictionary>> results = new(); - foreach (KeyValuePair>> keyValuePair in keyValuePairs) + Dictionary>> results = new(); + foreach (KeyValuePair>> keyValuePair in keyValuePairs) results.Add(keyValuePair.Key, new(keyValuePair.Value)); return new(results); } - private static ReadOnlyCollection GetObjectCollection(Person? person) + private static ReadOnlyCollection GetObjectCollection(Person? person) { - List results; + List results; if (person is null) results = new() { "Id", "First-Name", "Last-Name", "Birth-Date", "Sex", "Address", "City", "State", "Zip", "Phone", "E-mail", "Facebook", "Facebook-Id", "Comment", "U-Id" }; else { string? facebook = GetFaceBook(person); string? facebookId = GetFaceBookId(person); - results = new() { person.Id, person.Name?.Given, person.Name?.Sur, person.Birth?.Date.ToString(), person.Sex, null, null, "NM", null, null, null, facebook, facebookId, null, person.UId }; + results = new() + { + person.Id.ToString(), + string.Concat(person.Name?.Given), + string.Concat(person.Name?.Sur), + string.Concat(person.Birth?.Date.ToString()), + string.Concat(person.Sex), + string.Empty, + string.Empty, + "NM", + string.Empty, + string.Empty, + string.Empty, + string.Concat(facebook), + string.Concat(facebookId), + string.Empty, + string.Concat(person.UId) + }; } return new(results); } @@ -103,11 +133,11 @@ internal static partial class HelperGenealogicalDataCommunication return result; } - private static Dictionary>> Convert(ReadOnlyCollection distinctSortedKKeys) + private static Dictionary>> Convert(ReadOnlyCollection distinctSortedKKeys) { - Dictionary>> results = new(); - ReadOnlyCollection collection; - List>? objectCollection; + Dictionary>> results = new(); + ReadOnlyCollection collection; + List>? objectCollection; foreach (string key in distinctSortedKKeys) { if (results.ContainsKey(key)) @@ -769,14 +799,14 @@ internal static partial class HelperGenealogicalDataCommunication return results; } - private static ReadOnlyDictionary>> GetKeyValuePairs(List familyCollection, char personTitleFilter) + private static ReadOnlyDictionary>> GetKeyValuePairs(List familyCollection, char personTitleFilter) { - Dictionary>> results; - Dictionary>> keyValuePairs; + Dictionary>> results; + Dictionary>> keyValuePairs; string id; string key; - ReadOnlyCollection collection; - List>? objectCollection; + ReadOnlyCollection collection; + List>? objectCollection; ReadOnlyCollection distinctSortedKeys = GetDistinctSortedKeys(familyCollection, personTitleFilter); keyValuePairs = Convert(distinctSortedKeys); foreach (Family family in familyCollection) @@ -801,19 +831,18 @@ internal static partial class HelperGenealogicalDataCommunication string json; if (people.Count != genealogicalDataCommunicationCollections.IndividualsToLines.Count) throw new NotSupportedException(); - JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true }; - ReadOnlyDictionary>> keyValuePairs; + ReadOnlyDictionary>> keyValuePairs; json = JsonSerializer.Serialize(new(people), PeopleSourceGenerationContext.Default.DictionaryInt64Person); File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, "people.json"), json); Dictionary? result = JsonSerializer.Deserialize(json, PeopleSourceGenerationContext.Default.DictionaryInt64Person); if (result is null) throw new NullReferenceException(nameof(result)); - json = JsonSerializer.Serialize(genealogicalDataCommunicationCollections.FamilyGroupLines, jsonSerializerOptions); + json = JsonSerializer.Serialize(genealogicalDataCommunicationCollections.FamilyGroupLines, CollectionSourceGenerationContext.Default.ReadOnlyCollectionReadOnlyCollectionString); File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, "family.json"), json); foreach (char personTitleFilter in appSettings.PersonTitleFilters) { keyValuePairs = GetKeyValuePairs(familyCollection, personTitleFilter); - json = JsonSerializer.Serialize(keyValuePairs, jsonSerializerOptions); + json = JsonSerializer.Serialize(keyValuePairs, DictionarySourceGenerationContext.Default.ReadOnlyDictionaryStringReadOnlyCollectionReadOnlyCollectionString); File.WriteAllText(Path.Combine(input.GenealogicalDataCommunicationDirectory, $"{personTitleFilter}.json"), json); } }