diff --git a/.gitignore b/.gitignore index 874b1050..4a3be230 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,431 @@ -# VisualStudioCode +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## .vscode/* !.vscode/settings.json !.vscode/tasks.json @@ -11,8 +438,12 @@ scripts/.vscode/* !scripts/.vscode/launch.json !scripts/.vscode/extensions.json +# Configuration +appsettings.json + # Custom source/official/* !source/official/README.md - -dist/table.md \ No newline at end of file +dist/table.md +temp/* +plantuml.jar \ No newline at end of file diff --git a/scripts/.vscode/launch.json b/scripts/.vscode/launch.json index c01cf07f..3525efeb 100644 --- a/scripts/.vscode/launch.json +++ b/scripts/.vscode/launch.json @@ -2,17 +2,25 @@ "version": "0.2.0", "configurations": [ { - "name": ".NET Script Debug", + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", - "program": "dotnet", - "args": [ - "exec", - "${env:USERPROFILE}/.dotnet/tools/.store/dotnet-script/0.53.0/dotnet-script/0.53.0/tools/netcoreapp3.1/any/dotnet-script.dll", - "${file}" - ], - "cwd": "${workspaceRoot}", - "stopAtEntry": true + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/net6.0/GeneratePuml.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" } ] } \ No newline at end of file diff --git a/scripts/.vscode/tasks.json b/scripts/.vscode/tasks.json new file mode 100644 index 00000000..47f51a93 --- /dev/null +++ b/scripts/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/GeneratePuml.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/GeneratePuml.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/GeneratePuml.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/scripts/GeneratePlantuml.cs b/scripts/GeneratePlantuml.cs new file mode 100644 index 00000000..501fdd46 --- /dev/null +++ b/scripts/GeneratePlantuml.cs @@ -0,0 +1,241 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; +using System.Drawing; +using System.Text; +using System.Diagnostics; + +public class GeneratePlantuml : IHostedService +{ + readonly ILogger logger; + readonly IHostApplicationLifetime lifetime; + readonly string sourceFolder; + readonly string originalSourceFolder; + readonly string manualSourceFolder; + readonly string targetFolder; + readonly int targetImageHeight = 70; + readonly Color azureColor; + readonly string plantUmlPath; + IImageManager svgManager; + + public GeneratePlantuml(ILogger logprovider, IConfiguration config, IImageManager exporter, IHostApplicationLifetime appLifetime) + { + logger = logprovider; + lifetime = appLifetime; + sourceFolder = config.GetSection("sourceFolderPath").Value; + originalSourceFolder = Path.Combine(sourceFolder, "official"); + manualSourceFolder = Path.Combine(sourceFolder, "manual"); + targetFolder = config.GetSection("targetFolderPath").Value; + azureColor = System.Drawing.ColorTranslator.FromHtml(config.GetSection("monochromeColorHex").Value); + plantUmlPath = config.GetSection("plantUmlPath").Value; + svgManager = exporter; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + logger.LogInformation("GeneratePlantUml is starting."); + _ = ProcessFiles(); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + logger.LogInformation("GeneratePlantUml is stopping."); + lifetime.StopApplication(); + return Task.CompletedTask; + } + + private async Task ProcessFiles() + { + var lookupTable = Config.ReadConfig("Config.yaml"); + + // Cleanup + if (Directory.Exists(targetFolder)) + { + Directory.Delete(targetFolder, true); + } + Directory.CreateDirectory(targetFolder); + + File.Copy(Path.Combine(sourceFolder, "AzureRaw.puml"), Path.Combine(targetFolder, "AzureRaw.puml")); + File.Copy(Path.Combine(sourceFolder, "AzureCommon.puml"), Path.Combine(targetFolder, "AzureCommon.puml")); + File.Copy(Path.Combine(sourceFolder, "AzureC4Integration.puml"), Path.Combine(targetFolder, "AzureC4Integration.puml")); + File.Copy(Path.Combine(sourceFolder, "AzureSimplified.puml"), Path.Combine(targetFolder, "AzureSimplified.puml")); + + foreach (var service in lookupTable) + { + await ProcessService(service); + } + + foreach (var category in lookupTable.Select(_ => _.Category).Distinct()) + { + var categoryDirectoryPath = Path.Combine(targetFolder, category!); + var catAllFilePath = Path.Combine(categoryDirectoryPath, "all.puml"); + CombineMultipleFilesIntoSingleFile(categoryDirectoryPath, "*.puml", catAllFilePath); + } + + await VSCodeSnippets.GenerateSnippets(targetFolder); + await MarkdownTable.GenerateTable(targetFolder); + await this.StopAsync(new System.Threading.CancellationToken()); + } + + async Task ProcessService(ConfigLookupEntry service) + { + logger.LogInformation($"Processing {service.ServiceSource}"); + + // NOTE: All icons as colored as supplied + var monochromExists = false; + var coloredExists = false; + var coloredSourceFilePath = GetSourceFilePath(service.ServiceSource!, true); + coloredExists = !string.IsNullOrEmpty(coloredSourceFilePath); + + if (!coloredExists && !monochromExists) + { + logger.LogWarning($"Error: Missing {service.ServiceSource} color or mono"); + return; + } + + var categoryDirectoryPath = Path.Combine(targetFolder, service.Category!); + Directory.CreateDirectory(categoryDirectoryPath); + + try + { + if (coloredExists) + { + + await svgManager.ResizeAndCopy(coloredSourceFilePath, Path.Combine(categoryDirectoryPath, service.ServiceTarget + ".svg"), targetImageHeight); + await svgManager.ExportToPng(Path.Combine(categoryDirectoryPath, service.ServiceTarget + ".svg"), Path.Combine(categoryDirectoryPath, service.ServiceTarget + ".png"), targetImageHeight); + } + else + { + logger.LogWarning($"Missing SVG file for: {service.ServiceSource}. Please add the file or remove the entry from Config.yaml "); + } + + var monochromSvgFilePath = Path.Combine(categoryDirectoryPath, service.ServiceTarget + "(m).svg"); + + // This should never be called if we assume only colored SVGs are provided on input. + if (monochromExists) + { + await svgManager.ResizeAndCopy(Path.Combine(categoryDirectoryPath, service.ServiceTarget + ".svg"), monochromSvgFilePath, targetImageHeight); + } + else + { + await svgManager.ResizeAndCopy(Path.Combine(categoryDirectoryPath, service.ServiceTarget + ".svg"), monochromSvgFilePath, targetImageHeight); + await svgManager.ExportToMonochrome(monochromSvgFilePath, monochromSvgFilePath, azureColor); + } + + var monochromPngFilePath = Path.Combine(categoryDirectoryPath, service.ServiceTarget + "(m).png"); + // First generation with background needed for PUML sprite generation + await svgManager.ExportToPng(monochromSvgFilePath, monochromPngFilePath, targetImageHeight, omitBackground: false); + ConvertToPuml(monochromPngFilePath, service.ServiceTarget + ".puml"); + + // Second generation without background needed other usages + await svgManager.ExportToPng(monochromSvgFilePath, monochromPngFilePath, targetImageHeight, omitBackground: true); + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return; + } + + } + + void CombineMultipleFilesIntoSingleFile(string inputDirectoryPath, string inputFileNamePattern, string outputFilePath) + { + string[] inputFilePaths = Directory.GetFiles(inputDirectoryPath, inputFileNamePattern, SearchOption.AllDirectories); + using (var outputStream = File.Create(outputFilePath)) + { + foreach (var inputFilePath in inputFilePaths) + { + using (var inputStream = File.OpenRead(inputFilePath)) + { + inputStream.CopyTo(outputStream); + byte[] newline = Encoding.ASCII.GetBytes(Environment.NewLine); + outputStream.Write(newline, 0, newline.Length); + } + } + } + } + + string GetSourceFilePath(string sourceFileName, bool color = true) + { + var patterns = new List { + "*-icon-service-{0}.svg", + "{0}_COLOR.svg", + "{0}.svg" + }; + + foreach (var p in patterns) + { + var x = string.Format(p, sourceFileName); + if (x.Contains("-") || x.Contains(" ")) + { + // Original files have hyphen not space so assume that based on pattern + x = x.Replace(" ", "-"); + } + var files = Directory.GetFiles(originalSourceFolder, x, new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = true }); + + if (files.Length == 0) + { + files = Directory.GetFiles(manualSourceFolder, x, new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = true }); + } + + if (files.Length >= 1) + { + if (files.Length > 1) + { + var matches = String.Join(",", files); + logger.LogWarning($"Warning: File found in multiple locations {sourceFileName}: {matches}"); + } + return files[0]; + } + } + + return string.Empty; // no result + } + + string ConvertToPuml(string pngPath, string pumlFileName) + { + var format = "16z"; + var entityName = Path.GetFileNameWithoutExtension(pumlFileName); + var pumlPath = Path.Combine(Directory.GetParent(pngPath)!.FullName, pumlFileName); + var processInfo = new ProcessStartInfo(); + + if (OperatingSystem.IsMacOS()) + { + processInfo.FileName = "/bin/zsh"; + // processInfo.FileName = "java"; + string commandArgs = $"-c \"java -Djava.awt.headless=true -jar {plantUmlPath} -encodesprite {format} '{pngPath}'\""; + processInfo.Arguments = commandArgs; + // processInfo.Arguments = $"-Djava.awt.headless=true -jar {plantUmlPath} -encodesprite {format} {pngPath}"; + } + if (OperatingSystem.IsWindows()) + { + processInfo.FileName = "java"; + processInfo.Arguments = $"-jar {plantUmlPath} -encodesprite {format} \"{pngPath}\""; + } + if (OperatingSystem.IsLinux()) + { + processInfo.FileName = "java"; + processInfo.Arguments = $"-jar {plantUmlPath} -encodesprite {format} {pngPath}"; + } + + processInfo.RedirectStandardOutput = true; + processInfo.UseShellExecute = false; + processInfo.CreateNoWindow = true; + + var pumlContent = new StringBuilder(); + using (var process = Process.Start(processInfo)) + { + process!.WaitForExit(); + pumlContent.Append(process.StandardOutput.ReadToEnd()); + } + + pumlContent.AppendLine($"AzureEntityColoring({entityName})"); + pumlContent.AppendLine($"!define {entityName}(e_alias, e_label, e_techn) AzureEntity(e_alias, e_label, e_techn, AZURE_SYMBOL_COLOR, {entityName}, {entityName})"); + pumlContent.AppendLine($"!define {entityName}(e_alias, e_label, e_techn, e_descr) AzureEntity(e_alias, e_label, e_techn, e_descr, AZURE_SYMBOL_COLOR, {entityName}, {entityName})"); + + File.WriteAllText(pumlPath, pumlContent.ToString()); + return pumlPath; + } + +} diff --git a/scripts/GeneratePuml.csproj b/scripts/GeneratePuml.csproj new file mode 100644 index 00000000..6eca1db3 --- /dev/null +++ b/scripts/GeneratePuml.csproj @@ -0,0 +1,21 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/scripts/Program.cs b/scripts/Program.cs new file mode 100644 index 00000000..c5eb569c --- /dev/null +++ b/scripts/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; + +public class Program +{ + public static async Task Main(string[] args) + { + await Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddHostedService(); + services.AddSingleton(); + }) + .Build() + .RunAsync(); + } +} diff --git a/scripts/README.md b/scripts/README.md index ccf5c7b9..51cfb608 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -4,20 +4,47 @@ If you want to have customized builds and/or experiment with Azure-PlantUML, you ## Prerequisites -* You need [dotnet script](https://github.com/filipw/dotnet-script) installed for executing the scripts (`dotnet tool install -g dotnet-script --version 0.53`) -* You also need to download the [Microsoft Azure architecture icons](https://docs.microsoft.com/en-us/azure/architecture/icons/) and copy all folders from `Azure_Public_Service_Icons_V4\Azure_Public_Service_Icons\Icons` to [source/official](../source/official) -* For additional icons not in the official set, we suggest using the [Amazing Icon Downloader](https://chrome.google.com/webstore/detail/amazing-icon-downloader/kllljifcjfleikiipbkdcgllbllahaob/) +The build applicaiton is supported on Windows, MacOS, and Linux (Ubuntu 20.04) with the following software pre-requisites installed: + +* [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download) +* [PlantUML](https://plantuml.com/download)` + +### Playwright CLI + +Download and install the Playwright CLI by running the following command: + +```bash +dotnet tool install -g Microsoft.Playwright.CLI +``` + +### Additional Setup for Linux (Ubuntu 20.04) + +For Linux systems, Playwright requires the installation of additinal dependencies. Navigate to the `scripts` folder and run: + +```bash +dotnet build +playwright install-deps +``` ## Configure -It is required to have [PlantUML](https://https://plantuml.com/) (`choco install plantuml`) , [Inkscape](https://inkscape.org/) and `rsvg_convert` (`choco install rsvg-convert`)installed. +### Icon files + +Download the [Microsoft Azure architecture icons](https://docs.microsoft.com/en-us/azure/architecture/icons/) and copy all folders from `Azure_Public_Service_Icons_V4\Azure_Public_Service_Icons\Icons` to [source/official](../source/official) + +Place any icons that are not part of the Microsoft Azure Architecture bundle into [source/manual](../source/manual). For additional icons not in the official set, we suggest using the [Amazing Icon Downloader](https://chrome.google.com/webstore/detail/amazing-icon-downloader/kllljifcjfleikiipbkdcgllbllahaob/) + +### Application Settings -Please make sure, that the following variables at the beginning of `main.csx` are correct configured for your system, if you used chocolately for `PlantUml` and `rsvg_convert` and the inkscape installer, they should be correct: +Create a new file named `appsettings.json` within the `scripts` directory and save the following contents: -```csharp -var plantUmlPath = @"C:\ProgramData\chocolatey\lib\plantuml\tools\plantuml.jar"; -var inkScapePath = @"C:\Program Files\Inkscape\inkscape.exe"; -static string rsvgConvertPath = @"C:\ProgramData\chocolatey\lib\rsvg-convert\tools\rsvg-convert.exe"; +```json +{ + "sourceFolderPath": "..\\source", + "targetFolderPath": "..\\dist", + "monochromeColorHex": "#0072C6", + "plantUmlPath": "C:\\ProgramData\\chocolatey\\lib\\plantuml\\tools\\plantuml.jar" +} ``` ### Configuration File: Config.yaml @@ -27,11 +54,7 @@ On top each Azure service is mapped to his primary category. ## Run -You can just execute - -```text -dotnet script main.csx -``` +Use `dotnet run` from the `scripts` folder to execute the application. ### What happens @@ -51,9 +74,6 @@ From a logical point of view, the following happens: * This is needed for PlantUML sprite generation 1. A PlantUML sprite is generated 1. In a second round a PNG without background is generated from the monochrome SVG - - During all generations, it is ensured that the max size of length and width are not exeeding `targetMaxSize`. - 1. In addition to single Azure services PUML files, also a combined PUML file per category is generated 1. A markdown table with all Azure services, their colored and monochrome symbols and the PUML files is generated -1. VSCode snippets for all Azure services for their PlantUML usage are generated \ No newline at end of file +1. VSCode snippets for all Azure services for their PlantUML usage are generated diff --git a/scripts/lib/Config.cs b/scripts/lib/Config.cs new file mode 100644 index 00000000..fe69d749 --- /dev/null +++ b/scripts/lib/Config.cs @@ -0,0 +1,60 @@ +using System.Linq; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + + +public class ConfigLookupEntry +{ + public string? Category { get; set; } + public string? ServiceSource { get; set; } + public string? ServiceTarget { get; set; } + public bool FitToCanvas { get; set; } +} + +public class Config +{ + public List? Categories { get; set; } + + public static IEnumerable ReadConfig(string configFilePath) + { + Config config; + using (var input = File.OpenText("Config.yaml")) + { + var deserializer = new DeserializerBuilder() + .Build(); + + config = deserializer.Deserialize(input); + } + + var lookupTable = config.Categories!.SelectMany(cat => cat.Services!.Select(service => new ConfigLookupEntry + { + Category = cat.Name, + ServiceSource = service.Source, + ServiceTarget = service.Target, + FitToCanvas = service.Fit + })); + + return lookupTable; + } +} + +public class AzureCategory +{ + public string? Name { get; set; } + + public List? Services { get; set; } +} + +public class AzureService +{ + public string? Source { get; set; } + + public string? Target { get; set; } + + public bool Fit { get; set; } +} + + + + + diff --git a/scripts/lib/Config.csx b/scripts/lib/Config.csx deleted file mode 100644 index c18b2b5d..00000000 --- a/scripts/lib/Config.csx +++ /dev/null @@ -1,59 +0,0 @@ -#r "nuget: YamlDotNet, 11.1.1" - -using System.Linq; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -public IEnumerable ReadConfig(string configFilePath) -{ - Config config; - using (var input = File.OpenText("Config.yaml")) - { - var deserializer = new DeserializerBuilder() - .Build(); - - config = deserializer.Deserialize(input); - } - - var lookupTable = config.Categories.SelectMany(cat => cat.Services.Select(service => new ConfigLookupEntry - { - Category = cat.Name, - ServiceSource = service.Source, - ServiceTarget = service.Target, - FitToCanvas = service.Fit - })); - - return lookupTable; -} - -public class ConfigLookupEntry -{ - public string Category { get; set; } - - public string ServiceSource { get; set; } - - public string ServiceTarget { get; set; } - - public bool FitToCanvas { get; set; } -} - -private class Config -{ - public List Categories { get; set; } -} - -private class AzureCategory -{ - public string Name { get; set; } - - public List Services { get; set; } -} - -private class AzureService -{ - public string Source { get; set; } - - public string Target { get; set; } - - public bool Fit { get; set; } -} \ No newline at end of file diff --git a/scripts/lib/HSLColor.csx b/scripts/lib/HSLColor.cs similarity index 99% rename from scripts/lib/HSLColor.csx rename to scripts/lib/HSLColor.cs index da8824dd..47ce14d7 100644 --- a/scripts/lib/HSLColor.csx +++ b/scripts/lib/HSLColor.cs @@ -1,4 +1,3 @@ -#r "System.Drawing" // From Rich Newman // https://richnewman.wordpress.com/about/code-listings-and-diagrams/hslcolor-class/ @@ -7,6 +6,7 @@ using System.Text; using System.Drawing; + public class HSLColor { // Private data members below are on scale 0-1 @@ -138,4 +138,4 @@ public HSLColor(double hue, double saturation, double luminosity) this.Luminosity = luminosity; } -} \ No newline at end of file +} diff --git a/scripts/lib/IImageManager.cs b/scripts/lib/IImageManager.cs new file mode 100644 index 00000000..8778c3f0 --- /dev/null +++ b/scripts/lib/IImageManager.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +public interface IImageManager +{ + Task ExportToPng(string inputPath, string outputPath, int targetImageHeight, bool omitBackground = true); + Task ResizeAndCopy(string inputPath, string outputPath, int targetImageHeight); + Task ExportToMonochrome(string inputPath, string outputPath, Color color); +} diff --git a/scripts/lib/MarkdownGeneration.csx b/scripts/lib/MarkdownGeneration.csx deleted file mode 100644 index eec93edd..00000000 --- a/scripts/lib/MarkdownGeneration.csx +++ /dev/null @@ -1,45 +0,0 @@ -using System.Text.RegularExpressions; - -public static void GenerateMarkdownTable(string distFolder) -{ - Console.WriteLine("Generating Markdown table..."); - - var sbTable = new StringBuilder(); - sbTable.AppendLine("Category | Macro (Name) |
Color
|
Mono 
| Url"); - sbTable.AppendLine(" --- | --- | :---: | :---: | ---"); - - var currentCategory = ""; - - foreach (var filePath in Directory.GetFiles(distFolder, "*.puml", SearchOption.AllDirectories)) - { - var entityName = Path.GetFileNameWithoutExtension(filePath); - var category = Directory.GetParent(filePath).Name; - - if("dist" == category) - { - continue; - } - if(currentCategory != category) - { - sbTable.AppendLine($"**{category}** | | | | **{category}/all.puml**"); - currentCategory = category; - continue; - } - - sbTable.Append($"{category} | "); - sbTable.Append($"{entityName}
({Regex.Replace(entityName, "(?Color |
Mono 
| Url"); + sbTable.AppendLine(" --- | --- | :---: | :---: | ---"); + + var currentCategory = ""; + + foreach (var filePath in Directory.GetFiles(distFolder, "*.puml", SearchOption.AllDirectories)) + { + var entityName = Path.GetFileNameWithoutExtension(filePath); + var category = Directory.GetParent(filePath)!.Name; + + if("dist" == category) + { + continue; + } + if(currentCategory != category) + { + sbTable.AppendLine($"**{category}** | | | | **{category}/all.puml**"); + currentCategory = category; + continue; + } + + sbTable.Append($"{category} | "); + sbTable.Append($"{entityName}
({Regex.Replace(entityName, "(? logger) + { + _logger = logger; + } + + private async Task Init() + { + // Ensure browsers are installed: https://playwright.dev/dotnet/docs/browsers#prerequisites-for-net + Microsoft.Playwright.Program.Main(new string[] { "install" }); + + try + { + playwrightContext = await Playwright.CreateAsync(); + browser = await playwrightContext.Chromium.LaunchAsync(new() { Headless = true }); + page = await browser.NewPageAsync(); + } + catch(Exception ex) + { + _logger.LogCritical(ex.Message); + if (OperatingSystem.IsLinux()) + { + _logger.LogWarning("Ensure Linux dependencies are installed using the Microsoft.Playwright.CLI. See: https://playwright.dev/dotnet/docs/browsers#prerequisites-for-net"); + } + } + } + + public async Task ExportToPng(string inputPath, string outputPath, int targetImageHeight, bool omitBackground = true) + { + var currentDirectory = Environment.CurrentDirectory; + var rootDirectory = System.IO.Directory.GetParent(currentDirectory); + + var inputPathNormalized = inputPath.Replace(@"..\", "").Replace(@"../", "").Replace(@"\", @"/"); + var inputTokens = inputPathNormalized.Split(@"/"); + var inputFile = inputTokens.Last(); + + var outputPathNormalized = outputPath.Replace(@"..\", "").Replace(@"../", "").Replace(@"\", @"/"); + var outputTokens = outputPathNormalized.Split(@"/"); + var outputFile = outputTokens.Last(); + + var newInputPath = Path.Combine(rootDirectory!.FullName, inputTokens[0], inputTokens[1], inputFile); + + if (!OperatingSystem.IsWindows()) + newInputPath = "file://" + newInputPath; + + var newOutputPath = Path.Combine(rootDirectory.FullName, outputTokens[0], outputTokens[1], outputFile); + + if (page == null) + await Init(); + + try + { + await page!.SetViewportSizeAsync(targetImageHeight, targetImageHeight); + await page!.GotoAsync(newInputPath); + var item = await page.QuerySelectorAsync("svg"); + await item!.ScreenshotAsync(new() { Path = newOutputPath, OmitBackground = omitBackground }); + return await Task.FromResult(true); + } + catch(Exception ex) + { + _logger.LogError($"Failed to export png {inputPath} Message: {ex.Message}"); + return await Task.FromResult(false); + } + } + + public async Task ResizeAndCopy(string inputPath, string outputPath, int targetImageHeight) + { + try + { + XmlDocument xmldoc = new XmlDocument(); + xmldoc.Load(inputPath); + XmlElement svg = xmldoc.DocumentElement; + + if (svg == null) + { + throw new Exception("unable to load svg element from file"); + } + + // Check for a valid svg attribute in the document namespace. + var docNamespace = svg.Attributes["xmlns"]; + + if (docNamespace == null || docNamespace!.Value != "http://www.w3.org/2000/svg") + { + throw new Exception($"Invalid svg namespace attribute in {inputPath}. Confirm file has xmlns='http://www.w3.org/2000/svg' in the svg element."); + } + + // Set document dimensions + if (svg.Attributes["width"] == null) + svg.Attributes.Append(xmldoc.CreateAttribute("width")); + + // svg.Attributes["width"]!.Value = targetImageHeight.ToString(); + svg.Attributes["width"]!.Value = "100%"; + + if (svg.Attributes["height"] == null) + svg.Attributes.Append(xmldoc.CreateAttribute("height")); + + svg.Attributes["height"]!.Value = targetImageHeight.ToString(); + + // Set aspect ratio + if (svg.Attributes["preserveAspectRatio"] == null) + svg.Attributes.Append(xmldoc.CreateAttribute("preserveAspectRatio")); + + svg.Attributes["preserveAspectRatio"]!.Value = "xMidYMid meet"; + + XmlWriter writer = XmlWriter.Create(outputPath); + xmldoc.Save(writer); + writer.Close(); + return await Task.FromResult(true); + } + catch (Exception ex) + { + throw ex; + } + } + + public async Task ExportToMonochrome(string inputPath, string outputPath, Color azureColor) + { + // Making it first grayscale and after that monochrom gives best result + await ManipulateSvgFills(inputPath, outputPath, (color) => + { + var grayScale = (int)((color.R * 0.3f) + (color.G * 0.59f) + (color.B * 0.11f)); + return Color.FromArgb(grayScale, grayScale, grayScale); + }); + + await ManipulateSvgFills(outputPath, outputPath, (color) => + { + HSLColor lowerColor = new HSLColor(color); + var upperColor = new HSLColor(azureColor) { Luminosity = lowerColor.Luminosity }; + return upperColor; + }); + + return await Task.FromResult(true); + } + + private async Task ManipulateSvgFills(string inputPath, string outputPath, Func manipulation) + { + var content = File.ReadAllText(inputPath); + + // get all hexidecimal colors in the SVG + var hexColors = Regex.Matches(content, "(#[a-fA-F0-9]{6})(\")|(#[a-fA-F0-9]{3})(\")|(#[a-fA-F0-9]{6})(;)"); + + foreach(var hexColor in hexColors) + { + var hexColorString = hexColor.ToString().Trim('"'); + hexColorString = hexColorString.Replace(";", ""); + var currentColor = System.Drawing.ColorTranslator.FromHtml(hexColorString); + var newColor = manipulation(currentColor); + content = content.Replace(hexColorString, newColor.ToHexString()); + } + + // Handle RGB colors + var start = "rgb("; + var end = ")"; + var delimiter = ','; + var fillStartPos = content.IndexOf(start, 0); + + while (fillStartPos > 0) + { + var fillContentStartPos = fillStartPos + start.Length; + var fillContentEndPos = content.IndexOf(end, fillContentStartPos); + + var rgbPerc = content + .Substring(fillContentStartPos, fillContentEndPos - fillContentStartPos) + .Split(delimiter); + + int rValue, gValue, bValue; + if (rgbPerc[0].Contains('%')) + { + var rPercValue = float.Parse(rgbPerc[0].TrimEnd('%'), CultureInfo.InvariantCulture); + var gPercValue = float.Parse(rgbPerc[1].TrimEnd('%'), CultureInfo.InvariantCulture); + var bPercValue = float.Parse(rgbPerc[2].TrimEnd('%'), CultureInfo.InvariantCulture); + + rValue = (int)Math.Round(rPercValue / 100 * 255); + gValue = (int)Math.Round(gPercValue / 100 * 255); + bValue = (int)Math.Round(bPercValue / 100 * 255); + } + else + { + rValue = int.Parse(rgbPerc[0]); + gValue = int.Parse(rgbPerc[1]); + bValue = int.Parse(rgbPerc[2]); + } + + var currentColor = Color.FromArgb(rValue, gValue, bValue); + + var newColor = manipulation(currentColor); + content = content.ReplaceAt(fillStartPos, fillContentEndPos + 1 - fillStartPos, $"rgb({newColor.R.ToString(CultureInfo.InvariantCulture)},{newColor.G.ToString(CultureInfo.InvariantCulture)},{newColor.B.ToString(CultureInfo.InvariantCulture)})"); + + fillStartPos = content.IndexOf(start, fillContentEndPos); + } + + await File.WriteAllTextAsync(outputPath, content); + return await Task.FromResult(true); + } + +} diff --git a/scripts/lib/Util.cs b/scripts/lib/Util.cs new file mode 100644 index 00000000..5a0559d3 --- /dev/null +++ b/scripts/lib/Util.cs @@ -0,0 +1,13 @@ +using System.Drawing; + +public static class Util +{ + public static string ToHexString(this Color c) => $"#{c.R:X2}{c.G:X2}{c.B:X2}"; + + public static string ToRgbString(this Color c) => $"RGB({c.R}, {c.G}, {c.B})"; + + public static string ReplaceAt(this string str, int index, int length, string replace) + { + return str.Remove(index, Math.Min(length, str.Length - index)).Insert(index, replace); + } +} diff --git a/scripts/lib/VSCodeSnippets.cs b/scripts/lib/VSCodeSnippets.cs new file mode 100644 index 00000000..a6c355b9 --- /dev/null +++ b/scripts/lib/VSCodeSnippets.cs @@ -0,0 +1,68 @@ +using System.Text.RegularExpressions; +using Newtonsoft.Json; + +public static class VSCodeSnippets +{ + public static async Task GenerateSnippets(string distFolder) + { + Console.WriteLine("Generating VSCode Snippets..."); + + var snippets = new Dictionary(); + + foreach (var filePath in Directory.GetFiles(distFolder, "*.puml", SearchOption.AllDirectories)) + { + var entityName = Path.GetFileNameWithoutExtension(filePath); + if (entityName == "all") + { + continue; + } + + snippets.Add($"{entityName}", new Snippet{ + prefix = $"{SplitCamelCase(entityName)}", + description = $"Add {SplitCamelCase(entityName)} to diagram", + body = new List{ + $"{entityName}(${{1:alias}}, \"${{2:label}}\", \"${{3:technology}}\")", + "$0" + } + }); + + snippets.Add($"{entityName}_Descr", new Snippet{ + prefix = $"{SplitCamelCase(entityName)} with Description", + description = $"Add {SplitCamelCase(entityName)} with Description to diagram", + body = new List{ + $"{entityName}(${{1:alias}}, \"${{2:label}}\", \"${{3:technology}}\", \"${{4:description}}\")", + "$0" + } + }); + } + + var snippetsDirectory = Path.Combine(distFolder, ".vscode", "snippets"); + Directory.CreateDirectory(snippetsDirectory); + + using (StreamWriter file = File.CreateText(Path.Combine(snippetsDirectory, "diagram.json"))) + { + var serializer = new JsonSerializer + { + Formatting = Formatting.Indented, + }; + serializer.Serialize(file, snippets); + } + + return await Task.FromResult(true); + } + + private static string SplitCamelCase(string camelCaseString) => Regex.Replace(camelCaseString, "(\\B[A-Z])", " $1"); + + private class Snippet + { + public string? prefix { get; set; } + + public string? description { get; set; } + + public List? body { get; set; } + } + +} + + + diff --git a/scripts/lib/VSCodeSnippets.csx b/scripts/lib/VSCodeSnippets.csx deleted file mode 100644 index 5d916527..00000000 --- a/scripts/lib/VSCodeSnippets.csx +++ /dev/null @@ -1,60 +0,0 @@ -#r "nuget: Newtonsoft.Json, 12.0.3" - -using System.Text.RegularExpressions; -using Newtonsoft.Json; - -public void GenerateVSCodeSnippets(string distFolder) -{ - Console.WriteLine("Generating VSCode Snippets..."); - - var snippets = new Dictionary(); - - foreach (var filePath in Directory.GetFiles(distFolder, "*.puml", SearchOption.AllDirectories)) - { - var entityName = Path.GetFileNameWithoutExtension(filePath); - if (entityName == "all") - { - continue; - } - - snippets.Add($"{entityName}", new Snippet{ - prefix = $"{SplitCamelCase(entityName)}", - description = $"Add {SplitCamelCase(entityName)} to diagram", - body = new List{ - $"{entityName}(${{1:alias}}, \"${{2:label}}\", \"${{3:technology}}\")", - "$0" - } - }); - - snippets.Add($"{entityName}_Descr", new Snippet{ - prefix = $"{SplitCamelCase(entityName)} with Description", - description = $"Add {SplitCamelCase(entityName)} with Description to diagram", - body = new List{ - $"{entityName}(${{1:alias}}, \"${{2:label}}\", \"${{3:technology}}\", \"${{4:description}}\")", - "$0" - } - }); - } - - var snippetsDirectory = Path.Combine(distFolder, ".vscode", "snippets"); - Directory.CreateDirectory(snippetsDirectory); - - using (StreamWriter file = File.CreateText(Path.Combine(snippetsDirectory, "diagram.json"))) - { - var serializer = new JsonSerializer - { - Formatting = Formatting.Indented, - }; - serializer.Serialize(file, snippets); - } -} - -private static string SplitCamelCase(string camelCaseString) => Regex.Replace(camelCaseString, "(\\B[A-Z])", " $1"); - -private class Snippet { - public string prefix { get; set; } - - public string description { get; set; } - - public List body { get; set; } -} \ No newline at end of file diff --git a/scripts/main.csx b/scripts/main.csx deleted file mode 100644 index c74eccbd..00000000 --- a/scripts/main.csx +++ /dev/null @@ -1,368 +0,0 @@ -#! "netcoreapp3.1" - -#r "nuget: System.Drawing.Common, 4.7.0" - -#load "lib/Config.csx" -#load "lib/HSLColor.csx" -#load "lib/MarkdownGeneration.csx" -#load "lib/VSCodeSnippets.csx" - -using System.Diagnostics; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.Globalization; -using System.IO; - -var sourceFolder = @"../source"; - -var originalSourceFolder = Path.Combine(sourceFolder, "official"); - -var manualSourceFolder = Path.Combine(sourceFolder, "manual"); - -var targetFolder = @"../dist"; - -var targetImageHeight = 70; - -var azureColor = System.Drawing.ColorTranslator.FromHtml("#0072C6"); - -var plantUmlPath = @"C:\ProgramData\chocolatey\lib\plantuml\tools\plantuml.jar"; - -var inkScapePath = @"C:\Program Files\Inkscape\bin\inkscape.exe"; - -static string rsvgConvertPath = @"C:\ProgramData\chocolatey\lib\rsvg-convert\tools\rsvg-convert.exe"; - - -Main(); - -public void Main() -{ - var lookupTable = ReadConfig("Config.yaml"); - - // Cleanup - if (Directory.Exists(targetFolder)) - { - Directory.Delete(targetFolder, true); - } - Directory.CreateDirectory(targetFolder); - - File.Copy(Path.Combine(sourceFolder, "AzureRaw.puml"), Path.Combine(targetFolder, "AzureRaw.puml")); - File.Copy(Path.Combine(sourceFolder, "AzureCommon.puml"), Path.Combine(targetFolder, "AzureCommon.puml")); - File.Copy(Path.Combine(sourceFolder, "AzureC4Integration.puml"), Path.Combine(targetFolder, "AzureC4Integration.puml")); - File.Copy(Path.Combine(sourceFolder, "AzureSimplified.puml"), Path.Combine(targetFolder, "AzureSimplified.puml")); - - foreach (var service in lookupTable) - { - ProcessService(service); - } - - foreach (var category in lookupTable.Select(_ => _.Category).Distinct()) - { - var categoryDirectoryPath = Path.Combine(targetFolder, category); - var catAllFilePath = Path.Combine(categoryDirectoryPath, "all.puml"); - - CombineMultipleFilesIntoSingleFile(categoryDirectoryPath, "*.puml", catAllFilePath); - } - - GenerateMarkdownTable(targetFolder); - GenerateVSCodeSnippets(targetFolder); - - Console.WriteLine("Finished"); -} - -public void ProcessService(ConfigLookupEntry service) -{ - Console.WriteLine($"Processing {service.ServiceSource}"); - - //var coloredSourceFileName = $"{service.ServiceSource}_COLOR.svg"; - //#var monochromSourceFileName = $"{service.ServiceSource}.svg"; - - // NOTE: All icons as colored as supplied - var coloredSourceFilePath = GetSourceFilePath(service.ServiceSource, true); - //var monochromSourceFilePath = GetSourceFilePath(service.ServiceSource, false); - var monochromSourceFilePath = (string) null; - - var coloredExists = coloredSourceFilePath != null; - var monochromExists = false; - - if (!coloredExists && !monochromExists) - { - WriteErrorLine($"Error: Missing {service.ServiceSource} color or mono"); - return; - } - - var categoryDirectoryPath = Path.Combine(targetFolder, service.Category); - Directory.CreateDirectory(categoryDirectoryPath); - - if (coloredExists) - { - // Only if needed - takes too long - if (service.FitToCanvas) - { - FitCanvasToDrawing(coloredSourceFilePath); - } - - // Resize and copy SVG - RsvgConvert(coloredSourceFilePath, Path.Combine(categoryDirectoryPath, service.ServiceTarget + ".svg"), targetImageHeight); - // Resize and export PNG - RsvgConvert(coloredSourceFilePath, Path.Combine(categoryDirectoryPath, service.ServiceTarget + ".png"), targetImageHeight, exportAsPng: true); - } - else - { - WriteWarningLine($"Warning: Missing COLOR {service.ServiceSource}"); - } - - var monochromSvgFilePath = Path.Combine(categoryDirectoryPath, service.ServiceTarget + "(m).svg"); - if (monochromExists) - { - // Only if needed - takes too long - //FitCanvasToDrawing(monochromSourceFilePath); - - // Resize and copy SVG - RsvgConvert(monochromSourceFilePath, monochromSvgFilePath, targetImageHeight); - } - else - { - // We generated every mono file so don't bother warning - //WriteWarningLine($"Warning: Missing MONOCHROM {service.ServiceSource}, generating..."); - - // Resize and copy colored SVG, making monochrom afterwards - RsvgConvert(coloredSourceFilePath, monochromSvgFilePath, targetImageHeight); - CreateMonochromNew(monochromSvgFilePath, monochromSvgFilePath); - } - - var monochromPngFilePath = Path.Combine(categoryDirectoryPath, service.ServiceTarget + "(m).png"); - // First generation with background needed for PUML sprite generation - RsvgConvert(monochromSvgFilePath, monochromPngFilePath, targetImageHeight, exportAsPng: true, withWhiteBackground: true); - ConvertToPuml(monochromPngFilePath, service.ServiceTarget + ".puml"); - - // Second generation without background needed other usages - RsvgConvert(monochromSvgFilePath, monochromPngFilePath, targetImageHeight, exportAsPng: true, withWhiteBackground: false); -} - -public bool FitCanvasToDrawing(string inputPath) -{ - Console.WriteLine("Fit canvas to drawing"); - - var processInfo = new ProcessStartInfo - { - FileName = inkScapePath, - Arguments = $"--verb=FitCanvasToDrawing --verb=FileSave --verb=FileClose --verb=FileQuit \"{inputPath}\"", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using (var process = Process.Start(processInfo)) - { - if (!process.WaitForExit(10000)) - { - Console.WriteLine($"Killing InkScape for FitCanvasToDrawing {inputPath}"); - process.Kill(); - return false; - } - } - - return true; -} - -public static bool RsvgConvert(string inputPath, string outputPath, int targetImageHeight, bool exportAsPng = false, bool withWhiteBackground = false) -{ - var processInfo = new ProcessStartInfo - { - FileName = rsvgConvertPath, - Arguments = $"--height {targetImageHeight} --width {targetImageHeight} --keep-aspect-ratio --output \"{outputPath}\" --format {(exportAsPng ? "png" : "svg")} --background-color {(withWhiteBackground ? "white" : "none")} \"{inputPath}\"", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using (var process = Process.Start(processInfo)) - { - if (!process.WaitForExit(10000)) - { - Console.WriteLine($"Killing rsvg-convert {inputPath}"); - process.Kill(); - return false; - } - } - - return true; -} - -public bool CreateMonochromNew(string inputPath, string outputPath) -{ - // Making it first grayscale and after that monochrom gives best result - ManipulateSvgFills(inputPath, outputPath, (color) => - { - var grayScale = (int)((color.R * 0.3f) + (color.G * 0.59f) + (color.B * 0.11f)); - return Color.FromArgb(grayScale, grayScale, grayScale); - }); - - ManipulateSvgFills(outputPath, outputPath, (color) => - { - HSLColor lowerColor = new HSLColor(color); - var upperColor = new HSLColor(azureColor) { Luminosity = lowerColor.Luminosity }; - return upperColor; - }); - - return true; -} - -private static void ManipulateSvgFills(string inputPath, string outputPath, Func manipulation) -{ - var content = File.ReadAllText(inputPath); - - var start = "rgb("; - var end = ")"; - var delimiter = ','; - - var fillStartPos = content.IndexOf(start, 0); - while (fillStartPos > 0) - { - var fillContentStartPos = fillStartPos + start.Length; - var fillContentEndPos = content.IndexOf(end, fillContentStartPos); - - var rgbPerc = content - .Substring(fillContentStartPos, fillContentEndPos - fillContentStartPos) - .Split(delimiter); - - int rValue, gValue, bValue; - if (rgbPerc[0].Contains('%')) - { - var rPercValue = float.Parse(rgbPerc[0].TrimEnd('%'), CultureInfo.InvariantCulture); - var gPercValue = float.Parse(rgbPerc[1].TrimEnd('%'), CultureInfo.InvariantCulture); - var bPercValue = float.Parse(rgbPerc[2].TrimEnd('%'), CultureInfo.InvariantCulture); - - rValue = (int)Math.Round(rPercValue / 100 * 255); - gValue = (int)Math.Round(gPercValue / 100 * 255); - bValue = (int)Math.Round(bPercValue / 100 * 255); - } - else - { - rValue = int.Parse(rgbPerc[0]); - gValue = int.Parse(rgbPerc[1]); - bValue = int.Parse(rgbPerc[2]); - } - - var currentColor = Color.FromArgb(rValue, gValue, bValue); - - var newColor = manipulation(currentColor); - content = content.ReplaceAt(fillStartPos, fillContentEndPos + 1 - fillStartPos, $"rgb({newColor.R.ToString(CultureInfo.InvariantCulture)},{newColor.G.ToString(CultureInfo.InvariantCulture)},{newColor.B.ToString(CultureInfo.InvariantCulture)})"); - - fillStartPos = content.IndexOf(start, fillContentEndPos); - } - - File.WriteAllText(outputPath, content); -} - -//// str - the source string -//// index- the start location to replace at (0-based) -//// length - the number of characters to be removed before inserting -//// replace - the string that is replacing characters -public static string ReplaceAt(this string str, int index, int length, string replace) -{ - return str.Remove(index, Math.Min(length, str.Length - index)) - .Insert(index, replace); -} - -private static void CombineMultipleFilesIntoSingleFile(string inputDirectoryPath, string inputFileNamePattern, string outputFilePath) -{ - string[] inputFilePaths = Directory.GetFiles(inputDirectoryPath, inputFileNamePattern, SearchOption.AllDirectories); - using (var outputStream = File.Create(outputFilePath)) - { - foreach (var inputFilePath in inputFilePaths) - { - using (var inputStream = File.OpenRead(inputFilePath)) - { - inputStream.CopyTo(outputStream); - - byte[] newline = Encoding.ASCII.GetBytes(Environment.NewLine); - outputStream.Write(newline, 0, newline.Length); - } - } - } -} - -public string GetSourceFilePath(string sourceFileName, bool color = true) -{ - var patterns = new List { - "*-icon-service-{0}.svg", - "{0}_COLOR.svg", - "{0}.svg" - }; - - foreach (var p in patterns) - { - var x = string.Format(p, sourceFileName); - if (x.Contains("-") || x.Contains(" ")) - { - // Original files have hyphen not space so assume that based on pattern - x = x.Replace(" ", "-"); - } - var files = Directory.GetFiles(originalSourceFolder, x, SearchOption.AllDirectories); - if (files.Length == 0) - { - files = Directory.GetFiles(manualSourceFolder, x, SearchOption.AllDirectories); - } - - if (files.Length >= 1) - { - if (files.Length > 1) - { - var matches = String.Join(",", files); - WriteWarningLine($"Warning: File found in multiple locations {sourceFileName}: {matches}"); - } - return files[0]; - } - } - - return null; -} - -public void WriteWarningLine(string message) -{ - var tmp = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(message); - Console.ForegroundColor = tmp; -} - -public void WriteErrorLine(string message) -{ - var tmp = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(message); - Console.ForegroundColor = tmp; -} - -public string ConvertToPuml(string pngPath, string pumlFileName) -{ - var format = "16z"; - - var entityName = Path.GetFileNameWithoutExtension(pumlFileName); - var pumlPath = Path.Combine(Directory.GetParent(pngPath).FullName, pumlFileName); - - var processInfo = new ProcessStartInfo - { - FileName = "java", - Arguments = $"-jar {plantUmlPath} -encodesprite {format} \"{pngPath}\"", - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - var pumlContent = new StringBuilder(); - using (var process = Process.Start(processInfo)) - { - process.WaitForExit(); - pumlContent.Append(process.StandardOutput.ReadToEnd()); - } - - pumlContent.AppendLine($"AzureEntityColoring({entityName})"); - pumlContent.AppendLine($"!define {entityName}(e_alias, e_label, e_techn) AzureEntity(e_alias, e_label, e_techn, AZURE_SYMBOL_COLOR, {entityName}, {entityName})"); - pumlContent.AppendLine($"!define {entityName}(e_alias, e_label, e_techn, e_descr) AzureEntity(e_alias, e_label, e_techn, e_descr, AZURE_SYMBOL_COLOR, {entityName}, {entityName})"); - - File.WriteAllText(pumlPath, pumlContent.ToString()); - return pumlPath; -} \ No newline at end of file diff --git a/scripts/omnisharp.json b/scripts/omnisharp.json deleted file mode 100644 index 5c14541a..00000000 --- a/scripts/omnisharp.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "script": { - "enableScriptNuGetReferences": true, - "defaultTargetFramework": "netcoreapp2.1" - } -} \ No newline at end of file