Compare commits

..

9 Commits

15 changed files with 1471 additions and 251 deletions

1
.gitignore vendored
View File

@ -336,3 +336,4 @@ ASALocalRun/
.extensions-vscode-oss .extensions-vscode-oss
.extensions-vscode-insiders .extensions-vscode-insiders
.vscode/.UserSecrets/secrets.json .vscode/.UserSecrets/secrets.json
.vscode/.helper

312
.vscode/launch.json vendored
View File

@ -1,120 +1,196 @@
{ {
// Use IntelliSense to learn about possible attributes. // Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes. // Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": ".NET Core Launch (console)", "name": ".NET Core Launch (console)",
"type": "coreclr", "type": "coreclr",
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/net8.0/win-x64/File-Folder-Helper.dll", "program": "${workspaceFolder}/bin/Debug/net8.0/win-x64/File-Folder-Helper.dll",
"args": [ "args": [
"s", "s",
"X", "X",
"D:/Tmp", "D:/ProgramData/EC_Characterization_Si/Dummy/DEP08CEPIEPSILON/PollPath",
"Day-Helper-2025-06-02", "Day-Helper-2025-07-01",
"infineon\\MESPhares", "*.pdsf",
"BACKLOG~BIORAD2~BIORAD3~BIORAD4~BIORAD5~CDE4~CDE5~CDE6~DEP08CEPIEPSILON~DEP08SIASM~DEP08SIHTRPLC~EC~HGCV1~HGCV2~HGCV3~MESAFIBACKLOG~MET06AWCT~MET08ANLYSDIFAAST230~MET08AWCT~MET08DDUPSFS6420~MET08DDUPSP1TBI~MET08RESIHGCV~MET08RESIMAPCDE~MET08RESISRP2100~MET08THFTIRQS408M~MET08THFTIRSTRATUS~METCLIMATEC~R29~R32~R36~R47~R55~R57~R61~R62~R65~R70~R72~R73~R74~R75~R77~SP101~SPV01~SRP~TENCOR1~TENCOR2~TENCOR3~TRENDLOG~WC6INCH1~WC6INCH2~WC6INCH3~WC6INCH4~WC8INCH1~WC8INCH2~WC8INCH3", "987654321",
"s", "Time",
"X", ".json",
"D:/5-Other-Small/Proxmox/ffnm", "id12~CenterTemp,id13~CenterSetpt,id15~FrontTemp,id153~PPSTEPNAME,id154~SystemState,id16~FrontSetpt,id172~LVC1Ratio,id173~LVC1Carrier,id176~TotalWaferCount,i-d-1-7-8~TIME,id18~SideTemp,id183~SCRDrive4,id19~SideSetpt,id193~SCRLOAD4,id21~RearTemp,id22~RearSetpt,id221~LeftDefaultRecipe,id222~RightDefaultRecipe,id223~RecipeCompleteMsg,id25~N2H2Setpt,id26~N2H2Flow,id27~HCLSetpt,id28~HCLFlow,id29~HCLHISetpt,id30~HCLHIFlow,id37~NSRCSetpt,id38~NSRCFlow,id39~NDILSetpt,id40~NDILFlow,id41~NINJSetpt,id42~NINJFlow,id57~LVC1Setpt,id58~LVC1Flow,id61~ROTSetpt,id62~ROTSpeed,id78~LL1State,id79~LL1Init,id80~LL1Lotid,id81~LL1WafersIn,id82~LL1WfrCnt,id83~LL2State,id84~LL2Init,id85~LL2Lotid,id86~LL2WafersIn,id87~LL2WfrCnt,id93~ProcessState,vp93~ProcessState,vp154~SystemState,vp78~LL1State,vp83~LL2State,vp176~TotalWaferCount,vp80~LL1Lotid,vp85~LL2Lotid,vp153~PPSTEPNAME,vp221~LeftDefaultRecipe,vp222~RightDefaultRecipe,vp223~RecipeCompleteMsg",
"Day-Helper-2025-05-21", "D:/ProgramData/EC_Characterization_Si/Dummy/DEP08CEPIEPSILON/JavaScriptObjectNotation",
"*.pdf", "D:/ProgramData/EC_Characterization_Si/Dummy/DEP08CEPIEPSILON/Markdown",
"*.md", "D:/ProgramData/EC_Characterization_Si/Dummy/DEP08CEPIEPSILON/KeyValuePairs",
"2", "654321",
"MM-dd-yy", "s",
"Trans Date~Effective Date~Description~Withdrawal Deposit~Balance", "s",
"s", "X",
"X", "D:/ProgramData/EC_Characterization_Si/Dummy/DEP08CEPIEPSILON/R55",
"D:/Tmp/phares/VisualStudioCode", "Day-Helper-2023-11-30",
"Day-Helper-2025-05-19", "yyMMddhhmmssfff",
"D:/Tmp/phares/VisualStudioCode/.vscode/input.json", "\"vp154\"",
"s", "s",
"X", "X",
"D:/Tmp/phares/VisualStudioCode", "V:/Tmp/Phares/Pictures/2023 TI2023.6 Fall Samsung",
"Day-Helper-2025-05-19", "Day-Helper-2025-07-05",
"D:/Tmp/phares/VisualStudioCodeLeft", "x-653889110721.jpg~401223300869.jpg",
"z-include-patterns.nsv", "3648,2736,1~3024,4032,6",
"z-exclude-patterns.nsv", "0.341694,0.599963,0.1642,0.279605~0.552357,0.65095,0.195175,0.32383~0.31002,0.42328,0.0379464,0.0459656",
"http://localhost:5004", "0.259594,0.460161,0.1642,0.279605~0.45477,0.489035,0.195175,0.32383~0.328993,0.446263,0.0379464,0.0459656",
"/api/SyncV1/?", "9",
",L", "x-825511723~x-444522128~831410304",
".G", "X",
"+~G~~L~+~Custom-Default", "F:/0-ISO-A",
"", "Day-Helper-2025-06-28",
"+~G~~G~-~Mirror", "*.iso",
"+~G~~~~Update", "F",
"+~G~~L~+~Custom-Default", "s",
"-~G~~G~+~Custom-A", "X",
"-~L~~L~+~Custom-B", "D:/5-Other-Small",
"+~L~~L~-~Custom-C", "Day-Helper-2024-12-17",
"s", ".job.json",
"X", "thumbs.db~sync.ffs_db~verify.json~.html",
"\\\\mesfs.infineon.com\\EC_Characterization_Si\\Archive\\BIORAD4\\2025_Week_16\\2025-04-17", "D:/0-ISO-A",
"Day-Helper-2025-02-19", "D:/5-Other-Small/Proxmox/Snap2HTML/Snap2HTML.exe",
"csv-*.pdsf", "s",
"*.pdsf", "M",
"Time,HeaderUniqueId,UniqueId,Date,Wafer,Position,BIORAD4", "D:/5-Other-Small/Notes/EC-Documentation",
",BIORAD4", "-d",
",BIORAD4", "D:/5-Other-Small/Notes/EC-Documentation/.vscode/helper",
"Test|EventId,Date|DateTime,Position|Slot,DeltaThicknessSlotsOneAndTwentyFive|Actual Delta Thick Pts 1 and 25,PercentDeltaThicknessSlotsOneAndTwentyFive|% Delta Thick Pts 1 and 25,MID|Cassette,Lot|Batch,Title|Batch,Wafer|Text,Thickness|Site,MeanThickness|GradeMean,|BIORAD4", "s",
"Time,A_LOGISTICS,B_LOGISTICS,Test,Count,Index,MesEntity,MID,Date,Employee,Lot,PSN,Reactor,Recipe,Cassette,GradeStdDev,HeaderUniqueId,Layer,MeanThickness,PassFail,RDS,Slot,Title,UniqueId,Wafer,Zone,Mean,Position,StdDev,Thickness,ThicknessSlotOne,ThicknessSlotTwentyFive,DeltaThicknessSlotsOneAndTwentyFive,PercentDeltaThicknessSlotsOneAndTwentyFive", "X",
"Time,A_LOGISTICS,B_LOGISTICS,Count,Sequence,MesEntity,Index,Batch,Cassette,DateTime,Destination,Mean,PassFail,Recipe,Reference,Site,Slot,Source,StdDev,Text,GradeMean,GradeStdDev,RDS,PSN,Reactor,Layer,Zone,Employee,InferredLot,Thickness First Slot,Thickness Last Slot,Actual Delta Thick Pts 1 and 25,% Delta Thick Pts 1 and 25,EventId", "D:/5-Other-Small/Proxmox/DiskInfo",
"0,1,2,31,3,6,5,8,9,27,7,23,24,13,8,21,-1,25,20,12,22,16,7,-1,19,26,11,16,18,15,-1,-1,29,30", "Day-Helper-2025-06-18",
"s", "*.json",
"X", "D:/5-Other-Small/Proxmox/Disk-Info-Old",
"C:/Users/phares/AppData/Roaming/FreeFileSync", "-2025-",
"Day-Helper-2025-04-21", "1",
"GlobalSettings.xml", "s",
"LastSync|Config", "X",
"s", "D:/Tmp",
"X", "Day-Helper-2025-06-02",
"L:/Tmp/MET08ANLYSDIFAAST230", "infineon\\MESPhares",
"Day-Helper-2025-03-06", "BACKLOG~BIORAD2~BIORAD3~BIORAD4~BIORAD5~CDE4~CDE5~CDE6~DEP08CEPIEPSILON~DEP08SIASM~DEP08SIHTRPLC~EC~HGCV1~HGCV2~HGCV3~MESAFIBACKLOG~MET06AWCT~MET08ANLYSDIFAAST230~MET08AWCT~MET08DDUPSFS6420~MET08DDUPSP1TBI~MET08RESIHGCV~MET08RESIMAPCDE~MET08RESISRP2100~MET08THFTIRQS408M~MET08THFTIRSTRATUS~METCLIMATEC~R29~R32~R36~R47~R55~R57~R61~R62~R65~R70~R72~R73~R74~R75~R77~SP101~SPV01~SRP~TENCOR1~TENCOR2~TENCOR3~TRENDLOG~WC6INCH1~WC6INCH2~WC6INCH3~WC6INCH4~WC8INCH1~WC8INCH2~WC8INCH3",
"*.pdsf", "s",
"s", "X",
"X", "D:/5-Other-Small/Proxmox/ffnm",
"D:/ProgramData/VisualStudioCode|D:/6-Other-Large-Z/Linux-Ubuntu-Phares/home/lphares/dorico", "Day-Helper-2025-05-21",
"Day-Helper-2025-04-07", "*.pdf",
"z-include-patterns.nsv", "*.md",
"z-exclude-patterns.nsv", "2",
"https://isccvm57294f1ed/VisualStudioCode|hxttps://dorico.phares.duckdns.org|hxttps://mestsa006.infineon.com/VisualStudioCode", "MM-dd-yy",
"+|G|G|G|-", "Trans Date~Effective Date~Description~Withdrawal Deposit~Balance",
"||||", "s",
"666", "X",
"777", "D:/Tmp/phares/VisualStudioCode",
"888", "Day-Helper-2025-05-19",
"999", "D:/Tmp/phares/VisualStudioCode/.vscode/input.json",
"s", "s",
"X", "X",
"C:/Users/PHARES/AppData/Local/IFXApps/gatus", "D:/Tmp/phares/VisualStudioCode",
"Day-Helper-2025-04-04", "Day-Helper-2025-05-19",
"*.json", "D:/Tmp/phares/VisualStudioCodeLeft",
".metrics", "z-include-patterns.nsv",
"https://messa010ec.infineon.com/metrics", "z-exclude-patterns.nsv",
"gatus_results_endpoint_success", "http://localhost:5004",
"666", "/api/SyncV1/?",
"777", ",L",
"888", ".G",
"999", "+~G~~L~+~Custom-Default",
"" "",
], "+~G~~G~-~Mirror",
"cwd": "${workspaceFolder}", "+~G~~~~Update",
"console": "integratedTerminal", "+~G~~L~+~Custom-Default",
"stopAtEntry": false "-~G~~G~+~Custom-A",
}, "-~L~~L~+~Custom-B",
{ "+~L~~L~-~Custom-C",
"name": ".NET Core Attach", "s",
"type": "coreclr", "X",
"request": "attach" "\\\\mesfs.infineon.com\\EC_Characterization_Si\\Archive\\BIORAD4\\2025_Week_16\\2025-04-17",
}, "Day-Helper-2025-02-19",
{ "csv-*.pdsf",
"type": "node", "*.pdsf",
"request": "launch", "Time,HeaderUniqueId,UniqueId,Date,Wafer,Position,BIORAD4",
"name": "node Launch Current Opened File", ",BIORAD4",
"program": "${file}" ",BIORAD4",
} "Test|EventId,Date|DateTime,Position|Slot,DeltaThicknessSlotsOneAndTwentyFive|Actual Delta Thick Pts 1 and 25,PercentDeltaThicknessSlotsOneAndTwentyFive|% Delta Thick Pts 1 and 25,MID|Cassette,Lot|Batch,Title|Batch,Wafer|Text,Thickness|Site,MeanThickness|GradeMean,|BIORAD4",
] "Time,A_LOGISTICS,B_LOGISTICS,Test,Count,Index,MesEntity,MID,Date,Employee,Lot,PSN,Reactor,Recipe,Cassette,GradeStdDev,HeaderUniqueId,Layer,MeanThickness,PassFail,RDS,Slot,Title,UniqueId,Wafer,Zone,Mean,Position,StdDev,Thickness,ThicknessSlotOne,ThicknessSlotTwentyFive,DeltaThicknessSlotsOneAndTwentyFive,PercentDeltaThicknessSlotsOneAndTwentyFive",
"Time,A_LOGISTICS,B_LOGISTICS,Count,Sequence,MesEntity,Index,Batch,Cassette,DateTime,Destination,Mean,PassFail,Recipe,Reference,Site,Slot,Source,StdDev,Text,GradeMean,GradeStdDev,RDS,PSN,Reactor,Layer,Zone,Employee,InferredLot,Thickness First Slot,Thickness Last Slot,Actual Delta Thick Pts 1 and 25,% Delta Thick Pts 1 and 25,EventId",
"0,1,2,31,3,6,5,8,9,27,7,23,24,13,8,21,-1,25,20,12,22,16,7,-1,19,26,11,16,18,15,-1,-1,29,30",
"s",
"X",
"C:/Users/phares/AppData/Roaming/FreeFileSync",
"Day-Helper-2025-04-21",
"GlobalSettings.xml",
"LastSync|Config",
"s",
"X",
"L:/Tmp/MET08ANLYSDIFAAST230",
"Day-Helper-2025-03-06",
"*.pdsf",
"s",
"X",
"D:/ProgramData/VisualStudioCode|D:/6-Other-Large-Z/Linux-Ubuntu-Phares/home/lphares/dorico",
"Day-Helper-2025-04-07",
"z-include-patterns.nsv",
"z-exclude-patterns.nsv",
"https://isccvm57294f1ed/VisualStudioCode|hxttps://dorico.phares.duckdns.org|hxttps://mestsa006.infineon.com/VisualStudioCode",
"+|G|G|G|-",
"||||",
"666",
"777",
"888",
"999",
"s",
"X",
"C:/Users/PHARES/AppData/Local/IFXApps/gatus",
"Day-Helper-2025-04-04",
"*.json",
".metrics",
"https://messa010ec.infineon.com/metrics",
"gatus_results_endpoint_success",
"666",
"777",
"888",
"999",
""
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
},
{
"type": "node",
"request": "launch",
"name": "node Launch Current Opened File",
"program": "${file}"
},
{
"type": "bun",
"internalConsoleOptions": "neverOpen",
"request": "launch",
"name": "Debug File",
"program": "${file}",
"cwd": "${workspaceFolder}",
"stopOnEntry": false,
"watchMode": false
},
{
"type": "bun",
"internalConsoleOptions": "neverOpen",
"request": "launch",
"name": "Run File",
"program": "${file}",
"cwd": "${workspaceFolder}",
"noDebug": true,
"watchMode": false
}
]
} }

