Documentarian.MicrosoftDocs.psm1

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

#region Functions.Public

function Get-HtmlMetaTags {

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [uri]$ArticleUrl,

        [switch]$ShowRequiredMetadata
    )

    $hash = [ordered]@{}

    $x = Invoke-WebRequest $ArticleUrl
    $lines = (($x -split "`n").trim() | Select-String -Pattern '\<meta').line | ForEach-Object {
        $_.trimstart('<meta ').trimend(' />') | Sort-Object
    }
    $pattern = '(name|property)="(?<key>[^"]+)"\s*content="(?<value>[^"]+)"'
    foreach ($line in $lines) {
        if ($line -match $pattern) {
            if ($hash.Contains($Matches.key)) {
                $hash[($Matches.key)] += ',' + $Matches.value
            } else {
                $hash.Add($Matches.key, $Matches.value)
            }
        }
    }

    $result = New-Object -type psobject -prop ($hash)
    if ($ShowRequiredMetadata) {
        $result | Select-Object title, 'og:title', description, 'ms.manager', 'ms.author', author, 'ms.service', 'ms.date', 'ms.topic', 'ms.subservice', 'ms.prod', 'ms.technology', 'ms.custom', 'ROBOTS'
    } else {
        $result
    }

}

function Get-LocaleFreshness {

    [CmdletBinding()]
    [OutputType('DocumentLocaleInfo')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [uri]$Uri,

        [Parameter(Position = 1)]
        [ValidatePattern('[a-z]{2}-[a-z]{2}')]
        [string[]]$Locales = (
            'en-us', 'cs-cz', 'de-de', 'es-es', 'fr-fr', 'hu-hu', 'id-id', 'it-it',
            'ja-jp', 'ko-kr', 'nl-nl', 'pl-pl', 'pt-br', 'pt-pt', 'ru-ru', 'sv-se',
            'tr-tr', 'zh-cn', 'zh-tw'
        )
    )

    $locale = $uri.Segments[1].Trim('/')
    if ($locale -notmatch '[a-z]{2}-[a-z]{2}') {
        Write-Error "URL does not contain a valid locale: $locale"
        return
    } else {
        $url = $uri.OriginalString
        $Locales | ForEach-Object {
            $locPath = $_
            $result = Get-HtmlMetaTags ($url -replace $locale, $locPath) |
                Select-Object @{n = 'locpath'; e = { $locPath } }, locale, 'ms.contentlocale',
                'ms.translationtype', 'ms.date', 'loc_version', 'updated_at', 'loc_source_id',
                'loc_file_id', 'original_content_git_url'
                $result.pstypenames.Insert(0, 'DocumentLocaleInfo')
                $result
            } | Sort-Object 'updated_at', 'ms.contentlocale'
    }

}

function Sync-BeyondCompare {

    [cmdletbinding()]
    param (
        [Parameter(Mandatory, Position = 0)]
        [string]$Path
    )

    ### Get-GitStatus comes from the posh-git module.
    $gitStatus = Get-GitStatus
    if ($gitStatus) {
        $reponame = $GitStatus.RepoName
    } else {
        Write-Warning 'Not a git repo.'
        return
    }
    $repoPath = $global:git_repos[$reponame].path
    $ops = Get-Content $repoPath\.openpublishing.publish.config.json |
        ConvertFrom-Json -Depth 10 -AsHashtable
    $srcPath = $ops.docsets_to_publish.build_source_folder
    if ($srcPath -eq '.') { $srcPath = '' }

    $basePath = Join-Path $repoPath $srcPath '\'
    $mapPath = Join-Path $basePath $ops.docsets_to_publish.monikerPath
    $monikers = Get-Content $mapPath | ConvertFrom-Json -Depth 10 -AsHashtable
    $startPath = (Get-Item $Path).fullname

    $vlist = $monikers.keys | ForEach-Object { $monikers[$_].packageRoot }
    if ($startpath) {
        $relPath = $startPath -replace [regex]::Escape($basepath)
        $version = ($relPath -split '\\')[0]
        foreach ($v in $vlist) {
            if ($v -ne $version) {
                $target = $startPath -replace [regex]::Escape($version), $v
                if (Test-Path $target) {
                    Start-Process -Wait "${env:ProgramFiles}\Beyond Compare 4\BComp.exe" -ArgumentList $startpath, $target
                }
            }
        }
    } else {
        Write-Error "Invalid path: $Path"
    }

}

function Sync-VSCode {

    [cmdletbinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Path
    )

    ### Get-GitStatus comes from the posh-git module.
    $gitStatus = Get-GitStatus
    if ($gitStatus) {
        $reponame = $GitStatus.RepoName
    } else {
        Write-Warning 'Not a git repo.'
        return
    }
    $repoPath = $global:git_repos[$reponame].path
    $ops = Get-Content $repoPath\.openpublishing.publish.config.json | ConvertFrom-Json -Depth 10 -AsHashtable
    $srcPath = $ops.docsets_to_publish.build_source_folder
    if ($srcPath -eq '.') { $srcPath = '' }
    $basePath = Join-Path $repoPath $srcPath '\'
    $mapPath = Join-Path $basePath $ops.docsets_to_publish.monikerPath
    $monikers = Get-Content $mapPath | ConvertFrom-Json -Depth 10 -AsHashtable
    $startPath = (Get-Item $Path).fullname

    $vlist = $monikers.keys | ForEach-Object { $monikers[$_].packageRoot }
    if ($startpath) {
        $relPath = $startPath -replace [regex]::Escape($basepath)
        $version = ($relPath -split '\\')[0]
        foreach ($v in $vlist) {
            if ($v -ne $version) {
                $target = $startPath -replace [regex]::Escape($version), $v
                if (Test-Path $target) {
                    Start-Process -Wait -WindowStyle Hidden 'code' -ArgumentList '--diff', '--wait', '--reuse-window', $startpath, $target
                }
            }
        }
    } else {
        Write-Error "Invalid path: $Path"
    }

}

function Test-YamlTOC {

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Path
    )

    $toc = Get-Item $Path
    $basepath = "$($toc.Directory)\"

    $hrefs = (Select-String -Pattern '\s*href:\s+([\w\-\._/]+)\s*$' -Path $toc.FullName).Matches |
        ForEach-Object { $_.Groups[1].Value } |
        Sort-Object

    $hrefs | ForEach-Object {
        $file = $basepath + ($_ -replace '/', '\')
        if (-not (Test-Path $file)) {
            "File does not exist - $_"
        }
    }

    $files = Get-ChildItem $basepath\*.md, $basepath\*.yml -Recurse -File |
        Where-Object Name -NE 'toc.yml'

    $files.FullName | ForEach-Object {
        $file = ($_ -replace [regex]::Escape($basepath)) -replace '\\', '/'
        if ($hrefs -notcontains $file) {
            "File not in TOC - $file"
        }
    }

}

#endregion Functions.Public

$ExportableFunctions = @(
  'Get-HtmlMetaTags'
  'Get-LocaleFreshness'
  'Sync-BeyondCompare'
  'Sync-VSCode'
  'Test-YamlTOC'
)

Export-ModuleMember -Alias * -Function $ExportableFunctions