mirror of https://github.com/raandree/NTFSSecurity
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
10 KiB
279 lines
10 KiB
<#
|
|
.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)
|
|
}
|
|
}
|
|
|