using File_Folder_Helper.Models; using Microsoft.Extensions.Logging; 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); [JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] [JsonSerializable(typeof(Dictionary))] internal partial class DictionaryStringAndJsonElementSourceGenerationContext : JsonSerializerContext { } /// /// 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[] GetFiles(AppSettings appSettings, Record record) { string[] results = record.StartAt is null ? GetFiles(appSettings, record.Source) : GetFiles(appSettings, record.StartAt); 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); } internal static List<(MarkdownFile, string[])> GetCollection(AppSettings appSettings, string[] files) { List<(MarkdownFile, string[])> results = new(); string h1; 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; (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); results.Add(new(markdownFile, lines.ToArray())); } return results; } private static List<(MarkdownFile, string[])> GetCollection(AppSettings appSettings, Record record) { List<(MarkdownFile, string[])> results; string[] files = GetFiles(appSettings, record.Source); results = GetCollection(appSettings, files); return results; } private static int SetFrontMatterAndH1(AppSettings appSettings, List<(MarkdownFile, string[])> collection) { int result = 0; string h1Line; string typeLine; string createdLine; string updatedLine; DateTime creationDateTime; string createdLineCompare; string updatedLineCompare; List results = new(); foreach ((MarkdownFile markdownFile, string[] lines) in collection) { if (!lines.Any()) continue; results.Clear(); 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.First() is '{' or '}') continue; segments = afterTrim.Split(": "); if (segments.Length != 2) { if (results.Last()[^1] == '[') { _ = stringBuilder.Clear(); _ = stringBuilder.Append(results.Last()); 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.Last(); 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.Last().Trim(); segmentsFirst = segments.First().Trim(); if (string.IsNullOrEmpty(segmentsLast)) continue; if (segmentsFirst.First() == '"' && segmentsFirst.Last() == '"') jsonLines.Add($"{segmentsFirst}: "); else if (segmentsFirst.First() == '\'' && segmentsFirst.Last() == '\'') jsonLines.Add($"\"{segmentsFirst[1..^1]}\": "); else jsonLines.Add($"\"{segmentsFirst}\": "); if (segmentsLast == "[]") jsonLines.RemoveAt(jsonLines.Count - 1); else if (segmentsLast.Length > 4 && segmentsLast.First() == '[' && segmentsLast.Last() == ']' && segmentsLast[1] == '"' && segmentsLast[^2] == '"') jsonLines.Add($"{segmentsLast},"); else if (segmentsLast.First() == '"' && segmentsLast.Last() == '"') jsonLines.Add($"{segmentsLast},"); else if (segmentsLast.First() == '"' && segmentsLast.Last() == '"') 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.First().Length < 7 && segmentsB.Last().Length < 7 && segmentsB.First().All(l => char.IsNumber(l)) && segmentsB.Last().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(List<(MarkdownFile, string[])> collection) { int result = 0; bool write; List results = new(); string[] frontMatterYamlLines; foreach ((MarkdownFile markdownFile, string[] lines) in collection) { if (!lines.Any()) continue; results.Clear(); 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, List<(MarkdownFile, string[])> collection) { int result = 0; string line; string check; bool circularReference; foreach ((MarkdownFile markdownFile, string[] lines) in collection) { if (!lines.Any()) continue; circularReference = false; 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(List<(MarkdownFile, string[])> collection) { int result = 0; bool found; string line; string check; foreach ((MarkdownFile markdownFile, string[] lines) in collection) { if (!lines.Any()) continue; found = false; 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 Dictionary> GetKeyValuePairs(List<(MarkdownFile MarkdownFile, string[] Lines)> collection) { Dictionary> results = new(); List? markdownFiles; string fileNameWithoutExtension; foreach ((MarkdownFile markdownFile, _) in collection) { fileNameWithoutExtension = markdownFile.FileNameWithoutExtension.ToLower(); if (!results.TryGetValue(markdownFile.FileNameWithoutExtension, out markdownFiles)) { results.Add(markdownFile.FileNameWithoutExtension, new()); if (!results.TryGetValue(markdownFile.FileNameWithoutExtension, out markdownFiles)) throw new NotSupportedException(); } if (markdownFiles.Contains(markdownFile)) continue; markdownFiles.Add(markdownFile); 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 (markdownFiles.Contains(markdownFile)) continue; markdownFiles.Add(markdownFile); } foreach ((MarkdownFile markdownFile, _) in collection) { if (!results.TryGetValue(markdownFile.H1, out markdownFiles)) { results.Add(markdownFile.H1, new()); if (!results.TryGetValue(markdownFile.H1, out markdownFiles)) throw new NotSupportedException(); } if (markdownFiles.Contains(markdownFile)) continue; markdownFiles.Add(markdownFile); } return results; } private static Dictionary> GetKeyValuePairs(AppSettings appSettings, Record record) { Dictionary> results; List<(MarkdownFile MarkdownFile, string[] Lines)> collection = GetCollection(appSettings, record); results = GetKeyValuePairs(collection); return results; } private static (string?, string?) GetMatchAndTitle(string? directory, List markdownFiles) { int check = 0; string? match = null; string? title = null; string? directoryName = Path.GetFileName(directory); foreach (MarkdownFile markdownFile in markdownFiles) { if (directory is null || directoryName is null) continue; if (markdownFiles.Count == 1) { check++; match = markdownFile.File; title = markdownFile.H1; } else { if (Path.GetFileName(markdownFile.Directory) == directoryName) { check++; match = markdownFile.File; title = markdownFile.H1; } } } if (check != 1) { match = null; title = null; } return (match, title); } private static (string?, string?) GetRelativePath(Dictionary> keyValuePairs, MarkdownFile markdownFile, string file) { string? result; string? match; string? title; List? markdownFiles; string? directory = Path.GetDirectoryName(file); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); if (keyValuePairs.TryGetValue(fileNameWithoutExtension, out markdownFiles)) (match, title) = GetMatchAndTitle(directory, markdownFiles); else { if (keyValuePairs.TryGetValue(fileNameWithoutExtension.ToLower(), out markdownFiles)) (match, title) = GetMatchAndTitle(directory, markdownFiles); else (match, title) = (null, null); } if (match is null) { List files = new(); List fileNames = new(); foreach (KeyValuePair> keyValuePair in keyValuePairs) { foreach (MarkdownFile keyValue in keyValuePair.Value) { files.Add(keyValue.File); fileNames.Add(keyValue.FileNameWithoutExtension); } } string[] matches = fileNames.Where(l => l.Length == fileNameWithoutExtension.Length && l.Contains(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)).ToArray(); if (matches.Length == 1) match = matches.First(); else { string checkName = fileNameWithoutExtension.ToLower().Replace("%20", "-").Replace(' ', '-'); matches = fileNames.Where(l => l.Length == checkName.Length && l.Contains(checkName, StringComparison.OrdinalIgnoreCase)).ToArray(); if (matches.Length == 1) match = matches.First(); else { if (!matches.Any()) match = null; else { checkName = matches.First(); matches = files.Where(l => l.Contains(checkName, StringComparison.OrdinalIgnoreCase)).ToArray(); if (matches.Length == 1) match = matches.First(); else { checkName = $"{checkName}{markdownFile.Extension}"; matches = files.Where(l => l.EndsWith(checkName, StringComparison.OrdinalIgnoreCase)).ToArray(); if (matches.Length == 1) match = matches.First(); else { checkName = $"\\{checkName}"; matches = files.Where(l => l.EndsWith(checkName, StringComparison.OrdinalIgnoreCase)).ToArray(); if (matches.Length == 1) match = matches.First(); else match = null; } } } } } } result = match is null ? null : Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(match)); return (result, title); } private static int ConvertToRelativePath(AppSettings appSettings, ILogger logger, Record record, List<(MarkdownFile MarkdownFile, string[] Lines)> collection) { int result = 0; bool write; string line; string? title; string[] segmentsA; string[] segmentsB; string[] segmentsC; string? relativePath; Dictionary> keyValuePairs = record.StartAt is null ? GetKeyValuePairs(collection) : GetKeyValuePairs(appSettings, record); foreach ((MarkdownFile markdownFile, string[] lines) in collection) { if (!lines.Any()) continue; write = false; for (int i = 0; i < lines.Length; i++) { segmentsA = lines[i].Split("]]"); if (segmentsA.Length is not 2 or 3) continue; segmentsB = segmentsA.First().Split("[["); if (segmentsB.Length is not 2 or 3) continue; segmentsC = segmentsB.Last().Split('|'); (relativePath, title) = GetRelativePath(keyValuePairs, markdownFile, segmentsC.First()); if (relativePath is null) { logger.LogInformation("Didn't find {line} in <{file}>", lines[i], markdownFile.FileNameWithoutExtension); continue; } if (title is null) { title = segmentsC.Last(); if (title.Length != segmentsC.Last().Length) title = segmentsC.Last(); } line = $"{segmentsB.First()}[{title}]({relativePath.Replace('\\', '/')}){segmentsA.Last()}"; 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 (string?, string?) GetRelativePath(string[] allSourceFiles, List<(MarkdownFile MarkdownFile, string[] Lines)> collection, MarkdownFile markdownFile, string file) { string? title; string? relativePath; List<(string RelativePath, string Title)> results = new(); foreach ((MarkdownFile MarkdownFile, string[] Lines) item in collection) { if (item.MarkdownFile.File != file) continue; results.Add((Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(item.MarkdownFile.File)), item.MarkdownFile.H1)); } if (results.Count == 1) (relativePath, title) = (results.First().RelativePath.Replace(" ", "%20"), results.First().Title); else { results.Clear(); foreach (string allSourceFile in allSourceFiles) { if (allSourceFile.EndsWith(".md")) continue; if (allSourceFile != file) continue; results.Add((Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(allSourceFile)), Path.GetFileNameWithoutExtension(allSourceFile))); } if (results.Count == 1) (relativePath, title) = (results.First().RelativePath.Replace(" ", "%20"), results.First().Title); else { results.Clear(); string fileLowered = file.ToLower(); foreach ((MarkdownFile MarkdownFile, string[] Lines) item in collection) { if (item.MarkdownFile.File.ToLower() != fileLowered) continue; results.Add((Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(item.MarkdownFile.File)), item.MarkdownFile.H1)); } if (results.Count == 1) (relativePath, title) = (results.First().RelativePath.Replace(" ", "%20"), results.First().Title); else { results.Clear(); foreach (string allSourceFile in allSourceFiles) { if (allSourceFile.EndsWith(".md")) continue; if (allSourceFile.ToLower() != fileLowered) continue; results.Add((Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(allSourceFile)), Path.GetFileNameWithoutExtension(allSourceFile))); } if (results.Count == 1) (relativePath, title) = (results.First().RelativePath.Replace(" ", "%20"), results.First().Title); else { results.Clear(); string fileFullPath = Path.GetFullPath(fileLowered); foreach ((MarkdownFile MarkdownFile, string[] Lines) item in collection) { if (Path.GetFullPath(item.MarkdownFile.File).ToLower() != fileFullPath) continue; results.Add((Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(item.MarkdownFile.File)), item.MarkdownFile.H1)); } if (results.Count == 1) (relativePath, title) = (results.First().RelativePath.Replace(" ", "%20"), results.First().Title); else { results.Clear(); foreach (string allSourceFile in allSourceFiles) { if (allSourceFile.EndsWith(".md")) continue; if (allSourceFile.ToLower() != fileFullPath) continue; results.Add((Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(allSourceFile)), Path.GetFileNameWithoutExtension(allSourceFile))); } if (results.Count == 1) (relativePath, title) = (results.First().RelativePath.Replace(" ", "%20"), results.First().Title); else { results.Clear(); string spaceNaming = fileFullPath.Replace(" ", "%20"); foreach ((MarkdownFile MarkdownFile, string[] Lines) item in collection) { if (Path.GetFullPath(item.MarkdownFile.File).ToLower().Replace(" ", "%20") != spaceNaming) continue; results.Add((Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(item.MarkdownFile.File)), item.MarkdownFile.H1)); } if (results.Count == 1) (relativePath, title) = (results.First().RelativePath.Replace(" ", "%20"), results.First().Title); else { results.Clear(); foreach (string allSourceFile in allSourceFiles) { if (allSourceFile.EndsWith(".md")) continue; if (Path.GetFullPath(allSourceFile).ToLower().Replace(" ", "%20") != spaceNaming) continue; results.Add((Path.GetRelativePath(markdownFile.Directory, Path.GetFullPath(allSourceFile)), Path.GetFileNameWithoutExtension(allSourceFile))); } if (results.Count == 1) (relativePath, title) = (results.First().RelativePath.Replace(" ", "%20"), results.First().Title); else (relativePath, title) = (null, null); } } } } } } } return (relativePath, title); } private static int ConvertFileToSlugName(AppSettings appSettings, ILogger logger, Record record, List<(MarkdownFile MarkdownFile, string[] Lines)> collection) { int result = 0; string h1; bool write; string file; string line; string? title; string h1Check; string fileName; string checkName; string? directory; string[] segmentsA; string[] segmentsB; string[] segmentsC; string checkFileName; string segmentsALast; string? relativePath; string segmentsBFirst; string[] allSourceFiles = Directory.GetFiles(record.Source, "*", SearchOption.AllDirectories); List<(MarkdownFile MarkdownFile, string[] Lines)> sourceCollection = record.StartAt is null ? collection : GetCollection(appSettings, record); foreach ((MarkdownFile markdownFile, string[] lines) in collection) { 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.Last(); if (appSettings.ExcludeSchemes.Any(l => segmentsALast.StartsWith(l))) continue; segmentsB = segmentsALast.Split(")"); if (segmentsB.Length != 2) continue; segmentsBFirst = segmentsB.First(); 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.First().Split('['); (relativePath, title) = GetRelativePath(allSourceFiles, sourceCollection, markdownFile, file); if (relativePath is null) { logger.LogInformation("Didn't find {line} in <{file}>", lines[i], markdownFile.FileNameWithoutExtension); continue; } if (title is null) { title = segmentsC.Last(); if (title.Length != segmentsC.Last().Length) title = segmentsC.Last(); } line = $"{segmentsC.First()}[{title}]({relativePath.Replace('\\', '/')}){segmentsB.Last()}"; 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) { foreach ((MarkdownFile markdownFile, string[] lines) in collection) { 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 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.First()), startAt, destination); return result; } internal static void MarkdownWikiLinkVerification(AppSettings appSettings, ILogger logger, List args) { int updated; Record record = GetRecord(args); List<(MarkdownFile MarkdownFile, string[] Lines)> collection; collection = GetCollection(appSettings, GetFiles(appSettings, record)); updated = SetFrontMatterAndH1(appSettings, collection); if (updated != 0) { collection = GetCollection(appSettings, GetFiles(appSettings, record)); logger.LogInformation("{updated} Markdown file(s) were updated", updated); } updated = ConvertFrontMatterToJsonFriendly(collection); if (updated != 0) { collection = GetCollection(appSettings, GetFiles(appSettings, record)); logger.LogInformation("{updated} Markdown file(s) were updated", updated); } updated = CircularReference(logger, collection); if (updated != 0) { collection = GetCollection(appSettings, GetFiles(appSettings, record)); logger.LogInformation("{updated} Markdown file(s) were updated", updated); } updated = FindReplace(collection); if (updated != 0) { collection = GetCollection(appSettings, GetFiles(appSettings, record)); logger.LogInformation("{updated} Markdown file(s) were updated", updated); } updated = ConvertToRelativePath(appSettings, logger, record, collection); if (updated != 0) { collection = GetCollection(appSettings, GetFiles(appSettings, record)); logger.LogInformation("{updated} Markdown file(s) were updated", updated); } updated = ConvertFileToSlugName(appSettings, logger, record, collection); if (updated != 0) { collection = GetCollection(appSettings, GetFiles(appSettings, record)); logger.LogInformation("{updated} Markdown file(s) were updated", updated); } string directory = Path.Combine(Environment.CurrentDirectory, ".vscode"); if (!Directory.Exists(directory)) { string json; MarkdownFile markdownFile = collection.First().MarkdownFile; json = JsonSerializer.Serialize(markdownFile, MarkdownFileSourceGenerationContext.Default.MarkdownFile); if (json != "{}") { json = JsonSerializer.Serialize(collection.Select(l => l.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 fileName; string? directory; string[] segmentsA; string[] segmentsB; string relativeFile; string segmentsALast; string segmentsBFirst; int sourceDirectoryLength = record.Source.Length; List<(MarkdownFile MarkdownFile, string[] Lines)> collection = GetCollection(appSettings, GetFiles(appSettings, record)); foreach ((MarkdownFile markdownFile, string[] lines) in collection) { 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.Last(); if (appSettings.ExcludeSchemes.Any(l => segmentsALast.StartsWith(l))) continue; segmentsB = segmentsALast.Split(")"); if (segmentsB.Length != 2) continue; segmentsBFirst = segmentsB.First(); 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.First()}]({relativeFile}){segmentsB.Last()}"; 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); } }