diff --git a/.gitea/workflows/update-profile.yml b/.gitea/workflows/update-profile.yml index ef63985..e457486 100644 --- a/.gitea/workflows/update-profile.yml +++ b/.gitea/workflows/update-profile.yml @@ -14,6 +14,7 @@ on: - 'heatmap.svg' - 'team-stats.svg' - 'productivity.svg' + - 'languages.svg' - 'README.md' jobs: @@ -51,9 +52,9 @@ jobs: git config --global user.email "bot@webcore-consulting.dev" # PrΓΌfen, ob sich SVG-Dateien oder README.md geΓ€ndert haben - if git status --porcelain | grep -E "(heatmap.svg|team-stats.svg|productivity.svg|README.md)"; then + if git status --porcelain | grep -E "(heatmap.svg|team-stats.svg|productivity.svg|languages.svg|README.md)"; then echo "Γ„nderungen an Grafiken oder README.md erkannt. Committe..." - git add heatmap.svg team-stats.svg productivity.svg README.md + git add heatmap.svg team-stats.svg productivity.svg languages.svg README.md # GANZ WICHTIG: [skip ci] verhindert Endlosschleifen in Gitea Actions! git commit -m "chore(profile): update personal & team telemetry [skip ci]" diff --git a/README.md b/README.md index e6877fb..e5b5b23 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Gitea Online Debian 12 Runner Active - Last Sync + Last Sync

