From 69406c9ce3b8bd7aca69772d3ddf68e605ae5c5c Mon Sep 17 00:00:00 2001 From: Felix-CodingClimber Date: Sat, 29 Jun 2024 17:25:12 +0200 Subject: [PATCH] Added icons extension project. --- DotNetElements.sln | 6 + .../ConsoleApplication.cs | 58 ++++++ .../DotNetElements.Extensions.Icons.csproj | 17 ++ .../FontAwesomeSvgGenerator.cs | 176 ++++++++++++++++ .../MaterialIconsFontGenerator.cs | 188 ++++++++++++++++++ .../MaterialIconsSvgGenerator.cs | 188 ++++++++++++++++++ .../Program.cs | 32 +++ .../StringExtensions.cs | 68 +++++++ 8 files changed, 733 insertions(+) create mode 100644 src/DotNetElements.Extensions.Icons/ConsoleApplication.cs create mode 100644 src/DotNetElements.Extensions.Icons/DotNetElements.Extensions.Icons.csproj create mode 100644 src/DotNetElements.Extensions.Icons/FontAwesomeSvgGenerator.cs create mode 100644 src/DotNetElements.Extensions.Icons/MaterialIconsFontGenerator.cs create mode 100644 src/DotNetElements.Extensions.Icons/MaterialIconsSvgGenerator.cs create mode 100644 src/DotNetElements.Extensions.Icons/Program.cs create mode 100644 src/DotNetElements.Extensions.Icons/StringExtensions.cs diff --git a/DotNetElements.sln b/DotNetElements.sln index e426688..1e0fb6c 100644 --- a/DotNetElements.sln +++ b/DotNetElements.sln @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetElements.Web.Blazor", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetElements.Web.AspNetCore", "src\DotNetElements.Web.AspNetCore\DotNetElements.Web.AspNetCore.csproj", "{8097E610-C806-44A2-B367-9F7EA4888BEA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetElements.Extensions.Icons", "src\DotNetElements.Extensions.Icons\DotNetElements.Extensions.Icons.csproj", "{12509DFE-E26F-4B58-84C3-3E099AD94082}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {8097E610-C806-44A2-B367-9F7EA4888BEA}.Debug|Any CPU.Build.0 = Debug|Any CPU {8097E610-C806-44A2-B367-9F7EA4888BEA}.Release|Any CPU.ActiveCfg = Release|Any CPU {8097E610-C806-44A2-B367-9F7EA4888BEA}.Release|Any CPU.Build.0 = Release|Any CPU + {12509DFE-E26F-4B58-84C3-3E099AD94082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12509DFE-E26F-4B58-84C3-3E099AD94082}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12509DFE-E26F-4B58-84C3-3E099AD94082}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12509DFE-E26F-4B58-84C3-3E099AD94082}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/DotNetElements.Extensions.Icons/ConsoleApplication.cs b/src/DotNetElements.Extensions.Icons/ConsoleApplication.cs new file mode 100644 index 0000000..5b9d7e2 --- /dev/null +++ b/src/DotNetElements.Extensions.Icons/ConsoleApplication.cs @@ -0,0 +1,58 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DotNetElements.Extensions.Icons; + +internal sealed class ConsoleApplication : IHostedService +{ + private readonly ILogger logger; + private readonly FontAwesomeSvgGenerator fontAwesomeSvgGenerator; + private readonly MaterialIconsSvgGenerator materialIconsSvgGenerator; + + public ConsoleApplication( + ILogger logger, + IHostApplicationLifetime appLifetime, + FontAwesomeSvgGenerator fontAwesomeSvgGenerator, + MaterialIconsSvgGenerator materialIconsSvgGenerator) + { + this.logger = logger; + this.fontAwesomeSvgGenerator = fontAwesomeSvgGenerator; + this.materialIconsSvgGenerator = materialIconsSvgGenerator; + + appLifetime.ApplicationStarted.Register(OnStarted); + appLifetime.ApplicationStopping.Register(OnStopping); + appLifetime.ApplicationStopped.Register(OnStopped); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + logger.LogInformation("Starting..."); + + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + logger.LogInformation("Stopping..."); + return Task.CompletedTask; + } + + private async void OnStarted() + { + logger.LogInformation("Application started."); + + await fontAwesomeSvgGenerator.Run(); + await materialIconsSvgGenerator.Run(); + } + + private void OnStopping() + { + logger.LogInformation("Application stopping."); + } + + private void OnStopped() + { + logger.LogInformation("Application stoped."); + } +} diff --git a/src/DotNetElements.Extensions.Icons/DotNetElements.Extensions.Icons.csproj b/src/DotNetElements.Extensions.Icons/DotNetElements.Extensions.Icons.csproj new file mode 100644 index 0000000..6d1f3aa --- /dev/null +++ b/src/DotNetElements.Extensions.Icons/DotNetElements.Extensions.Icons.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + diff --git a/src/DotNetElements.Extensions.Icons/FontAwesomeSvgGenerator.cs b/src/DotNetElements.Extensions.Icons/FontAwesomeSvgGenerator.cs new file mode 100644 index 0000000..df054c6 --- /dev/null +++ b/src/DotNetElements.Extensions.Icons/FontAwesomeSvgGenerator.cs @@ -0,0 +1,176 @@ +using Microsoft.Extensions.Logging; +using System.Net.Http.Json; +using System.Text; + +namespace DotNetElements.Extensions.Icons; + +internal class FontAwesomeSvgGenerator +{ + private readonly HttpClient httpClient; + private readonly ILogger logger; + + public FontAwesomeSvgGenerator(HttpClient httpClient, ILogger logger) + { + this.httpClient = httpClient; + this.logger = logger; + } + + public async Task Run() + { + IReadOnlyList? iconInfo = await GetIconInfoAsync(); + + if (iconInfo is null) + return; + + await WriteToFileAsync(iconInfo); + + logger.LogInformation("Generated FontAwesome icons"); + } + + private async Task?> GetIconInfoAsync() + { + Dictionary? iconInfo = await httpClient.GetFromJsonAsync>("metadata/icons.json"); + + if (iconInfo is null) + { + logger.LogError("Failed to get icon info from Github!"); + return null; + } + + return iconInfo.Select(kvp => + { + kvp.Value.Id = kvp.Key; + + return kvp.Value; + }).ToList(); + } + + private async Task WriteToFileAsync(IReadOnlyList iconInfo) + { + StringBuilder resultBuilder = new StringBuilder(); + resultBuilder.AppendLine(fileHeader); + + StringBuilder iconRegularBuilder = new StringBuilder(); + StringBuilder iconSolidBuilder = new StringBuilder(); + StringBuilder iconBrandsBuilder = new StringBuilder(); + + foreach (FontAwesomeIcon icon in iconInfo) + { + if (icon.Id is null) + { + logger.LogWarning($"Skipped icon {icon.Label}, no icon id was found"); + continue; + } + + ISvgDescription? svgDescription = icon.Svg.GetDescription(); + + if (svgDescription is null) + { + logger.LogWarning($"Skipped icon {icon.Id}, no svg description was found"); + continue; + } + + if (icon.Svg.Regular is not null) + AppendIconValue(ref iconRegularBuilder, icon, svgDescription); + else if (icon.Svg.Solid is not null) + AppendIconValue(ref iconSolidBuilder, icon, svgDescription); + else + AppendIconValue(ref iconBrandsBuilder, icon, svgDescription); + } + + resultBuilder.Append(iconRegularBuilder); + resultBuilder.AppendLine( + """ + } + + public static class Solid + { + """); + resultBuilder.Append(iconSolidBuilder); + resultBuilder.AppendLine( + """ + } + + public static class Brands + { + """); + resultBuilder.Append(iconBrandsBuilder); + resultBuilder.Append(fileFooter); + + await File.WriteAllTextAsync("FontAwesomeIcons.cs", resultBuilder.ToString()); + } + + private const string fileHeader = + """ + //---------------------- + // + // Generated by the BlazorSpa.Tools FontAwesomeGenerator. DO NOT EDIT! + // source: FontAwesomeGenerator.cs + // + //---------------------- + + namespace BlazorSpa.Components; + + public static partial class Icons + { + public static partial class FontAwesome + { + public static class Regular + { + """; + + private static void AppendIconValue(ref StringBuilder stringBuilder, FontAwesomeIcon icon, ISvgDescription svgDescription) + { + stringBuilder.AppendLine( + $""" + /// + /// + /// FontAwesomeIcon + /// + /// + /// Style: {svgDescription.GetType().Name.ToLowerInvariant()} + /// Label: {icon.Label} + /// + /// + """); + + string varName = icon.Id!.ConvertDashToPascalCase(); + if (varName == "FontAwesome") + varName = $"_{varName}"; + + stringBuilder.AppendLine($" public const string {varName} = \"{svgDescription.Width},{svgDescription.Height},{svgDescription.Path}\";"); + stringBuilder.AppendLine(); + } + + private const string fileFooter = + """ + } + } + } + + """; + + private record FontAwesomeIcon(string[] Styles, string Label, Svg Svg) + { + public string? Id { get; set; } + } + private record Svg(Regular? Regular, Solid? Solid, Brands? Brands) + { + public ISvgDescription? GetDescription() => + Regular is not null ? Regular + : Solid is not null ? Solid + : Brands is not null ? Brands + : null; + } + + private record Regular(int Width, int Height, string Path) : ISvgDescription; + private record Solid(int Width, int Height, string Path) : ISvgDescription; + private record Brands(int Width, int Height, string Path) : ISvgDescription; + + private interface ISvgDescription + { + int Height { get; init; } + string Path { get; init; } + int Width { get; init; } + } +} diff --git a/src/DotNetElements.Extensions.Icons/MaterialIconsFontGenerator.cs b/src/DotNetElements.Extensions.Icons/MaterialIconsFontGenerator.cs new file mode 100644 index 0000000..51aefd8 --- /dev/null +++ b/src/DotNetElements.Extensions.Icons/MaterialIconsFontGenerator.cs @@ -0,0 +1,188 @@ +using Microsoft.Extensions.Logging; +using System.Net.Http.Json; +using System.Text; +using System.Text.RegularExpressions; + +namespace DotNetElements.Extensions.Icons; + +internal partial class MaterialIconsFontGenerator +{ + private readonly HttpClient httpClient; + private readonly ILogger logger; + + public MaterialIconsFontGenerator(HttpClient httpClient, ILogger logger) + { + this.httpClient = httpClient; + this.logger = logger; + } + + [GeneratedRegex("<\\s*svg.*height=\"(?\\d+)\".*width=\"(?\\d+)\"[^>]*>\\s*<\\s*path\\s*d=\"(?.*?)\"\\/>\\s*<\\s*\\/svg>")] + private partial Regex SvgRegex(); + + public async Task Run() + { + IReadOnlyList? iconInfo = await GetIconInfoAsync(); + + if (iconInfo is null) + return; + + await WriteToFileAsync(iconInfo); + + logger.LogInformation("Generated Material icons"); + } + + private async Task?> GetIconInfoAsync() + { + GitRef? masterBranchRef = await httpClient.GetFromJsonAsync("https://api.github.com/repos/google/material-design-icons/git/refs/heads/master"); + if (masterBranchRef is null) + { + logger.LogError("Failed to get available icons from Github! (Failed to fetch master branch info.)"); + return null; + } + + GitTreeResult? mainBranchTree = await httpClient.GetFromJsonAsync($"https://api.github.com/repos/google/material-design-icons/git/trees/{masterBranchRef.Object.Sha}"); + if (mainBranchTree is null) + { + logger.LogError("Failed to get available icons from Github! (Failed to fetch master branch main tree.)"); + return null; + } + + GitTree? symbolsFolder = mainBranchTree.Tree.FirstOrDefault(tree => tree.Path == "symbols"); + if (symbolsFolder is null) + { + logger.LogError("Failed to get available icons from Github! (Missing symbols folder tree.)"); + return null; + } + + GitTreeResult? symbolsFolderTree = await httpClient.GetFromJsonAsync($"https://api.github.com/repos/google/material-design-icons/git/trees/{symbolsFolder.Sha}"); + if (symbolsFolderTree is null) + { + logger.LogError("Failed to get available icons from Github! (Failed to fetch symbols folder tree.)"); + return null; + } + + GitTree? webFolder = symbolsFolderTree.Tree.FirstOrDefault(tree => tree.Path == "web"); + if (webFolder is null) + { + logger.LogError("Failed to get available icons from Github! (Missing web folder tree.)"); + return null; + } + + GitTreeResult? webFolderTree = await httpClient.GetFromJsonAsync($"https://api.github.com/repos/google/material-design-icons/git/trees/{webFolder.Sha}"); + if (webFolderTree is null) + { + logger.LogError("Failed to get available icons from Github! (Failed to fetch web folder tree.)"); + return null; + } + + List iconSet = new List(); + + foreach (string iconName in webFolderTree.Tree.Select(treeItem => treeItem.Path)) + { + HttpResponseMessage response = await httpClient.GetAsync($"https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web/{iconName}/materialsymbolsrounded/{iconName}_24px.svg"); + + if (!response.IsSuccessStatusCode) + { + logger.LogError($"Failed to get icon description from Github! (Icon: {iconName}, Error: {response.StatusCode})"); + continue; + } + + string? iconDescription = await response.Content.ReadAsStringAsync(); + + Match match = SvgRegex().Match(iconDescription); + + if (!match.Success) + { + logger.LogError($"Failed to parse icon description from Github! (Icon: {iconName})"); + continue; + } + + iconSet.Add(new MaterialIcon(iconName, new SvgDescription(match.Groups["width"].Value, match.Groups["height"].Value, match.Groups["path"].Value))); + + //// Uncomment for debug purpose + //if (iconSet.Count > 30) + // break; + } + + return iconSet.ToList(); + } + + private async Task WriteToFileAsync(IReadOnlyList iconInfo) + { + StringBuilder resultBuilder = new StringBuilder(); + resultBuilder.AppendLine(fileHeader); + + StringBuilder iconBuilder = new StringBuilder(); + + foreach (MaterialIcon icon in iconInfo) + { + SvgDescription? svgDescription = icon.SvgDescription; + + if (svgDescription is null + || string.IsNullOrEmpty(svgDescription.Width) + || string.IsNullOrEmpty(svgDescription.Height) + || string.IsNullOrEmpty(svgDescription.Path)) + { + logger.LogWarning($"Skipped icon {icon.Id}, invalid svg description"); + continue; + } + + iconBuilder.AppendLine( + $""" + /// + /// + /// GoogleFontIcon + /// + /// + /// Label: {icon.Id} + /// + /// + """); + + string varName = icon.Id!.ConvertSnakeToPascalCase(); + + iconBuilder.AppendLine($" public const string {varName} = \"{svgDescription.Width},{svgDescription.Height},{svgDescription.Path}\";"); + iconBuilder.AppendLine(); + } + + resultBuilder.Append(iconBuilder); + resultBuilder.Append(fileFooter); + + await File.WriteAllTextAsync("MaterialIcons.cs", resultBuilder.ToString()); + } + + private const string fileHeader = + """ + //---------------------- + // + // Generated by the BlazorSpa.Tools MaterialIconsGenerator. DO NOT EDIT! + // source: MaterialIconsGenerator.cs + // + //---------------------- + + namespace BlazorSpa.Components; + + public static partial class Icons + { + public static partial class Material + { + """; + + private const string fileFooter = + """ + } + } + + """; + + private record MaterialIcon(string Id, SvgDescription SvgDescription); + + private record SvgDescription(string Width, string Height, string Path); + + private record SymbolIconName(string Name); + + private record GitTreeResult(string Sha, IReadOnlyList Tree); + private record GitTree(string Path, string Sha); + private record GitRef(string Ref, GitObject Object); + private record GitObject(string Sha); +} diff --git a/src/DotNetElements.Extensions.Icons/MaterialIconsSvgGenerator.cs b/src/DotNetElements.Extensions.Icons/MaterialIconsSvgGenerator.cs new file mode 100644 index 0000000..7235c8a --- /dev/null +++ b/src/DotNetElements.Extensions.Icons/MaterialIconsSvgGenerator.cs @@ -0,0 +1,188 @@ +using Microsoft.Extensions.Logging; +using System.Net.Http.Json; +using System.Text; +using System.Text.RegularExpressions; + +namespace DotNetElements.Extensions.Icons; + +internal partial class MaterialIconsSvgGenerator +{ + private readonly HttpClient httpClient; + private readonly ILogger logger; + + public MaterialIconsSvgGenerator(HttpClient httpClient, ILogger logger) + { + this.httpClient = httpClient; + this.logger = logger; + } + + [GeneratedRegex("<\\s*svg.*height=\"(?\\d+)\".*width=\"(?\\d+)\"[^>]*>\\s*<\\s*path\\s*d=\"(?.*?)\"\\/>\\s*<\\s*\\/svg>")] + private partial Regex SvgRegex(); + + public async Task Run() + { + IReadOnlyList? iconInfo = await GetIconInfoAsync(); + + if (iconInfo is null) + return; + + await WriteToFileAsync(iconInfo); + + logger.LogInformation("Generated Material icons"); + } + + private async Task?> GetIconInfoAsync() + { + GitRef? masterBranchRef = await httpClient.GetFromJsonAsync("https://api.github.com/repos/google/material-design-icons/git/refs/heads/master"); + if (masterBranchRef is null) + { + logger.LogError("Failed to get available icons from Github! (Failed to fetch master branch info.)"); + return null; + } + + GitTreeResult? mainBranchTree = await httpClient.GetFromJsonAsync($"https://api.github.com/repos/google/material-design-icons/git/trees/{masterBranchRef.Object.Sha}"); + if (mainBranchTree is null) + { + logger.LogError("Failed to get available icons from Github! (Failed to fetch master branch main tree.)"); + return null; + } + + GitTree? symbolsFolder = mainBranchTree.Tree.FirstOrDefault(tree => tree.Path == "symbols"); + if (symbolsFolder is null) + { + logger.LogError("Failed to get available icons from Github! (Missing symbols folder tree.)"); + return null; + } + + GitTreeResult? symbolsFolderTree = await httpClient.GetFromJsonAsync($"https://api.github.com/repos/google/material-design-icons/git/trees/{symbolsFolder.Sha}"); + if (symbolsFolderTree is null) + { + logger.LogError("Failed to get available icons from Github! (Failed to fetch symbols folder tree.)"); + return null; + } + + GitTree? webFolder = symbolsFolderTree.Tree.FirstOrDefault(tree => tree.Path == "web"); + if (webFolder is null) + { + logger.LogError("Failed to get available icons from Github! (Missing web folder tree.)"); + return null; + } + + GitTreeResult? webFolderTree = await httpClient.GetFromJsonAsync($"https://api.github.com/repos/google/material-design-icons/git/trees/{webFolder.Sha}"); + if (webFolderTree is null) + { + logger.LogError("Failed to get available icons from Github! (Failed to fetch web folder tree.)"); + return null; + } + + List iconSet = new List(); + + foreach (string iconName in webFolderTree.Tree.Select(treeItem => treeItem.Path)) + { + HttpResponseMessage response = await httpClient.GetAsync($"https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web/{iconName}/materialsymbolsrounded/{iconName}_24px.svg"); + + if (!response.IsSuccessStatusCode) + { + logger.LogError($"Failed to get icon description from Github! (Icon: {iconName}, Error: {response.StatusCode})"); + continue; + } + + string? iconDescription = await response.Content.ReadAsStringAsync(); + + Match match = SvgRegex().Match(iconDescription); + + if (!match.Success) + { + logger.LogError($"Failed to parse icon description from Github! (Icon: {iconName})"); + continue; + } + + iconSet.Add(new MaterialIcon(iconName, new SvgDescription(match.Groups["width"].Value, match.Groups["height"].Value, match.Groups["path"].Value))); + + //// Uncomment for debug purpose + //if (iconSet.Count > 30) + // break; + } + + return iconSet.ToList(); + } + + private async Task WriteToFileAsync(IReadOnlyList iconInfo) + { + StringBuilder resultBuilder = new StringBuilder(); + resultBuilder.AppendLine(fileHeader); + + StringBuilder iconBuilder = new StringBuilder(); + + foreach (MaterialIcon icon in iconInfo) + { + SvgDescription? svgDescription = icon.SvgDescription; + + if (svgDescription is null + || string.IsNullOrEmpty(svgDescription.Width) + || string.IsNullOrEmpty(svgDescription.Height) + || string.IsNullOrEmpty(svgDescription.Path)) + { + logger.LogWarning($"Skipped icon {icon.Id}, invalid svg description"); + continue; + } + + iconBuilder.AppendLine( + $""" + /// + /// + /// GoogleFontIcon + /// + /// + /// Label: {icon.Id} + /// + /// + """); + + string varName = icon.Id!.ConvertSnakeToPascalCase(); + + iconBuilder.AppendLine($" public const string {varName} = \"{svgDescription.Width},{svgDescription.Height},{svgDescription.Path}\";"); + iconBuilder.AppendLine(); + } + + resultBuilder.Append(iconBuilder); + resultBuilder.Append(fileFooter); + + await File.WriteAllTextAsync("MaterialIcons.cs", resultBuilder.ToString()); + } + + private const string fileHeader = + """ + //---------------------- + // + // Generated by the BlazorSpa.Tools MaterialIconsGenerator. DO NOT EDIT! + // source: MaterialIconsGenerator.cs + // + //---------------------- + + namespace BlazorSpa.Components; + + public static partial class Icons + { + public static partial class Material + { + """; + + private const string fileFooter = + """ + } + } + + """; + + private record MaterialIcon(string Id, SvgDescription SvgDescription); + + private record SvgDescription(string Width, string Height, string Path); + + private record SymbolIconName(string Name); + + private record GitTreeResult(string Sha, IReadOnlyList Tree); + private record GitTree(string Path, string Sha); + private record GitRef(string Ref, GitObject Object); + private record GitObject(string Sha); +} diff --git a/src/DotNetElements.Extensions.Icons/Program.cs b/src/DotNetElements.Extensions.Icons/Program.cs new file mode 100644 index 0000000..f31ecea --- /dev/null +++ b/src/DotNetElements.Extensions.Icons/Program.cs @@ -0,0 +1,32 @@ +using DotNetElements.Extensions.Icons; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +IHostBuilder builder = CreateHostBuilder(); +using IHost host = builder.Build(); +await host.RunAsync(); + +static IHostBuilder CreateHostBuilder() +{ + return Host.CreateDefaultBuilder() + .ConfigureLogging(logging => + { + // Configure logging + }) + .ConfigureServices(services => + { + // Register services + services.AddHostedService(); + services.AddLogging(builder => builder.AddConsole()); + services.AddHttpClient(options => + { + options.BaseAddress = new Uri("https://raw.githubusercontent.com/FortAwesome/Font-Awesome/d3a7818c253fcbafff9ebd1d4abb2866c192e1d7/"); + }); + services.AddHttpClient(options => + { + options.DefaultRequestHeaders.Add("User-Agent", "request"); + }); + }); +} + diff --git a/src/DotNetElements.Extensions.Icons/StringExtensions.cs b/src/DotNetElements.Extensions.Icons/StringExtensions.cs new file mode 100644 index 0000000..05dddf9 --- /dev/null +++ b/src/DotNetElements.Extensions.Icons/StringExtensions.cs @@ -0,0 +1,68 @@ +using System.Text; + +namespace DotNetElements.Extensions.Icons; + +internal static class StringExtensions +{ + public static string ConvertDashToPascalCase(this string value) + { + StringBuilder sb = new StringBuilder(); + bool caseFlag = false; + for (int i = 0; i < value.Length; ++i) + { + char c = value[i]; + if (c == '-') + { + caseFlag = true; + } + else if (caseFlag) + { + sb.Append(char.ToUpper(c)); + caseFlag = false; + } + else if (i == 0) + { + if (char.IsDigit(c)) + sb.Append('_'); + + sb.Append(char.ToUpper(c)); + } + else + { + sb.Append(c); + } + } + return sb.ToString(); + } + + public static string ConvertSnakeToPascalCase(this string value) + { + StringBuilder sb = new StringBuilder(); + bool caseFlag = false; + for (int i = 0; i < value.Length; ++i) + { + char c = value[i]; + if (c == '_') + { + caseFlag = true; + } + else if (caseFlag) + { + sb.Append(char.ToUpper(c)); + caseFlag = false; + } + else if (i == 0) + { + if (char.IsDigit(c)) + sb.Append('_'); + + sb.Append(char.ToUpper(c)); + } + else + { + sb.Append(c); + } + } + return sb.ToString(); + } +}