lib/Wildling.Cli.ps1

function New-WildlingCliArgs {
    return [pscustomobject]@{
        Selects      = New-Object System.Collections.Generic.List[int]
        Ranges       = New-Object System.Collections.Generic.List[object]
        Check        = $false
        Dictionaries = [ordered]@{}
        Patterns     = New-Object System.Collections.Generic.List[string]
        Help         = $false
        Version      = $false
    }
}

function ConvertFrom-WildlingRange {
    param([string] $Value)

    $dash = $Value.IndexOf('-')
    if ($dash -le 0 -or $dash -eq ($Value.Length - 1)) {
        return $null
    }

    $startText = $Value.Substring(0, $dash)
    $endText = $Value.Substring($dash + 1)
    if ($startText -notmatch '^\d+$' -or $endText -notmatch '^\d+$') {
        return $null
    }

    $start = [int]$startText
    $end = [int]$endText
    if ($start -gt $end) {
        return $null
    }

    return [pscustomobject]@{ Start = $start; End = $end }
}

function Get-WildlingDictionaryFile {
    param([string] $Path)

    return @(
        Get-Content -LiteralPath $Path -Encoding UTF8 |
            ForEach-Object { $_.Trim() } |
            Where-Object { $_ -ne '' }
    )
}

function Add-WildlingDictionary {
    param(
        $Result,
        [string] $Name,
        $Value
    )

    if ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) {
        $Result.Dictionaries[$Name] = @($Value | ForEach-Object { "$_" })
        return
    }

    $path = [string]$Value
    if ($path -and (Test-Path -LiteralPath $path)) {
        try {
            $Result.Dictionaries[$Name] = Get-WildlingDictionaryFile -Path $path
        }
        catch {
            # ignore unreadable dictionary files
        }
    }
}

function Import-WildlingTemplate {
    param(
        $Result,
        [string] $Path
    )

    if (-not (Test-Path -LiteralPath $Path)) {
        [Console]::Error.WriteLine("Template file not found: $Path")
        exit 1
    }

    try {
        $raw = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
        $template = $raw | ConvertFrom-Json
    }
    catch {
        [Console]::Error.WriteLine("Invalid JSON template: $Path")
        exit 1
    }

    if ($template -isnot [pscustomobject]) {
        [Console]::Error.WriteLine("Invalid JSON template: $Path")
        exit 1
    }

    if ($template.check -eq $true) {
        $Result.Check = $true
    }

    if ($null -ne $template.select) {
        foreach ($val in @($template.select)) {
            $number = 0
            if ([int]::TryParse([string]$val, [ref]$number) -and $number -ge 0) {
                $Result.Selects.Add($number)
            }
        }
    }

    if ($null -ne $template.range) {
        foreach ($rangeStr in @($template.range)) {
            $parsed = ConvertFrom-WildlingRange -Value ([string]$rangeStr)
            if ($null -ne $parsed) {
                $Result.Ranges.Add($parsed)
            }
        }
    }

    if ($null -ne $template.dictionaries) {
        foreach ($prop in $template.dictionaries.PSObject.Properties) {
            Add-WildlingDictionary -Result $Result -Name $prop.Name -Value $prop.Value
        }
    }

    if ($null -ne $template.patterns) {
        foreach ($pattern in @($template.patterns)) {
            $Result.Patterns.Add([string]$pattern)
        }
    }
}

function ConvertFrom-WildlingCliArgs {
    param([string[]] $Argv)

    $result = New-WildlingCliArgs
    $i = 0
    while ($i -lt $Argv.Count) {
        $arg = $Argv[$i]

        switch ($arg) {
            { $_ -in @('--help', '-h') } {
                $result.Help = $true
                $i++
                continue
            }
            { $_ -in @('--version', '-v') } {
                $result.Version = $true
                $i++
                continue
            }
            '--check' {
                $result.Check = $true
                $i++
                continue
            }
            '--select' {
                $i++
                if ($i -ge $Argv.Count) { break }
                $number = 0
                if ([int]::TryParse($Argv[$i], [ref]$number) -and $number -ge 0) {
                    $result.Selects.Add($number)
                }
                $i++
                continue
            }
            '--range' {
                $i++
                if ($i -ge $Argv.Count) { break }
                $parsed = ConvertFrom-WildlingRange -Value $Argv[$i]
                if ($null -ne $parsed) {
                    $Result.Ranges.Add($parsed)
                }
                $i++
                continue
            }
            '--dictionary' {
                $i++
                if ($i -ge $Argv.Count) { break }
                $spec = $Argv[$i]
                $colon = $spec.IndexOf(':')
                if ($colon -gt 0 -and $colon -lt ($spec.Length - 1)) {
                    $name = $spec.Substring(0, $colon)
                    $path = $spec.Substring($colon + 1)
                    Add-WildlingDictionary -Result $result -Name $name -Value $path
                }
                $i++
                continue
            }
            '--template' {
                $i++
                if ($i -ge $Argv.Count) {
                    [Console]::Error.WriteLine('Missing path for --template')
                    exit 1
                }
                Import-WildlingTemplate -Result $result -Path $Argv[$i]
                $i++
                continue
            }
            default {
                $result.Patterns.Add($arg)
                $i++
            }
        }
    }

    return $result
}

