Private/New-Slug.ps1

# Copyright (c) 2026 Jeffrey Snover. All rights reserved.
# Licensed under the MIT License. See LICENSE file in the project root.

# Generates a URL-safe slug from arbitrary text.

function New-Slug {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Text,
        [int]$MaxLength = 60
    )

    # Normalise Unicode -> ASCII approximation where possible
    $Normalized = $Text.Normalize([System.Text.NormalizationForm]::FormD)
    $AsciiOnly  = [System.Text.StringBuilder]::new()
    foreach ($c in $Normalized.ToCharArray()) {
        $cat = [System.Globalization.CharUnicodeInfo]::GetUnicodeCategory($c)
        if ($cat -ne [System.Globalization.UnicodeCategory]::NonSpacingMark) {
            [void]$AsciiOnly.Append($c)
        }
    }

    $Slug = $AsciiOnly.ToString().ToLower()
    $Slug = [regex]::Replace($Slug, '[^\w\s\-]', '')   # keep word chars, spaces, hyphens
    $Slug = [regex]::Replace($Slug, '[\s_]+', '-')     # collapse whitespace to hyphens
    $Slug = $Slug.Trim('-')

    # Remove common stop-words that pad slugs without adding meaning
    $StopWords = @('\bthe\b', '\ba\b', '\ban\b', '\band\b', '\bof\b', '\bin\b',
                   '\bto\b', '\bfor\b', '\bwith\b', '\bon\b', '\bat\b')
    foreach ($sw in $StopWords) {
        $Slug = [regex]::Replace($Slug, $sw, '', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
    }
    $Slug = [regex]::Replace($Slug, '-{2,}', '-').Trim('-')

    if ($Slug.Length -gt $MaxLength) {
        # Cut at last hyphen before limit so we don't split a word mid-stream
        $Slug = $Slug.Substring(0, $MaxLength)
        $lastHyphen = $Slug.LastIndexOf('-')
        if ($lastHyphen -gt 10) { $Slug = $Slug.Substring(0, $lastHyphen) }
    }

    if ([string]::IsNullOrWhiteSpace($Slug)) {
        $Slug = 'document-' + (Get-Date -Format 'yyyyMMdd-HHmmss')
    }
    return $Slug
}