From f1083b01958a4a4b3cde397c7b85342df6888629 Mon Sep 17 00:00:00 2001 From: Timothy Aldrich Date: Wed, 8 Nov 2023 15:58:29 -0500 Subject: [PATCH] feat: Added tasks for formatting platyps docs --- .build/profiles/docs/runbook.ps1 | 7 +- .../tasks/add.component.heading.build.ps1 | 59 ++++ .../docs/tasks/add.task.config.build.ps1 | 88 ++++++ .../tasks/convert.codeblocks.pwsh.build.ps1 | 18 ++ .../docs/tasks/format.common.params.build.ps1 | 11 + .../docs/tasks/invoke.markdownlint.build.ps1 | 35 +++ .../docs/tasks/set.external.help.build.ps1 | 47 +++ .markdownlint.json | 291 ++++++++++++++++++ 8 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 .build/profiles/docs/tasks/add.component.heading.build.ps1 create mode 100644 .build/profiles/docs/tasks/add.task.config.build.ps1 create mode 100644 .build/profiles/docs/tasks/convert.codeblocks.pwsh.build.ps1 create mode 100644 .build/profiles/docs/tasks/format.common.params.build.ps1 create mode 100644 .build/profiles/docs/tasks/invoke.markdownlint.build.ps1 create mode 100644 .build/profiles/docs/tasks/set.external.help.build.ps1 create mode 100644 .markdownlint.json diff --git a/.build/profiles/docs/runbook.ps1 b/.build/profiles/docs/runbook.ps1 index c84a004..1eb2136 100644 --- a/.build/profiles/docs/runbook.ps1 +++ b/.build/profiles/docs/runbook.ps1 @@ -2,6 +2,9 @@ Use this file to manage the phases and tasks. #> -Build | jobs @( - +'Build' | jobs @( + 'convert.codeblocks.pwsh', + 'format.common.params', + 'add.component.heading', + 'invoke.markdownlint' ) diff --git a/.build/profiles/docs/tasks/add.component.heading.build.ps1 b/.build/profiles/docs/tasks/add.component.heading.build.ps1 new file mode 100644 index 0000000..77c1ba3 --- /dev/null +++ b/.build/profiles/docs/tasks/add.component.heading.build.ps1 @@ -0,0 +1,59 @@ + +param( + +) + +#Synopsis: Add a COMPONENT header to the markdown file +task add.component.heading { + Import-Module 'C:\Users\TAldrich\projects\github\PSMarkdig\source\PSMarkdig' -Force + $componentMap = @{} + $BuildInfo | Foreach-Module { + $config = $_ + $config.SourceInfo + | Where-Object Type -Like 'function' + | ForEach-Object { + if (-not ([string]::IsNullorEmpty($_.Component))) { + $componentMap.Add($_.Name, $_.Component) + } + } + } + + foreach ($mdDoc in (Get-ChildItem -Path .\docs -Filter '*.md' -Recurse)) { + # Check to see if Component heading already exists first + $componentHeading = "`r`n## COMPONENT`r`n" | PSMarkdig\New-MarkdownElement + + logDebug "Markdown file $($mdDoc.BaseName)" + + if ($componentMap.ContainsKey( $mdDoc.BaseName )) { + $componentName = $componentMap[$mdDoc.BaseName] + $componentText = "`r`n$componentName`r`n" | PSMarkdig\New-MarkdownElement + + $doc = PSMarkdig\Import-Markdown $mdDoc + + logDebug "- Adding Component $componentName" + + if ($null -ne $doc) { + logDebug "- Imported markdown. $($doc.Count) elements" + + $headings = PSMarkdig\Get-MarkdownHeading $doc + if ($null -ne $headings) { + logDebug " - There are $($headings.Count) headings" + $lastHeading = $headings | Select-Object -Last 1 + } + + try { + logDebug "- Adding $($componentHeading.GetType().FullName) to doc" + + PSMarkdig\Add-MarkdownElement -Element $componentHeading -Document $doc -Before $lastHeading + logDebug "- Adding $($componentText.GetType().FullName) to doc" + PSMarkdig\Add-MarkdownElement -Element $componentText -Document $doc -Before $lastHeading + + PSMarkdig\Write-MarkdownElement $doc | Set-Content $mdDoc -NoNewline + } catch { + $PSCmdlet.ThrowTerminatingError($_) + } + Remove-Variable componentHeading, componentText, doc + } + } + } +} diff --git a/.build/profiles/docs/tasks/add.task.config.build.ps1 b/.build/profiles/docs/tasks/add.task.config.build.ps1 new file mode 100644 index 0000000..93700dd --- /dev/null +++ b/.build/profiles/docs/tasks/add.task.config.build.ps1 @@ -0,0 +1,88 @@ + +#Synopsis: Create task config files from the source +task add.task.config { + logDebug "CopyAdditional: ($($CopyAdditionalItems | ConvertTo-Psd))" + Import-Module '.\tools\BuildProperties.psm1' + + $taskConfigDir = '.\.build\profiles\default\config\tasks' + $scriptPropertyMap = Get-BuildScriptProperty + + $propertyData = Get-ParameterData + #TODO: To make this task idempotent, the additon should skip if the field and value already exist + + foreach ($map in $scriptPropertyMap.GetEnumerator()) { + $name = $map.Name + logDebug "Property: $name" + try { + $currentValue = Get-BuildProperty $name + } catch { + $currentValue = (Get-Variable $name -ValueOnly) + } + logDebug "Value: $($currentValue | ConvertTo-Psd)" + $commentText = ( + ($propertyData + | Where-Object Name -Like $name + | Select-Object -Expand Help + ) -join "`n") + + $instances = $map.Value + :file foreach ($instance in $instances) { + $taskConfigFile = $instance.File -replace 'build\.ps1', 'config.psd1' + logDebug "- Task configuration file: $taskConfigFile" + $taskConfigPath = (Join-Path $taskConfigDir $taskConfigFile) + + if (Test-Path $taskConfigPath) { + logDebug " - already exists" + $currentContent = Import-Psd $taskConfigPath + if ($currentContent.ContainsKey($name)) { + logDebug " - already has a $name key" + if ($currentContent['$name'] -eq $currentValue) { + logDebug " - and content matches" + continue file + } else { + logDebug " - but content doesn't match" + $xml = Import-PsdXml -Path $taskConfigPath + Set-Psd -Xml $xml -Value $currentValue -XPath (-join ('//Data/Table/Item[@Key="', $name, '"]')) + Export-PsdXml -Path $taskConfigPath -Xml $xml + continue file + } + } else { + logDebug " - does not have a $name key" + $xml = Import-PsdXml -Path $taskConfigPath + $table = $xml.Data.Table + $newLine = $xml.CreateElement('NewLine') + $comment = $xml.CreateElement('Comment') + [void]$table.AppendChild($newLine) + [void]$table.AppendChild($comment) + [void]$table.AppendChild($newLine) + $currentValueData = $currentValue | ConvertTo-Psd | Convert-PsdToXml + $newItem = $xml.CreateElement('Item') + [void]$newItem.SetAttribute('Key', $name) + $newItem.InnerXml = $currentValueData + + Export-PsdXml -Path $taskConfigPath -Xml $xml + } + } else { + logDebug " - does not exist" + $data = @{ + $name = $currentValue + } + + $data | ConvertTo-Psd | Set-Content $taskConfigPath + $xml = Import-PsdXml -Path $taskConfigPath + $newLine = $xml.CreateElement('NewLine') + $comment = $xml.CreateElement('Comment') + $comment.InnerText = (-join ( + '<# ', + $commentText, + ' #>')) + $table = $xml.Data.Table + [void]$table.PrependChild($newLine) + [void]$table.PrependChild($comment) + [void]$table.PrependChild($newLine) + Export-PsdXml -Path $taskConfigPath -Xml $xml + } + $taskConfigPath | Convert-LineEnding -LF + } + } +} diff --git a/.build/profiles/docs/tasks/convert.codeblocks.pwsh.build.ps1 b/.build/profiles/docs/tasks/convert.codeblocks.pwsh.build.ps1 new file mode 100644 index 0000000..3d2a121 --- /dev/null +++ b/.build/profiles/docs/tasks/convert.codeblocks.pwsh.build.ps1 @@ -0,0 +1,18 @@ + +#SYNOPSIS: PlatyPS does not add 'powershell' to the fenced code blocks. This task does. +task convert.codeblocks.pwsh { + foreach ($mdDoc in (Get-ChildItem -Path .\docs -Filter "*.md" -Recurse)) { + $doc = Import-Markdown $mdDoc.FullName + $blocks = $doc | Where-Object { + $_.GetType().FullName -like 'Markdig.Syntax.FencedCodeBlock' + } + foreach ($block in $blocks) { + if ([string]::IsNullOrEmpty($block.UnescapedInfo)) { + $block.Info = 'powershell' + $block.UnescapedInfo = 'powershell' + } + } + logInfo "Setting codeblocks to powershell in $($mdDoc.Name)" + $doc | Write-MarkdownElement | Set-Content $mdDoc.FullName -NoNewline + } +} diff --git a/.build/profiles/docs/tasks/format.common.params.build.ps1 b/.build/profiles/docs/tasks/format.common.params.build.ps1 new file mode 100644 index 0000000..c7a4156 --- /dev/null +++ b/.build/profiles/docs/tasks/format.common.params.build.ps1 @@ -0,0 +1,11 @@ + +# Synopsis: Remove the list of common parameters from the documentation +task format.common.params { + $replacementText = "This cmdlet supports the common parameters.`nFor more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216)." + foreach ($mdDoc in (Get-ChildItem -Path .\docs -Filter '*.md' -Recurse)) { + logInfo "Replacing Common Parameters extra info in $($mdDoc.Name)" + (get-content $mdDoc -Raw ) -replace [regex]::Escape( + 'This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).' + ) , $replacementText | Set-Content $mdDoc -NoNewline + } +} diff --git a/.build/profiles/docs/tasks/invoke.markdownlint.build.ps1 b/.build/profiles/docs/tasks/invoke.markdownlint.build.ps1 new file mode 100644 index 0000000..14fb93d --- /dev/null +++ b/.build/profiles/docs/tasks/invoke.markdownlint.build.ps1 @@ -0,0 +1,35 @@ + +# Synopsis: Format documentation with markdownlint +task invoke.markdownlint { + $tempFile = [System.IO.Path]::GetTempFileName() + $basedir = "$env:APPDATA\npm" + $node = 'node.exe' + $mdLintPath = "$basedir/node_modules/markdownlint-cli/markdownlint.js" + + if (Test-Path $mdLintPath) { + + [string[]]$argList = @() + + $argList += $mdLintPath + $argList += '.\docs\stitch' + $argList += '--fix' + $argList += '--output' + $argList += $tempFile + $argList += '--json' + $argList += '--quiet' + logInfo "Calling markdownlint" + & $node $argList + + $lintErrors = Get-Content $tempFile | ConvertFrom-Json + if ($lintErrors.Count -gt 0) { + foreach ($lintError in $lintErrors) { + '{0}:{1} - {2}' -f $lintError.fileName , $lintError.lineNumber, $lineError.ruleDescription + } + } + Remove-Item $tempFile + + logInfo "Markdown Errors: $($lintErrors.Count)" + } else { + throw "Markdownlint is not installed. Run npm install -g markdownlint-cli" + } +} diff --git a/.build/profiles/docs/tasks/set.external.help.build.ps1 b/.build/profiles/docs/tasks/set.external.help.build.ps1 new file mode 100644 index 0000000..e03994d --- /dev/null +++ b/.build/profiles/docs/tasks/set.external.help.build.ps1 @@ -0,0 +1,47 @@ + +#SYNOPSIS: Remove the Comment-based help in the given file and replace it with `.EXTERNALHELPFILE +task set.external.help { + $BuildInfo | Foreach-Module { + $config = $_ + $externalHelpText = @" + <# + .EXTERNALHELPFILE $($config.Name)-help.xml + #> +"@ + $functions = $config.SourceInfo | Where-Object Type -Like 'function' + foreach ($source in $functions) { + if ($null -ne $env:TestExternalHelp) { + if ($source.Name -notlike $env:TestExternalHelp) { + continue + } else { + logInfo "TestExternalHelp is set to $env:TestExternalHelp" + logInfo "processing File" + } + } + if ($source.Tokens.Count -gt 0) { + $commentBasedHelp = $source.Tokens | Where-Object { + ($_.Kind -like 'Comment' ) -and + ($_.Extent.Text -match '\.SYNOPSIS' ) + } + } + if ($null -ne $commentBasedHelp) { + logInfo "$($source.Name) has comment-based help" + #! in Get-SourceItemInfo ToString is overloaded to output the content of the file + $content = $source.ToString() + $start = ($commentBasedHelp.Extent.StartOffSet - 1) + $end = ($commentBasedHelp.Extent.EndOffset + 1) + + logInfo "help starts at $start and ends at $end" + logInfo "Help Content:`n$(-join ($content[$start..$end]))" + + # Now we "splice" the content + logInfo "Splicing content" + $preComment = (-join ($content[0..$start])) + $postComment = (-join ($content[$end..-1])) + + $preComment, $externalHelpText, $postComment | Set-Content $source.Path + + } + } + } +} diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..05aa9a1 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,291 @@ +// code: language=jsonc + +{ + // Default state for all rules + "default": true, + + // Path to configuration file to extend + "extends": null, + + // MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time + "MD001": true, + + // MD002/first-heading-h1/first-header-h1 - First heading should be a top-level heading + "MD002": { + // Heading level + "level": 1 + }, + + // MD003/heading-style/header-style - Heading style + "MD003": { + // Heading style + "style": "consistent" + }, + + // MD004/ul-style - Unordered list style + "MD004": { + // List style + "style": "consistent" + }, + + // MD005/list-indent - Inconsistent indentation for list items at the same level + "MD005": true, + + // MD006/ul-start-left - Consider starting bulleted lists at the beginning of the line + "MD006": true, + + // MD007/ul-indent - Unordered list indentation + "MD007": { + // Spaces for indent + "indent": 2, + // Whether to indent the first level of the list + "start_indented": false, + // Spaces for first level indent (when start_indented is set) + "start_indent": 2 + }, + + // MD009/no-trailing-spaces - Trailing spaces + "MD009": { + // Spaces for line break + "br_spaces": 2, + // Allow spaces for empty lines in list items + "list_item_empty_lines": false, + // Include unnecessary breaks + "strict": false + }, + + // MD010/no-hard-tabs - Hard tabs + "MD010": { + // Include code blocks + "code_blocks": true, + // Fenced code languages to ignore + "ignore_code_languages": [], + // Number of spaces for each hard tab + "spaces_per_tab": 4 + }, + + // MD011/no-reversed-links - Reversed link syntax + "MD011": true, + + // MD012/no-multiple-blanks - Multiple consecutive blank lines + "MD012": { + // Consecutive blank lines + "maximum": 1 + }, + + // MD013/line-length - Line length + "MD013": { + // Number of characters + "line_length": 200, + // Number of characters for headings + "heading_line_length": 200, + // Number of characters for code blocks + "code_block_line_length": 200, + // Include code blocks + "code_blocks": true, + // Include tables + "tables": true, + // Include headings + "headings": true, + // Include headings + "headers": true, + // Strict length checking + "strict": false, + // Stern length checking + "stern": false + }, + + // MD014/commands-show-output - Dollar signs used before commands without showing output + "MD014": true, + + // MD018/no-missing-space-atx - No space after hash on atx style heading + "MD018": true, + + // MD019/no-multiple-space-atx - Multiple spaces after hash on atx style heading + "MD019": true, + + // MD020/no-missing-space-closed-atx - No space inside hashes on closed atx style heading + "MD020": true, + + // MD021/no-multiple-space-closed-atx - Multiple spaces inside hashes on closed atx style heading + "MD021": true, + + // MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines + "MD022": { + // Blank lines above heading + "lines_above": 1, + // Blank lines below heading + "lines_below": 1 + }, + + // MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line + "MD023": true, + + // MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content + "MD024": { + // Only check sibling headings + "allow_different_nesting": true, + // Only check sibling headings + "siblings_only": true + }, + + // MD025/single-title/single-h1 - Multiple top-level headings in the same document + "MD025": { + // Heading level + "level": 1, + // RegExp for matching title in front matter + "front_matter_title": "^\\s*title\\s*[:=]" + }, + + // MD026/no-trailing-punctuation - Trailing punctuation in heading + "MD026": { + // Punctuation characters not allowed at end of headings + "punctuation": ".,;:!。,;:!" + }, + + // MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol + "MD027": true, + + // MD028/no-blanks-blockquote - Blank line inside blockquote + "MD028": true, + + // MD029/ol-prefix - Ordered list item prefix + "MD029": { + // List style + "style": "one_or_ordered" + }, + + // MD030/list-marker-space - Spaces after list markers + "MD030": { + // Spaces for single-line unordered list items + "ul_single": 1, + // Spaces for single-line ordered list items + "ol_single": 1, + // Spaces for multi-line unordered list items + "ul_multi": 1, + // Spaces for multi-line ordered list items + "ol_multi": 1 + }, + + // MD031/blanks-around-fences - Fenced code blocks should be surrounded by blank lines + "MD031": { + // Include list items + "list_items": false + }, + + // MD032/blanks-around-lists - Lists should be surrounded by blank lines + "MD032": false, + + // MD033/no-inline-html - Inline HTML + "MD033": { + // Allowed elements + "allowed_elements": [] + }, + + // MD034/no-bare-urls - Bare URL used + "MD034": true, + + // MD035/hr-style - Horizontal rule style + "MD035": { + // Horizontal rule style + "style": "consistent" + }, + + // MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading + "MD036": { + // Punctuation characters + "punctuation": ".,;:!?。,;:!?" + }, + + // MD037/no-space-in-emphasis - Spaces inside emphasis markers + "MD037": true, + + // MD038/no-space-in-code - Spaces inside code span elements + "MD038": true, + + // MD039/no-space-in-links - Spaces inside link text + "MD039": true, + + // MD040/fenced-code-language - Fenced code blocks should have a language specified + "MD040": { + // List of languages + "allowed_languages": [], + // Require language only + "language_only": false + }, + + // MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading + "MD041": { + // Heading level + "level": 1, + // RegExp for matching title in front matter + "front_matter_title": "^\\s*title\\s*[:=]" + }, + + // MD042/no-empty-links - No empty links + "MD042": true, + + // MD043/required-headings/required-headers - Required heading structure + "MD043": { + // List of headings + "headings": [ + "*" + ], + // List of headings + // "headers": [], + // Match case of headings + "match_case": false + }, + + // MD044/proper-names - Proper names should have the correct capitalization + "MD044": { + // List of proper names + "names": [], + // Include code blocks + "code_blocks": true, + // Include HTML elements + "html_elements": true + }, + + // MD045/no-alt-text - Images should have alternate text (alt text) + "MD045": true, + + // MD046/code-block-style - Code block style + "MD046": { + // Block style + "style": "consistent" + }, + + // MD047/single-trailing-newline - Files should end with a single newline character + "MD047": true, + + // MD048/code-fence-style - Code fence style + "MD048": { + // Code fence style + "style": "consistent" + }, + + // MD049/emphasis-style - Emphasis style should be consistent + "MD049": { + // Emphasis style should be consistent + "style": "consistent" + }, + + // MD050/strong-style - Strong style should be consistent + "MD050": { + // Strong style should be consistent + "style": "consistent" + }, + + // MD051/link-fragments - Link fragments should be valid + "MD051": true, + + // MD052/reference-links-images - Reference links and images should use a label that is defined + "MD052": true, + + // MD053/link-image-reference-definitions - Link and image reference definitions should be needed + "MD053": { + // Ignored definitions + "ignored_definitions": ["//"] + } +}