1+ <#
2+ . SYNOPSIS
3+ Views Help as html
4+ . DESCRIPTION
5+ Views the help for Commands as html.
6+ . NOTES
7+ This _can_ -InvokeExample.
8+
9+ You should not -InvokeExamples of commands you do not know and trust will output as html.
10+ #>
11+ param (
12+ # The command
13+ [PSObject ]
14+ $Command ,
15+
16+ # If set, will invoke examples.
17+ # This can be as dangerous as your examples.
18+ [switch ]
19+ $InvokeExample ,
20+
21+ # If set, will not show the command name
22+ [switch ]
23+ $HideName
24+ )
25+
26+ # Try to get command help
27+ $CommandHelp = Get-Help - Name $Command - ErrorAction Ignore
28+ # If we cannot get help, return
29+ if (-not $CommandHelp ) { return }
30+
31+ # If the help was a string
32+ if ($CommandHelp -is [string ]) {
33+ # return preformatted text.
34+ return " <pre>$ ( [Web.HttpUtility ]::HtmlEncode($CommandHelp )) </pre>"
35+ }
36+
37+ # Get the core parts of help we need.
38+ $synopsis = $CommandHelp.Synopsis
39+ $description = $CommandHelp.description.text -join [Environment ]::NewLine
40+ $notes = $CommandHelp.alertSet.alert.text -join [Environment ]::NewLine
41+
42+ filter looksLikeMarkdown {
43+ if (
44+ # If it's got a markdown-like link
45+ $_ -match ' \[[^\]]+\]\(' -or
46+ # Or any of the lines start with markdown special characters
47+ $_ -split ' (?>\r\n|\n)' -match ' \s{0,3}[\#*~`]'
48+ ) {
49+ # it's probably markdown
50+ $_
51+ }
52+ }
53+
54+ filter looksLikeTags {
55+ if ($_ -match ' ^\s{0,}<' ) {
56+ $_
57+ }
58+ }
59+
60+ # Display the command name, synopsis, and description
61+ if (-not $HideName ) {
62+ " <h1>$ ( [Web.HttpUtility ]::HtmlEncode(
63+ $CommandHelp.Name -replace ' (?:.+?/){0,}' -replace ' \.ps1$' -replace ' \..+?$'
64+ )) </h1>"
65+ }
66+
67+ if ($synopsis ) {
68+ if ($synopsis | looksLikeMarkdown) {
69+ (ConvertFrom-Markdown - InputObject $synopsis ).Html
70+ }
71+ elseif ($synopsis | looksLikeTags) {
72+ $synopsis
73+ } else {
74+ if ($page -is [Collections.IDictionary ] -and -not $page.Title ) {
75+ $page.Title = $ ([Web.HttpUtility ]::HtmlEncode($synopsis ))
76+ }
77+ " <h2>$ ( [Web.HttpUtility ]::HtmlEncode($synopsis )) </h2>"
78+ }
79+ }
80+
81+ if ($description ) {
82+ if ($description | lookslikeMarkdown) {
83+ $markdown = (ConvertFrom-Markdown - InputObject $description ).Html
84+ if ($page -is [Collections.IDictionary ] -and -not $page.Description ) {
85+ $xmarkdown = " <xhtml>$markdown </xhtml>"
86+ $page.Description = $xmarkdown.xhtml.innerText
87+ }
88+
89+ }
90+ elseif ($description | looksLikeTags) {
91+ $description
92+ }
93+ else {
94+ if ($page -is [Collections.IDictionary ] -and -not $page.Description ) {
95+ $page.Description = $description
96+ }
97+ " <h3>$ ( [Web.HttpUtility ]::HtmlEncode($description )) </h3>"
98+ }
99+ }
100+
101+ if ($notes ) {
102+ # If there were notes, convert them from markdown
103+ (ConvertFrom-Markdown - InputObject $notes ).Html
104+ }
105+
106+ # region Grid Styles
107+ " <style>"
108+ " .example-grid {
109+ width: 90vw;
110+ margin-left: 5vw;
111+ margin-right: 5vw;
112+ text-align: center;
113+ }"
114+ " .example {
115+ text-align: center;
116+ }"
117+ " .example-code {
118+ text-align: center;
119+ font-size: .9rem;
120+ margin-left: auto;
121+ margin-right: auto;
122+ }"
123+
124+ " code { text-align: left}"
125+
126+ " .example-menu { text-align: right; margin-top: -.5rem; margin-bottom: -.5rem }"
127+ " .copy-button { float: right; }"
128+
129+ " .example-outputs {
130+ display: flex;
131+ flex-direction: column;
132+ align-items: center;
133+ justify-content: center;
134+ gap: 1rem;
135+ margin: 0.8rem;
136+ padding: 0.2rem;
137+ width: 90vw;
138+ }"
139+ " .example-output { text-align: center }"
140+ "
141+ pre {
142+ position: relative;
143+ }
144+ "
145+ " </style>"
146+ # endregion Grid Styles
147+
148+ $exampleCount = @ ($CommandHelp.examples.example ).Length
149+ $progress = [Ordered ]@ {id = Get-Random }
150+ $progress.Activity = " $ ( $command ) examples"
151+ # Create a grid for examples
152+ " <div class='example-grid'>"
153+ $exampleNumber = 0
154+ # Walk over each example
155+ foreach ($example in $CommandHelp.examples.example ) {
156+ $exampleNumber ++
157+
158+ # Combine the code and remarks
159+ $exampleLines =
160+ @ (
161+ $example.Code
162+ foreach ($remark in $example.Remarks.text ) {
163+ if (-not $remark ) { continue }
164+ $remark
165+ }
166+ ) -join ([Environment ]::NewLine) -split ' (?>\r\n|\n)' # and split into lines
167+
168+ # Anything until the first non-comment line is a markdown predicate to the example
169+ $nonCommentLine = $false
170+ $markdownLines = @ ()
171+
172+ $progress.PercentComplete = $exampleNumber * 100 / $exampleCount
173+ $progress.Activity = " $ ( $command ) examples $exampleNumber "
174+ $progress.Status = " $ ( $exampleLines [0 ]) "
175+ Write-Progress @progress
176+
177+ # Go thru each line in the example as part of a loop
178+ $codeBlock = @ (foreach ($exampleLine in $exampleLines ) {
179+ # Any comments until the first uncommentedLine are markdown
180+ if ($exampleLine -match ' ^\#' -and -not $nonCommentLine ) {
181+ $markdownLines += $exampleLine -replace ' ^\#' -replace ' ^\s+'
182+ } else {
183+ $nonCommentLine = $true
184+ $exampleLine
185+ }
186+ }) -join [Environment ]::NewLine
187+
188+ # Join all of our markdown lines together
189+ $Markdown = $markdownLines -join [Environment ]::NewLine
190+
191+ # and start our example div
192+ " <div class='example'>"
193+ # If we had markdown, output it
194+ if ($markdown ) {
195+ (ConvertFrom-Markdown - InputObject $Markdown ).Html
196+ }
197+ # followed by our sample code
198+ " <div class='example-code'>"
199+ " <pre>"
200+ " <code class='language-powershell'>" +
201+ [Web.HttpUtility ]::HtmlEncode($codeBlock ) +
202+ " </code>"
203+ " </pre>"
204+ " </div>"
205+
206+ # If we do not want to invoke examples, we can continue to the next example.
207+ if (-not $InvokeExample ) {
208+ " </div>"
209+ " <hr/>"
210+ continue
211+ }
212+ # Otherwise, try to make our example a script block
213+ $exampleCode =
214+ try {
215+ [scriptblock ]::Create($codeBlock )
216+ } catch {
217+ Write-Warning " Unable to convert $ ( $example.code ) to a script"
218+ continue
219+ }
220+
221+ if (-not $global :ExampleOutputCache ) {
222+ $global :ExampleOutputCache = [Ordered ]@ {}
223+ }
224+ if (-not $global :ExampleOutputCache [$codeBlock ]) {
225+ $global :ExampleOutputCache [$codeBlock ] = @ (. $exampleCode )
226+ }
227+ # then run it and capture the output
228+ $exampleOutputs = $global :ExampleOutputCache [$codeBlock ]
229+
230+ # Keep track of our example output count
231+ $exampleOutputNumber = 0
232+ # and start walking thru the list
233+ " <div class='example-outputs'>"
234+ foreach ($exampleOutput in $exampleOutputs ) {
235+ $exampleOutputNumber ++
236+ # Each output goes in a div
237+ " <div class='example-output'>"
238+ # if the output was a file
239+ if ($exampleOutput -is [IO.FileInfo ]) {
240+ # and that file is SVG
241+ if ($exampleOutput.Extension -eq ' .svg' ) {
242+ # include it inline.
243+ Get-Content $exampleOutput.FullName - Raw
244+ }
245+ } else {
246+ # If the output was a turtle object
247+ if ($exampleOutput.pstypenames -contains ' Turtle' ) {
248+ # set it's ID
249+ $exampleOutput.ID = " $ ( $Command ) -Example-$exampleCounter "
250+ if ($exampleOutputs.Length -gt 1 ) {
251+ # If we have more than out output,
252+ # attach our example counter
253+ $exampleOutput.ID += " -$ ( $exampleOutputNumber ) "
254+ }
255+ }
256+ # Include our example output inline
257+ if ($exampleOutput.ToHtml.Invoke ) {
258+ $exampleOutput.ToHtml ()
259+ } else {
260+ " $exampleOutput "
261+ }
262+ }
263+ " </div>"
264+ }
265+ " </div>"
266+ " <hr/>"
267+ " </div>"
268+ }
269+ " </div>"
270+
271+ $progress.Remove (' PercentComplete' )
272+ $progress [' Completed' ] = $true
273+ Write-Progress @progress
274+
275+ @"
276+ <script>
277+ document.querySelectorAll('pre > code').forEach(element => {
278+ const copyCodeButton = document.createElement('div')
279+ copyCodeButton.classList.add('copy-button')
280+ copyCodeButton.onclick = () => navigator.clipboard.writeText(element.innerText)
281+ copyCodeButton.innerHTML = `` $ (
282+ Format-OpenPackage - View FeatherIcon.svg - Option @ {icon = ' clipboard' }
283+ ) ``
284+ element.parentNode.prepend(copyCodeButton)
285+ });
286+ </script>
287+ "@
0 commit comments