34
.vscode/mklink.md vendored
View File

@ -14,18 +14,28 @@ mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.kanbn" "D:\5-Other-Small\Kanban
mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.kanbn" "D:\5-Other-Small\Kanban\File-Folder-Helper" mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.kanbn" "D:\5-Other-Small\Kanban\File-Folder-Helper"
``` ```
```bash
del "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode"
del "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode-oss"
del "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode-insiders"
mkdir "C:\Users\phares\.vscode\extensions\ifx.type-script-helper-1.111.0\net8.0\win-x64\publish"
mkdir "C:\Users\phares\.vscode-oss\extensions\ifx.type-script-helper-1.111.0\net8.0\win-x64\publish"
mkdir "C:\Users\phares\.vscode-insiders\extensions\ifx.type-script-helper-1.111.0\net8.0\win-x64\publish"
mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode" "C:\Users\phares\.vscode\extensions\ifx.type-script-helper-1.111.0"
mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode-oss" "C:\Users\phares\.vscode-oss\extensions\ifx.type-script-helper-1.111.0"
mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode-insiders" "C:\Users\phares\.vscode-insiders\extensions\ifx.type-script-helper-1.111.0"
```
```bash Thu Jul 18 2024 13:47:40 GMT-0700 (Mountain Standard Time) ```bash Thu Jul 18 2024 13:47:40 GMT-0700 (Mountain Standard Time)
mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.vscode\.UserSecrets" "C:\Users\phares\AppData\Roaming\Microsoft\UserSecrets\8da397d4-13ec-4576-9722-3c79cad25563" mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.vscode\.UserSecrets" "C:\Users\phares\AppData\Roaming\Microsoft\UserSecrets\8da397d4-13ec-4576-9722-3c79cad25563"
``` ```
```bash 1749414316830 = 638850111168300000 = 2025-2.Spring = Sun Jun 08 2025 13:25:16 GMT-0700 (Mountain Standard Time)
C:\Users\PHARES\.vscode\extensions\infineon-technologies-ag-mesa-fi.infineon-technologies-ag-mesa-fi-cost-of-delay-helper-1.124.0
del "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode"
del "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode-oss"
del "L:\DevOps\Mesa_FI\File-Folder-Helper\.extensions-vscode-insiders"
mkdir "C:\Users\phares\.vscode\extensions\infineon-technologies-ag-mesa-fi.infineon-technologies-ag-mesa-fi-cost-of-delay-helper-1.124.0\net8.0\win-x64"
mkdir "C:\Users\phares\.vscode-oss\extensions\infineon-technologies-ag-mesa-fi.infineon-technologies-ag-mesa-fi-cost-of-delay-helper-1.124.0\net8.0\win-x64"
mkdir "C:\Users\phares\.vscode-insiders\extensions\infineon-technologies-ag-mesa-fi.infineon-technologies-ag-mesa-fi-cost-of-delay-helper-1.124.0\net8.0\win-x64"
mklink /J "C:\Users\phares\.vscode\extensions\infineon-technologies-ag-mesa-fi.infineon-technologies-ag-mesa-fi-cost-of-delay-helper-1.124.0\net8.0\win-x64\publish" "L:\DevOps\Mesa_FI\File-Folder-Helper\bin\Release\net8.0\win-x64\publish"
mklink /J "C:\Users\phares\.vscode-oss\extensions\infineon-technologies-ag-mesa-fi.infineon-technologies-ag-mesa-fi-cost-of-delay-helper-1.124.0\net8.0\win-x64\publish" "L:\DevOps\Mesa_FI\File-Folder-Helper\bin\Release\net8.0\win-x64\publish"
mklink /J "C:\Users\phares\.vscode-insiders\extensions\infineon-technologies-ag-mesa-fi.infineon-technologies-ag-mesa-fi-cost-of-delay-helper-1.124.0\net8.0\win-x64\publish" "L:\DevOps\Mesa_FI\File-Folder-Helper\bin\Release\net8.0\win-x64\publish"
```
```bash 1749957317559 = 638855541175590000 = 2025-2.Spring = Sat Jun 14 2025 20:15:17 GMT-0700 (Mountain Standard Time)
mkdir "L:\DevOps\MESA_FI\file-folder-helper\bin\Release\net8.0\win-x64"
mklink /J "L:\DevOps\MESA_FI\file-folder-helper\bin\Release\net8.0\win-x64\publish" "D:\5-Other-Small\Proxmox\publish"
```
```bash 1750459968132 = 638860567681320000 = 2025-3.Summer = Fri Jun 20 2025 15:52:47 GMT-0700 (Mountain Standard Time)
mklink /J "L:\DevOps\Mesa_FI\File-Folder-Helper\.vscode\.helper" "D:\5-Other-Small\Notes\Infineon\.vscode\helper"
```

View File