function Get-WildlingHelpText {
    $libDir = $PSScriptRoot
    $root = Split-Path -Parent $libDir
    $candidates = @(
        (Join-Path $root 'help.txt'),
        (Join-Path $root '..' 'docs' 'help.txt')
    )

    foreach ($path in $candidates) {
        if (Test-Path -LiteralPath $path) {
            return Get-Content -LiteralPath $path -Raw -Encoding UTF8
        }
    }

    return "wildling - pattern based string generator`n`nHelp text unavailable.`n"
}

function Format-WildlingList {
    param([object[]] $Values)

    if ($null -eq $Values -or $Values.Count -eq 0) {
        return ''
    }
    return ' ' + (($Values | ForEach-Object { "$_" }) -join ' ')
}

function Format-WildlingCheckOutput {
    param(
        $CliArgs,
        [int] $Total,
        [WildlingGenerator[]] $Generators
    )

    $rangeValues = @($CliArgs.Ranges | ForEach-Object { "$($_.Start)-$($_.End)" })
    $lines = @(
        "patterns:$(Format-WildlingList @($CliArgs.Patterns))",
        "dictionaries:$(Format-WildlingList @($CliArgs.Dictionaries.Keys))",
        "select:$(Format-WildlingList @($CliArgs.Selects))",
        "range:$(Format-WildlingList $rangeValues)",
        "total: $Total"
    )

    foreach ($gen in $Generators) {
        $lines += "generator: $($gen.Source) $($gen.GetCount())"
    }

    return ($lines -join "`n")
}

function Write-WildlingValue {
    param([object] $Value)

    [Console]::Out.WriteLine([string]$Value)
}

function Invoke-WildlingCli {
    param([string[]] $Argv)

    $parsed = ConvertFrom-WildlingCliArgs -Argv $Argv

    if ($parsed.Help) {
        [Console]::Out.WriteLine((Get-WildlingHelpText).TrimEnd())
        exit 0
    }

    if ($parsed.Version) {
        [Console]::Out.WriteLine("wildling $Script:WildlingVersion")
        exit 0
    }

    if ($parsed.Patterns.Count -eq 0) {
        [Console]::Error.WriteLine('No pattern provided. Use --help for usage information.')
        exit 1
    }

    $wildcard = New-WildlingClient -Patterns @($parsed.Patterns) -Dictionaries $parsed.Dictionaries

    if ($parsed.Check) {
        [Console]::Out.WriteLine((Format-WildlingCheckOutput -CliArgs $parsed -Total $wildcard.GetCount() -Generators $wildcard.GetGenerators()))
        exit 0
    }

    if ($parsed.Selects.Count -gt 0 -or $parsed.Ranges.Count -gt 0) {
        $oor = $false
        foreach ($index in $parsed.Selects) {
            $value = $wildcard.Get($index)
            if ($value -is [bool] -and $value -eq $false) {
                [Console]::Error.WriteLine("out of range: $index")
                $oor = $true
            }
            else {
                Write-WildlingValue $value
            }
        }
        foreach ($range in $parsed.Ranges) {
            for ($index = $range.Start; $index -le $range.End; $index++) {
                $value = $wildcard.Get($index)
                if ($value -is [bool] -and $value -eq $false) {
                    [Console]::Error.WriteLine("out of range: $index")
                    $oor = $true
                }
                else {
                    Write-WildlingValue $value
                }
            }
        }
        if ($oor) { exit 1 } else { exit 0 }
    }

    $value = $wildcard.Next()
    while ($value -isnot [bool] -or $value -ne $false) {
        Write-WildlingValue $value
        $value = $wildcard.Next()
    }
}