Commands/Get-CTAN.ps1

function Get-CTAN
{
    <#
    .SYNOPSIS
        Gets CTAN
    .DESCRIPTION
        Gets data from CTAN, the Comprehesive TeX Archive Network
    .EXAMPLE
        # Get all CTAN packages
        Get-CTAN
    .EXAMPLE
        # Get all CTAN packages using the alias `ctan`
        ctan
    .EXAMPLE
        # Get all CTAN packages using the alias `ctan.org`
        ctan.org
    .EXAMPLE
        ctan -ListAuthor
    .EXAMPLE
        ctan.org -Author knuth
    .EXAMPLE
        ctan -ListTopic
    .EXAMPLE
        ctan.org -Topic word-count
    .EXAMPLE
        ctan -ListLicense
    .LINK
        https://ctan.org/
    .LINK
        https://ctan.org/help/json
    .LINK
        https://ctan.org/help/json/2.0/pkg
    .LINK
        https://ctan.org/help/json/2.0/authors
    .LINK
        https://ctan.org/help/json/2.0/topics
    #>

    [CmdletBinding(
        DefaultParameterSetName='https://ctan.org/json/2.0/packages',
        SupportsPaging
    )]
    [Alias('ctan', 'ctan.org')]
    param(
    # The name of a specific package.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/json/2.0/pkg')]
    [string]
    $Package,

    # The name of a specific ctan author.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/json/2.0/author')]
    [string]
    $Author,

    # The name of a specific ctan topic.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/json/2.0/topic')]
    [string]
    $Topic,

    # The phrase to search for using the ctan api.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/search/json')]
    [Alias('Phrase')]
    [string]
    $Search,

    # The maximum number of items to return.
    [Parameter(ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/search/json')]
    [ValidateRange(1,256)]
    [Alias('Maxiumum')]
    [int]    
    $Max = 128,

    # The sections to search. If none are provided, all sections will be searched.
    [Parameter(ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/search/json')]
    [ValidateSet('Package','Author','Topic','Portal')]
    [string[]]    
    $SearchSection,

    # If set, will get a list of topics from ctan.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/json/2.0/topics')]
    [Alias('ListTopics', 'TopicList')]
    [switch]
    $ListTopic,

    # If set, will get a list of authors from ctan.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/json/2.0/authors')]
    [Alias('ListAuthors', 'AuthorList')]
    [switch]
    $ListAuthor,

    # If set, will get a list of licenses from ctan.
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,
        ParameterSetName='https://ctan.org/json/2.0/licenses')]
    [Alias('ListLicenses', 'LicenseList')]
    [switch]
    $ListLicense,

    # If set, will remove any cached response.
    [switch]
    $Force
    )
    
    begin {
        # First create ourselves a cache
        if (-not $script:ctanCache) {
            $script:ctanCache = [Ordered]@{}
        }        
    }

    process {
        # Next get the url by looking at the parameter set name
        # Parameter set names represent a base url to the ctan api
        # Thus this one command can represent any number of endpoints.
        $url = switch -regex ($PSCmdlet.ParameterSetName) {
            '(?>author|topic|pkg)$' {
                # Packages, authors, and topics are all the same
                @(                    
                    $PSCmdlet.ParameterSetName                    
                    "$Author".ToLower()
                    "$Topic".ToLower()
                    "$Package".ToLower()
                ) -ne '' -join '/'
                # just join things by a slash
                break
            }
            '/search/' {
                # Search is the "complicated" one. We have to:
                @(
                    # * Include the base url
                    "$($PSCmdlet.ParameterSetName)?"
                    @(
                        # * Encode the search term
                        "phrase=$(
                            [Web.HttpUtility]::UrlEncode($search) -replace '\+', '%20'
                        )"

                        # * Add an offset if we `-Skip` N items
                        if ($psCmdlet.PagingParameters.Skip) {
                            "offset=$($psCmdlet.PagingParameters.Skip)"
                            $psCmdlet.PagingParameters.Remove('Skip')                            
                        }
                        # * Provide a maximum value
                        "max=$max"
                        # * If the provided a section
                        if ($SearchSection) {
                            # * Set ext to true
                            "ext=true"
                            # * and include the right term for the section
                            switch ($SearchSection) {
                                package {"PKG=true"}
                                author {"AUTHORS=true"}
                                topic {"TOPICS=true"}
                                portal {"PORTAL=true"}
                            }                            
                        }
                    ) -join '&' # * Join all of our query strings with `&`
                ) -join ''
            }            
            default {
                # For any other parameter set, we simply use it as the url.
                $PSCmdlet.ParameterSetName
            }
        }

        # Return if magically there is no url.
        if (-not $url) { return }
        # If -Force is passed
        if ($Force)  {
            # invalidate the cache.
            $script:ctanCache.Remove($url)
        }
        # If we don't have a cached value
        if (-not $script:ctanCache[$url]) {
            # get one.
            $script:ctanCache[$url] = Invoke-RestMethod -Uri $url
        }
        # If -First was provided (and less than 1mb)
        if ($psCmdlet.PagingParameters.First -lt 1mb) {
            # we will pipe to select-object
            $selectSplat = [Ordered]@{First = $psCmdlet.PagingParameters.First}
            # If we were searching and had hits
            if (@($script:ctanCache[$url].hits -ne $null).Count) {
                # output the first N hits
                $script:ctanCache[$url].hits | 
                    Select-Object @selectSplat                    
            } else {
                # otherwise
                # -Skip could also be useful
                if ($PSCmdlet.PagingParameters.Skip) {
                    $selectSplat.Skip = $PSCmdlet.PagingParameters.Skip
                }
                $script:ctanCache[$url] |
                    Select-Object @selectSplat                    
            }
        } else {
            # If no -First was provided, then output the cached value
            $script:ctanCache[$url]
        }

        # and we're done.
    }
}