@ -24,9 +24,12 @@ internal static partial class Helper20241217
long Length, long Length,
string RelativePath); string RelativePath);
private record Record(string Directory, private record Record(string DestinationDirectory,
string DirectoryName,
Job Job, Job Job,
string Path); string Path,
string Snap2HyperTextMarkupLanguage,
string SourceDirectory);
private record Job(string? AlternatePath, private record Job(string? AlternatePath,
string Directory, string Directory,
@ -49,14 +52,17 @@ internal static partial class Helper20241217
{ {
} }
private static ReadOnlyCollection<File> GetFiles(string searchPattern, string[] ignoreFileNames, Record record) =>
GetFiles(record.SourceDirectory, searchPattern, ignoreFileNames);
internal static void Backup(ILogger<Worker> logger, List<string> args) internal static void Backup(ILogger<Worker> logger, List<string> args)
{ {
Job jobNew; Job jobNew;
string path; string path;
string? json; string? json;
string asidePath; string fileName;
bool areTheyTheSame; bool areTheyTheSame;
string directoryName; IEnumerable<Record> records;
logger.LogInformation(args[0]); logger.LogInformation(args[0]);
logger.LogInformation(args[1]); logger.LogInformation(args[1]);
logger.LogInformation(args[2]); logger.LogInformation(args[2]);
@ -64,44 +70,51 @@ internal static partial class Helper20241217
logger.LogInformation(args[4]); logger.LogInformation(args[4]);
ReadOnlyCollection<File> files; ReadOnlyCollection<File> files;
string searchPattern = args[2]; string searchPattern = args[2];
IEnumerable<string> searchPatternFiles;
string[] ignoreFileNames = args[3].Split('~'); string[] ignoreFileNames = args[3].Split('~');
string destination = Path.GetFullPath(args[4]); string destination = Path.GetFullPath(args[4]);
string sourceDirectory = Path.GetFullPath(args[0]); string sourceDirectory = Path.GetFullPath(args[0]);
char destinationDriveLetter = destination.Split(':')[0][0]; string? snap2HyperTextMarkupLanguage = args.Count < 6 ? null : Path.GetFullPath(args[5]);
logger.LogInformation("Searching <{sourceDirectory}> with search pattern {searchPattern}", args[0], searchPattern); logger.LogInformation("Searching <{sourceDirectory}> with search pattern {searchPattern}", args[0], searchPattern);
if (Debugger.IsAttached) if (Debugger.IsAttached)
Verify(searchPattern, ignoreFileNames); Verify(searchPattern, ignoreFileNames);
IEnumerable<Record> records = GetRecords(sourceDirectory, searchPattern); for (int i = 1; i < 3; i++)
foreach (Record record in records)
{ {
if (record.Job is null || string.IsNullOrEmpty(record.Job.Extension)) if (i == 1)
continue;
logger.LogInformation("Searching <{directory}>", record.Directory);
files = GetFiles(searchPattern, ignoreFileNames, record);
jobNew = GetJob(searchPattern, ignoreFileNames, record, files);
json = JsonSerializer.Serialize(jobNew, JobSourceGenerationContext.Default.Job);
areTheyTheSame = GetAreTheyTheSame(logger, searchPattern, ignoreFileNames, record, jobNew);
if (areTheyTheSame)
{ {
WriteAllText(record.Path, json); searchPatternFiles = Directory.EnumerateFiles(sourceDirectory, searchPattern, new EnumerationOptions { IgnoreInaccessible = true, RecurseSubdirectories = true });
continue; }
else if (i == 2)
{
searchPatternFiles = Directory.GetFiles(sourceDirectory, searchPattern, SearchOption.AllDirectories);
}
else
throw new NotImplementedException();
records = GetRecords(sourceDirectory, destination, searchPatternFiles);
foreach (Record record in records)
{
if (record.Job is null || string.IsNullOrEmpty(record.Job.Extension))
continue;
logger.LogInformation("Searching <{directory}>", record.SourceDirectory);
if (snap2HyperTextMarkupLanguage is not null && System.IO.File.Exists(snap2HyperTextMarkupLanguage))
WriteSnap2HyperTextMarkupLanguage(logger, snap2HyperTextMarkupLanguage, record);
files = GetFiles(searchPattern, ignoreFileNames, record);
jobNew = GetJob(searchPattern, ignoreFileNames, record, files);
json = JsonSerializer.Serialize(jobNew, JobSourceGenerationContext.Default.Job);
areTheyTheSame = GetAreTheyTheSame(logger, searchPattern, ignoreFileNames, record, jobNew);
if (areTheyTheSame)
{
WriteAllText(record.Path, json);
continue;
}
fileName = $"{record.DirectoryName}-{DateTime.Now:yyyy-MM-dd-HH-mm-ss-fff}{record.Job.Extension}";
path = Path.Combine(record.DestinationDirectory, fileName);
logger.LogInformation("Writing <{path}> extension", path);
WritePassedExtension(record, files, record.DirectoryName, path);
logger.LogInformation("Wrote <{path}> extension", path);
logger.LogInformation("Moved <{path}> extension", path);
WriteAllText(record, json, path);
} }
directoryName = Path.GetFileName(record.Directory);
asidePath = Path.Combine(record.Directory, $"{directoryName}-{DateTime.Now:yyyy-MM-dd-HH-mm-ss-fff}{record.Job.Extension}");
path = $"{destinationDriveLetter}{asidePath[1..]}";
logger.LogInformation("Writing <{path}> extension", path);
WritePassedExtension(record, files, directoryName, path);
logger.LogInformation("Wrote <{path}> extension", path);
MovePassedExtension(destination, path);
logger.LogInformation("Moved <{path}> extension", path);
WriteAllText(record.Path, json);
Helpers.HelperDeleteEmptyDirectories.DeleteEmptyDirectories(logger, $"{destinationDriveLetter}{record.Directory[1..]}");
}
if (Debugger.IsAttached && records.Count() == 0)
{
files = GetFiles(sourceDirectory, searchPattern, ignoreFileNames);
json = JsonSerializer.Serialize(files.ToArray(), FilesSourceGenerationContext.Default.FileArray);
WriteAllText(Path.Combine(Environment.CurrentDirectory, ".vscode", "helper", ".json"), json);
} }
} }
@ -150,24 +163,28 @@ internal static partial class Helper20241217
return results.AsReadOnly(); return results.AsReadOnly();
} }
private static IEnumerable<Record> GetRecords(string directory, string searchPattern) private static IEnumerable<Record> GetRecords(string directory, string destination, IEnumerable<string> files)
{ {
Job? job; Job? job;
string json; string json;
Record record; Record record;
string fileName; string fileName;
string directoryName; string directoryName;
IEnumerable<string> files = Directory.EnumerateFiles(directory, searchPattern, new EnumerationOptions { IgnoreInaccessible = true, RecurseSubdirectories = true }); string sourceDirectory;
string destinationDirectory;
string snap2HyperTextMarkupLanguage;
foreach (string file in files) foreach (string file in files)
{ {
fileName = Path.GetFileName(file); fileName = Path.GetFileName(file);
directoryName = Path.GetDirectoryName(file) ?? throw new Exception(); sourceDirectory = Path.GetDirectoryName(file) ?? throw new Exception();
directoryName = Path.GetFileName(sourceDirectory);
if (!fileName.StartsWith('.')) if (!fileName.StartsWith('.'))
{ {
System.IO.File.Move(file, Path.Combine(directoryName, $".{fileName}")); System.IO.File.Move(file, Path.Combine(sourceDirectory, $".{fileName}"));
continue; continue;
} }
json = System.IO.File.ReadAllText(file); json = System.IO.File.ReadAllText(file);
snap2HyperTextMarkupLanguage = Path.Combine(sourceDirectory, ".html");
if (string.IsNullOrEmpty(json) || json is "{}" or "[]") if (string.IsNullOrEmpty(json) || json is "{}" or "[]")
job = null; job = null;
else else
@ -180,11 +197,36 @@ internal static partial class Helper20241217
FilesTotalLength: 0, FilesTotalLength: 0,
Keep: 3, Keep: 3,
Targets: []); Targets: []);
record = new(Directory: directoryName, Job: job, Path: file); destinationDirectory = $"{destination}{sourceDirectory[2..]}";
if (!Directory.Exists(destinationDirectory))
_ = Directory.CreateDirectory(destinationDirectory);
record = new(DestinationDirectory: destinationDirectory,
DirectoryName: directoryName,
Job: job,
Path: file,
Snap2HyperTextMarkupLanguage: snap2HyperTextMarkupLanguage,
SourceDirectory: sourceDirectory);
yield return record; yield return record;
} }
} }
private static void WriteSnap2HyperTextMarkupLanguage(ILogger<Worker> logger, string snap2HyperTextMarkupLanguage, Record record)
{
string title = Path.GetFileName(record.SourceDirectory);
ProcessStartInfo processStartInfo = new()
{
Arguments = $"-path:\"{record.SourceDirectory}\" -outfile:\"{record.Snap2HyperTextMarkupLanguage}\" -title:\"{title}\" -hidden -silent",
FileName = snap2HyperTextMarkupLanguage,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
WorkingDirectory = record.SourceDirectory
};
Process? process = Process.Start(processStartInfo) ?? throw new Exception("Process should not be null.");
process.WaitForExit();
logger.LogInformation($"Snap2HyperTextMarkupLanguage: {process.StandardOutput.ReadToEnd()}{Environment.NewLine}{process.StandardError.ReadToEnd()}{Environment.NewLine}{process.ExitCode}");
}
private static ReadOnlyCollection<File> GetFiles(string directory, string searchPattern, string[] ignoreFileNames) private static ReadOnlyCollection<File> GetFiles(string directory, string searchPattern, string[] ignoreFileNames)
{ {
List<File> results = []; List<File> results = [];
@ -209,16 +251,13 @@ internal static partial class Helper20241217
return results.AsReadOnly(); return results.AsReadOnly();
} }
private static ReadOnlyCollection<File> GetFiles(string searchPattern, string[] ignoreFileNames, Record record) =>
GetFiles(record.Directory, searchPattern, ignoreFileNames);
private static Job GetJob(string searchPattern, string[] ignoreFileNames, Record record, ReadOnlyCollection<File> files) private static Job GetJob(string searchPattern, string[] ignoreFileNames, Record record, ReadOnlyCollection<File> files)
{ {
Job result; Job result;
ReadOnlyCollection<File> collection = GetFilteredFiles(searchPattern, ignoreFileNames, files); ReadOnlyCollection<File> collection = GetFilteredFiles(searchPattern, ignoreFileNames, files);
double filesTotalLengthNew = collection.Select(l => l.Length).Sum(); double filesTotalLengthNew = collection.Select(l => l.Length).Sum();
result = new(AlternatePath: record.Job.AlternatePath, result = new(AlternatePath: record.Job.AlternatePath,
Directory: record.Directory, Directory: record.SourceDirectory,
Extension: record.Job.Extension, Extension: record.Job.Extension,
Files: collection.ToArray(), Files: collection.ToArray(),
FilesCount: collection.Count, FilesCount: collection.Count,
@ -237,7 +276,7 @@ internal static partial class Helper20241217
if (filesCountNew != filesCountOld) if (filesCountNew != filesCountOld)
{ {
result = false; result = false;
logger.LogInformation("<{directory}> file count has changed {filesCountNew} != {filesCountOld}", record.Directory, filesCountNew, filesCountOld); logger.LogInformation("<{directory}> file count has changed {filesCountNew} != {filesCountOld}", record.SourceDirectory, filesCountNew, filesCountOld);
} }
else else
{ {
@ -246,7 +285,7 @@ internal static partial class Helper20241217
if (filesTotalLengthNew != filesTotalLengthOld) if (filesTotalLengthNew != filesTotalLengthOld)
{ {
result = false; result = false;
logger.LogInformation("<{directory}> file length has changed {filesTotalLengthNew} != {filesTotalLengthOld}", record.Directory, filesTotalLengthNew, filesTotalLengthOld); logger.LogInformation("<{directory}> file length has changed {filesTotalLengthNew} != {filesTotalLengthOld}", record.SourceDirectory, filesTotalLengthNew, filesTotalLengthOld);
} }
else else
{ {
@ -262,7 +301,7 @@ internal static partial class Helper20241217
WriteAllText(Path.Combine(Environment.CurrentDirectory, ".vscode", "helper", "old.json"), jsonOld); WriteAllText(Path.Combine(Environment.CurrentDirectory, ".vscode", "helper", "old.json"), jsonOld);
WriteAllText(Path.Combine(Environment.CurrentDirectory, ".vscode", "helper", "new.json"), jsonNew); WriteAllText(Path.Combine(Environment.CurrentDirectory, ".vscode", "helper", "new.json"), jsonNew);
} }
logger.LogInformation("<{directory}> file serialized are different {filesTotalLengthNew} != {filesTotalLengthOld}", record.Directory, filesTotalLengthNew, filesTotalLengthOld); logger.LogInformation("<{directory}> file serialized are different {filesTotalLengthNew} != {filesTotalLengthOld}", record.SourceDirectory, filesTotalLengthNew, filesTotalLengthOld);
} }
} }
} }
@ -286,33 +325,16 @@ internal static partial class Helper20241217
throw new NotImplementedException(); throw new NotImplementedException();
} }
private static void MovePassedExtension(string destination, string path)
{
string checkPath = $"{destination}{path[2..]}";
string checkDirectory = Path.GetDirectoryName(checkPath) ?? throw new Exception();
if (!Directory.Exists(checkDirectory))
_ = Directory.CreateDirectory(checkDirectory);
if (System.IO.File.Exists(checkPath))
throw new NotImplementedException($"<{checkPath}> already exists!");
System.IO.File.Move(path, checkPath);
}
private static void WriteISO(Record record, ReadOnlyCollection<File> files, string path, string directoryName) private static void WriteISO(Record record, ReadOnlyCollection<File> files, string path, string directoryName)
{ {
string checkDirectory = Path.GetDirectoryName(path) ?? throw new Exception();
if (!Directory.Exists(checkDirectory))
_ = Directory.CreateDirectory(checkDirectory);
CDBuilder builder = new() { UseJoliet = true, VolumeIdentifier = directoryName.Length < 25 ? directoryName : directoryName[..25] }; CDBuilder builder = new() { UseJoliet = true, VolumeIdentifier = directoryName.Length < 25 ? directoryName : directoryName[..25] };
foreach (File file in files) foreach (File file in files)
_ = builder.AddFile(file.RelativePath, Path.Combine(record.Directory, file.RelativePath)); _ = builder.AddFile(file.RelativePath, Path.Combine(record.SourceDirectory, file.RelativePath));
builder.Build(path); builder.Build(path);
} }
private static void WriteZIP(Record record, ReadOnlyCollection<File> files, string path) private static void WriteZIP(Record record, ReadOnlyCollection<File> files, string path)
{ {
string checkDirectory = Path.GetDirectoryName(path) ?? throw new Exception();
if (!Directory.Exists(checkDirectory))
_ = Directory.CreateDirectory(checkDirectory);
using ZipArchive zip = ZipFile.Open(path, ZipArchiveMode.Create); using ZipArchive zip = ZipFile.Open(path, ZipArchiveMode.Create);
string directoryEntry; string directoryEntry;
List<string> directoryEntries = []; List<string> directoryEntries = [];
@ -325,7 +347,16 @@ internal static partial class Helper20241217
_ = zip.CreateEntry(file.RelativePath); _ = zip.CreateEntry(file.RelativePath);
} }
foreach (File file in files) foreach (File file in files)
_ = zip.CreateEntryFromFile(Path.Combine(record.Directory, file.RelativePath), file.RelativePath); _ = zip.CreateEntryFromFile(Path.Combine(record.SourceDirectory, file.RelativePath), file.RelativePath);
}
private static void WriteAllText(Record record, string text, string path)
{
WriteAllText(record.Path, text);
System.IO.File.Copy(record.Path, $"{path}.json");
string checkFile = Path.Combine(record.SourceDirectory, ".html");
if (System.IO.File.Exists(checkFile))
System.IO.File.Copy(checkFile, $"{path}.html");
} }
} }

View File

