psSearchGitHub.psm1

function Search-GitHubRepository {
    <#
    .SYNOPSIS
        Search GitHub repositories for a specific string.
    
    .DESCRIPTION
        Search GitHub repositories for a specific string using the GitHub CLI.
    
    .PARAMETER SearchValue
        The string to search for in the repositories.
    
    .PARAMETER Owner
        The owner of the repositories to search. If not provided, searches all accessible repositories.

    .PARAMETER Summary
        If specified, returns a summary of repositories containing matches instead of detailed match information.
    
    .EXAMPLE
        Search-GitHubRepository -SearchValue "PowerShell" -Owner "skatterbrainz"
        Searches the repositories owned by "skatterbrainz" for the string "PowerShell".
    
    .EXAMPLE
        Search-GitHubRepository -SearchValue "PowerShell" -Summary
        Searches all accessible repositories for the string "PowerShell" and returns a summary of repositories containing matches.
    
    .NOTES
        Requires GitHub CLI (gh) to be installed and authenticated.
    #>

    [CmdletBinding()]
    param (
        [parameter(Mandatory = $true)][string]$SearchValue,
        [parameter(Mandatory = $false)][string]$Owner,
        [parameter(Mandatory = $false)][switch]$Summary
    )
    try {
        if ([string]::IsNullOrEmpty($SearchValue)) {
            throw "No SearchValue was provided"
        }
        if ($IsLinux -or $IsMacOS) {
            $cmd = "gh"
        } else {
            $cmd = "gh.exe"
        }
        if (-not (Get-Command $cmd)) {
            throw "Install GitHub CLI first."
        }
        $arglist = "search code `"$SearchValue`""
        if (![string]::IsNullOrEmpty($Owner)) {
            $arglist += " --owner=$Owner"
        }
        $arglist += " --json repository,path,url,textMatches"

        # Build arguments array for direct execution
        $ghArgs = @('search', 'code', $SearchValue)
        if (![string]::IsNullOrEmpty($Owner)) {
            $ghArgs += "--owner=$Owner"
        }
        $ghArgs += @('--json', 'repository,path,url,textMatches')

        Write-Verbose "Command: $cmd $($arglist -join ' ')"
        $textmatches = & $cmd $ghArgs | ConvertFrom-Json
        Write-Host "$($textmatches.count) matches found" -ForegroundColor Cyan
        if ($Summary.IsPresent) {
            $textmatches | Select-Object -Property @{l = 'Repository'; e = { $_.repository.nameWithOwner } } |
                Select-Object -Property Repository | Sort-Object -Unique -Property Repository
        } else {
            $textmatches |
                Select-Object -Property path,url,@{l = 'Repository'; e = { $_.repository.nameWithOwner } },
                    @{l = 'Text'; e = { $_.textMatches.fragment } } |
                Sort-Object -Property Repository
        }
    } catch {
        [pscustomobject]@{
            Status   = "Error"
            Message  = $_.Exception.Message
            Trace    = $_.Exception.ScriptStackTrace
            Category = $_.Exception.CategoryInfo.Activity
        }
    }
}

function Search-GitHubGist {
    <#
    .SYNOPSIS
        Search GitHub gists for a specific string.
    
    .DESCRIPTION
        Search GitHub gists for a specific string using the GitHub CLI.
    
    .PARAMETER SearchValue
        The string to search for in the gists.
    
    .PARAMETER IncludeContent
        If specified, includes the matching content in the results.

    .PARAMETER Limit
        The maximum number of gists to search. Default is 100.

    .EXAMPLE
        Search-GitHubGist -SearchValue "PowerShell" -IncludeContent -Limit 50
        Searches the gists for the string "PowerShell", includes matching content, and limits the search to 50 gists.

    .EXAMPLE
        Search-GitHubGist -SearchValue "PowerShell"
        Searches the gists for the string "PowerShell" without including matching content and uses the default limit of 100 gists.
    
    .NOTES
        Requires GitHub CLI (gh) to be installed and authenticated.
    #>

    [CmdletBinding()]
    param (
        [parameter(Mandatory=$true)][string]$SearchValue,
        [parameter(Mandatory=$false)][switch]$IncludeContent,
        [parameter(Mandatory=$false)][int]$Limit = 100
    )
    try {
        if ([string]::IsNullOrEmpty($SearchValue)) {
            throw "No SearchValue was provided"
        }
        if ($IsLinux -or $IsMacOS) {
            $cmd = "gh"
        } else {
            $cmd = "gh.exe"
        }
        if (-not (Get-Command $cmd)) {
            throw "Install GitHub CLI first."
        }
        $ghArgs = @('gist', 'list', '--filter', $SearchValue, '--include-content', '--limit', $Limit)
        $gists = & $cmd @ghArgs
        <#
        Filter results to map lines to properties as follows:

        b5db0c256f73f300eaea8c50d7973f9d boxstarter_sample2.txt
            BoxStarter Examples
                Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | iex

        No spaces at the beginning of the line = id and filename
        4 spaces at the beginning of the line = description
        8 spaces at the beginning of the line = matching content
        #>

        $results = @()
        for ($i = 0; $i -lt $gists.Count; $i++) {
            $line = $gists[$i]
            if (![string]::IsNullOrEmpty($line)) {
                if (-not $line.StartsWith(" ")) {
                    # Line with no leading spaces = id and filename
                    $gistId      = $line.Substring(0, 32)
                    $filename    = $line.Substring(33)
                    $description = ""
                    $content     = ""

                    # Check next lines for description (4 spaces) and content (8 spaces)
                    if ($i + 1 -lt $gists.Count -and $gists[$i + 1].StartsWith(" ") -and -not $gists[$i + 1].StartsWith(" ")) {
                        $description = $gists[$i + 1].Trim()
                        if ($i + 2 -lt $gists.Count -and $gists[$i + 2].StartsWith(" ")) {
                            $content = $gists[$i + 2].Trim()
                        }
                    }

                    $results += [pscustomobject]@{
                        id          = $gistId
                        filename    = $filename
                        gistname    = $description
                        content     = $content
                    }
                }
            }
        }

        $results | foreach-object {
            $gistId   = $_.id
            $filename = $_.filename
            Write-Verbose "gist id: $gistId - filename: $filename"
            $gistContent = gh gist view $gistId --raw
            if ($IncludeContent.IsPresent) {
                Write-Verbose "Including content in results"
                $gistContent | select-string -Pattern $SearchValue -List |
                    select-object -Property @{l='gistId';e={$gistId}}, @{l='filename';e={$filename}}, @{l='line';e={$_.LineNumber}}, @{l='match';e={$_.Line}}
            } else {
                $gistContent | select-string -Pattern $SearchValue -List |
                    select-object -Property @{l='gistId';e={$gistId}}, @{l='filename';e={$filename}}, @{l='line';e={$_.LineNumber}}
            }
        }
    } catch {
        [pscustomobject]@{
            Status   = "Error"
            Message  = $_.Exception.Message
            Trace    = $_.Exception.StackTrace
            Category = $_.Exception.CategoryInfo.Activity
        }
    }
}