|
| 1 | +<# |
| 2 | +.Synopsis |
| 3 | + Shows Invoke-Build task graph using Mermaid. |
| 4 | + Copyright (c) Roman Kuzmin |
| 5 | +
|
| 6 | +.Description |
| 7 | + Requirements: |
| 8 | + - Invoke-Build command is available for calls |
| 9 | + - Internet connection for Mermaid, https://mermaid.js.org |
| 10 | +
|
| 11 | + The script calls Invoke-Build in order to get the tasks, generates the HTML |
| 12 | + page with Mermaid graph code and scripts and shows the page in the browser. |
| 13 | +
|
| 14 | + Tasks without code are shown as ovals, conditional tasks as hexagons, other |
| 15 | + tasks as boxes. Safe calls are shown with dotted arrows, regular calls with |
| 16 | + solid arrows. Task synopses are shown at the bottom left corner on mouse |
| 17 | + hovering over tasks. Job numbers are optionally shown on arrows. |
| 18 | +
|
| 19 | +.Parameter File |
| 20 | + See: help Invoke-Build -Parameter File |
| 21 | +
|
| 22 | +.Parameter Output |
| 23 | + Specifies the output HTML file. |
| 24 | + The default is in the temp directory. |
| 25 | +
|
| 26 | +.Parameter Direction |
| 27 | + Specifies the direction, Top-Bottom or Left-Right: TB, BT, LR, RL. |
| 28 | + The default is LR. |
| 29 | +
|
| 30 | +.Parameter Directive |
| 31 | + Specifies the directive text, see Mermaid manuals. |
| 32 | + Example: %%{init: {"theme": "forest", "fontFamily": "monospace"}}%% |
| 33 | +
|
| 34 | +.Parameter Parameters |
| 35 | + Build script parameters needed in special cases when they alter tasks. |
| 36 | +
|
| 37 | +.Parameter NoShow |
| 38 | + Tells to create the output file without showing it. |
| 39 | + Use Output in order to specify the file exactly. |
| 40 | +
|
| 41 | +.Parameter Number |
| 42 | + Tells to show job numbers on arrows connecting tasks. |
| 43 | +
|
| 44 | +.Link |
| 45 | + https://github.com/nightroman/Invoke-Build |
| 46 | +#> |
| 47 | + |
| 48 | +param( |
| 49 | + [Parameter(Position = 0)] |
| 50 | + [string]$File |
| 51 | + , |
| 52 | + [Parameter(Position = 1)] |
| 53 | + [string]$Output |
| 54 | + , |
| 55 | + [ValidateSet('TB', 'BT', 'LR', 'RL')] |
| 56 | + [string]$Direction = 'LR' |
| 57 | + , |
| 58 | + [string]$Directive |
| 59 | + , |
| 60 | + [hashtable]$Parameters |
| 61 | + , |
| 62 | + [switch]$NoShow |
| 63 | + , |
| 64 | + [switch]$Number |
| 65 | +) |
| 66 | + |
| 67 | +$ErrorActionPreference = 1 |
| 68 | + |
| 69 | +function Escape-Text($Text) { |
| 70 | + $Text.Replace('"', '#quot;').Replace('<', '#lt;').Replace('>', '#gt;') |
| 71 | +} |
| 72 | + |
| 73 | +### resolve output |
| 74 | +if ($Output) { |
| 75 | + $Output = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Output) |
| 76 | +} else { |
| 77 | + $path = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($(if ($File) { $File } else { '' })) |
| 78 | + $name = [System.IO.Path]::GetFileNameWithoutExtension($path) |
| 79 | + $hash = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName($path)) |
| 80 | + $Output = [System.IO.Path]::GetTempPath() + "$name-$hash.html" |
| 81 | +} |
| 82 | + |
| 83 | +### get tasks |
| 84 | +if (!$Parameters) { $Parameters = @{} } |
| 85 | +$all = Invoke-Build ?? $File @Parameters |
| 86 | + |
| 87 | +### for synopses |
| 88 | +$docs = @{} |
| 89 | +. Invoke-Build |
| 90 | + |
| 91 | +### make text |
| 92 | +$map = @{} |
| 93 | +$text = @( |
| 94 | + if ($Directive) { $Directive } |
| 95 | + "graph $($Direction.ToUpper())" |
| 96 | + |
| 97 | + $id = 0 |
| 98 | + foreach ($it in $all.get_Values()) { |
| 99 | + ++$id |
| 100 | + $name = $it.Name |
| 101 | + $map[$name] = $id |
| 102 | + $name = Escape-Text $name |
| 103 | + $hasScript = foreach ($job in $it.Jobs) { if ($job -is [scriptblock]) { 1; break } } |
| 104 | + if (!$hasScript) { |
| 105 | + "$id([`"$name`"])" |
| 106 | + } elseif ($it.Inputs -or !(-9).Equals($it.If)) { |
| 107 | + "$id{{`"$name`"}}" |
| 108 | + } else { |
| 109 | + "$id[`"$name`"]" |
| 110 | + } |
| 111 | + |
| 112 | + if ($synopsis = Get-BuildSynopsis $it $docs) { |
| 113 | + $synopsis = Escape-Text $synopsis |
| 114 | + "click $id callback `"$synopsis`"" |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + $id = 0 |
| 119 | + foreach ($it in $all.get_Values()) { |
| 120 | + ++$id |
| 121 | + $jobNumber = 0 |
| 122 | + foreach ($job in $it.Jobs) { |
| 123 | + ++$jobNumber |
| 124 | + if ($job -is [string]) { |
| 125 | + $job, $safe = if ($job[0] -eq '?') { $job.Substring(1), 1 } else { $job } |
| 126 | + $id2 = $map[$job] |
| 127 | + $arrow = if ($safe) { '-.->' } else { '-->' } |
| 128 | + $text = if ($Number) { "|$jobNumber|" } else { '' } |
| 129 | + '{0} {1} {2} {3}' -f $id, $arrow, $text, $id2 |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | +) |
| 134 | + |
| 135 | +### write HTML |
| 136 | +@( |
| 137 | + @" |
| 138 | +<!DOCTYPE html> |
| 139 | +<html lang="en"> |
| 140 | +<head> |
| 141 | +<meta charset="utf-8"> |
| 142 | +<title>$([System.IO.Path]::GetFileNameWithoutExtension($Output)) tasks</title> |
| 143 | +</head> |
| 144 | +<body> |
| 145 | +<pre class="mermaid"> |
| 146 | +"@ |
| 147 | + |
| 148 | + $text |
| 149 | + |
| 150 | + @' |
| 151 | +</pre> |
| 152 | +<script type="module"> |
| 153 | + import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; |
| 154 | + mermaid.initialize({ startOnLoad: true }); |
| 155 | +</script> |
| 156 | +</body> |
| 157 | +</html> |
| 158 | +'@ |
| 159 | +) | Set-Content -LiteralPath $Output -Encoding UTF8 |
| 160 | + |
| 161 | +### show file |
| 162 | +if (!$NoShow) { |
| 163 | + Invoke-Item -LiteralPath $Output |
| 164 | +} |
0 commit comments