@ -277,12 +277,12 @@ internal static partial class Helper20250219 {
} }
if (!lookForNumbers) { if (!lookForNumbers) {
for (int c = 0; c < segments.Length; c++) { for (int c = 0; c < segments.Length; c++) {
value = segments[c].Replace("\"", "\\\"").Replace("\\", "\\\\"); value = segments[c].Replace("\\", "\\\\").Replace("\"", "\\\"");
_ = stringBuilder.Append('"').Append(processDataStandardFormat.Columns[c]).Append("\":\"").Append(value).Append("\","); _ = stringBuilder.Append('"').Append(processDataStandardFormat.Columns[c]).Append("\":\"").Append(value).Append("\",");
} }
} else { } else {
for (int c = 0; c < segments.Length; c++) { for (int c = 0; c < segments.Length; c++) {
value = segments[c].Replace("\"", "\\\"").Replace("\\", "\\\\"); value = segments[c].Replace("\\", "\\\\").Replace("\"", "\\\"");
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
_ = stringBuilder.Append('"').Append(processDataStandardFormat.Columns[c]).Append("\":").Append(value).Append("null,"); _ = stringBuilder.Append('"').Append(processDataStandardFormat.Columns[c]).Append("\":").Append(value).Append("null,");
else if (value.All(char.IsDigit)) else if (value.All(char.IsDigit))

View File

@ -71,7 +71,7 @@ internal static partial class Helper20250306 {
if (segments.Length != columnsLength) if (segments.Length != columnsLength)
continue; continue;
for (int c = 1; c < segments.Length; c++) { for (int c = 1; c < segments.Length; c++) {
value = segments[c].Replace("\"", "\\\"").Replace("\\", "\\\\"); value = segments[c].Replace("\\", "\\\\").Replace("\"", "\\\"");
line += '"' + columns[c].Trim('"') + '"' + ':' + '"' + value + '"' + ','; line += '"' + columns[c].Trim('"') + '"' + ':' + '"' + value + '"' + ',';
} }
line = line.Substring(0, line.Length - 1) + '}' + ',' + '\n'; line = line.Substring(0, line.Length - 1) + '}' + ',' + '\n';

View File

@ -0,0 +1,89 @@
using System.Collections.ObjectModel;
using Microsoft.Extensions.Logging;
namespace File_Folder_Helper.ADO2025.PI6;
internal static partial class Helper20250618 {
private record Record(string Directory, List<string> Files);
internal static void MoveAllButXOfEach(ILogger<Worker> logger, List<string> args) {
int keep = int.Parse(args[5]);
logger.LogInformation(args[0]);
logger.LogInformation(args[1]);
logger.LogInformation(args[2]);
string searchPattern = args[2];
string split = args[4].Split('~')[0];
string sourceDirectory = Path.GetFullPath(args[0].Split('~')[0]);
string destinationDirectory = Path.GetFullPath(args[3].Split('~')[0]);
if (destinationDirectory.Contains(sourceDirectory)) {
throw new Exception("Not allowed!");
}
ReadOnlyCollection<string> directories = GetDirectories(sourceDirectory);
MoveAllButXOfEachB(logger, searchPattern, sourceDirectory, destinationDirectory, split, keep, directories);
}
private static ReadOnlyCollection<string> GetDirectories(string sourceDirectory) {
List<string> results = [sourceDirectory];
results.AddRange(Directory.GetDirectories(sourceDirectory, "*", SearchOption.AllDirectories));
return results.AsReadOnly();
}
private static void MoveAllButXOfEachB(ILogger<Worker> logger, string searchPattern, string sourceDirectory, string destinationDirectory, string split, int keep, ReadOnlyCollection<string> directories) {
string[] files;
int sourceDirectoryLength = sourceDirectory.Length;
ReadOnlyDictionary<string, ReadOnlyCollection<string>> keyValuePairs;
foreach (string directory in directories) {
files = Directory.GetFiles(directory, searchPattern, SearchOption.TopDirectoryOnly);
keyValuePairs = GetFiles(split, files);
foreach (KeyValuePair<string, ReadOnlyCollection<string>> keyValuePair in keyValuePairs) {
if (keyValuePair.Value.Count <= keep) {
continue;
} else {
MoveAllButXOfEachC(logger, sourceDirectoryLength, destinationDirectory, keyValuePair);
}
}
}
}
private static ReadOnlyDictionary<string, ReadOnlyCollection<string>> GetFiles(string split, string[] files) {
Dictionary<string, ReadOnlyCollection<string>> results = [];
string key;
List<string>? collection;
Dictionary<string, List<string>> keyValuePairs = [];
foreach (string file in files) {
key = Path.GetFileName(file).Split(split)[0];
if (!keyValuePairs.TryGetValue(key, out collection)) {
keyValuePairs.Add(key, []);
if (!keyValuePairs.TryGetValue(key, out collection)) {
throw new Exception();
}
}
collection.Add(file);
}
foreach (KeyValuePair<string, List<string>> keyValuePair in keyValuePairs) {
results.Add(keyValuePair.Key, keyValuePair.Value.OrderByDescending(l => l).ToArray().AsReadOnly());
}
return results.AsReadOnly();
}
private static void MoveAllButXOfEachC(ILogger<Worker> logger, int sourceDirectoryLength, string destinationDirectory, KeyValuePair<string, ReadOnlyCollection<string>> keyValuePair) {
string file;
string checkFile;
string checkDirectory;
for (int i = 1; i < keyValuePair.Value.Count; i++) {
file = keyValuePair.Value[i];
checkFile = $"{destinationDirectory}{file[sourceDirectoryLength..]}";
checkDirectory = Path.GetDirectoryName(checkFile) ?? throw new Exception();
if (!Directory.Exists(checkDirectory))
_ = Directory.CreateDirectory(checkDirectory);
if (File.Exists(checkFile))
continue;
File.Move(file, checkFile);
logger.LogInformation("<{file}> moved", file);
}
}
}

View File

@ -0,0 +1,89 @@
using System.Collections.ObjectModel;
using DiscUtils.Iso9660;
using Microsoft.Extensions.Logging;
namespace File_Folder_Helper.ADO2025.PI6;
internal static partial class Helper20250628 {
private record Record(string Path,
long Size,
long Ticks);
internal static void LogIsoInformation(ILogger<Worker> logger, List<string> args) {
logger.LogInformation(args[0]);
logger.LogInformation(args[1]);
logger.LogInformation(args[2]);
string searchPattern = args[2];
string sourceDirectory = Path.GetFullPath(args[0].Split('~')[0]);
string[] searchPatternFiles = Directory.GetFiles(sourceDirectory, searchPattern, SearchOption.AllDirectories);
ReadOnlyCollection<Record> records = GetRecords(searchPatternFiles);
LogIsoInformation(logger, records, args[3]);
}
private static ReadOnlyCollection<Record> GetRecords(string[] searchPatternFiles) {
List<Record> results = [];
Record record;
string[] files;
FileInfo fileInfo;
foreach (string searchPatternFile in searchPatternFiles) {
fileInfo = new(searchPatternFile);
record = new(searchPatternFile, fileInfo.Length, fileInfo.LastWriteTime.Ticks);
results.Add(record);
}
return results.AsReadOnly();
}
private static string GetSizeWithSuffix(long value) {
string result;
int i = 0;
string[] SizeSuffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
if (value < 0) {
result = "-" + GetSizeWithSuffix(-value);
} else {
while (Math.Round(value / 1024f) >= 1) {
value /= 1024;
i++;
}
result = string.Format("{0:n1} {1}", value, SizeSuffixes[i]);
}
return result;
}
private static void LogIsoInformation(ILogger<Worker> logger, ReadOnlyCollection<Record> records, string letter) {
string size;
string[] files;
List<string> messages = [];
Record[] sorted = records.OrderBy(l => l.Ticks).ToArray();
string directory = Path.Combine(Environment.CurrentDirectory, ".vscode", "helper");
foreach (Record record in sorted) {
if (string.IsNullOrEmpty(record.Path)) {
using FileStream fileStream = File.OpenRead(record.Path);
CDReader reader = new(fileStream, true);
files = reader.GetFiles("", "*");
foreach (string _ in files) {
}
}
size = GetSizeWithSuffix(record.Size);
messages.Add($"#; {size}");
messages.Add($"#; {new DateTime(record.Ticks):yyyy-MM-dd HH:mm:ss.fff}");
messages.Add($"$driveLetter = \"{letter}:\"");
messages.Add($"$isoImg = \"{record.Path}\"");
messages.Add("$diskImg = Mount-DiskImage -ImagePath $isoImg -NoDriveLetter");
messages.Add("$volInfo = $diskImg | Get-Volume");
messages.Add("mountvol $driveLetter $volInfo.UniqueId");
messages.Add(string.Empty);
}
if (Directory.Exists(directory)) {
File.WriteAllText(Path.Combine(directory, ".lsv"), string.Join(Environment.NewLine, messages));
} else {
messages.Reverse();
foreach (string message in messages) {
logger.LogInformation(message);
}
}
}
}

View File

@ -0,0 +1,427 @@
using System.Collections.ObjectModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
namespace File_Folder_Helper.ADO2025.PI6;
internal static partial class Helper20250701 {
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(JsonElement))]
internal partial class JsonElementSourceGenerationContext : JsonSerializerContext {
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(ReadOnlyDictionary<string, ReadOnlyDictionary<string, string>>))]
internal partial class ReadOnlyDictionaryStringReadOnlyDictionaryStringStringSourceGenerationContext : JsonSerializerContext {
}
internal static void ProcessDataStandardFormatTo(ILogger<Worker> logger, List<string> args) {
logger.LogInformation(args[0]);
logger.LogInformation(args[1]);
logger.LogInformation(args[2]);
logger.LogInformation(args[3]);
logger.LogInformation(args[4]);
logger.LogInformation(args[5]);
logger.LogInformation(args[6]);
logger.LogInformation(args[7]);
string[] segments;
string extension = args[5];
string timeColumn = args[4];
string searchPattern = args[2];
int sizeFilter = int.Parse(args[3]);
string[] columns = args[6].Split(',');
Dictionary<string, string> columnMapping = [];
string sourceDirectory = Path.GetFullPath(args[0].Split('~')[0]);
string destinationDirectory = Path.GetFullPath(args[7].Split('~')[0]);
foreach (string column in columns) {
segments = column.Split('~');
columnMapping.Add(segments[0], segments[1]);
}
string[] directories = Directory.GetDirectories(sourceDirectory, "*", SearchOption.TopDirectoryOnly);
ProcessDataStandardFormatTo(logger, sourceDirectory, searchPattern, sizeFilter, timeColumn, extension, columnMapping, destinationDirectory, directories);
Helpers.HelperDeleteEmptyDirectories.DeleteEmptyDirectories(logger, sourceDirectory);
}
private static void ProcessDataStandardFormatTo(ILogger<Worker> logger, string sourceDirectory, string searchPattern, int sizeFilter, string timeColumn, string extension, Dictionary<string, string> columnMapping, string destinationDirectory, string[] directories) {
string text;
string? json;
string[] files;
string markdown;
string checkFile;
string[] matches;
FileInfo fileInfo;
string? pipeTable;
string? collections;
string directoryName;
string checkDirectory;
foreach (string directory in directories) {
if (sizeFilter < 987654321 && Path.GetFileName(directory).Contains('-')) {
continue;
}
files = Directory.GetFiles(directory, searchPattern, SearchOption.TopDirectoryOnly);
foreach (string file in files) {
fileInfo = new(file);
if (fileInfo.LastWriteTime > DateTime.Now.AddSeconds(-5)) {
continue;
}
directoryName = Path.GetFileName(fileInfo.DirectoryName);
if (fileInfo.Length > sizeFilter && !directoryName.StartsWith('Z')) {
checkDirectory = Path.Combine(sourceDirectory, $"Z{directoryName}");
if (!Directory.Exists(checkDirectory)) {
_ = Directory.CreateDirectory(checkDirectory);
}
checkFile = Path.Combine(checkDirectory, fileInfo.Name);
if (File.Exists(checkFile)) {
continue;
}
File.Move(file, checkFile);
continue;
}
checkDirectory = Path.Combine(destinationDirectory, directoryName);
if (!Directory.Exists(checkDirectory)) {
_ = Directory.CreateDirectory(checkDirectory);
}
checkFile = Path.Combine(checkDirectory, $"{fileInfo.Name}{extension}");
if (File.Exists(checkFile)) {
continue;
}
if (extension == ".md") {
collections = GetMarkdown(logger, timeColumn, columnMapping, file, fileInfo.Name);
if (string.IsNullOrEmpty(collections)) {
logger.LogWarning("collections is null");
continue;
}
text = collections;
} else if (extension == ".pipe.md") {
json = GetJavaScriptObjectNotation(logger, file);
if (string.IsNullOrEmpty(json)) {
logger.LogWarning("json is null");
continue;
}
pipeTable = GetPipeTable(logger, json);
if (string.IsNullOrEmpty(pipeTable)) {
logger.LogWarning("pipeTable is null");
continue;
}
markdown = $"# {fileInfo.Name}{Environment.NewLine}{Environment.NewLine}{pipeTable}{Environment.NewLine}";
text = markdown;
} else if (extension == ".json") {
json = GetJavaScriptObjectNotation(logger, file);
if (string.IsNullOrEmpty(json)) {
logger.LogWarning("json is null");
continue;
}
text = json;
} else {
logger.LogWarning("{extension} is not mapped!", extension);
continue;
}
File.WriteAllText(checkFile, text);
File.SetLastWriteTime(checkFile, fileInfo.LastAccessTime);
logger.LogInformation("<{checkFile}> was written", checkFile);
}
}
}
private static string? GetMarkdown(ILogger<Worker> logger, string timeColumn, Dictionary<string, string> columnMapping, string file, string name) {
string? result = null;
string[] lines = File.ReadAllLines(file);
int? columnTitlesLine = GetProcessDataStandardFormatColumnTitlesLine(lines);
if (columnTitlesLine is null) {
logger.LogWarning("<{columnTitlesLine}> is null", nameof(columnTitlesLine));
} else {
if (lines.Length < columnTitlesLine.Value + 1) {
logger.LogWarning("<{lines}>(s)", lines.Length);
} else {
result = GetMarkdown(timeColumn, columnMapping, name, lines, columnTitlesLine);
}
}
return result;
}
private static int? GetProcessDataStandardFormatColumnTitlesLine(string[] lines) {
int? result = null;
for (int i = 0; i < lines.Length; i++) {
if (lines[i].StartsWith("END_OFFSET") && i + 3 < lines.Length) {
result = i + 1;
break;
}
}
return result;
}
private static ReadOnlyDictionary<string, ReadOnlyCollection<string>> GetKeyValuePairs(int columnTitlesLine, string[] lines) {
Dictionary<string, ReadOnlyCollection<string>> results = [];
string value;
string[] segments;
List<List<string>> collections = [];
string[] columns = lines[columnTitlesLine].Split('\t');
foreach (string column in columns) {
collections.Add([]);
}
for (int i = columnTitlesLine + 1; i < lines.Length; i++) {
if (lines[i].StartsWith("NUM_DATA_ROWS")) {
break;
}
segments = lines[i].Split('\t');
if (segments.Length > columns.Length) {
continue;
}
for (int c = 0; c < segments.Length; c++) {
collections[c].Add(segments[c]);
}
}
for (int i = 0; i < collections.Count; i++) {
if (collections[i].Count > 1) {
if (string.IsNullOrEmpty(collections[i][0])) {
collections[i][0] = collections[i][1];
}
if (string.IsNullOrEmpty(collections[i][^1])) {
collections[i][^1] = collections[i][^2];
}
}
results.Add(columns[i].Trim('"'), collections[i].AsReadOnly());
}
return results.AsReadOnly();
}
private static string? GetMarkdown(string timeColumn, Dictionary<string, string> columnMapping, string name, string[] lines, int? columnTitlesLine) {
string? result;
List<string> charts = [];
List<string> results = [];
ReadOnlyDictionary<string, ReadOnlyCollection<string>> keyValuePairs = GetKeyValuePairs(columnTitlesLine.Value, lines);
string[] columns = keyValuePairs.Keys.OrderBy(l => l).ToArray();
if (!columns.Contains(timeColumn)) {
result = null;
} else {
string? alias;
string[] mappedColumns = columnMapping.Keys.ToArray();
string labels = string.Join(',', keyValuePairs[timeColumn].Select(l => l.Replace(',', '_')));
foreach (string column in columns) {
if (column == timeColumn) {
continue;
}
if (!columnMapping.TryGetValue(column, out alias) || keyValuePairs[column].Count == 0 || string.IsNullOrEmpty(keyValuePairs[column][0])) {
results.Add(string.Concat("## ", column, Environment.NewLine, Environment.NewLine,
"- ", string.Join(',', keyValuePairs[column].Select(l => l.Replace(',', '_'))), Environment.NewLine
));
} else {
charts.Add(string.Concat(
"## ", column, " - ", alias, Environment.NewLine, Environment.NewLine,
"```chart", Environment.NewLine,
"{\"type\": \"line\", \"data\": {\"datasets\": [{", Environment.NewLine,
"\"label\": \"", column, " - ", alias, "\",", Environment.NewLine,
"\"data\": [", string.Join(',', keyValuePairs[column].Select(l => l.Replace(',', '_'))), "],", Environment.NewLine,
"\"borderColor\": \"rgb(75, 192, 192)\"", Environment.NewLine,
"}],", Environment.NewLine,
"\"labels\": [", labels, "]", Environment.NewLine,
"}}", Environment.NewLine,
"```", Environment.NewLine
));
}
}
if (results.Count == 0 && charts.Count == 0) {
result = null;
} else {
string[] segments;
results.Add($"## Footer{Environment.NewLine}");
results.Insert(0, $"# {name}{Environment.NewLine}");
for (int i = columnTitlesLine.Value + 1; i < lines.Length; i++) {
if (lines[i].StartsWith("NUM_DATA_ROWS")) {
for (int j = i; j < lines.Length; j++) {
results.Add($"- {lines[j]}");
}
}
}
results.Add(string.Empty);
results.Add(string.Empty);
result = $"{string.Join(Environment.NewLine, results)}{string.Join(Environment.NewLine, charts)}";
}
}
return result;
}
private static string? GetJavaScriptObjectNotation(ILogger<Worker> logger, string file) {
string? result = null;
string[] lines = File.ReadAllLines(file);
int? columnTitlesLine = GetProcessDataStandardFormatColumnTitlesLine(lines);
if (columnTitlesLine is null) {
logger.LogWarning("<{columnTitlesLine}> is null", nameof(columnTitlesLine));
} else {
if (lines.Length < columnTitlesLine.Value + 1) {
logger.LogWarning("<{lines}>(s)", lines.Length);
} else {
result = GetJavaScriptObjectNotation(columnTitlesLine.Value, lines);
}
}
return result;
}
private static string GetJavaScriptObjectNotation(int columnTitlesLine, string[] lines) {
#pragma warning disable CA1845, IDE0057
string result;
string record;
string value;
string[] segments;
string? json = null;
List<string> records = [];
ReadOnlyCollection<string>? footerLines = null;
string[] columns = lines[columnTitlesLine].Split('\t');
for (int i = columnTitlesLine + 1; i < lines.Length; i++) {
if (lines[i].StartsWith("NUM_DATA_ROWS")) {
footerLines = GetFooterLines(lines, i);
break;
}
record = "{";
segments = lines[i].Split('\t');
if (segments.Length > columns.Length) {
continue;
}
for (int c = 0; c < segments.Length; c++) {
value = segments[c].Replace("\\", "\\\\").Replace("\"", "\\\"");
record += string.Concat('"', columns[c].Trim('"'), '"', ':', '"', value, '"', ',');
}
record = string.Concat(record.Substring(0, record.Length - 1), '}');
records.Add(record);
}
if (footerLines is not null && footerLines.Count > 0) {
ReadOnlyDictionary<string, string> footerKeyValuePairs = GetFooterKeyValuePairs(footerLines);
ReadOnlyDictionary<string, ReadOnlyDictionary<string, string>> logisticKeyValuePairs = GetLogisticKeyValuePairs(footerLines, footerKeyValuePairs);
json = JsonSerializer.Serialize(logisticKeyValuePairs, ReadOnlyDictionaryStringReadOnlyDictionaryStringStringSourceGenerationContext.Default.ReadOnlyDictionaryStringReadOnlyDictionaryStringString);
}
string footerText = string.IsNullOrEmpty(json) || json == "{}" ? string.Empty : $",{Environment.NewLine}\"PDSF\":{Environment.NewLine}{json}";
result = string.Concat(
'{',
Environment.NewLine,
'"',
"Count",
'"',
": ",
records.Count,
',',
Environment.NewLine,
'"',
"Records",
'"',
": ",
Environment.NewLine,
'[',
Environment.NewLine,
string.Join($",{Environment.NewLine}", records),
Environment.NewLine,
']',
footerText,
'}');
return result;
#pragma warning restore CA1845, IDE0057
}
private static ReadOnlyCollection<string> GetFooterLines(string[] lines, int i) {
List<string> results = [];
for (int j = i; j < lines.Length; j++) {
results.Add(lines[j]);
if (lines[j].StartsWith("END_HEADER"))
break;
}
return results.AsReadOnly();
}
private static ReadOnlyDictionary<string, string> GetFooterKeyValuePairs(ReadOnlyCollection<string> footerLines) {
Dictionary<string, string> results = [];
string[] segments;
foreach (string footerLine in footerLines) {
segments = footerLine.Split('\t');
if (segments.Length != 2 || string.IsNullOrEmpty(segments[1].Trim())) {
continue;
}
if (segments[1].Contains(';')) {
continue;
} else {
results.Add(segments[0], segments[1]);
}
}
return results.AsReadOnly();
}
private static ReadOnlyDictionary<string, ReadOnlyDictionary<string, string>> GetLogisticKeyValuePairs(ReadOnlyCollection<string> footerLines, ReadOnlyDictionary<string, string> footerKeyValuePairs) {
Dictionary<string, ReadOnlyDictionary<string, string>> results = [];
string[] segments;
string[] subSegments;
string[] subSubSegments;
Dictionary<string, string>? keyValue;
results.Add("Footer", footerKeyValuePairs);
Dictionary<string, Dictionary<string, string>> keyValuePairs = [];
foreach (string footerLine in footerLines) {
segments = footerLine.Split('\t');
if (segments.Length != 2 || string.IsNullOrEmpty(segments[1].Trim())) {
continue;
}
if (!segments[1].Contains(';') || !segments[1].Contains('=')) {
continue;
} else {
subSegments = segments[1].Split(';');
if (subSegments.Length < 1) {
continue;
}
if (!keyValuePairs.TryGetValue(segments[0], out keyValue)) {
keyValuePairs.Add(segments[0], []);
if (!keyValuePairs.TryGetValue(segments[0], out keyValue)) {
throw new Exception();
}
}
foreach (string segment in subSegments) {
subSubSegments = segment.Split('=');
if (subSubSegments.Length != 2) {
continue;
}
keyValue.Add(subSubSegments[0], subSubSegments[1]);
}
}
}
foreach (KeyValuePair<string, Dictionary<string, string>> keyValuePair in keyValuePairs) {
results.Add(keyValuePair.Key, keyValuePair.Value.AsReadOnly());
}
return results.AsReadOnly();
}
private static string? GetPipeTable(ILogger<Worker> logger, string json) {
string? result = null;
string? value;
string[]? columns = null;
List<string> values = [];
List<string> results = [];
Dictionary<string, string> keyValuePairs = [];
JsonElement jsonElement = JsonSerializer.Deserialize(json, JsonElementSourceGenerationContext.Default.JsonElement);
JsonElement[] jsonElements = jsonElement.EnumerateArray().ToArray();
foreach (JsonElement j in jsonElements) {
values.Clear();
keyValuePairs.Clear();
foreach (JsonProperty jsonProperty in j.EnumerateObject().ToArray()) {
if (columns is null) {
JsonProperty[] jsonProperties = j.EnumerateObject().OrderBy(l => l.Name).ToArray();
columns = jsonProperties.Select(l => l.Name).ToArray();
results.Add($"|{string.Join('|', columns)}|");
results.Add($"|{string.Join('|', columns.Select(l => '-'))}|");
}
keyValuePairs.Add(jsonProperty.Name, jsonProperty.Value.ToString());
}
foreach (string column in columns) {
if (!keyValuePairs.TryGetValue(column, out value)) {
values.Add(new string(' ', column.Length));
} else {
values.Add(value.PadLeft(column.Length, ' '));
}
}
results.Add($"|{string.Join('|', values)}|");
}
if (results.Count > 0) {
result = string.Join(Environment.NewLine, results);
}
return result;
}
}

View File

@ -0,0 +1,112 @@
using System.Drawing;
using Microsoft.Extensions.Logging;
namespace File_Folder_Helper.ADO2025.PI6;
internal static partial class Helper20250705 {
internal static void ExportFaces(ILogger<Worker> logger, List<string> args) {
string checkFile;
logger.LogInformation(args[0]);
logger.LogInformation(args[1]);
logger.LogInformation(args[2]);
logger.LogInformation(args[3]);
logger.LogInformation(args[4]);
logger.LogInformation(args[5]);
logger.LogInformation(args[6]);
logger.LogInformation(args[7]);
string locationDigits = args[6];
string[] mp = args[4].Split('~');
string[] mwg = args[5].Split('~');
string[] xyz = args[3].Split(',');
string[] fileNames = args[2].Split('~');
string[] wholePercentages = args[7].Split('~');
string sourceDirectory = Path.GetFullPath(args[0].Split('~')[0]);
if (mp.Length != mwg.Length || mp.Length != wholePercentages.Length) {
logger.LogWarning("Lengths don't match!");
} else {
foreach (string fileName in fileNames) {
checkFile = Path.Combine(sourceDirectory, fileName);
if (!File.Exists(checkFile)) {
logger.LogWarning("<{file}> doesn't exist!", args[2]);
continue;
}
for (int i = 0; i < mp.Length; i++) {
if (wholePercentages[i].StartsWith("x-")) {
logger.LogWarning("Skipping {wholePercentages}", wholePercentages[i]);
continue;
}
Ext(checkFile, xyz, i, mp[i], mwg[i], locationDigits, wholePercentages[i]);
}
}
}
}
private static void Ext(string checkFile, string[] xyz, int i, string mp, string mwg, string locationDigits, string wholePercentages) {
int width = int.Parse(xyz[0]);
int height = int.Parse(xyz[1]);
string[] mpValues = mp.Split(',');
string[] mwgValues = mwg.Split(',');
float mpWidth = float.Parse(mpValues[2]) * width;
float mpHeight = float.Parse(mpValues[3]) * height;
double mpLeft = (float.Parse(mpValues[0]) * width) - (mpWidth * .5);
double mpTop = (float.Parse(mpValues[1]) * height) - (mpHeight * .5);
Extract(file: checkFile,
width: mpWidth,
height: mpHeight,
left: mpLeft,
top: mpTop,
suffix: $"-{i}mp.jpg");
float mwgWidth = float.Parse(mwgValues[2]) * width;
float mwgHeight = float.Parse(mwgValues[3]) * height;
double mwgLeft = double.Parse(mwgValues[0]) * width;
double mwgTop = double.Parse(mwgValues[1]) * height;
Extract(file: checkFile,
width: mwgWidth,
height: mwgHeight,
left: mwgLeft,
top: mwgTop,
suffix: $"-{i}mwg.jpg");
int length = (int.Parse(locationDigits) - 1) / 4;
string[] segments =
[
wholePercentages[..1],
wholePercentages.Substring(1, length),
wholePercentages.Substring(3, length),
wholePercentages.Substring(5, length),
wholePercentages.Substring(7, length)
];
if (string.Join(string.Empty, segments) == wholePercentages) {
if (int.TryParse(segments[1], out int xWholePercent)
&& int.TryParse(segments[2], out int yWholePercent)
&& int.TryParse(segments[3], out int wWholePercent)
&& int.TryParse(segments[4], out int hWholePercent)) {
float factor = 100;
RectangleF rectangleF = new(xWholePercent / factor, yWholePercent / factor, wWholePercent / factor, hWholePercent / factor);
float rectangleFWidth = rectangleF.Width * width;
float rectangleFHeight = rectangleF.Height * height;
double rectangleFLeft = rectangleF.Left * width;
double rectangleFTop = rectangleF.Top * height;
Extract(file: checkFile,
width: rectangleFWidth,
height: rectangleFHeight,
left: rectangleFLeft,
top: rectangleFTop,
suffix: $"-{i}whole-percentages.jpg");
}
}
}
private static void Extract(string file, float width, float height, double left, double top, string suffix) {
RectangleF rectangle = new((float)left, (float)top, width, height);
using (Bitmap source = new(file)) {
using (Bitmap bitmap = new((int)width, (int)height)) {
using (Graphics graphics = Graphics.FromImage(bitmap))
graphics.DrawImage(source, new RectangleF(0, 0, width, height), rectangle, GraphicsUnit.Pixel);
bitmap.Save($"{file}{suffix}");
}
}
}
}

View File

@ -169,6 +169,14 @@ internal static class HelperDay
ADO2025.PI6.Helper20250601.EquipmentAutomationFrameworkStatus(logger, args); ADO2025.PI6.Helper20250601.EquipmentAutomationFrameworkStatus(logger, args);
else if (args[1] == "Day-Helper-2025-06-02") else if (args[1] == "Day-Helper-2025-06-02")
ADO2025.PI6.Helper20250602.EquipmentAutomationFrameworkCellInstanceStateImageVerbIf(logger, args); ADO2025.PI6.Helper20250602.EquipmentAutomationFrameworkCellInstanceStateImageVerbIf(logger, args);
else if (args[1] == "Day-Helper-2025-06-18")
ADO2025.PI6.Helper20250618.MoveAllButXOfEach(logger, args);
else if (args[1] == "Day-Helper-2025-06-28")
ADO2025.PI6.Helper20250628.LogIsoInformation(logger, args);
else if (args[1] == "Day-Helper-2025-07-01")
ADO2025.PI6.Helper20250701.ProcessDataStandardFormatTo(logger, args);
else if (args[1] == "Day-Helper-2025-07-05")
ADO2025.PI6.Helper20250705.ExportFaces(logger, args);
else else
throw new Exception(appSettings.Company); throw new Exception(appSettings.Company);
} }

