diff --git a/Architecture/README.md b/Architecture/README.md index 760c393..d346c76 100644 --- a/Architecture/README.md +++ b/Architecture/README.md @@ -1 +1,124 @@ -[//]: # (TODO Introduce docs for generating Sequence Diagrams from code) \ No newline at end of file +# Synergy.Architecture nuget packages + +Here is the documentation of the `Synergy.Architecture.Diagrams` and `Synergy.Architecture.Annotations` nuget packages. +They were created to help developers to add sequence diagrams from code. +It is based on the idea of *Diagrams as Code* + +Find packages on nuget: +- [Synergy.Architecture.Diagrams](https://www.nuget.org/packages/Synergy.Architecture.Diagrams/) +- [Synergy.Architecture.Annotations](https://www.nuget.org/packages/Synergy.Architecture.Annotations/) + +## Enlisting Public API + +To enlist public API, we use the following tool: + +```csharp +using Synergy.Architecture.Annotations.Diagrams.Sequence; +using Synergy.Architecture.Diagrams.Documentation; +using Synergy.Architecture.Diagrams.Sequence; +using Synergy.Documentation.Annotations; +using Synergy.Documentation.Code; +using static Synergy.Architecture.Annotations.Diagrams.Sequence.SequenceDiagramGroupType; + +namespace Synergy.Architecture.Tests.Samples; + +[CodeFilePath] +public class SequenceDiagramSamples +{ + public static CodeFile SequenceDiagrams => CodeFolder.Current() + .File($"{nameof(SequenceDiagramSamples)}.md"); + + [Fact] + public async Task Sequence() + { + var blueprint = TechnicalBlueprint + .Titled("Sequence diagrams samples") + .Add(this.IfElseDiagrams()) + .Add(this.LoopAfterLoopDiagram()) + .Add(this.DatabaseDiagrams()) + .Add(this.OverrideMessageAndResultDiagrams()) + ; + + await File.WriteAllTextAsync(SequenceDiagrams.FilePath, blueprint.Render()); + } + + private IEnumerable IfElseDiagrams() + { + yield return SequenceDiagram + .From(new SequenceDiagramActor("Some Actor", Note: "very hand some")) + .Calling(c => c.IfElse()) + .Footer("This diagram shows if-else." + ); + } + + [SequenceDiagramExternalCall("Chrome", "https://www.google.com", Group = SequenceDiagramGroupType.Alt, GroupHeader = "when google is available")] + [SequenceDiagramExternalCall("Firefox", "https://www.foogle.com", Group = SequenceDiagramGroupType.Alt, GroupHeader = "when google is available")] + [SequenceDiagramExternalCall("Firefox", "https://www.google.com", Group = SequenceDiagramGroupType.Else, GroupHeader = "otherwise")] + private void IfElse() + { + } + + private IEnumerable LoopAfterLoopDiagram() + { + yield return SequenceDiagram + .From(new SequenceDiagramActor("Upset Actor", Note: "very upset", Colour: "red")) + .Calling(c => c.LoopAfterLoop()) + .Footer("This diagram shows loop placed after another loop." + ); + } + + [SequenceDiagramExternalCall("Chrome", "https://www.google.com", Group = Loop, GroupHeader = "Looping until something happens")] + [SequenceDiagramExternalCall("Firefox", "https://www.foogle.com", Group = Loop, GroupHeader = "Looping until something happens")] + [SequenceDiagramExternalCall("Firefox", "https://www.google.com", Group = Loop, GroupHeader = "This should be different loop")] + private void LoopAfterLoop() + { + } + + private IEnumerable DatabaseDiagrams() + { + yield return SequenceDiagram + .From(new SequenceDiagramActor("Some\\nactor", Archetype:SequenceDiagramArchetype.Participant)) + .Calling(c => c.Upsert()) + .Footer("This diagram shows standard database operations." + ); + } + + [SequenceDiagramDatabaseCall($"SELECT * FROM [Item] WHERE [Id] = @itemId")] + [SequenceDiagramDatabaseCall($"INSERT INTO [Item] VALUES ...", Group = Alt, GroupHeader = "if item does not exist yet")] + [SequenceDiagramDatabaseCall($"UPDATE [Item] SET ... WHERE [Id] = @itemId", Group = Else, GroupHeader = "else")] + private void Upsert() + { + } + + private IEnumerable OverrideMessageAndResultDiagrams() + { + yield return SequenceDiagram + .From(new SequenceDiagramActor("Some\\nactor", Archetype:SequenceDiagramArchetype.Control)) + .Calling(c => c.OverrideMessageAndResult()) + .Footer("This diagram shows how to override message and result for ordinary [SequenceDiagramCall]." + ); + } + + [SequenceDiagramCall(typeof(Helper), nameof(Helper.SomeStaticMethod), + Message = "GET https://www.google.com", + Result = "200 OK")] + private void OverrideMessageAndResult() + { + } +} + +internal class Helper +{ + public static void SomeStaticMethod() + { + } +} +``` + +For sample code, please check: [SequenceDiagramSamples.cs](Synergy.Architecture.Tests/Samples/SequenceDiagramSamples.cs) + +To see the results, please check: [SequenceDiagramSamples.md](Synergy.Architecture.Tests/Samples/SequenceDiagramSamples.md) + +**Note:** + + diff --git a/Architecture/Synergy.Architecture.Annotations/Synergy.Architecture.Annotations.csproj b/Architecture/Synergy.Architecture.Annotations/Synergy.Architecture.Annotations.csproj index ddb1934..fc0b534 100644 --- a/Architecture/Synergy.Architecture.Annotations/Synergy.Architecture.Annotations.csproj +++ b/Architecture/Synergy.Architecture.Annotations/Synergy.Architecture.Annotations.csproj @@ -11,7 +11,7 @@ license.txt Architecture testing annotations Copyright © Synergy Marcin Celej 2023 - https://github.com/synergy-software/synergy.framework + https://github.com/synergy-software/synergy.framework/tree/master/Architecture/README.md synergy.png Diagrams as Code $(Version) diff --git a/Architecture/Synergy.Architecture.Diagrams/Synergy.Architecture.Diagrams.csproj b/Architecture/Synergy.Architecture.Diagrams/Synergy.Architecture.Diagrams.csproj index b681099..8bc1c7b 100644 --- a/Architecture/Synergy.Architecture.Diagrams/Synergy.Architecture.Diagrams.csproj +++ b/Architecture/Synergy.Architecture.Diagrams/Synergy.Architecture.Diagrams.csproj @@ -11,7 +11,7 @@ license.txt Behaviour Driven Development support Copyright © Synergy Marcin Celej 2023 - https://github.com/synergy-software/synergy.framework + https://github.com/synergy-software/synergy.framework/tree/master/Architecture/README.md synergy.png BDD Behaviour Driven Development $(Version) diff --git a/Architecture/Synergy.Architecture.Tests/Architecture/Debt/Todos.Technical.Debt.verified.md b/Architecture/Synergy.Architecture.Tests/Architecture/Debt/Todos.Technical.Debt.verified.md index 2015704..2990eca 100644 --- a/Architecture/Synergy.Architecture.Tests/Architecture/Debt/Todos.Technical.Debt.verified.md +++ b/Architecture/Synergy.Architecture.Tests/Architecture/Debt/Todos.Technical.Debt.verified.md @@ -1,6 +1,6 @@ # Technical Debt for Synergy.Contracts -Total: 10 +Total: 11 ## [SequenceDiagramDeactivationAttribute.cs](../../../Synergy.Architecture.Annotations/Diagrams/Sequence/SequenceDiagramDeactivationAttribute.cs) - TODO: Marcin Celej [from: Marcin Celej on: 21-05-2023]: Use this attribute in some sample @@ -25,3 +25,6 @@ Total: 10 - TODO: Marcin Celej [from: Marcin Celej on: 13-07-2023]: add configuration setting to: autoactivate on - TODO: Marcin Celej [from: Marcin Celej on: 06-07-2023]: Allow to inline self call - TODO: Marcin Celej [from: Marcin Celej on: 06-07-2023]: Add configuration setting to generate only 'new' word here without exact ctor mentioned here - to simplify diagram + +## [README.Generate.cs](../../Docs/README.Generate.cs) +- TODO: Marcin Celej [from: Marcin Celej on: 09-01-2024]: Extract this to a separate project Synergy.Documentation.Docs with all needed samples inside diff --git a/Architecture/Synergy.Architecture.Tests/Docs/README.Generate.cs b/Architecture/Synergy.Architecture.Tests/Docs/README.Generate.cs new file mode 100644 index 0000000..9e1148d --- /dev/null +++ b/Architecture/Synergy.Architecture.Tests/Docs/README.Generate.cs @@ -0,0 +1,25 @@ +using Synergy.Architecture.Tests.Samples; +using Synergy.Documentation.Code; +using Synergy.Documentation.Markup; + +namespace Synergy.Architecture.Tests.Docs; + +// TODO: Marcin Celej [from: Marcin Celej on: 09-01-2024]: Extract this to a separate project Synergy.Documentation.Docs with all needed samples inside + +partial class README +{ + private static readonly CodeFile readmeFile = CodeFolder.Current().Up(2).File($"{nameof(README)}.md"); + + private CodeFile SampleSequenceDiagramsFile => CodeFile.For(); + private Markdown.Link SampleSequenceDiagramsLink => Markdown.Link.To(this.SampleSequenceDiagramsFile).RelativeFrom(readmeFile); + + private CodeFile SequenceDiagramsFile => SequenceDiagramSamples.SequenceDiagrams; + private Markdown.Link SequenceDiagramsLink => Markdown.Link.To(this.SequenceDiagramsFile).RelativeFrom(readmeFile); + + [Fact] + public void Generate() + { + var content = this.TransformText(); + File.WriteAllText(readmeFile.FilePath, content); + } +} \ No newline at end of file diff --git a/Architecture/Synergy.Architecture.Tests/Docs/README.cs b/Architecture/Synergy.Architecture.Tests/Docs/README.cs new file mode 100644 index 0000000..a6fad7b --- /dev/null +++ b/Architecture/Synergy.Architecture.Tests/Docs/README.cs @@ -0,0 +1,328 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 16.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Synergy.Architecture.Tests.Docs +{ + using System; + + /// + /// Class to produce the template output + /// + + #line 1 "C:\Projects\Synergy\framework\src\Architecture\Synergy.Architecture.Tests\Docs\README.tt" + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public partial class README : READMEBase + { +#line hidden + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("# Synergy.Architecture nuget packages\r\n\r\nHere is the documentation of the `Synergy.Architecture.Diagrams` and `Synergy.Architecture.Annotations` nuget packages.\r\nThey were created to help developers to add sequence diagrams from code.\r\nIt is based on the idea of *Diagrams as Code*\r\n \r\nFind packages on nuget: \r\n- [Synergy.Architecture.Diagrams](https://www.nuget.org/packages/Synergy.Architecture.Diagrams/)\r\n- [Synergy.Architecture.Annotations](https://www.nuget.org/packages/Synergy.Architecture.Annotations/)\r\n\r\n## Enlisting Public API\r\n\r\nTo enlist public API, we use the following tool:\r\n\r\n```csharp\r\n"); + + #line 18 "C:\Projects\Synergy\framework\src\Architecture\Synergy.Architecture.Tests\Docs\README.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.SampleSequenceDiagramsFile.ReadAllText())); + + #line default + #line hidden + this.Write("\r\n```\r\n\r\nFor sample code, please check: "); + + #line 21 "C:\Projects\Synergy\framework\src\Architecture\Synergy.Architecture.Tests\Docs\README.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.SampleSequenceDiagramsLink)); + + #line default + #line hidden + this.Write("\r\n\r\nTo see the results, please check: "); + + #line 23 "C:\Projects\Synergy\framework\src\Architecture\Synergy.Architecture.Tests\Docs\README.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.SequenceDiagramsLink)); + + #line default + #line hidden + this.Write("\r\n\r\n**Note:**\r\n\r\n\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + + #line default + #line hidden + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] + public class READMEBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Architecture/Synergy.Architecture.Tests/Docs/README.tt b/Architecture/Synergy.Architecture.Tests/Docs/README.tt new file mode 100644 index 0000000..3dbe557 --- /dev/null +++ b/Architecture/Synergy.Architecture.Tests/Docs/README.tt @@ -0,0 +1,27 @@ +<#@ template language="C#" #> +<#@output extension=".md"#> +# Synergy.Architecture nuget packages + +Here is the documentation of the `Synergy.Architecture.Diagrams` and `Synergy.Architecture.Annotations` nuget packages. +They were created to help developers to add sequence diagrams from code. +It is based on the idea of *Diagrams as Code* + +Find packages on nuget: +- [Synergy.Architecture.Diagrams](https://www.nuget.org/packages/Synergy.Architecture.Diagrams/) +- [Synergy.Architecture.Annotations](https://www.nuget.org/packages/Synergy.Architecture.Annotations/) + +## Enlisting Public API + +To enlist public API, we use the following tool: + +```csharp +<#= this.SampleSequenceDiagramsFile.ReadAllText() #> +``` + +For sample code, please check: <#= this.SampleSequenceDiagramsLink #> + +To see the results, please check: <#= this.SequenceDiagramsLink #> + +**Note:** + + diff --git a/Architecture/Synergy.Architecture.Tests/Samples/SequenceDiagramSamples.cs b/Architecture/Synergy.Architecture.Tests/Samples/SequenceDiagramSamples.cs index 43d2d25..d78d395 100644 --- a/Architecture/Synergy.Architecture.Tests/Samples/SequenceDiagramSamples.cs +++ b/Architecture/Synergy.Architecture.Tests/Samples/SequenceDiagramSamples.cs @@ -1,11 +1,13 @@ using Synergy.Architecture.Annotations.Diagrams.Sequence; using Synergy.Architecture.Diagrams.Documentation; using Synergy.Architecture.Diagrams.Sequence; +using Synergy.Documentation.Annotations; using Synergy.Documentation.Code; using static Synergy.Architecture.Annotations.Diagrams.Sequence.SequenceDiagramGroupType; namespace Synergy.Architecture.Tests.Samples; +[CodeFilePath] public class SequenceDiagramSamples { public static CodeFile SequenceDiagrams => CodeFolder.Current() diff --git a/Architecture/Synergy.Architecture.Tests/Synergy.Architecture.Tests.csproj b/Architecture/Synergy.Architecture.Tests/Synergy.Architecture.Tests.csproj index 94aa3a4..eaacbd5 100644 --- a/Architecture/Synergy.Architecture.Tests/Synergy.Architecture.Tests.csproj +++ b/Architecture/Synergy.Architecture.Tests/Synergy.Architecture.Tests.csproj @@ -8,11 +8,11 @@ - TRACE;CODE_ANALYSIS + TRACE;CODE_ANALYSIS;DOCUMENTATION - TRACE;CODE_ANALYSIS + TRACE;CODE_ANALYSIS;DOCUMENTATION @@ -34,6 +34,10 @@ Api Api.cs + + TextTemplatingFilePreprocessor + README.cs + @@ -41,4 +45,15 @@ + + + README.tt + + + True + True + README.tt + + +