diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index a45d4d3..cdd2716 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -7,7 +7,7 @@ on: branches: [ main ] env: - VERSION: 3.1.${{ github.run_number }} + VERSION: 3.2.${{ github.run_number }} NUGET_INDEX: https://api.nuget.org/v3/index.json BUILD_TYPE: Release diff --git a/src/components/C4Sharp/C4Sharp.csproj b/src/components/C4Sharp/C4Sharp.csproj index 4b9735b..beba49d 100644 --- a/src/components/C4Sharp/C4Sharp.csproj +++ b/src/components/C4Sharp/C4Sharp.csproj @@ -11,7 +11,7 @@ https://github.com/8T4/c4sharp git c4, diagrams - 3.1.0 + 3.2.0 https://github.com/8T4/c4sharp/blob/main/LICENSE true true diff --git a/src/components/C4Sharp/Diagrams/Core/ComponentDiagram.cs b/src/components/C4Sharp/Diagrams/Core/ComponentDiagram.cs index 6a530df..7c9d09f 100644 --- a/src/components/C4Sharp/Diagrams/Core/ComponentDiagram.cs +++ b/src/components/C4Sharp/Diagrams/Core/ComponentDiagram.cs @@ -5,13 +5,5 @@ /// components are, their responsibilities and the technology/implementation details. /// /// - public record ComponentDiagram: Diagram - { - /// - /// Constructor - /// - public ComponentDiagram() : base("C4_Component") - { - } - } + public record ComponentDiagram() : Diagram("C4_Component"); } \ No newline at end of file diff --git a/src/components/C4Sharp/Diagrams/Core/ContainerDiagram.cs b/src/components/C4Sharp/Diagrams/Core/ContainerDiagram.cs index df3230a..b6fdacd 100644 --- a/src/components/C4Sharp/Diagrams/Core/ContainerDiagram.cs +++ b/src/components/C4Sharp/Diagrams/Core/ContainerDiagram.cs @@ -7,13 +7,5 @@ /// support/operations staff alike. /// /// - public record ContainerDiagram: Diagram - { - /// - /// Constructor - /// - public ContainerDiagram() : base("C4_Container") - { - } - } + public record ContainerDiagram() : Diagram("C4_Container"); } \ No newline at end of file diff --git a/src/components/C4Sharp/Diagrams/Core/ContextDiagram.cs b/src/components/C4Sharp/Diagrams/Core/ContextDiagram.cs index ee117b0..ca6b37a 100644 --- a/src/components/C4Sharp/Diagrams/Core/ContextDiagram.cs +++ b/src/components/C4Sharp/Diagrams/Core/ContextDiagram.cs @@ -6,14 +6,5 @@ /// by its users and the other systems that it interacts with. /// /// - public record ContextDiagram: Diagram - { - /// - /// Constructor - /// - public ContextDiagram() : base("C4_Context") - { - - } - } + public record ContextDiagram() : Diagram("C4_Context"); } \ No newline at end of file diff --git a/src/components/C4Sharp/Diagrams/Diagram.cs b/src/components/C4Sharp/Diagrams/Diagram.cs index 674063d..6e268cc 100644 --- a/src/components/C4Sharp/Diagrams/Diagram.cs +++ b/src/components/C4Sharp/Diagrams/Diagram.cs @@ -1,4 +1,5 @@ -using C4Sharp.Extensions; +using System; +using C4Sharp.Extensions; using C4Sharp.Models; using C4Sharp.Models.Relationships; @@ -30,8 +31,8 @@ protected Diagram(string name) ShowLegend = false; FlowVisualization = DiagramLayout.TopDown; Name = name; - Structures = default; - Relationships = default; + Structures = Array.Empty(); + Relationships = Array.Empty(); } /// diff --git a/src/components/C4Sharp/Diagrams/Supplementary/DeploymentDiagram.cs b/src/components/C4Sharp/Diagrams/Supplementary/DeploymentDiagram.cs index 302a184..bff2d37 100644 --- a/src/components/C4Sharp/Diagrams/Supplementary/DeploymentDiagram.cs +++ b/src/components/C4Sharp/Diagrams/Supplementary/DeploymentDiagram.cs @@ -9,13 +9,5 @@ namespace C4Sharp.Diagrams.Supplementary /// (e.g. a database server, Java EE web/application server, Microsoft IIS), etc. Deployment nodes can be nested. /// /// - public record DeploymentDiagram : Diagram - { - /// - /// Constructor - /// - public DeploymentDiagram() : base("C4_Deployment") - { - } - } + public record DeploymentDiagram() : Diagram("C4_Deployment"); } \ No newline at end of file diff --git a/src/components/C4Sharp/FileSystem/C4Directory.cs b/src/components/C4Sharp/FileSystem/C4SharpDirectory.cs similarity index 94% rename from src/components/C4Sharp/FileSystem/C4Directory.cs rename to src/components/C4Sharp/FileSystem/C4SharpDirectory.cs index fa484e9..da62fea 100644 --- a/src/components/C4Sharp/FileSystem/C4Directory.cs +++ b/src/components/C4Sharp/FileSystem/C4SharpDirectory.cs @@ -7,7 +7,7 @@ namespace C4Sharp.FileSystem /// /// Manipulate the C4 folder and their resoucers /// - internal static class C4Directory + internal static class C4SharpDirectory { /// /// Default Directory Name @@ -16,7 +16,7 @@ internal static class C4Directory /// /// Default Resource Folder Name /// - public static string ResourcesFolderName => "resources"; + public static string ResourcesFolderName => Path.Join("..", ".c4s"); /// /// Load all C4_Plantuml files diff --git a/src/components/C4Sharp/Models/EnterpriseBoundary.cs b/src/components/C4Sharp/Models/EnterpriseBoundary.cs new file mode 100644 index 0000000..8e120df --- /dev/null +++ b/src/components/C4Sharp/Models/EnterpriseBoundary.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace C4Sharp.Models +{ + public record EnterpriseBoundary(string Alias, string Label) : Structure(Alias, Label) + { + public IEnumerable Structures { get; init; } = Array.Empty(); + } +} \ No newline at end of file diff --git a/src/components/C4Sharp/Models/Plantuml/PlantumlDiagram.cs b/src/components/C4Sharp/Models/Plantuml/PlantumlDiagram.cs index 7e73968..0548c90 100644 --- a/src/components/C4Sharp/Models/Plantuml/PlantumlDiagram.cs +++ b/src/components/C4Sharp/Models/Plantuml/PlantumlDiagram.cs @@ -81,7 +81,7 @@ private static string GetPumlFilePath(this Diagram diagram, bool useUrlInclude) return useUrlInclude ? $"{standardLibraryBaseUrl}/{pumlFileName}" - : Path.Join(C4Directory.ResourcesFolderName, pumlFileName); + : Path.Join(C4SharpDirectory.ResourcesFolderName, pumlFileName); } } } \ No newline at end of file diff --git a/src/components/C4Sharp/Models/Plantuml/PlantumlFile.cs b/src/components/C4Sharp/Models/Plantuml/PlantumlFile.cs index 066c53b..cbffd6f 100644 --- a/src/components/C4Sharp/Models/Plantuml/PlantumlFile.cs +++ b/src/components/C4Sharp/Models/Plantuml/PlantumlFile.cs @@ -11,6 +11,8 @@ namespace C4Sharp.Models.Plantuml /// public static class PlantumlFile { + private static readonly object Lock = new object(); + /// /// It creates a Puml file into the default directory "./c4" /// If the attribute of Session GenerateDiagramImages is true @@ -21,7 +23,7 @@ public static class PlantumlFile public static void Export(this PlantumlSession session, IEnumerable diagrams) { var dirPath = Directory.GetCurrentDirectory(); - var path = Path.Join(dirPath, C4Directory.DirectoryName); + var path = Path.Join(dirPath, C4SharpDirectory.DirectoryName); Export(session, path, diagrams); } @@ -66,9 +68,13 @@ private static void Save(Diagram diagram, string path, PlantumlSession session) { try { - C4Directory.LoadResources(path); - var filePath = Path.Combine(path, $"{diagram.Slug()}.puml"); - File.WriteAllText(filePath, diagram.ToPumlString(session.StandardLibraryBaseUrl)); + lock (Lock) + { + C4SharpDirectory.LoadResources(path); + var filePath = Path.Combine(path, $"{diagram.Slug()}.puml"); + Directory.CreateDirectory(path); + File.WriteAllText(filePath, diagram.ToPumlString(session.StandardLibraryBaseUrl)); + } } catch (Exception e) { diff --git a/src/components/C4Sharp/Models/Plantuml/PlantumlSession.cs b/src/components/C4Sharp/Models/Plantuml/PlantumlSession.cs index a984f52..86da69c 100644 --- a/src/components/C4Sharp/Models/Plantuml/PlantumlSession.cs +++ b/src/components/C4Sharp/Models/Plantuml/PlantumlSession.cs @@ -92,12 +92,12 @@ public PlantumlSession UseDiagramSvgImageBuilder() /// internal void Execute(string path, bool processWholeDirectory, string generatedImageFormat) { - var directory = processWholeDirectory - ? path - : new FileInfo(path)?.Directory?.FullName; - try { + var directory = processWholeDirectory + ? path + : new FileInfo(path)?.Directory?.FullName; + if (string.IsNullOrEmpty(directory)) { throw new PlantumlException($"{nameof(PlantumlException)}: puml file not found."); @@ -134,6 +134,53 @@ private string CalculateJarCommand(bool useStandardLibrary, string generatedImag return $"-jar {FilePath} {resourcesOriginArg} {imageFormatOutputArg} -verbose -o \"{directory}\" -charset UTF-8"; } + /// + /// Using the -pipe option, you can easily use PlantUML in your scripts. + /// With this option, a diagram description is received through standard input and the PNG file is generated to standard output. + /// No file is written on the local file system. + /// + /// puml content + /// + internal (string, Stream) GetStream(string input) + { + try + { + var results = new StringBuilder(); + + var jar = StandardLibraryBaseUrl + ? $"-jar {FilePath} -verbose -charset UTF-8" + : $"-jar {FilePath} -DRELATIVE_INCLUDE=\".\" -verbose -charset UTF-8"; + + var fileName = Guid.NewGuid().ToString("N"); + + ProcessInfo.Arguments = $"{jar} -pipe > {fileName}.png"; + ProcessInfo.RedirectStandardOutput = true; + ProcessInfo.RedirectStandardInput = true; + ProcessInfo.StandardOutputEncoding = Encoding.UTF8; + + var process = new Process { StartInfo = ProcessInfo }; + + process.OutputDataReceived += (p, args) => + { + results.AppendLine(args.Data); + }; + + process.Start(); + process.StandardInput.Write(input); + process.StandardInput.Flush(); + process.StandardInput.Close(); + process.BeginOutputReadLine(); + process.WaitForExit(); + + var buffer = Encoding.UTF8.GetBytes(results.ToString()); + return (fileName, new MemoryStream(buffer)); + } + catch (Exception e) + { + throw new PlantumlException($"{nameof(PlantumlException)}: puml file not found.", e); + } + } + /// /// Clear Plantuml Resource /// diff --git a/src/components/C4Sharp/Models/Plantuml/PlantumlStream.cs b/src/components/C4Sharp/Models/Plantuml/PlantumlStream.cs new file mode 100644 index 0000000..57ce704 --- /dev/null +++ b/src/components/C4Sharp/Models/Plantuml/PlantumlStream.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.IO; +using C4Sharp.Diagrams; + +namespace C4Sharp.Models.Plantuml +{ + public static class PlantumlStream + { + private static readonly object Lock = new object(); + + public static (string, Stream) GetStream(this PlantumlSession session, Diagram diagram) + { + lock (Lock) + { + var puml = diagram.ToPumlString(session.StandardLibraryBaseUrl); + return session.GetStream(puml); + } + } + } +} \ No newline at end of file diff --git a/src/components/C4Sharp/Models/Plantuml/PlantumlStructure.cs b/src/components/C4Sharp/Models/Plantuml/PlantumlStructure.cs index fbb56e6..89561f2 100644 --- a/src/components/C4Sharp/Models/Plantuml/PlantumlStructure.cs +++ b/src/components/C4Sharp/Models/Plantuml/PlantumlStructure.cs @@ -24,6 +24,7 @@ public static string ToPumlString(this Structure structure) Component component => component.ToPumlString(), Container container => container.ToPumlString(), ContainerBoundary containerBoundary => containerBoundary.ToPumlString(), + EnterpriseBoundary enterpriseBoundary => enterpriseBoundary.ToPumlString(), _ => string.Empty }; } @@ -56,7 +57,26 @@ private static string ToPumlString(this SoftwareSystemBoundary boundary) stream.AppendLine("}"); return stream.ToString(); - } + } + + private static string ToPumlString(this EnterpriseBoundary boundary) + { + var stream = new StringBuilder(); + stream.AppendLine(); + stream.AppendLine($"Enterprise_Boundary({boundary.Alias}, \"{boundary.Label}\") {{"); + + foreach (var structure in boundary.Structures) + { + if (structure is (Person or SoftwareSystem or EnterpriseBoundary)) + { + stream.AppendLine($"{SpaceMethods.Indent()}{structure.ToPumlString()}"); + } + } + + stream.AppendLine("}"); + + return stream.ToString(); + } private static string ToPumlString(this Component component) { diff --git a/src/samples/C4Sharp.Sample/Diagrams/EnterpriseDiagramBuilder.cs b/src/samples/C4Sharp.Sample/Diagrams/EnterpriseDiagramBuilder.cs new file mode 100644 index 0000000..054f7e8 --- /dev/null +++ b/src/samples/C4Sharp.Sample/Diagrams/EnterpriseDiagramBuilder.cs @@ -0,0 +1,58 @@ +using C4Sharp.Diagrams.Core; +using C4Sharp.Models; +using C4Sharp.Models.Relationships; +using C4Sharp.Sample.Structures; + +namespace C4Sharp.Sample.Diagrams +{ + using static Position; + using static People; + using static Systems; + + public class EnterpriseDiagramBuilder + { + public static ContextDiagram Build() + { + return new () + { + Title = "System Enterprise diagram for Internet Banking System", + Structures = new Structure[] + { + Customer, + new EnterpriseBoundary("eboundary", "Domain A") + { + Structures = new Structure [] + { + BankingSystem, + new EnterpriseBoundary("eboundary1", "Domain Internal Users") + { + Structures = new Structure [] + { + InternalCustomer, + } + }, + new EnterpriseBoundary("eboundary2", "Domain Managers") + { + Structures = new Structure [] + { + Manager, + } + }, + } + }, + Mainframe, + MailSystem + }, + Relationships = new[] + { + Customer > BankingSystem, + InternalCustomer > BankingSystem, + Manager > BankingSystem, + (Customer < MailSystem)["Sends e-mails to"], + (BankingSystem > MailSystem)["Sends e-mails", "SMTP"][Neighbor], + BankingSystem > Mainframe, + } + }; + } + } +} \ No newline at end of file diff --git a/src/samples/C4Sharp.Sample/Program.cs b/src/samples/C4Sharp.Sample/Program.cs index 4b6c2f6..6a587c6 100644 --- a/src/samples/C4Sharp.Sample/Program.cs +++ b/src/samples/C4Sharp.Sample/Program.cs @@ -1,5 +1,5 @@ -using System; -using C4Sharp.Diagrams; +using C4Sharp.Diagrams; +using C4Sharp.Models; using C4Sharp.Models.Plantuml; using C4Sharp.Sample.Diagrams; @@ -14,11 +14,13 @@ private static void Main(string[] args) ContextDiagramBuilder.Build(), ContainerDiagramBuilder.Build(), ComponentDiagramBuilder.Build(), - DeploymentDiagramBuilder.Build() + DeploymentDiagramBuilder.Build(), + EnterpriseDiagramBuilder.Build(), }; new PlantumlSession() .UseDiagramImageBuilder() + .UseDiagramSvgImageBuilder() .UseStandardLibraryBaseUrl() .Export(diagrams); } diff --git a/src/samples/C4Sharp.Sample/Structures/Containers.cs b/src/samples/C4Sharp.Sample/Structures/Containers.cs index 9315899..7b6961b 100644 --- a/src/samples/C4Sharp.Sample/Structures/Containers.cs +++ b/src/samples/C4Sharp.Sample/Structures/Containers.cs @@ -7,7 +7,7 @@ public static class Containers private static Container _webApp; public static Container WebApp => _webApp ??= new Container( - "WebApp", "WebApp") + "Corporate.Finance.Limits.Service.ServiceBus", "WebApp") { ContainerType = ContainerType.WebApplication, Description = "Delivers the static content and the Internet banking SPA", diff --git a/src/samples/C4Sharp.Sample/Structures/People.cs b/src/samples/C4Sharp.Sample/Structures/People.cs index c495f39..d208a91 100644 --- a/src/samples/C4Sharp.Sample/Structures/People.cs +++ b/src/samples/C4Sharp.Sample/Structures/People.cs @@ -1,4 +1,5 @@ using C4Sharp.Models; +using C4Sharp.Models.Relationships; namespace C4Sharp.Sample.Structures { @@ -8,7 +9,20 @@ public static class People public static Person Customer => _customer ??= new Person("customer", "Personal Banking Customer") { - Description = "A customer of the bank, with personal bank accounts." + Description = "A customer of the bank, with personal bank accounts.", + Boundary = Boundary.External }; + + private static Person _internalCustomer; + public static Person InternalCustomer => _internalCustomer ??= new Person("internalcustomer", "Personal Banking Customer") + { + Description = "An internal customer of the bank, with personal bank accounts." + }; + + private static Person _manager; + public static Person Manager => _manager ??= new Person("manager", "Manager Banking Customer") + { + Description = "A manager of the bank, with personal bank accounts." + }; } } \ No newline at end of file diff --git a/src/samples/C4Sharp.Sample/Structures/Systems.cs b/src/samples/C4Sharp.Sample/Structures/Systems.cs index a831afc..e2262c6 100644 --- a/src/samples/C4Sharp.Sample/Structures/Systems.cs +++ b/src/samples/C4Sharp.Sample/Structures/Systems.cs @@ -11,7 +11,8 @@ public static class Systems "BankingSystem", "Internet Banking System") { - Description = "Allows customers to view information about their bank accounts, and make payments." + Description = "Allows customers to view information about their " + + "bank accounts, and make payments." }; private static SoftwareSystem _mainframe; @@ -20,7 +21,8 @@ public static class Systems "Mainframe", "Mainframe Banking System") { - Description = "Stores all of the core banking information about customers, accounts, transactions, etc.", + Description = "Stores all of the core banking information about customers, " + + "accounts, transactions, etc.", Boundary = Boundary.External }; diff --git a/src/tests/C4Sharp.IntegratedTests/ExportingDiagramFixture.cs b/src/tests/C4Sharp.IntegratedTests/ExportingDiagramFixture.cs index 1da5ef5..a0cad0a 100644 --- a/src/tests/C4Sharp.IntegratedTests/ExportingDiagramFixture.cs +++ b/src/tests/C4Sharp.IntegratedTests/ExportingDiagramFixture.cs @@ -21,11 +21,11 @@ protected static void VerifyIfResourceFilesExists(string path = "c4") { var files = new[] { - Path.Join(path, "resources", "C4.puml"), - Path.Join(path, "resources", "C4_Component.puml"), - Path.Join(path, "resources", "C4_Context.puml"), - Path.Join(path, "resources", "C4_Container.puml"), - Path.Join(path, "resources", "C4_Deployment.puml"), + Path.Join(path, "..", ".c4s", "C4.puml"), + Path.Join(path, "..", ".c4s", "C4_Component.puml"), + Path.Join(path, "..", ".c4s", "C4_Context.puml"), + Path.Join(path, "..", ".c4s", "C4_Container.puml"), + Path.Join(path, "..", ".c4s", "C4_Deployment.puml"), }; VerifyIfFilesExists(files); diff --git a/src/tests/C4Sharp.IntegratedTests/GettingStreamDiagramTests.cs b/src/tests/C4Sharp.IntegratedTests/GettingStreamDiagramTests.cs new file mode 100644 index 0000000..82f8b91 --- /dev/null +++ b/src/tests/C4Sharp.IntegratedTests/GettingStreamDiagramTests.cs @@ -0,0 +1,22 @@ +using C4Sharp.Diagrams; +using C4Sharp.IntegratedTests.Stubs.Diagrams; +using C4Sharp.Models.Plantuml; +using FluentAssertions; +using Xunit; + +namespace C4Sharp.IntegratedTests +{ + public class GettingStreamDiagramTests + { + [Fact] + public void TestGetStream() + { + var diagram = ContextDiagramBuilder.Build() with { Title = "Diagram" }; + + var session = new PlantumlSession(); + var (_, results) = session.GetStream(diagram); + + results.Should().NotBeNull(); + } + } +} \ No newline at end of file diff --git a/src/tests/C4Sharp.UnitTests/C4Sharp.UnitTests.csproj b/src/tests/C4Sharp.UnitTests/C4Sharp.UnitTests.csproj index a14ec47..2c912a6 100644 --- a/src/tests/C4Sharp.UnitTests/C4Sharp.UnitTests.csproj +++ b/src/tests/C4Sharp.UnitTests/C4Sharp.UnitTests.csproj @@ -22,6 +22,7 @@ + diff --git a/src/tests/C4Sharp.UnitTests/Diagrams/DiagramTests.cs b/src/tests/C4Sharp.UnitTests/Diagrams/DiagramTests.cs index 01018e2..dbeab6b 100644 --- a/src/tests/C4Sharp.UnitTests/Diagrams/DiagramTests.cs +++ b/src/tests/C4Sharp.UnitTests/Diagrams/DiagramTests.cs @@ -1,6 +1,10 @@ +using System; using C4Sharp.Diagrams; using C4Sharp.Diagrams.Core; using C4Sharp.Diagrams.Supplementary; +using C4Sharp.Models.Plantuml; +using C4Sharp.Models.Relationships; +using C4Sharp.Sample.Diagrams; using FluentAssertions; using Xunit; @@ -44,7 +48,7 @@ public void TestWhenSlugContainerDiagram(string title) [InlineData("TEST A")] public void TestWhenSlugDeploymentDiagram(string title) { - var diagram = new DeploymentDiagram { Title = title}; + var diagram = new DeploymentDiagram { Title = title }; diagram.Slug().Should().Be("test-a-c4deployment"); }