View File

@ -65,7 +65,7 @@ internal static class Helper20231130
return result; return result;
} }
private static ReadOnlyCollection<string> GetSystemStateValues(List<string> lines, string[] columns, int keyColumnIndex, ReadOnlyDictionary<string, string> systemStates) private static ReadOnlyCollection<string> GetSystemStateValues(List<string> lines, string[] columns, int keyColumnIndex, ReadOnlyDictionary<string, string> systemStateToNames)
{ {
List<string> results = []; List<string> results = [];
string[] values; string[] values;
@ -79,7 +79,7 @@ internal static class Helper20231130
keyColumnValue = values[keyColumnIndex]; keyColumnValue = values[keyColumnIndex];
if (string.IsNullOrEmpty(keyColumnValue)) if (string.IsNullOrEmpty(keyColumnValue))
continue; continue;
if (!systemStates.TryGetValue(keyColumnValue, out systemState)) if (!systemStateToNames.TryGetValue(keyColumnValue, out systemState))
continue; continue;
if (results.Contains(systemState)) if (results.Contains(systemState))
continue; continue;
@ -106,7 +106,7 @@ internal static class Helper20231130
string missingKeyDirectory = Path.Combine(sourceDirectory, "Missing-Key"); string missingKeyDirectory = Path.Combine(sourceDirectory, "Missing-Key");
if (!Directory.Exists(missingKeyDirectory)) if (!Directory.Exists(missingKeyDirectory))
_ = Directory.CreateDirectory(missingKeyDirectory); _ = Directory.CreateDirectory(missingKeyDirectory);
ReadOnlyDictionary<string, string> systemStates = GetSystemStates(); ReadOnlyDictionary<string, string> systemStateToNames = GetSystemStates();
ReadOnlyCollection<Record> records = GetRecords(sourceDirectory, timestampFormat); ReadOnlyCollection<Record> records = GetRecords(sourceDirectory, timestampFormat);
foreach (Record record in records) foreach (Record record in records)
{ {
@ -132,7 +132,7 @@ internal static class Helper20231130
continue; continue;
} }
logger.LogInformation("{timestamp} triggered", record.TimeStamp); logger.LogInformation("{timestamp} triggered", record.TimeStamp);
systemStateValues = GetSystemStateValues(lines, columns, keyColumnIndex.Value, systemStates); systemStateValues = GetSystemStateValues(lines, columns, keyColumnIndex.Value, systemStateToNames);
if (systemStateValues.Count == 0) if (systemStateValues.Count == 0)
{ {
File.Move(record.File, Path.Combine(sourceDirectory, missingKeyDirectory, record.FileName)); File.Move(record.File, Path.Combine(sourceDirectory, missingKeyDirectory, record.FileName));
@ -142,7 +142,6 @@ internal static class Helper20231130
systemState = string.Join('-', systemStateValues); systemState = string.Join('-', systemStateValues);
checkFileName = Path.Combine(Path.GetDirectoryName(record.File) ?? throw new Exception(), $"{record.Equipment}-{record.TimeStamp}-{systemState}.pdsf"); checkFileName = Path.Combine(Path.GetDirectoryName(record.File) ?? throw new Exception(), $"{record.Equipment}-{record.TimeStamp}-{systemState}.pdsf");
File.WriteAllLines(checkFileName, lines); File.WriteAllLines(checkFileName, lines);
File.Delete(record.File);
if (DateTime.TryParseExact(record.TimeStamp, timestampFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) if (DateTime.TryParseExact(record.TimeStamp, timestampFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime))
File.SetLastWriteTime(checkFileName, dateTime); File.SetLastWriteTime(checkFileName, dateTime);
} }

View File

@ -17,7 +17,8 @@
<PackageReference Include="MetadataExtractor" Version="2.8.1" /> <PackageReference Include="MetadataExtractor" Version="2.8.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="8.0.16" /> <PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="8.0.17" />
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
<PackageReference Include="System.Text.Json" Version="9.0.5" /> <PackageReference Include="System.Text.Json" Version="9.0.5" />
<PackageReference Include="TextCopy" Version="6.2.1" /> <PackageReference Include="TextCopy" Version="6.2.1" />
<PackageReference Include="WindowsShortcutFactory" Version="1.2.0" /> <PackageReference Include="WindowsShortcutFactory" Version="1.2.0" />

View File

@ -16,8 +16,20 @@ internal static partial class HelperMarkdown
private record Input(string? Destination, private record Input(string? Destination,
string Source, string Source,
string? ReplaceWithTitle,
bool UseProcessStart); bool UseProcessStart);
private record Table(ReadOnlyCollection<string> Columns,
int ColumnsLine,
ReadOnlyCollection<ReadOnlyCollection<string>> Rows,
string? Title);
private record Block(int Line,
string? Json,
int RowCount,
ReadOnlyCollection<string>? Rows,
string? Title);
private record Record(string Directory, private record Record(string Directory,
string File, string File,
string[] Lines); string[] Lines);
@ -30,7 +42,7 @@ internal static partial class HelperMarkdown
string FileNameWithoutExtension, string FileNameWithoutExtension,
ReadOnlyDictionary<string, object> FrontMatterYaml, ReadOnlyDictionary<string, object> FrontMatterYaml,
string H1, string H1,
bool IsGitOthersModifiedAndDeletedExcludingStandard, bool? IsGitOthersModifiedAndDeletedExcludingStandard,
bool IsKanbanIndex, bool IsKanbanIndex,
bool IsKanbanMarkdown, bool IsKanbanMarkdown,
DateTime LastWriteDateTime, DateTime LastWriteDateTime,
@ -49,15 +61,27 @@ internal static partial class HelperMarkdown
private record MarkdownFileH1AndRelativePath(MarkdownFile? MarkdownFile, string[]? Lines, string? H1, string? RelativePath); private record MarkdownFileH1AndRelativePath(MarkdownFile? MarkdownFile, string[]? Lines, string? H1, string? RelativePath);
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(Dictionary<string, object>))]
internal partial class DictionaryStringAndObjectSourceGenerationContext : JsonSerializerContext
{
}
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] [JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(Dictionary<string, JsonElement>))] [JsonSerializable(typeof(Dictionary<string, JsonElement>))]
internal partial class DictionaryStringAndJsonElementSourceGenerationContext : JsonSerializerContext internal partial class DictionaryStringAndJsonElementSourceGenerationContext : JsonSerializerContext
{ {
} }
[GeneratedRegex("([A-Z]+(.))")]
private static partial Regex UpperCase();
[GeneratedRegex("(~~)?(#)([a-zA-Z0-9]{6})(~~)?( )")] [GeneratedRegex("(~~)?(#)([a-zA-Z0-9]{6})(~~)?( )")]
private static partial Regex HtmlColor(); private static partial Regex HtmlColor();
[GeneratedRegex("[\\s!?.,@:;|\\\\/\"'`£$%\\^&*{}[\\]()<>~#+\\-=_¬]+")]
private static partial Regex InvalidCharacter();
private static MarkdownExtra GetMarkdownExtra(MarkdownFileAndLines markdownFileAndLines) private static MarkdownExtra GetMarkdownExtra(MarkdownFileAndLines markdownFileAndLines)
{ {
MarkdownExtra result; MarkdownExtra result;
@ -301,10 +325,10 @@ internal static partial class HelperMarkdown
if (!directoryInfo.Exists || (!string.IsNullOrEmpty(directoryInfo.LinkTarget) && !Directory.Exists(directoryInfo.LinkTarget))) if (!directoryInfo.Exists || (!string.IsNullOrEmpty(directoryInfo.LinkTarget) && !Directory.Exists(directoryInfo.LinkTarget)))
continue; continue;
collection.AddRange(GetFiles(appSettings, directoryInfo, SearchOption.TopDirectoryOnly)); collection.AddRange(GetFiles(appSettings, directoryInfo, SearchOption.TopDirectoryOnly));
foreach (FileInfo file in collection) foreach (FileInfo fileInfo in collection)
results.Add(file); results.Add(fileInfo);
} }
return new(results); return (from l in results orderby l.FullName select l).ToArray().AsReadOnly();
} }
internal static LineNumber GetLineNumbers(FileInfo fileInfo) internal static LineNumber GetLineNumbers(FileInfo fileInfo)
@ -525,7 +549,7 @@ internal static partial class HelperMarkdown
h1Check = $"# {h1[2..]}"; h1Check = $"# {h1[2..]}";
if (h1Check.Length == h1.Length && h1Check != h1) if (h1Check.Length == h1.Length && h1Check != h1)
{ {
if (!markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
lines[markdownFile.LineNumber.H1.Value] = h1Check; lines[markdownFile.LineNumber.H1.Value] = h1Check;
File.WriteAllLines(markdownFile.File, lines); File.WriteAllLines(markdownFile.File, lines);
@ -541,7 +565,7 @@ internal static partial class HelperMarkdown
checkName = Path.Combine(markdownFile.Directory, checkFileName); checkName = Path.Combine(markdownFile.Directory, checkFileName);
if (checkName == markdownFile.File) if (checkName == markdownFile.File)
continue; continue;
if (!markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
File.Move(markdownFile.File, checkName); File.Move(markdownFile.File, checkName);
result += 1; result += 1;
@ -658,6 +682,7 @@ internal static partial class HelperMarkdown
Input result; Input result;
string? destination = null; string? destination = null;
bool useProcessStart = false; bool useProcessStart = false;
string? replaceWithTitle = null;
string source = Path.GetFullPath(args[0]); string source = Path.GetFullPath(args[0]);
for (int i = 1; i < args.Count; i++) for (int i = 1; i < args.Count; i++)
{ {
@ -667,6 +692,8 @@ internal static partial class HelperMarkdown
useProcessStart = args[i + 1] == "true"; useProcessStart = args[i + 1] == "true";
else if (args[i][1] == 'd') else if (args[i][1] == 'd')
destination = Path.GetFullPath(args[i + 1]); destination = Path.GetFullPath(args[i + 1]);
else if (args[i][1] == 't')
replaceWithTitle = args[i + 1];
i++; i++;
} }
} }
@ -678,11 +705,11 @@ internal static partial class HelperMarkdown
if (!Directory.Exists(destination)) if (!Directory.Exists(destination))
_ = Directory.CreateDirectory(destination); _ = Directory.CreateDirectory(destination);
} }
result = new(destination, source, useProcessStart); result = new(Destination: destination, Source: source, ReplaceWithTitle: replaceWithTitle, UseProcessStart: useProcessStart);
return result; return result;
} }
private static ReadOnlyDictionary<string, MarkdownFileAndLines> GetRelativeToCollection(AppSettings appSettings, Input input, ReadOnlyCollection<string> gitOthersModifiedAndDeletedExcludingStandardFiles) private static ReadOnlyDictionary<string, MarkdownFileAndLines> GetRelativeToCollection(AppSettings appSettings, Input input, ReadOnlyCollection<string>? gitOthersModifiedAndDeletedExcludingStandardFiles)
{ {
Dictionary<string, MarkdownFileAndLines> results = []; Dictionary<string, MarkdownFileAndLines> results = [];
string h1; string h1;
@ -696,17 +723,22 @@ internal static partial class HelperMarkdown
string fileNameWithoutExtension; string fileNameWithoutExtension;
ReadOnlyCollection<string> lines; ReadOnlyCollection<string> lines;
ReadOnlyDictionary<string, object> frontMatterYaml; ReadOnlyDictionary<string, object> frontMatterYaml;
bool isGitOthersModifiedAndDeletedExcludingStandard; bool? isGitOthersModifiedAndDeletedExcludingStandard;
ReadOnlyCollection<FileInfo> files = GetFiles(appSettings, input); ReadOnlyCollection<FileInfo> files = GetFiles(appSettings, input);
foreach (FileInfo fileInfo in files) foreach (FileInfo fileInfo in files)
{ // cSpell:disable { // cSpell:disable
if (fileInfo.DirectoryName is null) if (fileInfo.DirectoryName is null)
continue; continue;
key = Path.GetRelativePath(input.Source, fileInfo.FullName); key = Path.GetRelativePath(input.Source, fileInfo.FullName);
isWithinSource = fileInfo.FullName.Contains(input.Source); if (gitOthersModifiedAndDeletedExcludingStandardFiles is null)
isGitOthersModifiedAndDeletedExcludingStandard = gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(fileInfo.FullName); isGitOthersModifiedAndDeletedExcludingStandard = null;
if (!isWithinSource && results.ContainsKey(key)) else
continue; {
isWithinSource = fileInfo.FullName.Contains(input.Source);
isGitOthersModifiedAndDeletedExcludingStandard = gitOthersModifiedAndDeletedExcludingStandardFiles.Contains(fileInfo.FullName);
if (!isWithinSource && results.ContainsKey(key))
continue;
}
lineNumber = GetLineNumbers(fileInfo); lineNumber = GetLineNumbers(fileInfo);
lines = lineNumber.Lines; lines = lineNumber.Lines;
fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName); fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName);
@ -716,7 +748,7 @@ internal static partial class HelperMarkdown
(type, h1) = GetTypeAndH1(appSettings, h1, lines, lineNumber); (type, h1) = GetTypeAndH1(appSettings, h1, lines, lineNumber);
else else
{ {
if (!isGitOthersModifiedAndDeletedExcludingStandard) if (isGitOthersModifiedAndDeletedExcludingStandard is not null && !isGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
type = appSettings.DefaultNoteType; type = appSettings.DefaultNoteType;
File.WriteAllLines(fileInfo.FullName, ["---", $"type: {type}\"", "---", string.Empty, $"# {h1}"]); File.WriteAllLines(fileInfo.FullName, ["---", $"type: {type}\"", "---", string.Empty, $"# {h1}"]);
@ -724,20 +756,20 @@ internal static partial class HelperMarkdown
} }
isKanbanMarkdown = fileInfo.Name.EndsWith(".knb.md"); isKanbanMarkdown = fileInfo.Name.EndsWith(".knb.md");
isKanbanIndex = fileNameWithoutExtension == "index" && type.StartsWith("kanb", StringComparison.OrdinalIgnoreCase); isKanbanIndex = fileNameWithoutExtension == "index" && type.StartsWith("kanb", StringComparison.OrdinalIgnoreCase);
markdownFile = new(fileInfo.CreationTime, markdownFile = new(CreationDateTime: fileInfo.CreationTime,
fileInfo.DirectoryName, Directory: fileInfo.DirectoryName,
fileInfo.Extension, Extension: fileInfo.Extension,
fileInfo.FullName, File: fileInfo.FullName,
fileInfo.Name, FileName: fileInfo.Name,
fileNameWithoutExtension, FileNameWithoutExtension: fileNameWithoutExtension,
frontMatterYaml, FrontMatterYaml: frontMatterYaml,
h1, H1: h1,
isGitOthersModifiedAndDeletedExcludingStandard, IsGitOthersModifiedAndDeletedExcludingStandard: isGitOthersModifiedAndDeletedExcludingStandard,
isKanbanIndex, IsKanbanIndex: isKanbanIndex,
isKanbanMarkdown, IsKanbanMarkdown: isKanbanMarkdown,
fileInfo.LastWriteTime, LastWriteDateTime: fileInfo.LastWriteTime,
lineNumber, LineNumber: lineNumber,
type); Type: type);
results.Add(key, new(markdownFile, lines.ToArray())); results.Add(key, new(markdownFile, lines.ToArray()));
} // cSpell:restore } // cSpell:restore
return new(results); return new(results);
@ -763,7 +795,7 @@ internal static partial class HelperMarkdown
markdownFile = relativeTo.Value.MarkdownFile; markdownFile = relativeTo.Value.MarkdownFile;
if (markdownFile.IsKanbanMarkdown) if (markdownFile.IsKanbanMarkdown)
continue; continue;
if (!markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
if (markdownFile.LineNumber.FrontMatterYamlEnd is null) if (markdownFile.LineNumber.FrontMatterYamlEnd is null)
continue; continue;
@ -797,7 +829,7 @@ internal static partial class HelperMarkdown
circularReference = false; circularReference = false;
lines = relativeTo.Value.Lines; lines = relativeTo.Value.Lines;
markdownFile = relativeTo.Value.MarkdownFile; markdownFile = relativeTo.Value.MarkdownFile;
if (!markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
for (int i = 0; i < lines.Length; i++) for (int i = 0; i < lines.Length; i++)
{ {
@ -859,7 +891,7 @@ internal static partial class HelperMarkdown
found = false; found = false;
lines = relativeTo.Value.Lines; lines = relativeTo.Value.Lines;
markdownFile = relativeTo.Value.MarkdownFile; markdownFile = relativeTo.Value.MarkdownFile;
if (!markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
for (int i = 0; i < lines.Length; i++) for (int i = 0; i < lines.Length; i++)
{ {
@ -912,7 +944,7 @@ internal static partial class HelperMarkdown
write = false; write = false;
lines = relativeTo.Value.Lines; lines = relativeTo.Value.Lines;
markdownFile = relativeTo.Value.MarkdownFile; markdownFile = relativeTo.Value.MarkdownFile;
if (!input.UseProcessStart && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (!input.UseProcessStart && markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
for (int i = 0; i < lines.Length; i++) for (int i = 0; i < lines.Length; i++)
{ {
@ -940,7 +972,7 @@ internal static partial class HelperMarkdown
} }
if (!write) if (!write)
continue; continue;
if (!markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
File.WriteAllLines(markdownFile.File, lines); File.WriteAllLines(markdownFile.File, lines);
result += 1; result += 1;
@ -975,7 +1007,7 @@ internal static partial class HelperMarkdown
markdownFile = relativeTo.Value.MarkdownFile; markdownFile = relativeTo.Value.MarkdownFile;
if (markdownFile.IsKanbanIndex) if (markdownFile.IsKanbanIndex)
continue; continue;
if (!input.UseProcessStart && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (!input.UseProcessStart && markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
if (!File.Exists(markdownFile.File)) if (!File.Exists(markdownFile.File))
continue; continue;
@ -1040,7 +1072,7 @@ internal static partial class HelperMarkdown
} }
if (!write) if (!write)
continue; continue;
if (!markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
File.WriteAllLines(markdownFile.File, lines); File.WriteAllLines(markdownFile.File, lines);
result += 1; result += 1;
@ -1057,18 +1089,12 @@ internal static partial class HelperMarkdown
MarkdownFileAndLines? markdownFileAndLines = GetKanbanIndexMarkdownFileAndLines(relativeToCollection); MarkdownFileAndLines? markdownFileAndLines = GetKanbanIndexMarkdownFileAndLines(relativeToCollection);
if (markdownFileAndLines is not null && File.Exists(markdownFileAndLines.MarkdownFile.File)) if (markdownFileAndLines is not null && File.Exists(markdownFileAndLines.MarkdownFile.File))
{ {
ReadOnlyDictionary<string, List<Card>> columnsToCards; ReadOnlyDictionary<string, List<Card>> columnsToCards = GetColumnsToCards(input, relativeToCollection, markdownFileAndLines);
string jsonFile = Path.Combine(input.Destination, $"{nameof(columnsToCards)}.json"); string jsonFile = Path.Combine(input.Destination, $"{nameof(columnsToCards)}.json");
if (File.Exists(jsonFile)) string old = !File.Exists(jsonFile) ? string.Empty : File.ReadAllText(jsonFile);
File.Delete(jsonFile); string json = columnsToCards.Count == 0 ? "{}" : JsonSerializer.Serialize(columnsToCards, ColumnsAndCardsSourceGenerationContext.Default.ReadOnlyDictionaryStringListCard);
columnsToCards = GetColumnsToCards(input, relativeToCollection, markdownFileAndLines); if (json != old)
if (columnsToCards.Count == 0)
File.WriteAllText(jsonFile, "{}");
else
{
string json = JsonSerializer.Serialize(columnsToCards, ColumnsAndCardsSourceGenerationContext.Default.ReadOnlyDictionaryStringListCard);
File.WriteAllText(jsonFile, json); File.WriteAllText(jsonFile, json);
}
} }
} }
@ -1122,7 +1148,7 @@ internal static partial class HelperMarkdown
results.Insert(0, string.Empty); results.Insert(0, string.Empty);
} }
results.Insert(0, "---"); results.Insert(0, "---");
if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
{ {
results.Insert(0, updatedLine); results.Insert(0, updatedLine);
results.Insert(0, createdLine); results.Insert(0, createdLine);
@ -1140,7 +1166,7 @@ internal static partial class HelperMarkdown
} }
if (markdownFile.LineNumber.Type is null) if (markdownFile.LineNumber.Type is null)
results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value, typeLine); results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value, typeLine);
if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
{ {
if (markdownFile.LineNumber.Updated is null) if (markdownFile.LineNumber.Updated is null)
results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value, updatedLine); results.Insert(markdownFile.LineNumber.FrontMatterYamlEnd.Value, updatedLine);
@ -1176,7 +1202,7 @@ internal static partial class HelperMarkdown
} }
if (results.Count == lines.Length && string.Join('\r', lines) == string.Join('\r', results)) if (results.Count == lines.Length && string.Join('\r', lines) == string.Join('\r', results))
continue; continue;
if (!markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard) if (markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard is not null && !markdownFile.IsGitOthersModifiedAndDeletedExcludingStandard.Value)
continue; continue;
File.WriteAllLines(markdownFile.File, results); File.WriteAllLines(markdownFile.File, results);
File.SetLastWriteTime(markdownFile.File, markdownFile.LastWriteDateTime); File.SetLastWriteTime(markdownFile.File, markdownFile.LastWriteDateTime);
@ -1235,7 +1261,351 @@ internal static partial class HelperMarkdown
logger.LogInformation("{updated} Markdown file(s) were updated", updated); logger.LogInformation("{updated} Markdown file(s) were updated", updated);
} }
if (!string.IsNullOrEmpty(input.Destination)) if (!string.IsNullOrEmpty(input.Destination))
{
SaveColumnToCards(input, relativeToCollection); SaveColumnToCards(input, relativeToCollection);
SaveTablesToJson(appSettings, logger, input);
}
}
private static void SaveTablesToJson(AppSettings appSettings, ILogger<Worker> logger, Input input)
{
if (string.IsNullOrEmpty(input.Destination))
throw new NotSupportedException();
string old;
string json;
string[] lines;
string fileName;
List<Block> blocks;
string directoryName;
List<string>? jsonLines;
MarkdownFile markdownFile;
List<string> fileNames = [];
ReadOnlyCollection<Table> tables;
Dictionary<string, List<string>> results = [];
ReadOnlyCollection<Block> javaScriptObjectNotationBlocks;
ReadOnlyCollection<Block> yetAnotherMarkupLanguageBlocks;
ReadOnlyDictionary<string, MarkdownFileAndLines> relativeToCollection = GetRelativeToCollection(appSettings, input, gitOthersModifiedAndDeletedExcludingStandardFiles: null);
foreach (KeyValuePair<string, MarkdownFileAndLines> relativeTo in relativeToCollection)
{
if (relativeTo.Value.Lines.Length == 0)
continue;
lines = relativeTo.Value.Lines;
markdownFile = relativeTo.Value.MarkdownFile;
if (markdownFile.IsKanbanIndex)
continue;
if (markdownFile.IsKanbanMarkdown)
continue;
if (markdownFile.FileName.StartsWith("index.yml.md"))
continue;
if (!File.Exists(markdownFile.File))
continue;
tables = GetTables(lines);
javaScriptObjectNotationBlocks = GetJavaScriptObjectNotationBlocks(lines);
yetAnotherMarkupLanguageBlocks = GetYetAnotherMarkupLanguageBlocks(lines);
if (tables.Count == 0 && javaScriptObjectNotationBlocks.Count == 0 && yetAnotherMarkupLanguageBlocks.Count == 0)
continue;
fileName = Path.Combine(input.Destination, $"{markdownFile.FileNameWithoutExtension}.json");
if (fileNames.Contains(fileName))
continue;
fileNames.Add(fileName);
blocks = [];
blocks.AddRange(GetBlocks(tables));
blocks.AddRange(javaScriptObjectNotationBlocks);
blocks.AddRange(GetBlocks(logger, markdownFile, yetAnotherMarkupLanguageBlocks));
if (blocks.Count == 0)
continue;
directoryName = Path.GetFileName(Path.Combine(input.Destination, markdownFile.Directory));
if (!results.TryGetValue(directoryName, out jsonLines))
{
results.Add(directoryName, []);
if (!results.TryGetValue(directoryName, out jsonLines))
throw new Exception();
}
json = GetJson(input, markdownFile, blocks.AsReadOnly());
jsonLines.Add(json);
}
foreach (KeyValuePair<string, List<string>> keyValuePair in results)
{
json = string.Concat('[', Environment.NewLine, string.Join($",{Environment.NewLine}", keyValuePair.Value), Environment.NewLine, ']');
fileName = Path.Combine(input.Destination, $"{keyValuePair.Key}.json");
old = !File.Exists(fileName) ? string.Empty : File.ReadAllText(fileName);
if (json != old)
{
File.WriteAllText(fileName, json);
logger.LogInformation("Updated json file for <{fileName}>", fileName);
}
}
}
private static ReadOnlyCollection<Table> GetTables(string[] lines)
{
List<Table> results = [];
Table table;
string? title;
string[] segments;
int? columnsLine = null;
List<ReadOnlyCollection<string>> rows = [];
string[]? columns = null;
for (int i = 0; i < lines.Length; i++)
{
if (columns is null && (!lines[i].StartsWith('|') || !lines[i].EndsWith('|') || i + 3 >= lines.Length))
continue;
if (columns is null && (lines[i + 1].Length < 3 || lines[i + 1][0] != '|' || (lines[i + 1][1] is not ':' and not '-')))
continue;
if (columns is null)
{
columnsLine = i;
segments = lines[i][1..^1].Split('|');
columns = (from l in segments select l.Trim()).ToArray();
i++;
continue;
}
segments = lines[i].Length < 3 ? [] : lines[i][1..^1].Split('|');
rows.Add((from l in segments select l.Trim()).ToArray().AsReadOnly());
if (columns is not null && columnsLine is not null && string.IsNullOrEmpty(lines[i]))
{
if (columnsLine.Value - 2 > 0 && lines[columnsLine.Value - 2].StartsWith('#'))
title = lines[columnsLine.Value - 2];
else if (columnsLine.Value - 4 > 0 && lines[columnsLine.Value - 4].StartsWith('#'))
title = lines[columnsLine.Value - 4];
else
title = null;
rows.RemoveAt(rows.Count - 1);
table = new(columns.AsReadOnly(), columnsLine.Value, rows.AsReadOnly(), title);
results.Add(table);
columnsLine = null;
columns = null;
rows = [];
}
}
return results.AsReadOnly();
}
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 ReadOnlyCollection<Block> GetBlocks(ReadOnlyCollection<Table> tables)
{
List<Block> results = [];
Block block;
string json;
foreach (Table table in tables)
{
if (table.Title is null)
continue;
if (table.Rows.Count == 0)
continue;
json = GetJson(table);
block = new(Line: table.ColumnsLine, Json: json, RowCount: table.Rows.Count, Rows: null, Title: table.Title);
results.Add(block);
}
return results.AsReadOnly();
}
private static string GetJson(Table table)
{
string result;
string line;
string value;
string[] segments;
List<string> lines = [];
for (int i = 0; i < table.Rows.Count; i++)
{
line = "{";
segments = table.Rows[i].ToArray();
if (segments.Length != table.Columns.Count)
break;
for (int c = 0; c < segments.Length; c++)
{
value = segments[c].Replace("\\", "\\\\").Replace("\"", "\\\"");
line += string.Concat('"', table.Columns[c].Trim('"'), '"', ':', '"', value, '"', ',');
}
line = string.Concat(line[..^1], '}');
lines.Add(line);
}
result = string.Concat('[', Environment.NewLine, string.Join($",{Environment.NewLine}", lines), Environment.NewLine, ']');
return result;
}
private static ReadOnlyCollection<Block> GetJavaScriptObjectNotationBlocks(string[] lines)
{
List<Block> results = [];
string json;
string? title;
int? blockLine = null;
List<string> rows = [];
Block javaScriptObjectNotationBlock;
for (int i = 0; i < lines.Length; i++)
{
if (blockLine is null && !lines[i].StartsWith("```json"))
continue;
if (blockLine is null)
{
blockLine = i;
continue;
}
rows.Add(lines[i]);
if (blockLine is not null && lines[i] == "```")
{
rows.RemoveAt(rows.Count - 1);
if (blockLine.Value - 2 > 0 && lines[blockLine.Value - 2].StartsWith('#'))
title = lines[blockLine.Value - 2];
else if (blockLine.Value - 4 > 0 && lines[blockLine.Value - 4].StartsWith('#'))
title = lines[blockLine.Value - 4];
else
title = null;
if (string.IsNullOrEmpty(title))
continue;
json = string.Join(Environment.NewLine, rows);
javaScriptObjectNotationBlock = new(Line: blockLine.Value, Json: json, RowCount: rows.Count, Rows: null, Title: title);
results.Add(javaScriptObjectNotationBlock);
blockLine = null;
rows = [];
}
}
return results.AsReadOnly();
}
private static ReadOnlyCollection<Block> GetYetAnotherMarkupLanguageBlocks(string[] lines)
{
List<Block> results = [];
string? title = null;
int? blockLine = null;
List<string> rows = [];
Block yetAnotherMarkupLanguageBlock;
if (lines[0] == "---")
{
for (int i = 1; i < lines.Length; i++)
{
rows.Add(lines[i]);
if (lines[i] == "---")
{
rows.RemoveAt(rows.Count - 1);
yetAnotherMarkupLanguageBlock = new(Line: 0, Json: null, RowCount: rows.Count, Rows: rows.AsReadOnly(), Title: title);
results.Add(yetAnotherMarkupLanguageBlock);
rows = [];
}
}
}
rows = [];
for (int i = 0; i < lines.Length; i++)
{
if (blockLine is null && !lines[i].StartsWith("```yml") && !lines[i].StartsWith("```yaml"))
continue;
if (blockLine is null)
{
blockLine = i;
continue;
}
rows.Add(lines[i]);
if (blockLine is not null && lines[i] == "```")
{
rows.RemoveAt(rows.Count - 1);
if (blockLine.Value - 2 > 0 && lines[blockLine.Value - 2].StartsWith('#'))
title = lines[blockLine.Value - 2];
else if (blockLine.Value - 4 > 0 && lines[blockLine.Value - 4].StartsWith('#'))
title = lines[blockLine.Value - 4];
else
title = null;
yetAnotherMarkupLanguageBlock = new(Line: blockLine.Value, Json: null, RowCount: rows.Count, Rows: rows.AsReadOnly(), Title: title);
results.Add(yetAnotherMarkupLanguageBlock);
blockLine = null;
rows = [];
}
}
return results.AsReadOnly();
}
private static ReadOnlyCollection<Block> GetBlocks(ILogger<Worker> logger, MarkdownFile markdownFile, ReadOnlyCollection<Block> yetAnotherMarkupLanguageBlocks)
{
List<Block> results = [];
string key;
Block block;
string json;
string text;
Dictionary<string, object>? keyValuePairs;
Dictionary<string, object> keyValuePairsB = [];
#pragma warning disable IL3050
IDeserializer deserializer = new DeserializerBuilder().Build();
#pragma warning restore IL3050
foreach (Block yaml in yetAnotherMarkupLanguageBlocks)
{
if (yaml.Rows is null || yaml.RowCount == 0)
continue;
if (yaml.Title is null && yaml.Line != 0)
continue;
text = string.Join(Environment.NewLine, yaml.Rows);
try
{ keyValuePairs = deserializer.Deserialize<Dictionary<string, object>>(text); }
catch (Exception)
{
keyValuePairs = null;
logger.LogWarning("Invalid yaml file for <{file}>", markdownFile.FileName);
}
if (keyValuePairs is null)
continue;
keyValuePairsB.Clear();
foreach (KeyValuePair<string, object> keyValuePair in keyValuePairs)
{
key = GetParamCase(keyValuePair.Key);
if (keyValuePairsB.ContainsKey(key))
continue;
if (keyValuePair.Value is IDictionary<object, object>)
{
logger.LogWarning("yaml contains a dictionary not allowed in AOT <{file}>", markdownFile.FileName);
break;
}
else if (keyValuePair.Value is not ICollection<object> collection)
keyValuePairsB.Add(key, keyValuePair.Value);
else
keyValuePairsB.Add(key, string.Join(',', collection));
}
try
{ json = JsonSerializer.Serialize(keyValuePairsB, DictionaryStringAndObjectSourceGenerationContext.Default.DictionaryStringObject); }
catch (Exception)
{
logger.LogWarning("yaml contains special values not allowed in AOT <{file}>", markdownFile.FileName);
continue;
}
block = new(Line: yaml.Line, Json: json, RowCount: yaml.Rows.Count, Rows: null, Title: yaml.Title);
results.Add(block);
}
return results.AsReadOnly();
}
private static string GetJson(Input input, MarkdownFile markdownFile, ReadOnlyCollection<Block> blocks)
{
string result;
string paramCase;
List<string> lines = [];
string fileNameParamCase = GetParamCase(markdownFile.FileNameWithoutExtension);
foreach (Block block in blocks.OrderBy(l => l.Line))
{
paramCase = block.Title is null ? $"Line-{block.Line}" : GetParamCase(block.Title);
if (!string.IsNullOrEmpty(input.ReplaceWithTitle) && paramCase == input.ReplaceWithTitle)
paramCase = $"{fileNameParamCase}-{block.Line}";
lines.Add($"\"{paramCase}\": {block.Json}");
}
result = string.Concat('{',
Environment.NewLine,
$"\"{fileNameParamCase}\": ",
'{',
Environment.NewLine,
string.Join($",{Environment.NewLine}", lines),
Environment.NewLine,
'}',
Environment.NewLine,
'}');
return result;
} }
} }

7
Scripts/markdown.js Normal file
View File

@ -0,0 +1,7 @@
// import data from './oi-metrology-viewer-0-Line-yaml.json' with { type: 'json' };
// import data from '../.vscode/oi-metrology-viewer-0-Line-yaml.json' with { type: 'json' };
import data from '../.vscode/.helper/hosts-legend-table.json' with { type: 'json' };
data.forEach(element => {
console.log(element.Concat);
});