@@ -78,15 +78,20 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus #### πŸ“ˆ Byte-genaue Sprachverteilung (Team-Codebase): + +
+ Byte-genaue Sprachverteilung +
+ -| Sprache | Anteil | Code-Volumen | Verteilung | -| :--- | :---: | :---: | :--- | -| `C#` | **56,2%** | 1,1 MB | `β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘` | -| `HTML` | **40,4%** | 797,4 KB | `β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘` | -| `CSS` | **2,3%** | 45,7 KB | `β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘` | -| `JavaScript` | **0,4%** | 8,6 KB | `β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘` | -| `PowerShell` | **0,4%** | 7,6 KB | `β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘` | -| `Shell` | **0,2%** | 3,4 KB | `β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘` | +| Sprache | Technologie | Anteil | Code-Volumen | Relative Verteilung | +| :--- | :---: | :---: | :---: | :--- | +| **C#** | ![C#](https://img.shields.io/badge/C%23-a855f7?style=flat-square&logo=csharp&logoColor=white) | **56,2%** | `1,1 MB` | `β–°β–°β–°β–°β–°β–°β–°β–°β–°β–°β–°β–±β–±β–±β–±β–±β–±β–±β–±β–±` | +| **HTML** | ![HTML](https://img.shields.io/badge/HTML-e34c26?style=flat-square&logo=html5&logoColor=white) | **40,4%** | `797,4 KB` | `β–°β–°β–°β–°β–°β–°β–°β–°β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±` | +| **CSS** | ![CSS](https://img.shields.io/badge/CSS-563d7c?style=flat-square&logo=css3&logoColor=white) | **2,3%** | `45,7 KB` | `β–°β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±` | +| **JavaScript** | ![JavaScript](https://img.shields.io/badge/JavaScript-f1e05a?style=flat-square&logo=javascript&logoColor=black) | **0,4%** | `8,6 KB` | `β–°β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±` | +| **PowerShell** | ![PowerShell](https://img.shields.io/badge/PowerShell-012456?style=flat-square&logo=powershell&logoColor=white) | **0,4%** | `7,6 KB` | `β–°β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±` | +| **Shell** | ![Shell](https://img.shields.io/badge/Shell-89e051?style=flat-square&logo=gnubash&logoColor=white) | **0,2%** | `3,4 KB` | `β–°β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±β–±` | #### πŸš€ Aktive Team-Repositories: @@ -109,11 +114,11 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus > *Echtzeit-Feed der letzten Aktionen und Commits ΓΌber alle privaten & Team-Repositories.* -- ⚑ **feat: add productivity biorhythm clock, developer achievements and quote [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 15 Minuten* -- ⚑ **feat: add live commit radar, activity feed and homelab telemetry [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 18 Minuten* -- ⚑ **feat: add team telemetry dashboard, byte-level language analytics and tabs [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 22 Minuten* -- ⚑ **fix(svg): remove utf-8 bom and add xml declaration for gitea typesniffer [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 30 Minuten* -- ⚑ **chore(profile): update activity heatmap & stats (22 contributions) [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 37 Minuten* +- ⚑ **fix(svg): escape xml characters in productivity badge [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 6 Minuten* +- ⚑ **feat: add productivity biorhythm clock, developer achievements and quote [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 22 Minuten* +- ⚑ **feat: add live commit radar, activity feed and homelab telemetry [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 26 Minuten* +- ⚑ **feat: add team telemetry dashboard, byte-level language analytics and tabs [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 29 Minuten* +- ⚑ **fix(svg): remove utf-8 bom and add xml declaration for gitea typesniffer [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) β€’ πŸ•’ *vor 37 Minuten* @@ -129,16 +134,6 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus Nicki's Coding Productivity Clock -#### πŸ† Entwickler-Erfolge & Achievements: - -| Trophy | Rang | Beschreibung | Status | -| :--- | :---: | :--- | :---: | -| πŸ† **Streak Master** | `Gold` | Mindestens 4 Tage ununterbrochen aktiv gecodet | **4 Tage** βœ… | -| πŸ’Ύ **Code Sovereign** | `Diamond` | Über 1,5 MB Code in Gitea verwaltet | **1,9 MB** βœ… | -| ⚑ **Zero-Dependency** | `Master` | Reines .NET 10 BCL ohne fremde NuGet-Pakete | **100% Native** βœ… | -| πŸŒ™ **Night-Owl Veteran** | `Elite` | Hohe nΓ€chtliche Coding-AktivitΓ€t | **21,1% Nachts** βœ… | -| 🏰 **Homelab Overlord** | `Legendary` | Self-Hosted Debian, Docker, Gitea, Mailcow | **Vollautonom** βœ… | - #### πŸ’‘ TΓ€gliche Entwickler-Philosophie: diff --git a/heatmap.svg b/heatmap.svg index 3487580..693c7ef 100644 --- a/heatmap.svg +++ b/heatmap.svg @@ -74,7 +74,7 @@ 🦊 NickiCloud β€’ PersΓΆnliche AktivitΓ€t - 27 Commits im letzten Jahr + 28 Commits im letzten Jahr Sep @@ -1173,7 +1173,7 @@ 01.09.2026: 9 Commits - 02.09.2026: 11 Commits + 02.09.2026: 12 Commits 03.09.2026: 0 Commits @@ -1192,7 +1192,7 @@ πŸ“¦ - 27 + 28 Jahres-BeitrΓ€ge diff --git a/languages.svg b/languages.svg new file mode 100644 index 0000000..40b3c83 --- /dev/null +++ b/languages.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + πŸ“ˆ Byte-genaue Sprachverteilung β€’ Team-Codebase + + + Gesamt: 1,9 MB analysierter Code + + + + C#: 56,2% (1,1 MB) + + + HTML: 40,4% (797,4 KB) + + + CSS: 2,3% (45,7 KB) + + + JavaScript: 0,4% (8,6 KB) + + + PowerShell: 0,4% (7,6 KB) + + + Shell: 0,2% (3,4 KB) + + + Dockerfile: 0,1% (2,4 KB) + + + + + + + + C# + 56,2% + (1,1 MB) + + + + + HTML + 40,4% + (797,4 KB) + + + + + CSS + 2,3% + (45,7 KB) + + + + + JavaScript + 0,4% + (8,6 KB) + + + + + PowerShell + 0,4% + (7,6 KB) + + + + + Shell + 0,2% + (3,4 KB) + + + diff --git a/productivity.svg b/productivity.svg index dc2c1ea..0773f91 100644 --- a/productivity.svg +++ b/productivity.svg @@ -69,15 +69,15 @@ πŸŒ† Abend (18:00 - 24:00) - - 73,7% + + 78,9% πŸŒ™ Nacht (00:00 - 06:00) - - 21,1% + + 15,8% diff --git a/src/ProfileManager/Program.cs b/src/ProfileManager/Program.cs index f4c341d..6af6e18 100644 --- a/src/ProfileManager/Program.cs +++ b/src/ProfileManager/Program.cs @@ -23,12 +23,14 @@ internal static class Program var svgPath = GetConfigValue(args, "--output-svg", "OUTPUT_SVG", "heatmap.svg"); var teamSvgPath = GetConfigValue(args, "--team-svg", "TEAM_SVG", "team-stats.svg"); var prodSvgPath = GetConfigValue(args, "--prod-svg", "PROD_SVG", "productivity.svg"); + var langSvgPath = GetConfigValue(args, "--lang-svg", "LANG_SVG", "languages.svg"); var readmePath = GetConfigValue(args, "--readme", "README_PATH", "README.md"); var repoRoot = FindRepoRoot(); if (!Path.IsPathRooted(svgPath)) svgPath = Path.GetFullPath(Path.Combine(repoRoot, svgPath)); if (!Path.IsPathRooted(teamSvgPath)) teamSvgPath = Path.GetFullPath(Path.Combine(repoRoot, teamSvgPath)); if (!Path.IsPathRooted(prodSvgPath)) prodSvgPath = Path.GetFullPath(Path.Combine(repoRoot, prodSvgPath)); + if (!Path.IsPathRooted(langSvgPath)) langSvgPath = Path.GetFullPath(Path.Combine(repoRoot, langSvgPath)); if (!Path.IsPathRooted(readmePath)) readmePath = Path.GetFullPath(Path.Combine(repoRoot, readmePath)); Console.WriteLine($"[Config] Target User : {username}"); @@ -36,6 +38,7 @@ internal static class Program Console.WriteLine($"[Config] Heatmap SVG : {svgPath}"); Console.WriteLine($"[Config] Team Stats SVG : {teamSvgPath}"); Console.WriteLine($"[Config] Productivity SVG : {prodSvgPath}"); + Console.WriteLine($"[Config] Languages SVG : {langSvgPath}"); Console.WriteLine($"[Config] Target README : {readmePath}"); Console.WriteLine(); @@ -105,7 +108,15 @@ internal static class Program EnsureDirectory(prodSvgPath); await File.WriteAllTextAsync(prodSvgPath, prodSvg, utf8WithoutBom); - // Step 7: Inject all sections into README.md + // Step 7: Generate Languages SVG + Console.WriteLine($"[SVG] Rendering Languages SVG: {langSvgPath}"); + var teamLangs = stats.TeamStats?.Languages ?? stats.OverallLanguages; + long totalCodeBytes = stats.TeamStats?.TotalCodeBytes ?? teamLangs.Sum(l => l.Bytes); + var langSvg = SvgGenerator.GenerateLanguages(teamLangs, totalCodeBytes); + EnsureDirectory(langSvgPath); + await File.WriteAllTextAsync(langSvgPath, langSvg, utf8WithoutBom); + + // Step 8: Inject all sections into README.md if (File.Exists(readmePath)) { Console.WriteLine($"[README] Injecting personal, team & language metrics into: {readmePath}"); diff --git a/src/ProfileManager/Services/ReadmeInjector.cs b/src/ProfileManager/Services/ReadmeInjector.cs index 778e7f8..de1761f 100644 --- a/src/ProfileManager/Services/ReadmeInjector.cs +++ b/src/ProfileManager/Services/ReadmeInjector.cs @@ -96,14 +96,19 @@ public static class ReadmeInjector content = ReplaceSection(content, StartLangMarker, EndLangMarker, sb => { var langs = stats.TeamStats?.Languages ?? stats.OverallLanguages; - sb.AppendLine("| Sprache | Anteil | Code-Volumen | Verteilung |"); - sb.AppendLine("| :--- | :---: | :---: | :--- |"); + sb.AppendLine("| Sprache | Technologie | Anteil | Code-Volumen | Relative Verteilung |"); + sb.AppendLine("| :--- | :---: | :---: | :---: | :--- |"); foreach (var l in langs.Take(6)) { - int filledBlocks = (int)Math.Round(l.Percentage / 5.0); - string bar = new string('β–ˆ', filledBlocks).PadRight(20, 'β–‘'); - sb.AppendLine($"| `{l.Name}` | **{l.Percentage:F1}%** | {l.FormattedSize} | `{bar}` |"); + var (logo, logoColor) = GetLanguageBadgeInfo(l.Name); + var colorHex = l.Color.TrimStart('#'); + var badgeUrl = $"https://img.shields.io/badge/{Uri.EscapeDataString(l.Name)}-{colorHex}?style=flat-square&logo={logo}&logoColor={logoColor}"; + + int filledBlocks = Math.Max(1, (int)Math.Round(l.Percentage / 5.0)); + string bar = new string('β–°', filledBlocks).PadRight(20, 'β–±'); + + sb.AppendLine($"| **{l.Name}** | ![{l.Name}]({badgeUrl}) | **{l.Percentage:F1}%** | `{l.FormattedSize}` | `{bar}` |"); } }); @@ -210,4 +215,18 @@ public static class ReadmeInjector if (diff.TotalDays < 30) return $"vor {(int)diff.TotalDays} Tagen"; return dateTime.ToString("dd.MM.yyyy"); } + + private static (string Logo, string LogoColor) GetLanguageBadgeInfo(string lang) => lang.ToLowerInvariant() switch + { + "c#" => ("csharp", "white"), + "html" => ("html5", "white"), + "css" => ("css3", "white"), + "javascript" => ("javascript", "black"), + "typescript" => ("typescript", "white"), + "powershell" => ("powershell", "white"), + "shell" or "bash" => ("gnubash", "white"), + "dockerfile" => ("docker", "white"), + "python" => ("python", "white"), + _ => ("code", "white") + }; } diff --git a/src/ProfileManager/Services/SvgGenerator.cs b/src/ProfileManager/Services/SvgGenerator.cs index db206a6..1a197cd 100644 --- a/src/ProfileManager/Services/SvgGenerator.cs +++ b/src/ProfileManager/Services/SvgGenerator.cs @@ -429,6 +429,121 @@ public static class SvgGenerator return sb.ToString(); } + public static string GenerateLanguages(List languages, long totalBytes) + { + var sb = new StringBuilder(8192); + + const int svgWidth = 740; + const int svgHeight = 150; + const int barStartX = 34; + const double barWidth = 672.0; + + string formattedTotal = totalBytes switch + { + >= 1024 * 1024 => $"{(double)totalBytes / (1024 * 1024):F1} MB", + >= 1024 => $"{(double)totalBytes / 1024:F1} KB", + _ => $"{totalBytes} B" + }; + + sb.AppendLine(""""""); + sb.AppendLine($""""""); + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.AppendLine($" "); + sb.AppendLine(" "); + sb.AppendLine(" "); + + sb.AppendLine(""" + + """); + + // Background + sb.AppendLine($" "); + + // Header + sb.AppendLine(" "); + sb.AppendLine(" πŸ“ˆ Byte-genaue Sprachverteilung β€’ Team-Codebase"); + + string badgeText = $"Gesamt: {formattedTotal} analysierter Code"; + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" {badgeText}"); + sb.AppendLine(" "); + + // Segmented Progress Bar + sb.AppendLine(" "); + double currentX = barStartX; + foreach (var lang in languages) + { + double w = Math.Max(2.5, (lang.Percentage / 100.0) * barWidth); + sb.AppendLine($" "); + sb.AppendLine($" {XmlEscape(lang.Name)}: {lang.Percentage:F1}% ({lang.FormattedSize})"); + sb.AppendLine(" "); + currentX += w; + } + sb.AppendLine(" "); + + // 3 Columns x 2 Rows Grid + sb.AppendLine(" "); + sb.AppendLine(" "); + + int colWidth = 224; + int rowHeight = 26; + + for (int i = 0; i < languages.Count && i < 6; i++) + { + var l = languages[i]; + int col = i % 3; + int row = i / 3; + int x = col * colWidth; + int y = row * rowHeight; + + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" {XmlEscape(l.Name)}"); + sb.AppendLine($" {l.Percentage:F1}%"); + sb.AppendLine($" ({l.FormattedSize})"); + sb.AppendLine(" "); + } + + sb.AppendLine(" "); + + sb.AppendLine(""); + return sb.ToString(); + } + private static string XmlEscape(string? text) { if (string.IsNullOrEmpty(text)) return string.Empty;