311 lines
13 KiB
C#
311 lines
13 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using System.Collections.ObjectModel;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace File_Folder_Helper.ADO2025.PI4;
|
|
|
|
internal static partial class Helper20250204
|
|
{
|
|
|
|
[GeneratedRegex("([A-Z]+(.))")]
|
|
private static partial Regex UpperCase();
|
|
|
|
[GeneratedRegex("[\\s!?.,@:;|\\\\/\"'`£$%\\^&*{}[\\]()<>~#+\\-=_¬]+")]
|
|
private static partial Regex InvalidCharacter();
|
|
|
|
private record H1ParamCaseAndState(string H1, string ParamCase, string State)
|
|
{
|
|
|
|
private static string GetParamCase(string value)
|
|
{
|
|
string result;
|
|
StringBuilder stringBuilder = new(value);
|
|
Match[] matches = UpperCase().Matches(value).ToArray();
|
|
for (int i = matches.Length - 1; i > -1; i--)
|
|
_ = stringBuilder.Insert(matches[i].Index, '-');
|
|
string[] segments = InvalidCharacter().Split(stringBuilder.ToString().ToLower());
|
|
result = string.Join('-', segments).Trim('-');
|
|
return result;
|
|
}
|
|
|
|
private static string GetState(string value) =>
|
|
value switch
|
|
{
|
|
"New" => "ToDo",
|
|
"Active" => "In Progress",
|
|
"Closed" => "Done",
|
|
_ => "Backlog",
|
|
};
|
|
|
|
internal static H1ParamCaseAndState Get(WorkItem workItem)
|
|
{
|
|
H1ParamCaseAndState result;
|
|
string paramCase = GetParamCase(workItem.Title);
|
|
string state = GetState(workItem.State);
|
|
result = new(workItem.Title, paramCase, state);
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
private record Attribute([property: JsonPropertyName("isLocked")] bool IsLocked,
|
|
[property: JsonPropertyName("name")] string Name,
|
|
[property: JsonPropertyName("parameterTitle")] string? ParameterTitle,
|
|
[property: JsonPropertyName("state")] string? State,
|
|
[property: JsonPropertyName("workItemType")] string? WorkItemType);
|
|
|
|
private record Relation([property: JsonPropertyName("attributes")] Attribute Attributes,
|
|
[property: JsonPropertyName("id")] int Id,
|
|
[property: JsonPropertyName("rel")] string Rel);
|
|
|
|
private record WorkItem(DateTime? ActivatedDate,
|
|
string AreaPath,
|
|
string? AssignedTo,
|
|
long? BusinessValue,
|
|
DateTime ChangedDate,
|
|
DateTime? ClosedDate,
|
|
int CommentCount,
|
|
DateTime CreatedDate,
|
|
string Description,
|
|
long? Effort,
|
|
int Id,
|
|
string IterationPath,
|
|
int? Parent,
|
|
int? Priority,
|
|
Relation[]? Relations,
|
|
long? RemainingWork,
|
|
string? Requester,
|
|
DateTime? ResolvedDate,
|
|
int Revision,
|
|
long? RiskReductionMinusOpportunityEnablement,
|
|
DateTime? StartDate,
|
|
string State,
|
|
long? StoryPoints,
|
|
string Tags,
|
|
DateTime? TargetDate,
|
|
long? TimeCriticality,
|
|
string Title,
|
|
string? Violation,
|
|
long? WeightedShortestJobFirst,
|
|
string WorkItemType)
|
|
{
|
|
|
|
public override string ToString() => $"{Id} - {WorkItemType} - {Title}";
|
|
|
|
}
|
|
|
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
|
[JsonSerializable(typeof(WorkItem))]
|
|
private partial class WorkItemSourceGenerationContext : JsonSerializerContext
|
|
{
|
|
}
|
|
|
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
|
[JsonSerializable(typeof(WorkItem[]))]
|
|
private partial class WorkItemCollectionSourceGenerationContext : JsonSerializerContext
|
|
{
|
|
}
|
|
|
|
private static string[] GetTaskLines(string directory, string rootDirectory) =>
|
|
[
|
|
"{",
|
|
"\"version\": \"2.0.0\",",
|
|
"\"tasks\": [",
|
|
"{",
|
|
"\"label\": \"File-Folder-Helper AOT s X Day-Helper-2025-02-04\",",
|
|
"\"type\": \"shell\",",
|
|
"\"command\": \"L:/DevOps/Mesa_FI/File-Folder-Helper/bin/Release/net8.0/win-x64/publish/File-Folder-Helper.exe\",",
|
|
"\"args\": [",
|
|
"\"s\",",
|
|
"\"X\",",
|
|
$"\"{directory}\",",
|
|
"\"Day-Helper-2025-02-04\",",
|
|
$"\"{rootDirectory}\",",
|
|
"],",
|
|
"\"problemMatcher\": []",
|
|
"},",
|
|
"{",
|
|
"\"label\": \"File-Folder-Helper AOT s X Day-Helper-2024-06-23\",",
|
|
"\"type\": \"shell\",",
|
|
"\"command\": \"L:/DevOps/Mesa_FI/File-Folder-Helper/bin/Release/net8.0/win-x64/publish/File-Folder-Helper.exe\",",
|
|
"\"args\": [",
|
|
"\"s\",",
|
|
"\"X\",",
|
|
$"\"{directory}\",",
|
|
"\"Day-Helper-2024-06-23\",",
|
|
"\"*.md\",",
|
|
"\"##_Sub-tasks\",",
|
|
"\"-_[code-insiders](\",",
|
|
"\"index.md\",",
|
|
"\"-_[,](\",",
|
|
"\"##_Done\",",
|
|
"\".kan\",",
|
|
$"\"{rootDirectory}\",",
|
|
"\"316940400000\",",
|
|
"],",
|
|
"\"problemMatcher\": []",
|
|
"}",
|
|
"]",
|
|
"}",
|
|
];
|
|
|
|
private static string GetTaskText(string directory, string rootDirectory) =>
|
|
string.Join(Environment.NewLine, GetTaskLines(directory, rootDirectory));
|
|
|
|
private static void WriteTaskFile(string sourceDirectory, string rootDirectory)
|
|
{
|
|
string tasksFile = Path.Combine(sourceDirectory, ".vscode", "tasks.json");
|
|
string oldText = File.ReadAllText(tasksFile);
|
|
string jsonSafeDirectory = sourceDirectory.Replace('\\', '/');
|
|
if (!oldText.Contains(jsonSafeDirectory))
|
|
{
|
|
string text = GetTaskText(jsonSafeDirectory, rootDirectory);
|
|
File.WriteAllText(tasksFile, text);
|
|
}
|
|
}
|
|
|
|
private static string GetFilter(ReadOnlyCollection<H1ParamCaseAndState> collection, string filter) =>
|
|
string.Join(Environment.NewLine, from l in collection where l.State == filter select $"- [{l.ParamCase}](tasks/{l.ParamCase}.md)");
|
|
|
|
private static string[] GetIndexLines(WorkItem workItem, H1ParamCaseAndState h1ParamCaseAndState, ReadOnlyCollection<H1ParamCaseAndState> collection) =>
|
|
[
|
|
"---",
|
|
"startedColumns:",
|
|
" - 'In Progress'",
|
|
"completedColumns:",
|
|
" - Done",
|
|
"---",
|
|
string.Empty,
|
|
$"# {workItem.Id} - {h1ParamCaseAndState.H1}",
|
|
string.Empty,
|
|
"## Backlog",
|
|
string.Empty,
|
|
GetFilter(collection, "Backlog"),
|
|
string.Empty,
|
|
"## Todo",
|
|
string.Empty,
|
|
GetFilter(collection, "ToDo"),
|
|
string.Empty,
|
|
"## In Progress",
|
|
string.Empty,
|
|
GetFilter(collection, "In Progress"),
|
|
string.Empty,
|
|
"## Done",
|
|
string.Empty,
|
|
GetFilter(collection, "Done"),
|
|
string.Empty
|
|
];
|
|
private static string GetIndexText(WorkItem workItem, H1ParamCaseAndState h1ParamCaseAndState, ReadOnlyCollection<H1ParamCaseAndState> collection) =>
|
|
string.Join(Environment.NewLine, GetIndexLines(workItem, h1ParamCaseAndState, collection));
|
|
|
|
private static string GetIndexMarkdown(FileInfo fileInfo, ReadOnlyCollection<WorkItem> workItems)
|
|
{
|
|
string result;
|
|
H1ParamCaseAndState h1ParamCaseAndState;
|
|
List<H1ParamCaseAndState> collection = [];
|
|
foreach (WorkItem w in workItems)
|
|
{
|
|
h1ParamCaseAndState = H1ParamCaseAndState.Get(w);
|
|
collection.Add(h1ParamCaseAndState);
|
|
}
|
|
string line = Environment.NewLine;
|
|
string json = File.ReadAllText(fileInfo.FullName);
|
|
WorkItem? workItem = JsonSerializer.Deserialize(json, WorkItemSourceGenerationContext.Default.WorkItem) ??
|
|
throw new NullReferenceException(nameof(WorkItem));
|
|
h1ParamCaseAndState = H1ParamCaseAndState.Get(workItem);
|
|
string text = GetIndexText(workItem, h1ParamCaseAndState, collection.AsReadOnly());
|
|
result = text.Replace($"{line}{line}{line}{line}", $"{line}{line}").Replace("408m](tasks", "408M](tasks");
|
|
return result;
|
|
}
|
|
|
|
private static ReadOnlyCollection<WorkItem> GetWorkItems(string[] files)
|
|
{
|
|
List<WorkItem> results = [];
|
|
string json;
|
|
WorkItem? workItem;
|
|
foreach (string file in files)
|
|
{
|
|
json = File.ReadAllText(file);
|
|
workItem = JsonSerializer.Deserialize(json, WorkItemSourceGenerationContext.Default.WorkItem);
|
|
if (workItem is null)
|
|
continue;
|
|
results.Add(workItem);
|
|
}
|
|
return results.AsReadOnly();
|
|
}
|
|
|
|
private static void ExtractKanban(string searchPattern, string rootDirectory, DirectoryInfo kanbanDirectory, FileInfo fileInfo)
|
|
{
|
|
string checkFile;
|
|
string weekOfYear;
|
|
string workItemDirectory;
|
|
string line = Environment.NewLine;
|
|
H1ParamCaseAndState h1ParamCaseAndState;
|
|
Calendar calendar = new CultureInfo("en-US").Calendar;
|
|
string tasksDirectory = Path.Combine(kanbanDirectory.FullName, "tasks");
|
|
if (!Directory.Exists(tasksDirectory))
|
|
_ = Directory.CreateDirectory(tasksDirectory);
|
|
string[] files = Directory.GetFiles(tasksDirectory, searchPattern, SearchOption.TopDirectoryOnly);
|
|
ReadOnlyCollection<WorkItem> workItems = GetWorkItems(files);
|
|
string markdown = GetIndexMarkdown(fileInfo, workItems);
|
|
string indexFile = Path.Combine(kanbanDirectory.FullName, "index.md");
|
|
string markdownOld = File.Exists(indexFile) ? File.ReadAllText(indexFile) : string.Empty;
|
|
if (markdown != markdownOld)
|
|
File.WriteAllText(indexFile, markdown);
|
|
foreach (WorkItem workItem in workItems)
|
|
{
|
|
h1ParamCaseAndState = H1ParamCaseAndState.Get(workItem);
|
|
checkFile = Path.Combine(tasksDirectory, $"{h1ParamCaseAndState.ParamCase}.md");
|
|
markdownOld = File.Exists(checkFile) ? File.ReadAllText(checkFile) : string.Empty;
|
|
if (markdownOld.Contains("]("))
|
|
continue;
|
|
weekOfYear = calendar.GetWeekOfYear(workItem.CreatedDate, CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString("00");
|
|
workItemDirectory = Path.GetFullPath(Path.Combine(rootDirectory, $"{workItem.CreatedDate:yyyy}", $"{workItem.CreatedDate:yyyy}_Week_{weekOfYear}", $"{workItem.Id}"));
|
|
markdown = $"# {h1ParamCaseAndState.H1}{line}{line}## Id {workItem.Id}{line}{line}## Code Insiders{line}{line}- [code-insiders]({workItemDirectory}){line}";
|
|
if (markdown != markdownOld)
|
|
File.WriteAllText(checkFile, markdown);
|
|
}
|
|
}
|
|
|
|
private static string GetSourceDirectory(string directory)
|
|
{
|
|
string? result = null;
|
|
DirectoryInfo directoryInfo;
|
|
string? checkDirectory = directory;
|
|
string? pathRoot = Path.GetPathRoot(directory);
|
|
for (int i = 0; i < int.MaxValue; i++)
|
|
{
|
|
checkDirectory = Path.GetDirectoryName(checkDirectory);
|
|
if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot)
|
|
break;
|
|
directoryInfo = new(checkDirectory);
|
|
if (string.IsNullOrEmpty(directoryInfo.LinkTarget))
|
|
continue;
|
|
result = directory.Replace(checkDirectory, directoryInfo.LinkTarget);
|
|
break;
|
|
}
|
|
result ??= directory;
|
|
return result;
|
|
}
|
|
|
|
internal static void ExtractKanban(ILogger<Worker> logger, List<string> args)
|
|
{
|
|
string searchPattern = "*.json";
|
|
string fullPath = Path.GetFullPath(args[0]);
|
|
string sourceDirectory = GetSourceDirectory(fullPath);
|
|
string rootDirectory = args.Count < 3 || args[2].Length < 16 ? "D:/5-Other-Small/Kanban-mestsa003/{}" : args[2];
|
|
WriteTaskFile(sourceDirectory, rootDirectory);
|
|
string sourceDirectoryName = Path.GetFileName(sourceDirectory);
|
|
DirectoryInfo directoryInfo = new(Path.Combine(sourceDirectory, ".kanbn"));
|
|
FileInfo? fileInfo = !directoryInfo.Exists ? null : new(Path.Combine(directoryInfo.FullName, $"{sourceDirectoryName}.json"));
|
|
if (directoryInfo.Exists && fileInfo is not null && fileInfo.Exists)
|
|
ExtractKanban(searchPattern, rootDirectory, directoryInfo, fileInfo);
|
|
else
|
|
logger.LogWarning("<{directoryInfo}> doesn't exist", directoryInfo.FullName);
|
|
}
|
|
|
|
} |