Skip to content

Commit 8898a35

Browse files
StartAutomatingStartAutomating
authored andcommitted
feat: OpenPackage.View.Help.html ( Fixes #205 )
1 parent 5ba7473 commit 8898a35

1 file changed

Lines changed: 292 additions & 0 deletions

File tree

OP.types.ps1xml

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8237,6 +8237,298 @@ $script:FeatherIconCache[$icon].OuterXml
82378237

82388238
</Script>
82398239
</ScriptMethod>
8240+
<ScriptMethod>
8241+
<Name>Help.html</Name>
8242+
<Script>
8243+
&lt;#
8244+
.SYNOPSIS
8245+
Views Help as html
8246+
.DESCRIPTION
8247+
Views the help for Commands as html.
8248+
.NOTES
8249+
This _can_ -InvokeExample.
8250+
8251+
You should not -InvokeExamples of commands you do not know and trust will output as html.
8252+
#&gt;
8253+
param(
8254+
# The command
8255+
[PSObject]
8256+
$Command,
8257+
8258+
# If set, will invoke examples.
8259+
# This can be as dangerous as your examples.
8260+
[switch]
8261+
$InvokeExample,
8262+
8263+
# If set, will not show the command name
8264+
[switch]
8265+
$HideName
8266+
)
8267+
8268+
# Try to get command help
8269+
$CommandHelp = Get-Help -Name $Command -ErrorAction Ignore
8270+
# If we cannot get help, return
8271+
if (-not $CommandHelp) { return }
8272+
8273+
# If the help was a string
8274+
if ($CommandHelp -is [string]) {
8275+
# return preformatted text.
8276+
return "&lt;pre&gt;$([Web.HttpUtility]::HtmlEncode($CommandHelp))&lt;/pre&gt;"
8277+
}
8278+
8279+
# Get the core parts of help we need.
8280+
$synopsis = $CommandHelp.Synopsis
8281+
$description = $CommandHelp.description.text -join [Environment]::NewLine
8282+
$notes = $CommandHelp.alertSet.alert.text -join [Environment]::NewLine
8283+
8284+
filter looksLikeMarkdown {
8285+
if (
8286+
# If it's got a markdown-like link
8287+
$_ -match '\[[^\]]+\]\(' -or
8288+
# Or any of the lines start with markdown special characters
8289+
$_ -split '(?&gt;\r\n|\n)' -match '\s{0,3}[\#*~`]'
8290+
) {
8291+
# it's probably markdown
8292+
$_
8293+
}
8294+
}
8295+
8296+
filter looksLikeTags {
8297+
if ($_ -match '^\s{0,}&lt;') {
8298+
$_
8299+
}
8300+
}
8301+
8302+
# Display the command name, synopsis, and description
8303+
if (-not $HideName) {
8304+
"&lt;h1&gt;$([Web.HttpUtility]::HtmlEncode(
8305+
$CommandHelp.Name -replace '(?:.+?/){0,}' -replace '\.ps1$' -replace '\..+?$'
8306+
))&lt;/h1&gt;"
8307+
}
8308+
8309+
if ($synopsis) {
8310+
if ($synopsis | looksLikeMarkdown) {
8311+
(ConvertFrom-Markdown -InputObject $synopsis).Html
8312+
}
8313+
elseif ($synopsis | looksLikeTags) {
8314+
$synopsis
8315+
} else {
8316+
if ($page -is [Collections.IDictionary] -and -not $page.Title) {
8317+
$page.Title = $([Web.HttpUtility]::HtmlEncode($synopsis))
8318+
}
8319+
"&lt;h2&gt;$([Web.HttpUtility]::HtmlEncode($synopsis))&lt;/h2&gt;"
8320+
}
8321+
}
8322+
8323+
if ($description) {
8324+
if ($description | lookslikeMarkdown) {
8325+
$markdown = (ConvertFrom-Markdown -InputObject $description).Html
8326+
if ($page -is [Collections.IDictionary] -and -not $page.Description) {
8327+
$xmarkdown = "&lt;xhtml&gt;$markdown&lt;/xhtml&gt;"
8328+
$page.Description = $xmarkdown.xhtml.innerText
8329+
}
8330+
8331+
}
8332+
elseif ($description | looksLikeTags) {
8333+
$description
8334+
}
8335+
else {
8336+
if ($page -is [Collections.IDictionary] -and -not $page.Description) {
8337+
$page.Description = $description
8338+
}
8339+
"&lt;h3&gt;$([Web.HttpUtility]::HtmlEncode($description))&lt;/h3&gt;"
8340+
}
8341+
}
8342+
8343+
if ($notes) {
8344+
# If there were notes, convert them from markdown
8345+
(ConvertFrom-Markdown -InputObject $notes).Html
8346+
}
8347+
8348+
#region Grid Styles
8349+
"&lt;style&gt;"
8350+
".example-grid {
8351+
width: 90vw;
8352+
margin-left: 5vw;
8353+
margin-right: 5vw;
8354+
text-align: center;
8355+
}"
8356+
".example {
8357+
text-align: center;
8358+
}"
8359+
".example-code {
8360+
text-align: center;
8361+
font-size: .9rem;
8362+
margin-left: auto;
8363+
margin-right: auto;
8364+
}"
8365+
8366+
"code { text-align: left}"
8367+
8368+
".example-menu { text-align: right; margin-top: -.5rem; margin-bottom: -.5rem }"
8369+
".copy-button { float: right; }"
8370+
8371+
".example-outputs {
8372+
display: flex;
8373+
flex-direction: column;
8374+
align-items: center;
8375+
justify-content: center;
8376+
gap: 1rem;
8377+
margin: 0.8rem;
8378+
padding: 0.2rem;
8379+
width: 90vw;
8380+
}"
8381+
".example-output { text-align: center }"
8382+
"
8383+
pre {
8384+
position: relative;
8385+
}
8386+
"
8387+
"&lt;/style&gt;"
8388+
#endregion Grid Styles
8389+
8390+
$exampleCount = @($CommandHelp.examples.example).Length
8391+
$progress = [Ordered]@{id=Get-Random}
8392+
$progress.Activity = "$($command) examples"
8393+
# Create a grid for examples
8394+
"&lt;div class='example-grid'&gt;"
8395+
$exampleNumber = 0
8396+
# Walk over each example
8397+
foreach ($example in $CommandHelp.examples.example) {
8398+
$exampleNumber++
8399+
8400+
# Combine the code and remarks
8401+
$exampleLines =
8402+
@(
8403+
$example.Code
8404+
foreach ($remark in $example.Remarks.text) {
8405+
if (-not $remark) { continue }
8406+
$remark
8407+
}
8408+
) -join ([Environment]::NewLine) -split '(?&gt;\r\n|\n)' # and split into lines
8409+
8410+
# Anything until the first non-comment line is a markdown predicate to the example
8411+
$nonCommentLine = $false
8412+
$markdownLines = @()
8413+
8414+
$progress.PercentComplete = $exampleNumber * 100 / $exampleCount
8415+
$progress.Activity = "$($command) examples $exampleNumber"
8416+
$progress.Status = "$($exampleLines[0])"
8417+
Write-Progress @progress
8418+
8419+
# Go thru each line in the example as part of a loop
8420+
$codeBlock = @(foreach ($exampleLine in $exampleLines) {
8421+
# Any comments until the first uncommentedLine are markdown
8422+
if ($exampleLine -match '^\#' -and -not $nonCommentLine) {
8423+
$markdownLines += $exampleLine -replace '^\#' -replace '^\s+'
8424+
} else {
8425+
$nonCommentLine = $true
8426+
$exampleLine
8427+
}
8428+
}) -join [Environment]::NewLine
8429+
8430+
# Join all of our markdown lines together
8431+
$Markdown = $markdownLines -join [Environment]::NewLine
8432+
8433+
# and start our example div
8434+
"&lt;div class='example'&gt;"
8435+
# If we had markdown, output it
8436+
if ($markdown) {
8437+
(ConvertFrom-Markdown -InputObject $Markdown).Html
8438+
}
8439+
# followed by our sample code
8440+
"&lt;div class='example-code'&gt;"
8441+
"&lt;pre&gt;"
8442+
"&lt;code class='language-powershell'&gt;" +
8443+
[Web.HttpUtility]::HtmlEncode($codeBlock) +
8444+
"&lt;/code&gt;"
8445+
"&lt;/pre&gt;"
8446+
"&lt;/div&gt;"
8447+
8448+
# If we do not want to invoke examples, we can continue to the next example.
8449+
if (-not $InvokeExample) {
8450+
"&lt;/div&gt;"
8451+
"&lt;hr/&gt;"
8452+
continue
8453+
}
8454+
# Otherwise, try to make our example a script block
8455+
$exampleCode =
8456+
try {
8457+
[scriptblock]::Create($codeBlock)
8458+
} catch {
8459+
Write-Warning "Unable to convert $($example.code) to a script"
8460+
continue
8461+
}
8462+
8463+
if (-not $global:ExampleOutputCache) {
8464+
$global:ExampleOutputCache = [Ordered]@{}
8465+
}
8466+
if (-not $global:ExampleOutputCache[$codeBlock]) {
8467+
$global:ExampleOutputCache[$codeBlock] = @(. $exampleCode)
8468+
}
8469+
# then run it and capture the output
8470+
$exampleOutputs = $global:ExampleOutputCache[$codeBlock]
8471+
8472+
# Keep track of our example output count
8473+
$exampleOutputNumber = 0
8474+
# and start walking thru the list
8475+
"&lt;div class='example-outputs'&gt;"
8476+
foreach ($exampleOutput in $exampleOutputs) {
8477+
$exampleOutputNumber++
8478+
# Each output goes in a div
8479+
"&lt;div class='example-output'&gt;"
8480+
# if the output was a file
8481+
if ($exampleOutput -is [IO.FileInfo]) {
8482+
# and that file is SVG
8483+
if ($exampleOutput.Extension -eq '.svg') {
8484+
# include it inline.
8485+
Get-Content $exampleOutput.FullName -Raw
8486+
}
8487+
} else {
8488+
# If the output was a turtle object
8489+
if ($exampleOutput.pstypenames -contains 'Turtle') {
8490+
# set it's ID
8491+
$exampleOutput.ID = "$($Command)-Example-$exampleCounter"
8492+
if ($exampleOutputs.Length -gt 1) {
8493+
# If we have more than out output,
8494+
# attach our example counter
8495+
$exampleOutput.ID += "-$($exampleOutputNumber)"
8496+
}
8497+
}
8498+
# Include our example output inline
8499+
if ($exampleOutput.ToHtml.Invoke) {
8500+
$exampleOutput.ToHtml()
8501+
} else {
8502+
"$exampleOutput"
8503+
}
8504+
}
8505+
"&lt;/div&gt;"
8506+
}
8507+
"&lt;/div&gt;"
8508+
"&lt;hr/&gt;"
8509+
"&lt;/div&gt;"
8510+
}
8511+
"&lt;/div&gt;"
8512+
8513+
$progress.Remove('PercentComplete')
8514+
$progress['Completed'] = $true
8515+
Write-Progress @progress
8516+
8517+
@"
8518+
&lt;script&gt;
8519+
document.querySelectorAll('pre &gt; code').forEach(element =&gt; {
8520+
const copyCodeButton = document.createElement('div')
8521+
copyCodeButton.classList.add('copy-button')
8522+
copyCodeButton.onclick = () =&gt; navigator.clipboard.writeText(element.innerText)
8523+
copyCodeButton.innerHTML = ``$(
8524+
Format-OpenPackage -View FeatherIcon.svg -Option @{icon='clipboard'}
8525+
)``
8526+
element.parentNode.prepend(copyCodeButton)
8527+
});
8528+
&lt;/script&gt;
8529+
"@
8530+
</Script>
8531+
</ScriptMethod>
82408532
<ScriptMethod>
82418533
<Name>Help.md</Name>
82428534
<Script>

0 commit comments

Comments
 (0)