Browse Source

docs: generate the wiki pages from Docs

- .github/scripts/Export-WikiContent.ps1 converts Docs, except the
  contributor guide, into flat wiki pages: Docs/README.md becomes Home,
  cmdlet pages lose their platyPS metadata, links point to wiki pages or
  to the files on GitHub, and links in code stay unchanged. It writes a
  sidebar from the cmdlet groups of Docs/README.md, a footer, and the
  former page How-to-install, and keeps the page name Version-History
  that the release notes link to.
- Tests/Wiki.Tests.ps1 checks the conversion rules with a sample of Docs
  and every link and anchor of the wiki generated from the real Docs.
- README, CHANGELOG, and the version history mention the wiki again.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/96/head
Raimund Andree 1 week ago
parent
commit
3582d0afb2
  1. 279
      .github/scripts/Export-WikiContent.ps1
  2. 6
      CHANGELOG.md
  3. 4
      Docs/Version-History.md
  4. 4
      README.md
  5. 228
      Tests/Wiki.Tests.ps1

279
.github/scripts/Export-WikiContent.ps1

@ -0,0 +1,279 @@
<#
.SYNOPSIS
Converts the documentation in the Docs folder into the pages of the GitHub wiki.
.DESCRIPTION
Writes a wiki page for every page in Docs except the contributor guide (Contributing.md and the Contributing
folder). Docs/README.md becomes the page Home, and every other page keeps its file name, so a page in
Docs/Cmdlets becomes a page named after its cmdlet. The platyPS metadata at the top of the cmdlet pages is
removed. Relative links point to the wiki pages; links to other files of the repository point to the files on
GitHub. Links in code stay unchanged.
The script also writes the sidebar, which lists the cmdlets in the groups of the cmdlet list in Docs/README.md;
the footer; and the page How-to-install, which keeps the address of the former wiki page working.
The script removes everything in DestinationPath except the .git folder, so that pages that no longer exist in
Docs disappear from the wiki.
.PARAMETER Path
Specifies the Docs folder of the repository.
.PARAMETER DestinationPath
Specifies the folder to write the wiki pages to, usually a clone of the wiki repository. The script creates the
folder if it doesn't exist.
.PARAMETER RepositoryUrl
Specifies the address of the repository on GitHub, for links to files that aren't wiki pages.
.PARAMETER Branch
Specifies the branch for links to files that aren't wiki pages.
.EXAMPLE
git clone https://github.com/raandree/NTFSSecurity.wiki.git $env:TEMP\wiki
.\.github\scripts\Export-WikiContent.ps1 -Path .\Docs -DestinationPath $env:TEMP\wiki
git -C $env:TEMP\wiki status
Writes the wiki pages into a clone of the wiki and shows which pages change.
#>
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]
$Path,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$DestinationPath,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]
$RepositoryUrl = 'https://github.com/raandree/NTFSSecurity',
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]
$Branch = 'master'
)
$ErrorActionPreference = 'Stop'
function Resolve-RepositoryPath {
<#
Returns the path of a link target relative to the repository root, with / as separator, or nothing if the
link leaves the repository.
#>
param (
[Parameter(Mandatory)]
[string]
$Directory,
[Parameter(Mandatory)]
[string]
$Link
)
$segments = New-Object -TypeName 'System.Collections.Generic.List[string]'
foreach ($segment in (('{0}/{1}' -f $Directory, $Link) -split '/')) {
if ($segment -eq '..') {
if ($segments.Count -eq 0) {
return
}
$segments.RemoveAt($segments.Count - 1)
} elseif ($segment -and $segment -ne '.') {
$segments.Add($segment)
}
}
$segments -join '/'
}
function ConvertTo-WikiLink {
<#
Returns the wiki address of a link on a page in Directory: the name of a wiki page, the address of a file of
the repository on GitHub, or the link itself if it is absolute or points to the same page.
#>
param (
[Parameter(Mandatory)]
[string]
$Url,
[Parameter(Mandatory)]
[string]
$Directory
)
if ($Url -match '^(?:[a-zA-Z][a-zA-Z0-9+.-]*:|//|#)') {
return $Url
}
$linkPath, $anchor = $Url -split '#', 2
$target = Resolve-RepositoryPath -Directory $Directory -Link ([uri]::UnescapeDataString($linkPath))
if (-not $target) {
return $Url
}
$fragment = if ($anchor) { '#' + $anchor } else { '' }
if ($pageNames.ContainsKey($target)) {
return $pageNames[$target] + $fragment
}
$view = if (Test-Path -LiteralPath (Join-Path -Path $repositoryRoot -ChildPath $target) -PathType Container) {
'tree'
} else {
'blob'
}
'{0}/{1}/{2}/{3}{4}' -f $RepositoryUrl.TrimEnd('/'), $view, $Branch, $target, $fragment
}
function Convert-MarkdownLink {
<#
Returns the Markdown text with the links of a page in Directory converted to wiki addresses. Links in code
spans and fenced code blocks stay unchanged.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSReviewUnusedParameter', 'Directory', Justification = 'The match evaluator script block uses it.'
)]
param (
[Parameter(Mandatory)]
[AllowEmptyString()]
[string]
$Markdown,
[Parameter(Mandatory)]
[string]
$Directory
)
$inlinePattern = '(?<code>(?<ticks>`+).+?\k<ticks>)|(?<prefix>!?\[(?:[^\[\]`]|`[^`]*`)*\]\()(?<url>[^)\s]+)(?<suffix>(?:\s+"[^"]*")?\))'
$definitionPattern = '^(?<prefix>\s{0,3}\[[^\]]+\]:\s*)(?<url>\S+)(?<suffix>.*)$'
$convertLink = {
param ($match)
if ($match.Groups['code'].Success) {
return $match.Value
}
$match.Groups['prefix'].Value + (ConvertTo-WikiLink -Url $match.Groups['url'].Value -Directory $Directory) +
$match.Groups['suffix'].Value
}
$fence = $null
$lines = foreach ($line in ($Markdown -split '\r?\n')) {
if ($line -match '^\s{0,3}(?<fence>`{3,}|~{3,})') {
if (-not $fence) {
$fence = $Matches['fence']
} elseif ($Matches['fence'].StartsWith($fence)) {
$fence = $null
}
$line
continue
}
if ($fence) {
$line
continue
}
$line = [regex]::Replace($line, $inlinePattern, $convertLink)
[regex]::Replace($line, $definitionPattern, $convertLink)
}
$lines -join "`n"
}
$docsRoot = (Resolve-Path -LiteralPath $Path).ProviderPath.TrimEnd('\', '/')
$repositoryRoot = Split-Path -Path $docsRoot -Parent
$docsName = Split-Path -Path $docsRoot -Leaf
$destinationRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath).TrimEnd('\', '/')
# Refuse a destination that contains the documentation, such as the repository itself.
$separator = [IO.Path]::DirectorySeparatorChar
if (($docsRoot + $separator).StartsWith($destinationRoot + $separator, [StringComparison]::OrdinalIgnoreCase)) {
throw "The destination '$destinationRoot' contains the documentation in '$docsRoot'. Specify a clone of the wiki."
}
# Collect the pages: Docs/README.md is the home page, and the contributor guide stays in the repository.
$pageNames = @{}
$pages = foreach ($file in Get-ChildItem -LiteralPath $docsRoot -Filter '*.md' -File -Recurse) {
$repositoryPath = $file.FullName.Substring($repositoryRoot.Length + 1) -replace '\\', '/'
if ($repositoryPath -match ('^{0}/Contributing(?:\.md$|/)' -f [regex]::Escape($docsName))) {
continue
}
$name = if ($repositoryPath -eq "$docsName/README.md") { 'Home' } else { $file.BaseName }
if ($pageNames.Values -contains $name) {
throw "Two pages in '$docsRoot' would become the wiki page '$name'."
}
$pageNames[$repositoryPath] = $name
$content = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8
$title = if ($content -match '(?m)^#\s+(?<title>.+?)\s*$') { $Matches['title'] } else { $name -replace '-', ' ' }
[pscustomobject]@{
Name = $name
Title = $title
Directory = $repositoryPath.Substring(0, $repositoryPath.LastIndexOf('/'))
Content = $content
}
}
# Remove the old pages, but keep the history of the wiki.
if (Test-Path -LiteralPath $destinationRoot) {
foreach ($item in Get-ChildItem -LiteralPath $destinationRoot -Force | Where-Object -Property Name -NE -Value '.git') {
if ($PSCmdlet.ShouldProcess($item.FullName, 'Remove')) {
Remove-Item -LiteralPath $item.FullName -Recurse -Force
}
}
} elseif ($PSCmdlet.ShouldProcess($destinationRoot, 'Create folder')) {
New-Item -ItemType Directory -Path $destinationRoot | Out-Null
}
$wikiPages = [ordered]@{}
foreach ($page in $pages) {
$content = [regex]::Replace($page.Content, '\A---\r?\n.*?\r?\n---[ \t]*(?:\r?\n|\z)\s*', '', 'Singleline')
$wikiPages[$page.Name] = Convert-MarkdownLink -Markdown $content -Directory $page.Directory
}
# The sidebar links the other pages and the cmdlets, grouped like the cmdlet list of the home page.
$homePage = $pages | Where-Object -Property Name -EQ -Value 'Home'
$sidebar = New-Object -TypeName 'System.Collections.Generic.List[string]'
$sidebar.Add('### [Home](Home)')
$sidebar.Add('')
foreach ($page in $pages | Where-Object { $_.Directory -eq $docsName -and $_.Name -ne 'Home' } | Sort-Object -Property Name) {
$sidebar.Add(('- [{0}]({1})' -f $page.Title, $page.Name))
}
if ($homePage) {
$section = $null
foreach ($line in ($homePage.Content -split '\r?\n')) {
if ($line -match '^##\s+(?<title>.+?)\s*$') {
$section = $Matches['title']
if ($section -eq 'Cmdlets') {
$sidebar.Add('')
$sidebar.Add('### Cmdlets')
}
} elseif ($section -eq 'Cmdlets' -and $line -match '^###\s+(?<title>.+?)\s*$') {
$sidebar.Add('')
$sidebar.Add(('**{0}**' -f $Matches['title']))
$sidebar.Add('')
} elseif ($section -eq 'Cmdlets' -and $line -match '^\|\s*\[(?<name>[^\]]+)\]\((?<url>[^)\s]+)\)') {
$sidebar.Add(('- [{0}]({1})' -f $Matches['name'], (ConvertTo-WikiLink -Url $Matches['url'] -Directory $docsName)))
}
}
}
$wikiPages['_Sidebar'] = $sidebar -join "`n"
$wikiPages['_Footer'] = ('This wiki is generated from the [{0}]({1}/tree/{2}/{0}) folder of the repository. ' +
'To change a page, edit its file there; changes made in the wiki are overwritten.') -f $docsName, $RepositoryUrl.TrimEnd('/'), $Branch
# The former wiki page How-to-install is linked from outside; it now points to the installation steps.
if ($homePage -and $homePage.Content -match '(?m)^##\s+Installation\s*$') {
$wikiPages['How-to-install'] = "# How to install`n`nThe installation steps are in the [Installation](Home#installation) section of the [Home](Home) page."
}
$encoding = New-Object -TypeName 'System.Text.UTF8Encoding' -ArgumentList $false
foreach ($name in $wikiPages.Keys) {
$file = Join-Path -Path $destinationRoot -ChildPath "$name.md"
if ($PSCmdlet.ShouldProcess($file, 'Write wiki page')) {
[IO.File]::WriteAllText($file, $wikiPages[$name].TrimEnd() + "`n", $encoding)
}
}

6
CHANGELOG.md

@ -8,6 +8,12 @@ The format is based on
## [Unreleased] ## [Unreleased]
### Added
- Publish the documentation in the
[wiki](https://github.com/raandree/NTFSSecurity/wiki), generated from the
`Docs` folder after every change, with a sidebar that lists all cmdlets
## [5.0.0] - 2026-10-04 ## [5.0.0] - 2026-10-04
### Changed ### Changed

4
Docs/Version-History.md

@ -1,8 +1,8 @@
# Version history # Version history
This page lists the changes in NTFSSecurity 4.2.6 and earlier. It replaces This page lists the changes in NTFSSecurity 4.2.6 and earlier. It replaces
the version history in the former GitHub wiki. For the changes since 4.2.6, the hand-written version history that the GitHub wiki kept until 2018. For the
see the [changelog](../CHANGELOG.md). changes since 4.2.6, see the [changelog](../CHANGELOG.md).
## 4.2.5 and 4.2.6 ## 4.2.5 and 4.2.6

4
README.md

@ -43,6 +43,10 @@ Get-ChildItem2 -Path C:\Data -Recurse | Get-NTFSAccess -ExcludeInherited
## Documentation ## Documentation
Read the documentation in the
[wiki](https://github.com/raandree/NTFSSecurity/wiki), which is generated
from the `Docs` folder, or in the folder itself:
- [Overview](Docs/README.md): features, requirements, and the list of cmdlets - [Overview](Docs/README.md): features, requirements, and the list of cmdlets
- [Concepts](Docs/Concepts.md): security descriptors, access rights, - [Concepts](Docs/Concepts.md): security descriptors, access rights,
inheritance, privileges, long paths, and module settings inheritance, privileges, long paths, and module settings

228
Tests/Wiki.Tests.ps1

@ -0,0 +1,228 @@
<#
Tests .github\scripts\Export-WikiContent.ps1, which converts the documentation in Docs into the pages of the
GitHub wiki: with a small sample of Docs for the conversion rules, and with the real Docs for complete pages and
working links.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeAll {
$exportScript = Join-Path -Path $PSScriptRoot -ChildPath '..\.github\scripts\Export-WikiContent.ps1'
function Get-GitHubAnchor {
# Returns the anchors that GitHub generates for the headings of a Markdown text.
param (
[Parameter(Mandatory)]
[string]
$Markdown
)
$text = [regex]::Replace($Markdown, '(?ms)^```.*?^```', '')
$seen = @{}
foreach ($match in [regex]::Matches($text, '(?m)^#{1,6}\s+(.+?)\s*#*\s*$')) {
$heading = $match.Groups[1].Value -replace '\[([^\]]*)\]\([^)]*\)', '$1' -replace '[`*]', ''
$anchor = [regex]::Replace($heading.Trim().ToLowerInvariant(), '[^\p{L}\p{Nd}\s_-]', '') -replace ' ', '-'
if ($seen.ContainsKey($anchor)) {
$seen[$anchor]++
'{0}-{1}' -f $anchor, $seen[$anchor]
} else {
$seen[$anchor] = 0
$anchor
}
}
}
}
Describe 'Export-WikiContent.ps1' {
Context 'When it converts a sample of Docs' {
BeforeAll {
$repositoryPath = Join-Path -Path $TestDrive -ChildPath 'repository'
$docsPath = Join-Path -Path $repositoryPath -ChildPath 'Docs'
$wikiPath = Join-Path -Path $TestDrive -ChildPath 'wiki'
foreach ($folder in "$docsPath\Cmdlets", "$docsPath\Contributing", "$wikiPath\.git") {
New-Item -ItemType Directory -Path $folder -Force | Out-Null
}
# A clone of the wiki with pages that Docs doesn't have
Set-Content -LiteralPath "$wikiPath\.git\config" -Value '[core]'
Set-Content -LiteralPath "$wikiPath\Version-History.textile" -Value '* 4.2.4'
Set-Content -LiteralPath "$wikiPath\Old-Page.md" -Value '# Old page'
Set-Content -LiteralPath "$repositoryPath\CHANGELOG.md" -Value '# Changelog'
Set-Content -LiteralPath "$docsPath\Contributing.md" -Value '# Contributing'
Set-Content -LiteralPath "$docsPath\Contributing\01-Getting-Started.md" -Value '# Get started'
Set-Content -LiteralPath "$docsPath\Concepts.md" -Value "# Concepts`n`n## Rights`n`nSee [Home](README.md)."
Set-Content -LiteralPath "$docsPath\README.md" -Value @'
# Thing
[Concepts](Concepts.md#rights), [Get-Thing](Cmdlets/Get-Thing.md), and [`Set-Thing`](Cmdlets/Set-Thing.md).
[Changelog](../CHANGELOG.md), [guide](Contributing.md), and [first steps](Contributing/01-Getting-Started.md).
[Example site](https://example.com/page.md) and [cmdlets](#cmdlets).
Code keeps its links: `[Concepts](Concepts.md)`.
```powershell
# [Concepts](Concepts.md)
```
## Installation
Install the module.
## Cmdlets
### Getting
| Cmdlet | Description |
| --- | --- |
| [Get-Thing](Cmdlets/Get-Thing.md) | Gets a thing. |
### Setting
| Cmdlet | Description |
| --- | --- |
| [Set-Thing](Cmdlets/Set-Thing.md) | Sets a thing. |
'@
$metadata = "---`nexternal help file: Thing.dll-Help.xml`nonline version: https://example.com`nschema: 2.0.0`n---`n`n"
Set-Content -LiteralPath "$docsPath\Cmdlets\Get-Thing.md" -Value ($metadata +
"# Get-Thing`n`nSee [rights](../Concepts.md#rights), [Set-Thing](Set-Thing.md), and [home](../README.md).")
Set-Content -LiteralPath "$docsPath\Cmdlets\Set-Thing.md" -Value ($metadata +
"# Set-Thing`n`nSee [Get-Thing](Get-Thing.md).")
& $exportScript -Path $docsPath -DestinationPath $wikiPath -RepositoryUrl 'https://github.com/contoso/Thing' -Branch 'main'
$homePage = Get-Content -LiteralPath "$wikiPath\Home.md" -Raw
$sidebar = Get-Content -LiteralPath "$wikiPath\_Sidebar.md" -Raw
}
It 'Should write Home, Concepts, the cmdlet pages, How-to-install, the sidebar, and the footer' {
$expected = 'Home.md', 'Concepts.md', 'Get-Thing.md', 'Set-Thing.md', 'How-to-install.md', '_Sidebar.md', '_Footer.md'
((Get-ChildItem -LiteralPath $wikiPath -File).Name | Sort-Object) -join ', ' |
Should -BeExactly (($expected | Sort-Object) -join ', ')
}
It 'Should keep the .git folder and remove the pages that Docs does not have' {
"$wikiPath\.git\config" | Should -Exist
"$wikiPath\Version-History.textile" | Should -Not -Exist
"$wikiPath\Old-Page.md" | Should -Not -Exist
}
It 'Should not publish the contributor guide' {
Get-ChildItem -LiteralPath $wikiPath -Recurse -File -Filter '*Get*Started*' | Should -BeNullOrEmpty
"$wikiPath\Contributing.md" | Should -Not -Exist
}
It 'Should convert <Link> on <Page> into <Expected>' -ForEach @(
@{ Page = 'Home'; Link = '[Concepts](Concepts.md#rights)'; Expected = '[Concepts](Concepts#rights)' }
@{ Page = 'Home'; Link = '[Get-Thing](Cmdlets/Get-Thing.md)'; Expected = '[Get-Thing](Get-Thing)' }
@{ Page = 'Home'; Link = '[`Set-Thing`](Cmdlets/Set-Thing.md)'; Expected = '[`Set-Thing`](Set-Thing)' }
@{ Page = 'Home'; Link = '[Changelog](../CHANGELOG.md)'; Expected = '[Changelog](https://github.com/contoso/Thing/blob/main/CHANGELOG.md)' }
@{ Page = 'Home'; Link = '[guide](Contributing.md)'; Expected = '[guide](https://github.com/contoso/Thing/blob/main/Docs/Contributing.md)' }
@{ Page = 'Home'; Link = '[first steps](Contributing/01-Getting-Started.md)'; Expected = '[first steps](https://github.com/contoso/Thing/blob/main/Docs/Contributing/01-Getting-Started.md)' }
@{ Page = 'Home'; Link = '[Example site](https://example.com/page.md)'; Expected = '[Example site](https://example.com/page.md)' }
@{ Page = 'Home'; Link = '[cmdlets](#cmdlets)'; Expected = '[cmdlets](#cmdlets)' }
@{ Page = 'Concepts'; Link = '[Home](README.md)'; Expected = '[Home](Home)' }
@{ Page = 'Get-Thing'; Link = '[rights](../Concepts.md#rights)'; Expected = '[rights](Concepts#rights)' }
@{ Page = 'Get-Thing'; Link = '[Set-Thing](Set-Thing.md)'; Expected = '[Set-Thing](Set-Thing)' }
@{ Page = 'Get-Thing'; Link = '[home](../README.md)'; Expected = '[home](Home)' }
) {
$content = Get-Content -LiteralPath "$wikiPath\$Page.md" -Raw
$content | Should -Match ([regex]::Escape($Expected))
if ($Link -ne $Expected) {
$content | Should -Not -Match ([regex]::Escape($Link))
}
}
It 'Should keep links in code unchanged' {
$homePage | Should -Match ([regex]::Escape('`[Concepts](Concepts.md)`'))
$homePage | Should -Match ([regex]::Escape('# [Concepts](Concepts.md)'))
}
It 'Should remove the platyPS metadata from the cmdlet pages' {
Get-Content -LiteralPath "$wikiPath\Get-Thing.md" -TotalCount 1 | Should -BeExactly '# Get-Thing'
}
It 'Should link Home and the other pages in the sidebar' {
$sidebar | Should -Match ([regex]::Escape('[Home](Home)'))
$sidebar | Should -Match ([regex]::Escape('[Concepts](Concepts)'))
}
It 'Should list the cmdlets in the sidebar under the groups of Docs/README.md' {
$sidebar | Should -Match '(?s)Getting.*\[Get-Thing\]\(Get-Thing\).*Setting.*\[Set-Thing\]\(Set-Thing\)'
}
It 'Should say in the footer that the wiki is generated from Docs' {
Get-Content -LiteralPath "$wikiPath\_Footer.md" -Raw |
Should -Match ([regex]::Escape('(https://github.com/contoso/Thing/tree/main/Docs)'))
}
It 'Should keep the address of the former page How-to-install' {
Get-Content -LiteralPath "$wikiPath\How-to-install.md" -Raw | Should -Match ([regex]::Escape('(Home#installation)'))
}
}
Context 'When it converts the documentation of the repository' {
BeforeAll {
$repositoryPath = Join-Path -Path $PSScriptRoot -ChildPath '..'
$docsPath = Join-Path -Path $repositoryPath -ChildPath 'Docs'
$wikiPath = Join-Path -Path $TestDrive -ChildPath 'wiki-of-the-repository'
& $exportScript -Path $docsPath -DestinationPath $wikiPath
$pages = Get-ChildItem -LiteralPath $wikiPath -Filter '*.md'
$cmdletNames = (Get-ChildItem -LiteralPath (Join-Path -Path $docsPath -ChildPath 'Cmdlets') -Filter '*.md').BaseName
}
It 'Should write a page for every page in Docs except the contributor guide, and one for every cmdlet' {
$topPages = (Get-ChildItem -LiteralPath $docsPath -Filter '*.md' |
Where-Object -Property Name -NotIn -Value 'README.md', 'Contributing.md').BaseName
$expected = @('Home', 'How-to-install', '_Sidebar', '_Footer') + $topPages + $cmdletNames
($pages.BaseName | Sort-Object) -join ', ' | Should -BeExactly (($expected | Sort-Object) -join ', ')
}
It 'Should keep the page name Version-History, which the release notes of 4.2.4 and 4.2.6 link to' {
Join-Path -Path $wikiPath -ChildPath 'Version-History.md' | Should -Exist
}
It 'Should list every cmdlet in the sidebar' {
$sidebar = Get-Content -LiteralPath (Join-Path -Path $wikiPath -ChildPath '_Sidebar.md') -Raw
$cmdletNames | Where-Object -FilterScript { $sidebar -notmatch ('\]\({0}\)' -f [regex]::Escape($_)) } |
Should -BeNullOrEmpty
}
It 'Should link only to existing wiki pages, their anchors, and existing files of the repository' {
$anchors = @{}
foreach ($page in $pages) {
$anchors[$page.BaseName] = @(Get-GitHubAnchor -Markdown (Get-Content -LiteralPath $page.FullName -Raw))
}
$brokenLinks = foreach ($page in $pages) {
$text = [regex]::Replace((Get-Content -LiteralPath $page.FullName -Raw), '(?ms)^```.*?^```', '')
$text = [regex]::Replace($text, '`[^`\n]+`', '')
foreach ($match in [regex]::Matches($text, '\]\((?<url>[^)\s]+)\)')) {
$url = $match.Groups['url'].Value
if ($url -match '^https://github\.com/raandree/NTFSSecurity/(?:blob|tree)/master/(?<path>[^#]+)') {
if (-not (Test-Path -LiteralPath (Join-Path -Path $repositoryPath -ChildPath $Matches['path']))) {
'{0}: {1}' -f $page.BaseName, $url
}
} elseif ($url -notmatch '^[a-z]+:') {
$target, $anchor = $url -split '#', 2
if (-not $target) {
$target = $page.BaseName
}
if (-not $anchors.ContainsKey($target) -or ($anchor -and $anchors[$target] -notcontains $anchor)) {
'{0}: {1}' -f $page.BaseName, $url
}
}
}
}
$brokenLinks | Should -BeNullOrEmpty
}
}
}
Loading…
Cancel
Save