|
|
@@ -1,90 +1,86 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
using System.Text;
|
|
|
using RackPeek.Domain.Resources;
|
|
|
|
|
|
-namespace RackPeek.Domain.UseCases.Mermaid;
|
|
|
-
|
|
|
-public static class MermaidDiagramGenerator
|
|
|
+namespace RackPeek.Domain.UseCases.Mermaid
|
|
|
{
|
|
|
- public static MermaidExportResult ToMermaidDiagram(
|
|
|
- this IReadOnlyList<Resource> resources,
|
|
|
- MermaidExportOptions? options = null)
|
|
|
+ public static class MermaidDiagramGenerator
|
|
|
{
|
|
|
- MermaidExportOptions resolvedOptions = options ?? new MermaidExportOptions();
|
|
|
+ public static MermaidExportResult ToMermaidDiagram(
|
|
|
+ this IReadOnlyList<Resource> resources,
|
|
|
+ MermaidExportOptions? options = null)
|
|
|
+ {
|
|
|
+ MermaidExportOptions resolvedOptions = options ?? new MermaidExportOptions();
|
|
|
+ var sb = new StringBuilder();
|
|
|
+ var warnings = new List<string>();
|
|
|
|
|
|
- var sb = new StringBuilder();
|
|
|
- var warnings = new List<string>();
|
|
|
+ sb.AppendLine(resolvedOptions.DiagramType);
|
|
|
|
|
|
- sb.AppendLine(resolvedOptions.DiagramType);
|
|
|
+ // Group resources by Kind
|
|
|
+ IOrderedEnumerable<IGrouping<string, Resource>> grouped = resources
|
|
|
+ .Where(r => resolvedOptions.IncludeTags.Count == 0
|
|
|
+ || (r.Tags != null && r.Tags.Any(t => resolvedOptions.IncludeTags.Contains(t, StringComparer.OrdinalIgnoreCase))))
|
|
|
+ .GroupBy(r => r.Kind)
|
|
|
+ .OrderBy(g => g.Key);
|
|
|
|
|
|
- foreach (Resource r in resources.OrderBy(x => x.Name))
|
|
|
- {
|
|
|
- if (resolvedOptions.IncludeTags.Any())
|
|
|
+ foreach (IGrouping<string, Resource> group in grouped)
|
|
|
{
|
|
|
- var tags = r.Tags ?? Array.Empty<string>();
|
|
|
-
|
|
|
- var match = resolvedOptions
|
|
|
- .IncludeTags
|
|
|
- .Any(t => tags.Contains(t, StringComparer.OrdinalIgnoreCase));
|
|
|
+ sb.AppendLine($" subgraph {SanitizeId(group.Key)}");
|
|
|
+ foreach (Resource r in group.OrderBy(x => x.Name))
|
|
|
+ {
|
|
|
+ var nodeId = SanitizeId(r.Name);
|
|
|
+ var label = BuildNodeLabel(r, resolvedOptions);
|
|
|
+ sb.AppendLine($" {nodeId}[\"{label}\"]");
|
|
|
+ }
|
|
|
+ sb.AppendLine(" end");
|
|
|
+ }
|
|
|
|
|
|
- if (!match)
|
|
|
- continue;
|
|
|
+ // Map RunsOn relationships if requested
|
|
|
+ if (resolvedOptions.IncludeEdges)
|
|
|
+ {
|
|
|
+ foreach (Resource r in resources)
|
|
|
+ {
|
|
|
+ var nodeId = SanitizeId(r.Name);
|
|
|
+ foreach (var depName in r.RunsOn ?? new List<string>())
|
|
|
+ {
|
|
|
+ var depId = SanitizeId(depName);
|
|
|
+ sb.AppendLine($" {nodeId} --> {depId}");
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- var nodeId = SanitizeId(r.Name);
|
|
|
- var label = BuildNodeLabel(r, resolvedOptions);
|
|
|
+ if (sb.Length == 0)
|
|
|
+ warnings.Add("No Mermaid diagram entries generated.");
|
|
|
|
|
|
- sb.AppendLine($" {nodeId}[\"{label}\"]");
|
|
|
+ return new MermaidExportResult(sb.ToString().TrimEnd(), warnings);
|
|
|
}
|
|
|
|
|
|
- if (sb.Length == 0)
|
|
|
- warnings.Add("No Mermaid diagram entries generated.");
|
|
|
-
|
|
|
- return new MermaidExportResult(sb.ToString().TrimEnd(), warnings);
|
|
|
- }
|
|
|
-
|
|
|
- private static string BuildNodeLabel(Resource r, MermaidExportOptions options)
|
|
|
- {
|
|
|
- if (!options.IncludeLabels || r.Labels.Count == 0)
|
|
|
- return r.Name;
|
|
|
-
|
|
|
- IEnumerable<KeyValuePair<string, string>> filtered;
|
|
|
-
|
|
|
- if (options.LabelWhitelist is null)
|
|
|
+ private static string BuildNodeLabel(Resource r, MermaidExportOptions options)
|
|
|
{
|
|
|
- filtered = r.Labels;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- filtered = r.Labels.Where(kvp =>
|
|
|
- options.LabelWhitelist.Contains(kvp.Key, StringComparer.OrdinalIgnoreCase));
|
|
|
- }
|
|
|
+ if (!options.IncludeLabels || r.Labels.Count == 0)
|
|
|
+ return r.Name;
|
|
|
|
|
|
- var labelParts = filtered
|
|
|
- .Select(kvp => $"{kvp.Key}: {kvp.Value}")
|
|
|
- .ToList();
|
|
|
-
|
|
|
- if (labelParts.Count == 0)
|
|
|
- return r.Name;
|
|
|
-
|
|
|
- return $"{r.Name}\\n{string.Join("\\n", labelParts)}";
|
|
|
- }
|
|
|
+ IEnumerable<KeyValuePair<string, string>> filtered = options.LabelWhitelist is null
|
|
|
+ ? r.Labels
|
|
|
+ : r.Labels.Where(kvp => options.LabelWhitelist.Contains(kvp.Key, StringComparer.OrdinalIgnoreCase));
|
|
|
|
|
|
- private static string SanitizeId(string name)
|
|
|
- {
|
|
|
- var sb = new StringBuilder();
|
|
|
+ var labelParts = filtered.Select(kvp => $"{kvp.Key}: {kvp.Value}").ToList();
|
|
|
+ return labelParts.Count == 0 ? r.Name : $"{r.Name}\\n{string.Join("\\n", labelParts)}";
|
|
|
+ }
|
|
|
|
|
|
- foreach (var ch in name.Trim().ToLowerInvariant())
|
|
|
+ private static string SanitizeId(string name)
|
|
|
{
|
|
|
- if (char.IsLetterOrDigit(ch) || ch == '_')
|
|
|
+ var sb = new StringBuilder();
|
|
|
+ foreach (var ch in name.Trim().ToLowerInvariant())
|
|
|
{
|
|
|
- sb.Append(ch);
|
|
|
- }
|
|
|
- else if (ch == '-' || ch == '.' || ch == ' ')
|
|
|
- {
|
|
|
- sb.Append('_');
|
|
|
+ if (char.IsLetterOrDigit(ch) || ch == '_')
|
|
|
+ sb.Append(ch);
|
|
|
+ else if (ch == '-' || ch == '.' || ch == ' ')
|
|
|
+ sb.Append('_');
|
|
|
}
|
|
|
+ return sb.Length == 0 ? "node" : sb.ToString();
|
|
|
}
|
|
|
-
|
|
|
- return sb.Length == 0 ? "node" : sb.ToString();
|
|
|
}
|
|
|
}
|