Commands/FormattingExtended/Out-Mermaid.ps1
function Out-Mermaid { <# .SYNOPSIS Outputs Mermaid .DESCRIPTION Outputs Mermaid Diagrams. .NOTES This will also attempt to transform non-string objects into the appropriate input for a diagram. .EXAMPLE Out-Mermaid "flowchart TD Start --> Stop" .EXAMPLE [Ordered]@{ title="Pets Adopted By Owners" Dogs=386 Cats=85 Rats=15 } | Out-Mermaid pie .EXAMPLE [Ordered]@{ title = "A Gantt Diagram" dateFormat = "YYYY-MM-DD" "section Section" = [Ordered]@{ "A task" = "a1,2014-01-01,30d" "Another Task" = "after a1, 20d" } "section Another" = [Ordered]@{ "Task in Another" = "2014-01-12",'12d' "Another Task" = "24d" } } | Out-Mermaid -Diagram "gantt" .EXAMPLE ' Alice->>John: Hello John, how are you? John-->>Alice: Great! Alice-)John: See you later! '| Out-Mermaid -Diagram sequenceDiagram #> [Management.Automation.Cmdlet("Format","Object")] [CmdletBinding(PositionalBinding=$false)] param( # The mermaid diagram. [Parameter(Position=0)] [string] $Diagram, # Any input to the diagram. # This will be appended to the diagram definition. # This tries to serialize properties and arrays into their appropriate format in Mermaid. # Strings will be included inine. Keys/values will be joined with either spaces or colons (depending on depth and type). [Parameter(ValueFromPipeline)] $InputObject, # If set, will include the Mermaid diagram within a `pre` element with the css class `mermaid`. [switch] $AsHtml, # If set, will not include either a markdown code fence or an HTML control around the Mermaid. [Alias('Sparse','Bare')] [switch] $Raw, # The String Value Separator (the value that separates a key from it's value). # If set, this will override any presumptions Out-Mermaid might make. [string] $StringValueSeparator = '' ) begin { $accumulateInput = [Collections.Queue]::new() $recursiveDepth = 0 foreach ($callstack in Get-PSCallStack) { if ($callstack.InvocationInfo.MyCommand.ScriptBlock -eq $MyInvocation.MyCommand.ScriptBlock) { $recursiveDepth++ } } $mySelf = $MyInvocation.MyCommand.ScriptBlock } process { if ($InputObject) { if ($InputObject -is [Collections.IDictionary]) { $accumulateInput.Enqueue([PSCustomObject]$InputObject) } else { $accumulateInput.Enqueue($InputObject) } } } end { if ($accumulateInput.Count) { if ($Diagram) { $Diagram += " " } $Diagram += @(foreach ($acc in $accumulateInput.ToArray()) { if ($acc -is [string]) { # Include strings as is $acc ' ' # with a space after } else { # Otherwise, we're producing a series of nested elements $propList = @($acc.psobject.properties) for ($propIndex = 0 ;$propIndex -lt $propList.Length; $propIndex++) { $prop = $propList[$propIndex] if ($prop.Value -is [array]) { if ($prop.Value -as [double[]]) { $prop.Name + ' ' + '[' + ($prop.Value -join ',') + ']' } elseif ($( :AllInnerValuesAreStrings do { foreach ($innerValue in $prop.Value) { if ($innerValue -isnot [string]) { $false; break AllInnerValuesAreStrings } } $true } while ($false) )) { $prop.Name + ':' + ($prop.Value -join ', ') } else { } } elseif ( # If the values are not strings $prop.Value -isnot [string] -and $prop.Value.GetType -and $prop.Value.GetType().IsPrimitive # (and are primitive types) ) { # Then we want to quote the name and put the value after a colon. "`"$($prop.Name)`"" + ':' + $prop.Value } elseif ($prop.Value -is [string]) { # Otherwise, we want to include the name and the value. if ($StringValueSeparator) { $prop.Name + $StringValueSeparator + $prop.Value } elseif ($recursiveDepth -eq 1) { $prop.Name + ' ' + $prop.Value } else { $prop.Name + ':' + $prop.Value } } else { $prop.Name [Environment]::NewLine (' ' * 4 * ($recursiveDepth + 1)) & $mySelf -Raw -InputObject $prop.Value } [Environment]::NewLine if ($propIndex + 1 -lt $propList.Length) { (' ' * 4 * $recursiveDepth) } } } }) -join '' } if ($Diagram) { if ($raw) { $Diagram } elseif ($AsHtml) { @('<pre class="mermaid">' $Diagram '</pre>') -join [Environment]::NewLine } else { @('```mermaid' $Diagram '```') -join [Environment]::NewLine } } } } |