using Adaptation.FileHandlers.json.WorkItems; using Adaptation.Shared; using Adaptation.Shared.Duplicator; using Adaptation.Shared.Methods; using log4net; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; namespace Adaptation.FileHandlers.Kanban; public class ProcessData : IProcessData { private readonly List _Details; List Shared.Properties.IProcessData.Details => _Details; private readonly ILog _Log; public ProcessData(IFileRead fileRead, Logistics logistics, string targetFileLocation, string url, ReadOnlyCollection cssLines, ReadOnlyCollection frontMatterLines, List fileInfoCollection) { if (fileRead.IsEAFHosted) { } fileInfoCollection.Clear(); _Details = new List(); _Log = LogManager.GetLogger(typeof(ProcessData)); WriteFiles(fileRead, logistics, url, cssLines, frontMatterLines, targetFileLocation, fileInfoCollection); } string IProcessData.GetCurrentReactor(IFileRead fileRead, Logistics logistics, Dictionary reactors) => throw new Exception(string.Concat("See ", nameof(WriteFiles))); Tuple> IProcessData.GetResults(IFileRead fileRead, Logistics logistics, List fileInfoCollection) => new(logistics.Logistics1[0], Array.Empty(), Array.Empty(), fileInfoCollection); #nullable enable internal static List GetDescriptions(JsonElement[] jsonElements) { List results = new(); Description? description; JsonSerializerOptions jsonSerializerOptions = new() { NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString }; foreach (JsonElement jsonElement in jsonElements) { if (jsonElement.ValueKind != JsonValueKind.Object) throw new Exception(); description = JsonSerializer.Deserialize(jsonElement.ToString(), jsonSerializerOptions); if (description is null) continue; results.Add(description); } return results; } private void WriteFiles(IFileRead fileRead, Logistics logistics, string url, ReadOnlyCollection cssLines, ReadOnlyCollection frontMatterLines, string destinationDirectory, List fileInfoCollection) { if (!Directory.Exists(destinationDirectory)) _ = Directory.CreateDirectory(destinationDirectory); string json = File.ReadAllText(logistics.ReportFullPath); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(logistics.ReportFullPath); WorkItem[]? workItems = JsonSerializer.Deserialize(json); if (workItems is null) throw new Exception(nameof(workItems)); _Details.Add(workItems); ReadOnlyDictionary keyValuePairs = GetWorkItems(workItems); WriteFileStructure(destinationDirectory, keyValuePairs); WriteFiles(fileRead, destinationDirectory, fileInfoCollection, fileNameWithoutExtension, keyValuePairs); WriteKanbanFiles(fileRead, url, cssLines, frontMatterLines, fileInfoCollection, destinationDirectory, keyValuePairs); } private static ReadOnlyDictionary GetWorkItems(WorkItem[] workItems) { ReadOnlyDictionary results; Dictionary keyValuePairs = new(); foreach (WorkItem workItem in workItems) keyValuePairs.Add(workItem.Id, workItem); results = GetKeyValuePairs(new(keyValuePairs)); return results; } private static void WriteFileStructure(string destinationDirectory, ReadOnlyDictionary keyValuePairs) { ReadOnlyCollection collection = GetDirectories(destinationDirectory, keyValuePairs); foreach (string directory in collection) { if (directory.Length > 222) continue; if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); } } private static void WriteFiles(IFileRead fileRead, string destinationDirectory, List fileInfoCollection, string fileNameWithoutExtension, ReadOnlyDictionary keyValuePairs) { string old; string json; string checkFile; WorkItem workItem; string singletonDirectory; JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true }; string rootDirectory = Path.Combine(destinationDirectory, fileNameWithoutExtension); if (string.IsNullOrEmpty(rootDirectory)) throw new NullReferenceException(nameof(rootDirectory)); foreach (KeyValuePair keyValuePair in keyValuePairs) { workItem = keyValuePair.Value.WorkItem; json = JsonSerializer.Serialize(workItem, jsonSerializerOptions); singletonDirectory = Path.Combine(rootDirectory, workItem.WorkItemType.Replace(" ", "-"), $"{workItem.Id}-{workItem.WorkItemType.Replace(" ", "-")}"); if (!Directory.Exists(singletonDirectory)) _ = Directory.CreateDirectory(singletonDirectory); checkFile = Path.Combine(singletonDirectory, ".json"); if (File.Exists(checkFile)) { old = File.ReadAllText(checkFile); if (old == json) continue; } File.WriteAllText(checkFile, json); if (!fileRead.IsEAFHosted) fileInfoCollection.Add(new(checkFile)); } } private static void WriteKanbanFiles(IFileRead fileRead, string url, ReadOnlyCollection cssLines, ReadOnlyCollection frontMatterLines, List fileInfoCollection, string destinationDirectory, ReadOnlyDictionary keyValuePairs) { string old; string json; Record record; string markdown; string checkFile; string jsonDirectory; string tasksDirectory; string kanbanDirectory; string vscodeDirectory; string[] iterationPaths; string singletonDirectory; string iterationPathDirectory; JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true }; foreach (KeyValuePair keyValuePair in keyValuePairs) { record = keyValuePair.Value; iterationPathDirectory = destinationDirectory; iterationPaths = record.WorkItem.IterationPath.Split('\\'); json = JsonSerializer.Serialize(record, jsonSerializerOptions); foreach (string iterationPath in iterationPaths) { if (iterationPath.Contains("Sprint")) continue; iterationPathDirectory = Path.Combine(iterationPathDirectory, iterationPath); } singletonDirectory = Path.Combine(iterationPathDirectory, record.WorkItem.WorkItemType.Replace(" ", "-"), $"{record.WorkItem.Id}-{record.WorkItem.WorkItemType.Replace(" ", "-")}"); kanbanDirectory = Path.Combine(singletonDirectory, ".kanbn"); if (!Directory.Exists(kanbanDirectory)) _ = Directory.CreateDirectory(kanbanDirectory); tasksDirectory = Path.Combine(kanbanDirectory, "tasks"); if (!Directory.Exists(tasksDirectory)) _ = Directory.CreateDirectory(tasksDirectory); vscodeDirectory = Path.Combine(singletonDirectory, ".vscode"); if (!Directory.Exists(vscodeDirectory)) _ = Directory.CreateDirectory(vscodeDirectory); jsonDirectory = Path.Combine(singletonDirectory, record.WorkItem.Id.ToString()); if (!Directory.Exists(jsonDirectory)) _ = Directory.CreateDirectory(jsonDirectory); checkFile = Path.Combine(vscodeDirectory, "settings.json"); if (!File.Exists(checkFile)) File.WriteAllText(checkFile, "{ \"[markdown]\": { \"editor.wordWrap\": \"off\" }, \"cSpell.words\": [ \"kanbn\" ] }"); markdown = GetIndexLines(frontMatterLines, record); checkFile = Path.Combine(kanbanDirectory, "board.css"); if (!File.Exists(checkFile)) File.WriteAllLines(checkFile, cssLines); checkFile = Path.Combine(kanbanDirectory, "index.md"); if (!File.Exists(checkFile)) File.WriteAllText(checkFile, markdown); checkFile = Path.Combine(jsonDirectory, ".json"); old = File.Exists(checkFile) ? File.ReadAllText(checkFile) : string.Empty; if (old != json) File.WriteAllText(checkFile, json); markdown = GetMarkdownLines(url, record, jsonDirectory, iterationPathDirectory); checkFile = Path.Combine(vscodeDirectory, "markdown.md"); old = File.Exists(checkFile) ? File.ReadAllText(checkFile) : string.Empty; if (old != markdown) File.WriteAllText(checkFile, markdown); if (!fileRead.IsEAFHosted) fileInfoCollection.Add(new(checkFile)); } } private static ReadOnlyDictionary GetKeyValuePairs(ReadOnlyDictionary keyValuePairs) { Dictionary results = new(); Record record; List nests = new(); WorkItem? parentWorkItem; ReadOnlyCollection records; foreach (KeyValuePair keyValuePair in keyValuePairs) { nests.Clear(); if (keyValuePair.Value.Parent is null) parentWorkItem = null; else _ = keyValuePairs.TryGetValue(keyValuePair.Value.Parent.Value, out parentWorkItem); try { records = GetKeyValuePairs(keyValuePairs, keyValuePair.Value, nests); record = new(keyValuePair.Value, parentWorkItem, records); } catch (Exception) { record = new(keyValuePair.Value, parentWorkItem, new(Array.Empty())); } results.Add(keyValuePair.Key, record); } return new(results); } private static ReadOnlyCollection GetDirectories(string destinationDirectory, ReadOnlyDictionary keyValuePairs) { List results = new(); Record record; string directory; List nests = new(); ReadOnlyCollection childrenDirectories; string dateDirectory = Path.Combine(destinationDirectory, "_", DateTime.Now.ToString("yyyy-MM-dd")); foreach (KeyValuePair keyValuePair in keyValuePairs) { record = keyValuePair.Value; if (record.Parent is not null && (record.WorkItem.Parent is null || record.Parent.Id != record.WorkItem.Parent.Value)) continue; if (record.Parent is not null) continue; // if (record.WorkItem.Id == 110730) // continue; // if (record.WorkItem.Id == 110732) // continue; nests.Clear(); directory = Path.Combine(dateDirectory, $"{record.WorkItem.WorkItemType.Substring(0, 1)}-{record.WorkItem.Id}-{record.WorkItem.Title.Trim().Substring(0, 1)}"); childrenDirectories = GetChildrenDirectories(keyValuePairs, nests, directory, record); results.AddRange(childrenDirectories); } return new(results.Distinct().ToArray()); } private static string GetIndexLines(ReadOnlyCollection frontMatterLines, Record record) { List results = new(); results.Clear(); results.AddRange(frontMatterLines); results.Add(string.Empty); results.Add($"# {record.WorkItem.Id}"); results.Add(string.Empty); results.Add("## Backlog"); results.Add(string.Empty); results.Add("## Todo"); results.Add(string.Empty); results.Add("## In Progress"); results.Add(string.Empty); results.Add("## Done"); results.Add(string.Empty); return string.Join(Environment.NewLine, results); } private static string GetMarkdownLines(string url, Record record, string jsonDirectory, string iterationPathDirectory) { List results = new(); string link; string target; results.Add($"# {record.WorkItem.Id}"); results.Add(string.Empty); results.Add($"## {record.WorkItem.Title}"); results.Add(string.Empty); foreach (Record r in record.Children) results.Add($"- [{r.WorkItem.Id}]({url}{r.WorkItem.Id})"); results.Add(string.Empty); results.Add("```bash"); foreach (Record r in record.Children) { link = Path.Combine(jsonDirectory, $"{r.WorkItem.Id}-{r.WorkItem.WorkItemType}"); target = Path.Combine(iterationPathDirectory, r.WorkItem.WorkItemType, $"{r.WorkItem.Id}-{r.WorkItem.WorkItemType}", r.WorkItem.Id.ToString()); results.Add($"mklink /J \"{link}\" \"{target}\""); } results.Add("```"); results.Add(string.Empty); return string.Join(Environment.NewLine, results); } private static ReadOnlyCollection GetKeyValuePairs(ReadOnlyDictionary keyValuePairs, WorkItem workItem, List nests) { List results = new(); int? childId; Record record; nests.Add(true); WorkItem? childWorkItem; WorkItem? parentWorkItem; List collection = new(); ReadOnlyCollection records; if (workItem.Relations is not null && workItem.Relations.Length > 0) { collection.Clear(); foreach (Relation relation in workItem.Relations) { childId = GetIdFromUrlIfChild(relation); if (childId is not null && workItem.Parent is not null && relation?.URL is not null && relation.URL.Contains(workItem.Parent.Value.ToString())) continue; if (childId is null || !keyValuePairs.TryGetValue(childId.Value, out childWorkItem)) continue; collection.Add(childWorkItem); } collection = (from l in collection orderby l.State != "Closed", l.Id select l).ToList(); foreach (WorkItem w in collection) { if (nests.Count > 99) break; if (w.Parent is null) parentWorkItem = null; else _ = keyValuePairs.TryGetValue(w.Parent.Value, out parentWorkItem); records = GetKeyValuePairs(keyValuePairs, w, nests); record = new(w, parentWorkItem, records); results.Add(record); } } return new(results); } private static ReadOnlyCollection GetChildrenDirectories(ReadOnlyDictionary keyValuePairs, List nests, string parentDirectory, Record record) { List results = new(); nests.Add(true); string directory; Record? childRecord; ReadOnlyCollection childrenDirectories; foreach (Record r in record.Children) { // if (record.WorkItem.Id == 110730) // continue; // if (record.WorkItem.Id == 110732) // continue; directory = Path.Combine(parentDirectory, $"{r.WorkItem.WorkItemType.Substring(0, 1)}-{r.WorkItem.Id}-{r.WorkItem.Title.Trim().Substring(0, 1)}"); results.Add(directory); if (!keyValuePairs.TryGetValue(r.WorkItem.Id, out childRecord)) continue; if (nests.Count > 99) break; childrenDirectories = GetChildrenDirectories(keyValuePairs, nests, directory, childRecord); results.AddRange(childrenDirectories); } return new(results); } private static int? GetIdFromUrlIfChild(Relation relation) { int? result; string[] segments = relation?.Attributes is null || relation.Attributes.Name != "Child" ? Array.Empty() : relation.URL.Split('/'); if (segments.Length < 2) result = null; else { if (!int.TryParse(segments[segments.Length - 1], out int id)) result = null; else result = id; } return result; } }