LeetABit.Build.Help.psm1
#requires -version 6 using namespace System.Collections Set-StrictMode -Version 3.0 Import-LocalizedData -BindingVariable LocalizedData -FileName LeetABit.Build.Help.Resources.psd1 $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { if (Get-Module 'LeetABit.Build.Extensibility') { LeetABit.Build.Extensibility\Unregister-BuildExtension "LeetABit.Build.Help" -ErrorAction SilentlyContinue } } $Regex_ScriptBlockSyntax_FunctionName = '(?<={0})(.+?)(?=\[(-WhatIf|-Confirm|\<CommonParameters\>))' ################################################################################################################## # Target Handlers ################################################################################################################## Register-BuildTask "help" -Jobs { <# .SYNOPSIS Gets help for the build script or one of its targets. #> [CmdletBinding(PositionalBinding = $False)] param ( # Optional name of the build extension for which help shall be obtained. [Parameter(Position = 0, Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)] [String] $ExtensionTopic, # Optional name of the build task for which help shall be obtained. [Parameter(Position = 1, Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)] [String] $TaskTopic ) begin { LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState } process { Get-BuildHelp $ExtensionTopic $TaskTopic | Out-String | Write-Information -InformationAction Continue } } ################################################################################################################## # Public Commands ################################################################################################################## function Get-BuildHelp { <# .SYNOPSIS Gets help about build scripts usage. .DESCRIPTION Get-BuildHelp cmdlet provides a concise documentation about each of the loaded extensions and build tasks. .EXAMPLE PC> Get-BuildHelp Gets help about all registered build extensions and tasks. .EXAMPLE PC> Get-BuildHelp -ExtensionTopic "PowerShell" Gets a detailed help about all tasks provided by "PowerShell" extension. .EXAMPLE PC> Get-BuildHelp -TaskTopic "build" Gets a detailed help about all build commands provided by different extensions. .EXAMPLE PC> Get-BuildHelp -ExtensionTopic "PowerShell" -TaskTopic "build" Gets a detailed help about "build" task provided by "PowerShell" extension. #> [CmdletBinding(PositionalBinding = $False)] param ( # Optional name of the build extension for which help shall be obtained. [Parameter(Position = 0, Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)] [String] $ExtensionTopic, # Optional name of the build task for which help shall be obtained. [Parameter(Position = 1, Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)] [String] $TaskTopic ) begin { LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $scriptName = Join-Path '.' 'run.ps1' } process { $typeNameSuffix = if ($ExtensionTopic) { if ($TaskTopic) { 'DetailedView' } else { 'ExtensionView' } } else { if ($TaskTopic) { 'TaskView' } else { 'GeneralView' } } $helpInfo = @{} $helpInfo.ScriptName = $scriptName $helpInfo.Synopsis = $LocalizedData.Get_BuildHelp_Buildstrapper_Synopsis $helpInfo.TaskTopic = $TaskTopic $helpInfo.ExtensionTopic = $ExtensionTopic $helpInfo.Extensions = @() foreach ($currentExtension in LeetABit.Build.Extensibility\Get-BuildExtension) { if ($ExtensionTopic -and $ExtensionTopic -ne $currentExtension.Name) { continue } $extension = @{} $extension.Name = $currentExtension.Name $extension.Description = '' if ($currentExtension.Resolver.Module -and $currentExtension.Resolver.Module.Name -ne 'LeetABit.Build.Extensibility') { $extension.Description = $currentExtension.Resolver.Module.Description } $extension.Tasks = @{} foreach ($currentTask in $currentExtension.Tasks.Values) { if ($TaskTopic -and $TaskTopic -ne $currentTask.Name) { continue } $task = @{} $task.Name = $currentTask.Name $task.IsDefault = $currentTask.IsDefault $task.Jobs = @() $task.Description = @() $task.Parameters = @() $currentTask.Jobs | ForEach-Object { if ($_ -is [String]) { $task.Jobs += $_ $task.Description += $extension.Tasks[$_].Description $task.Parameters += $extension.Tasks[$_].Parameters } else { if (-not $extension.Description) { if ($_.Module) { $extension.Description = $_.Module.Description } } $jobScriptBlock = [String]$_ $function:private:ScriptBlockCommand = $jobScriptBlock $helpObject = Get-Help 'ScriptBlockCommand' $helpString = (Get-Help 'ScriptBlockCommand' -Full | Out-String) $job = @{} $job.Description = $helpObject.Synopsis $task.Description += $helpObject.Synopsis $nextSection = if ($helpObject.PSObject.TypeNames -contains 'ExtendedCmdletHelpInfo') { 'PARAMETERS' } else { 'DESCRIPTION' } $startIndex = $helpString.IndexOf("SYNTAX") + "SYNTAX".Length $endIndex = $helpString.IndexOf($nextSection, $startIndex) $syntaxText = $helpString.Substring($startIndex, $endIndex - $startIndex).Trim() $syntaxString = ($syntaxText -split [Environment]::NewLine) -join '' $syntax = "$scriptName $($task.Name)" $regex = $Regex_ScriptBlockSyntax_FunctionName -f 'ScriptBlockCommand' if ($syntaxString -match $regex) { $syntax += "$($matches[1])" } $job.Syntax = $syntax.Trim() $job.Parameters = @() if ($helpObject.parameters.PSObject.Properties.Name -contains 'parameter') { foreach ($parameterObject in $helpObject.parameters.parameter) { $parameter = @{} $parameter.Name = $parameterObject.Name if ($parameterObject.PSObject.Properties.Name -contains "type") { $parameter.Type = $parameterObject.type.name } if ($parameterObject.PSObject.Properties.Name -contains "description") { if ($parameterObject.description -is [String]) { $parameter.Description = $parameterObject.description } else { $parameter.Description = $parameterObject.description.Text } } else { $parameter.Description = "" } $parameter.Mandatory = [System.Convert]::ToBoolean($parameterObject.required) $parameterObject = Convert-DictionaryToHelpObject -Properties $parameter -HelpObjectName 'Parameter' -HelpView $typeNameSuffix $task.Parameters += $parameterObject $job.Parameters += $parameterObject } } $task.Jobs += Convert-DictionaryToHelpObject -Properties $job -HelpObjectName 'Job' -HelpView $typeNameSuffix } } $parametersDictionary = @{} if ($task.Parameters) { $task.Parameters | ForEach-Object { if ($parametersDictionary.ContainsKey($_.Name)) { $alreadyStored = $parametersDictionary[$_.Name] $parametersDictionary.Remove($_.Name) if ($alreadyStored.Type -ne $_.Type) { $alreadyStored.Type = "String" } if ($alreadyStored.Description -notcontains $_.Description) { $alreadyStored.Description += $_.Description } $alreadyStored.Mandatory = $alreadyStored.Mandatory -or $_.Mandatory $parametersDictionary.Add($alreadyStored.Name, $alreadyStored) } else { $_.Description = @($_.Description) $parametersDictionary.Add($_.Name, $_) } } } $task.Description = (($task.Description | Get-Unique) -join " ").Trim() $task.Parameters = $parametersDictionary.Values | Sort-Object -Property Name $extension.Tasks.Add($task.Name, (Convert-DictionaryToHelpObject -Properties $task -HelpObjectName 'Task' -HelpView $typeNameSuffix)) } if (-not $TaskTopic -or $extension.Tasks) { $extension.Tasks = $extension.Tasks.Values; $helpInfo.Extensions += Convert-DictionaryToHelpObject -Properties $extension -HelpObjectName 'Extension' -HelpView $typeNameSuffix } } Convert-DictionaryToHelpObject -Properties $helpInfo -HelpObjectName 'HelpInfo' -HelpView $typeNameSuffix } } ################################################################################################################## # Private Commands ################################################################################################################## function Convert-DictionaryToHelpObject { <# .SYNOPSIS Converts a hashtable to a PSObject using keys as property names with associated values. #> [CmdletBinding(PositionalBinding = $False)] [OutputType([String])] param ( # A hashtable with desired object's properties. [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [IDictionary] $Properties, # A name of the help object's type that shall be assigned to the object. [Parameter(Position = 1, Mandatory = $True, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)] [String] $HelpObjectName, # A name of the help object's type suffix that shall be assigned to the object as a secondary type. [Parameter(Position = 2, Mandatory = $True, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)] [String] $HelpView ) begin { $typeNameNamespace = 'LeetABit.Build.' } process { LeetABit.Build.Common\New-PSObject (($typeNameNamespace + $HelpObjectName + ".$HelpView"), ($typeNameNamespace + $HelpObjectName)) $Properties } } Export-ModuleMember -Function '*' -Variable '*' -Alias '*' -Cmdlet '*' # SIG # Begin signature block # MIIM3wYJKoZIhvcNAQcCoIIM0DCCDMwCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBhjxPxaYFM0GdK # +VJfWwEOtYhSJtjU0h73jToO6iZr3KCCCe0wggTeMIIDxqADAgECAhBrMmoPAyjT # eh1TC/0jvUjiMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK # ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy # dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l # dHdvcmsgQ0EwHhcNMTUxMDI5MTEzMDI5WhcNMjcwNjA5MTEzMDI5WjCBgDELMAkG # A1UEBhMCUEwxIjAgBgNVBAoMGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl # BgNVBAsMHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAwwb # Q2VydHVtIENvZGUgU2lnbmluZyBDQSBTSEEyMIIBIjANBgkqhkiG9w0BAQEFAAOC # AQ8AMIIBCgKCAQEAt9uo2MjjvNrag7q5v9bVV1NBt0C6FwxEldTpZjt/tL6Qo5QJ # pa0hIBeARrRDJj6OSxpk7A5AMkP8gp//Si3qlN1aETaLYe/sFtRJA9jnXcNlW/JO # CyvDwVP6QC3CqzMkBYFwfsiHTJ/RgMIYew4UvU4DQ8soSLAt5jbfGz2Lw4ydN57h # BtclUN95Pdq3X+tGvnYoNrgCAEYD0DQbeLQox1HHyJU/bo2JGNxJ8cIPGvSBgcdt # 1AR3xSGjLlP5d8/cqZvDweXVZy8xvMDCaJxKluUf8fNINQ725LHF74eAOuKADDSd # +hRkceQcoaqyzwCn4zdy+UCtniiVAg3OkONbxQIDAQABo4IBUzCCAU8wDwYDVR0T # AQH/BAUwAwEB/zAdBgNVHQ4EFgQUwHu0yLduVqcJSJr4ck/X1yQsNj4wHwYDVR0j # BBgwFoAUCHbNywf/JPbFze27kLzihDdGdfcwDgYDVR0PAQH/BAQDAgEGMBMGA1Ud # JQQMMAoGCCsGAQUFBwMDMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9jcmwuY2Vy # dHVtLnBsL2N0bmNhLmNybDBrBggrBgEFBQcBAQRfMF0wKAYIKwYBBQUHMAGGHGh0 # dHA6Ly9zdWJjYS5vY3NwLWNlcnR1bS5jb20wMQYIKwYBBQUHMAKGJWh0dHA6Ly9y # ZXBvc2l0b3J5LmNlcnR1bS5wbC9jdG5jYS5jZXIwOQYDVR0gBDIwMDAuBgRVHSAA # MCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93d3cuY2VydHVtLnBsL0NQUzANBgkqhkiG # 9w0BAQsFAAOCAQEAquU/dlQCTHAOKak5lgYPMbcL8aaLUvsQj09CW4y9MSMBZp3o # KaFNw1D69/hFDh2C1/z+pjIEc/1x7MyID6OSCMWBWAL9C2k7zbg/ST3QjRwTFGgu # mw2arbAZ4p7SfDl3iG8j/XuE/ERttbprcJJVbJSx2Df9qVkdtGOy3BPNeI4lNcGa # jzeELtRFzOP1zI1zqOM6beeVlHBXkVC2be9zck8vAodg4uoioe0+/dGLZo0ucm1P # xl017pOomNJnaunaGc0Cg/l0/F96GAQoHt0iMzt2bEcFXdVS/g66dvODEMduMF+n # YMf6dCcxmyiD7SGKG/EjUoTtlbytOqWjQgGdvDCCBQcwggPvoAMCAQICECxWDYHo # gPTFxULdYYZu+b0wDQYJKoZIhvcNAQELBQAwgYAxCzAJBgNVBAYTAlBMMSIwIAYD # VQQKDBlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLDB5DZXJ0dW0g # Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxJDAiBgNVBAMMG0NlcnR1bSBDb2RlIFNp # Z25pbmcgQ0EgU0hBMjAeFw0yMDAzMTgwNjMzMDdaFw0yMTAzMTgwNjMzMDdaMHAx # CzAJBgNVBAYTAlBMMRAwDgYDVQQHDAdLcmFrw7N3MR4wHAYDVQQKDBVPcGVuIFNv # dXJjZSBEZXZlbG9wZXIxLzAtBgNVBAMMJk9wZW4gU291cmNlIERldmVsb3Blciwg # SHViZXJ0IEJ1a293c2tpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA # 3B0pw0zPe4KG0FR7q4ZrHDWpELuc3KyfaaaDkF6EXpbX5bCByq97QrQ4YJjru9UW # JK+u45hyUpiUXWPfeKHKKz3bLMCmIMaPq+FpfSh2xzB3lFnj/1LlO7htUpfKZ6Ks # FpCkrKk6ZiPs8PxXpfmoiOzxryySbcqVZr7ZEQnRKfl6Op4IKiZQ54lZOy4ORRMu # ghxlxJfK49XQ5gUrV1dRL3blFSIrfOl1K6wB0/5QxxWeO4WxHzD9WtfpSTs2/gML # fj4xe80QjaGGShpN+qtPRaa+2qEdm2Dm+Btto8Gy9eVcxUXAQNZFTZPMd2Sf71yl # whZIsqEChoTawyrnZTMwgwIDAQABo4IBijCCAYYwDAYDVR0TAQH/BAIwADAyBgNV # HR8EKzApMCegJaAjhiFodHRwOi8vY3JsLmNlcnR1bS5wbC9jc2Nhc2hhMi5jcmww # cQYIKwYBBQUHAQEEZTBjMCsGCCsGAQUFBzABhh9odHRwOi8vY3NjYXNoYTIub2Nz # cC1jZXJ0dW0uY29tMDQGCCsGAQUFBzAChihodHRwOi8vcmVwb3NpdG9yeS5jZXJ0 # dW0ucGwvY3NjYXNoYTIuY2VyMB8GA1UdIwQYMBaAFMB7tMi3blanCUia+HJP19ck # LDY+MB0GA1UdDgQWBBSqIB8Yxug7KjAkRKhvS62xLJPR1zAdBgNVHRIEFjAUgRJj # c2Nhc2hhMkBjZXJ0dW0ucGwwDgYDVR0PAQH/BAQDAgeAMEsGA1UdIAREMEIwCAYG # Z4EMAQQBMDYGCyqEaAGG9ncCBQEEMCcwJQYIKwYBBQUHAgEWGWh0dHBzOi8vd3d3 # LmNlcnR1bS5wbC9DUFMwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQEL # BQADggEBAKkWEKsxeIDC+mcLz+zJgNkK+eXZR1sEueM5LcK7iDzWPG8pPOfrKJMH # v67m3XG1PYy54Qn3AHGIZzXPF+HgIatkEFE931TUTjUFhuTuiEtKft+gsZgEyCXG # Km2e5fYiaBRUAtvQKPpDrocSazIP92x+blTaIKM1Z+Ysx/2YTwkpyMclviK7OisV # JHzbKmxgLxhatMwCPtLbFuAUffDxG8igXstCbQ3Qoa4qj2HldQy4HVCYDfdA3PcV # 9LGXPpCKGeSFCGekSdZW2f61xATc0mCpfECVTUQBJL4taNCeR219IfX20ETo+zH6 # epfSds5WtOnY/9uzFR6jLNtAx3IVrQwxggJIMIICRAIBATCBlTCBgDELMAkGA1UE # BhMCUEwxIjAgBgNVBAoMGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNV # BAsMHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAwwbQ2Vy # dHVtIENvZGUgU2lnbmluZyBDQSBTSEEyAhAsVg2B6ID0xcVC3WGGbvm9MA0GCWCG # SAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcN # AQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUw # LwYJKoZIhvcNAQkEMSIEIIDIPHwlLKEOOPkb/AGKE1cMCfL+6dzte3TNHSAeYCI0 # MA0GCSqGSIb3DQEBAQUABIIBAFe1gIXf6rm/DA7HeuWbAYEVhsq1AI+PLCpXxBCA # Bg6t8KBygjRclBnmhZ5B7mNN3RgiEUeSR95eKC4NsVWrZOVdP2W3YWTwjGPeNWYG # SAeDtqvf98/ZEuTXzUbTmhbqe1A73nyTGjRKMK6yGNNWembYH1Vv8KXobAdwfmdy # x7dQhspUqXw6F0vosO/7Hv5RGoX4fiHP8BVW0VS8ky9pen9Na7wjkxw4z6BVYF/q # +55sDvlZo3kF3FccTk2EXMPQH0IqvhpstrXR5D8C+va/TJ65KR62ohM8GwdQ3t5E # iIFxidMRRMlEuE6F3uKxKmig9tylhV8w7+YfKvEAmKyfDPo= # SIG # End signature block |