| | | 1 | | using System.Text; |
| | | 2 | | |
| | | 3 | | namespace NexusLabs.Needlr.Generators; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Helper class for writing documentation breadcrumbs to generated source code. |
| | | 7 | | /// Respects the configured <see cref="BreadcrumbLevel"/> to control output verbosity. |
| | | 8 | | /// </summary> |
| | | 9 | | internal sealed class BreadcrumbWriter |
| | | 10 | | { |
| | | 11 | | private const int BoxWidth = 73; |
| | | 12 | | private const string BoxTopLeft = "┌"; |
| | | 13 | | private const string BoxTopRight = "┐"; |
| | | 14 | | private const string BoxBottomLeft = "└"; |
| | | 15 | | private const string BoxBottomRight = "┘"; |
| | | 16 | | private const string BoxVertical = "│"; |
| | | 17 | | private const string BoxHorizontal = "─"; |
| | | 18 | | |
| | 292028 | 19 | | public BreadcrumbLevel Level { get; } |
| | | 20 | | |
| | 434 | 21 | | public BreadcrumbWriter(BreadcrumbLevel level) |
| | | 22 | | { |
| | 434 | 23 | | Level = level; |
| | 434 | 24 | | } |
| | | 25 | | |
| | | 26 | | /// <summary> |
| | | 27 | | /// Writes a simple inline comment. Only outputs at Minimal or Verbose level. |
| | | 28 | | /// </summary> |
| | | 29 | | public void WriteInlineComment(StringBuilder sb, string indent, string comment) |
| | | 30 | | { |
| | 29763 | 31 | | if (Level == BreadcrumbLevel.None) |
| | 1187 | 32 | | return; |
| | | 33 | | |
| | 28576 | 34 | | sb.AppendLine($"{indent}// {comment}"); |
| | 28576 | 35 | | } |
| | | 36 | | |
| | | 37 | | /// <summary> |
| | | 38 | | /// Writes a verbose box comment with multiple lines. Only outputs at Verbose level. |
| | | 39 | | /// At Minimal level, outputs a simple inline comment with the title. |
| | | 40 | | /// </summary> |
| | | 41 | | public void WriteVerboseBox(StringBuilder sb, string indent, string title, params string[] lines) |
| | | 42 | | { |
| | 15226 | 43 | | if (Level == BreadcrumbLevel.None) |
| | 0 | 44 | | return; |
| | | 45 | | |
| | 15226 | 46 | | if (Level == BreadcrumbLevel.Minimal) |
| | | 47 | | { |
| | 0 | 48 | | sb.AppendLine($"{indent}// {title}"); |
| | 0 | 49 | | return; |
| | | 50 | | } |
| | | 51 | | |
| | | 52 | | // Verbose: Full box |
| | 15226 | 53 | | var horizontalLine = new string('─', BoxWidth); |
| | 15226 | 54 | | sb.AppendLine($"{indent}// {BoxTopLeft}{horizontalLine}{BoxTopRight}"); |
| | 15226 | 55 | | sb.AppendLine($"{indent}// {BoxVertical} {PadRight(title, BoxWidth - 1)}{BoxVertical}"); |
| | | 56 | | |
| | 91566 | 57 | | foreach (var line in lines) |
| | | 58 | | { |
| | 30557 | 59 | | sb.AppendLine($"{indent}// {BoxVertical} {PadRight(line, BoxWidth - 1)}{BoxVertical}"); |
| | | 60 | | } |
| | | 61 | | |
| | 15226 | 62 | | sb.AppendLine($"{indent}// {BoxBottomLeft}{horizontalLine}{BoxBottomRight}"); |
| | 15226 | 63 | | } |
| | | 64 | | |
| | | 65 | | /// <summary> |
| | | 66 | | /// Writes the file header with breadcrumb level indicator. Only at Verbose level. |
| | | 67 | | /// </summary> |
| | | 68 | | public void WriteFileHeader(StringBuilder sb, string assemblyName, string fileDescription) |
| | | 69 | | { |
| | 969 | 70 | | sb.AppendLine("// <auto-generated/>"); |
| | | 71 | | |
| | 969 | 72 | | if (Level == BreadcrumbLevel.Verbose) |
| | | 73 | | { |
| | 59 | 74 | | sb.AppendLine("// ═══════════════════════════════════════════════════════════════════════════════"); |
| | 59 | 75 | | sb.AppendLine($"// {fileDescription}"); |
| | 59 | 76 | | sb.AppendLine($"// Assembly: {assemblyName}"); |
| | 59 | 77 | | sb.AppendLine($"// Breadcrumb Level: {Level}"); |
| | 59 | 78 | | sb.AppendLine("// ═══════════════════════════════════════════════════════════════════════════════"); |
| | | 79 | | } |
| | 969 | 80 | | } |
| | | 81 | | |
| | | 82 | | /// <summary> |
| | | 83 | | /// Gets a relative path from an absolute path, using the project directory as base. |
| | | 84 | | /// Falls back to just the filename if project root is unavailable. |
| | | 85 | | /// </summary> |
| | | 86 | | public static string GetRelativeSourcePath(string? absolutePath, string? projectDirectory) |
| | | 87 | | { |
| | 45541 | 88 | | if (absolutePath == null || absolutePath.Length == 0) |
| | 45535 | 89 | | return "[unknown]"; |
| | | 90 | | |
| | | 91 | | // Normalize path separators for cross-platform compatibility |
| | 6 | 92 | | var normalizedPath = absolutePath.Replace("\\", "/"); |
| | | 93 | | |
| | 6 | 94 | | if (projectDirectory == null || projectDirectory.Length == 0) |
| | | 95 | | { |
| | | 96 | | // Fallback to just the filename - use LastIndexOf for cross-platform |
| | 2 | 97 | | var lastSlash = normalizedPath.LastIndexOf('/'); |
| | 2 | 98 | | return lastSlash >= 0 ? normalizedPath.Substring(lastSlash + 1) : normalizedPath; |
| | | 99 | | } |
| | | 100 | | |
| | 4 | 101 | | var normalizedProjectDir = projectDirectory.Replace("\\", "/"); |
| | | 102 | | // Ensure project dir doesn't end with slash for consistent comparison |
| | 4 | 103 | | if (normalizedProjectDir.EndsWith("/")) |
| | 1 | 104 | | normalizedProjectDir = normalizedProjectDir.Substring(0, normalizedProjectDir.Length - 1); |
| | | 105 | | |
| | | 106 | | // Try to make it relative |
| | 4 | 107 | | if (normalizedPath.StartsWith(normalizedProjectDir, StringComparison.OrdinalIgnoreCase)) |
| | | 108 | | { |
| | 3 | 109 | | var relative = normalizedPath.Substring(normalizedProjectDir.Length); |
| | 3 | 110 | | if (relative.StartsWith("/")) |
| | 3 | 111 | | relative = relative.Substring(1); |
| | 3 | 112 | | return relative; |
| | | 113 | | } |
| | | 114 | | |
| | | 115 | | // Couldn't make relative, just return filename |
| | 1 | 116 | | var lastSlashFallback = normalizedPath.LastIndexOf('/'); |
| | 1 | 117 | | return lastSlashFallback >= 0 ? normalizedPath.Substring(lastSlashFallback + 1) : normalizedPath; |
| | | 118 | | } |
| | | 119 | | |
| | | 120 | | private static string PadRight(string text, int totalWidth) |
| | | 121 | | { |
| | 45783 | 122 | | if (text.Length >= totalWidth) |
| | 0 | 123 | | return text.Substring(0, totalWidth); |
| | 45783 | 124 | | return text + new string(' ', totalWidth - text.Length); |
| | | 125 | | } |
| | | 126 | | } |