file-folder-helper/Helpers/HelperKanbanMetadata.cs
Mike Phares 2923e86a94 VSCodeTask
Download SSL Certificates
Sort Subtasks of Markdown files
Test BioRad
EAF CopyDirectories
json to Markdown
Sort Day 2024 Q2
GitRemoteRemove
Handle directoryInfo.LinkTarget better
Remove StartAt
Handle directoryInfo.LinkTarget
2024-08-02 13:32:23 -07:00

243 lines
11 KiB
C#

using File_Folder_Helper.Models;
using Microsoft.Extensions.Logging;
using System.Collections.ObjectModel;
using System.Text;
using System.Text.RegularExpressions;
namespace File_Folder_Helper.Helpers;
internal static partial class HelperKanbanMetadata
{
[GeneratedRegex("([A-Z]+(.))")]
private static partial Regex UpperCase();
[GeneratedRegex("[\\s!?.,@:;|\\\\/\"'`£$%\\^&*{}[\\]()<>~#+\\-=_¬]+")]
private static partial Regex InvalidCharacter();
private record Record(FileInfo FileInfo,
string Group,
int GroupCount,
int ItemLineNumber);
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 void TestParamCases()
{ // cSpell:disable
if (GetParamCase("PascalCase") != "pascal-case")
throw new Exception("PascalCase");
if (GetParamCase("camelCase") != "camel-case")
throw new Exception("camelCase");
if (GetParamCase("snake_case") != "snake-case")
throw new Exception("snake_case");
if (GetParamCase("No Case") != "no-case")
throw new Exception("No Case");
if (GetParamCase("With 2 numbers 3") != "with-2-numbers-3")
throw new Exception("With 2 numbers 3");
if (GetParamCase("Multiple spaces") != "multiple-spaces")
throw new Exception("Multiple spaces");
if (GetParamCase("Tab\tCharacter") != "tab-character")
throw new Exception("Tab\tCharacter");
if (GetParamCase("New\nLine") != "new-line")
throw new Exception("New\nLine");
if (GetParamCase("Punctuation, Characters") != "punctuation-characters")
throw new Exception("Punctuation, Characters");
if (GetParamCase("M!o?r.e, @p:u;n|c\\t/u\"a\'t`i£o$n% ^c&h*a{r}a[c]t(e)r<s> ~l#i+k-e= _t¬hese") != "m-o-r-e-p-u-n-c-t-u-a-t-i-o-n-c-h-a-r-a-c-t-e-r-s-l-i-k-e-t-hese")
throw new Exception("M!o?r.e, @p:u;n|c\\t/u\"a\'t`i£o$n% ^c&h*a{r}a[c]t(e)r<s> ~l#i+k-e= _t¬hese");
if (GetParamCase("This string ends with punctuation!") != "this-string-ends-with-punctuation")
throw new Exception("This string ends with punctuation!");
if (GetParamCase("?This string starts with punctuation") != "this-string-starts-with-punctuation")
throw new Exception("?This string starts with punctuation");
if (GetParamCase("#This string has punctuation at both ends&") != "this-string-has-punctuation-at-both-ends")
throw new Exception("#This string has punctuation at both ends&");
if (GetParamCase("軟件 測試") != "軟件-測試")
throw new Exception("軟件 測試");
if (GetParamCase("実験 試し") != "実験-試し")
throw new Exception("実験 試し");
if (GetParamCase("יקספּערמענאַל פּרובירן") != "יקספּערמענאַל-פּרובירן")
throw new Exception("יקספּערמענאַל פּרובירן");
if (GetParamCase("я надеюсь, что это сработает") != "я-надеюсь-что-это-сработает")
throw new Exception("я надеюсь, что это сработает");
} // cSpell:restore
private static List<Record> GetCollectionFromIndex(string sourceDirectory, ReadOnlyCollection<string> lines)
{
List<Record> results = [];
string line;
FileInfo fileInfo;
string[] segments;
int groupCount = 0;
string? group = null;
for (int i = 0; i < lines.Count; i++)
{
line = lines[i];
if (line.Length < 4)
continue;
if (line[..3] == "## ")
{
group = line[3..];
groupCount += 1;
continue;
}
if (group is null || line[..3] != "- [" || line[^1] != ')')
continue;
segments = line.Split("](");
if (segments.Length != 2)
continue;
fileInfo = new(Path.Combine(sourceDirectory, segments[1][..^1]));
if (!fileInfo.Exists)
continue;
results.Add(new(fileInfo, group, groupCount, i));
}
return results;
}
private static void WriteKanbanBoardFile(string directory, List<Record> records, string h1)
{
string? last = null;
List<string> results = [h1];
foreach (Record record in records)
{
if (last is null || record.Group != last)
{
results.Add(string.Empty);
results.Add($"## {record.Group}");
results.Add(string.Empty);
}
results.Add($"- [ ] {Path.GetFileNameWithoutExtension(record.FileInfo.Name)}");
last = record.Group;
}
string file = Path.Combine(directory, "index.knb.md");
if (File.Exists(file))
{
string allText = File.ReadAllText(file);
if (string.Join(Environment.NewLine, results) == allText)
results.Clear();
}
if (results.Count > 0)
File.WriteAllText(file, string.Join(Environment.NewLine, results));
}
private static void WriteKanbanBoardYmlView(string directory, List<Record> records, string kanbanIndexH1)
{
List<string> results = [kanbanIndexH1, string.Empty];
string h1;
TimeSpan timeSpan;
List<string> lines;
LineNumber lineNumber;
Record[] sorted = (from l in records orderby l.GroupCount, l.FileInfo.LastWriteTime descending select l).ToArray();
foreach (Record record in sorted)
{
if (record.ItemLineNumber == 0)
throw new NotSupportedException();
(lines, lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(record.FileInfo);
if (lines.Count == 0)
continue;
timeSpan = new(record.FileInfo.LastWriteTime.Ticks - record.FileInfo.CreationTime.Ticks);
h1 = lineNumber.H1 is null ? Path.GetFileNameWithoutExtension(record.FileInfo.Name) : lines[lineNumber.H1.Value];
results.Add($"#{h1}");
results.Add(string.Empty);
results.Add("```yaml");
results.Add($"CreationTime: {record.FileInfo.CreationTime:yyyy-MM-dd}");
results.Add($"LastWriteTime: {record.FileInfo.LastWriteTime:yyyy-MM-dd}");
results.Add($"TotalDays: {Math.Round(timeSpan.TotalDays, 2)}");
if (lineNumber.FrontMatterYamlEnd is not null && lines.Count >= lineNumber.FrontMatterYamlEnd.Value)
{
for (int i = 0; i < lineNumber.FrontMatterYamlEnd; i++)
{
if (lines[i] == "---")
continue;
results.Add(lines[i]);
}
}
results.Add($"status: {record.GroupCount}-{record.Group}");
results.Add("```");
results.Add(string.Empty);
}
string file = Path.Combine(directory, "index.yml.md");
if (File.Exists(file))
{
string allText = File.ReadAllText(file);
if (string.Join(Environment.NewLine, results) == allText)
results.Clear();
}
if (results.Count > 0)
File.WriteAllText(file, string.Join(Environment.NewLine, results));
}
internal static void SetMetadata(string sourceDirectory, ReadOnlyCollection<string> kanbanIndexFileLines, LineNumber kanbanIndexFileLineNumber, ReadOnlyCollection<string> gitOthersModifiedAndDeletedExcludingStandardFiles)
{
bool? match;
bool gitCheck;
string? paramCase;
string statusLine;
List<string> lines;
LineNumber lineNumber;
string? directory = Path.GetDirectoryName(sourceDirectory);
List<Record> records = GetCollectionFromIndex(sourceDirectory, kanbanIndexFileLines);
if (directory is not null && kanbanIndexFileLineNumber.H1 is not null)
{
string checkDirectory = Path.Combine(directory, ".vscode", "helper");
if (Directory.Exists(checkDirectory))
{
WriteKanbanBoardFile(checkDirectory, records, kanbanIndexFileLines[kanbanIndexFileLineNumber.H1.Value]);
WriteKanbanBoardYmlView(checkDirectory, records, kanbanIndexFileLines[kanbanIndexFileLineNumber.H1.Value]);
}
}
foreach (Record record in records)
{
if (record.ItemLineNumber == 0)
throw new NotSupportedException();
(lines, lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(record.FileInfo);
if (lines.Count == 0)
continue;
statusLine = $"status: {record.GroupCount}-{record.Group}";
paramCase = lineNumber.H1 is null ? null : GetParamCase(lines[lineNumber.H1.Value]);
match = lineNumber.H1 is null || paramCase is null ? null : Path.GetFileNameWithoutExtension(record.FileInfo.Name) == paramCase;
if (lineNumber.FrontMatterYamlEnd is null)
throw new NotSupportedException($"{nameof(SetMetadata)} must be executed first!");
if (lineNumber.H1 is not null && paramCase is not null && match is not null && !match.Value)
lines[lineNumber.H1.Value] = $"# {paramCase}";
if (lineNumber.Status is null)
lines.Insert(lineNumber.FrontMatterYamlEnd.Value, statusLine);
else
{
if ((match is null || match.Value) && lines[lineNumber.Status.Value] == statusLine)
continue;
lines[lineNumber.Status.Value] = statusLine;
}
gitCheck = gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(record.FileInfo.FullName);
if (!gitCheck)
continue;
File.WriteAllLines(record.FileInfo.FullName, lines);
}
}
internal static void SetMetadata(ILogger logger, string sourceDirectory)
{
TestParamCases();
string fullPath = Path.GetFullPath(sourceDirectory);
if (!Directory.Exists(fullPath))
_ = Directory.CreateDirectory(fullPath);
string indexFile = Path.Combine(fullPath, "index.md");
if (!File.Exists(indexFile))
logger.LogInformation("<{indexFile}> doesn't exist!", indexFile);
else
{
FileInfo fileInfo = new(indexFile);
(List<string> lines, LineNumber lineNumber) = HelperMarkdown.GetStatusAndFrontMatterYamlEndLineNumbers(fileInfo);
SetMetadata(fullPath, new(lines), lineNumber, gitOthersModifiedAndDeletedExcludingStandardFiles: new([]));
}
}
}