Get-Tune.ps1

function Get-Tune {
    <#
    .SYNOPSIS
        Gets tunes
    .DESCRIPTION
        Gets musical tunes that can be played.
        Tunes can be stored in:
        |Format |Extension |
        |--------------------------|-------------------------|
        |Tune Clixml |```.tune.clixml``` |
        |Tune JSON |```.tune.json``` |
        |Tune PowerShell Data file |```.tune.psd1``` |
        |Tune Generator |```.tune.ps1``` |
        |Tune Text |```.tune.txt``` |
    .EXAMPLE
        Get-Tune
    #>
    
    param(
    # The path to tune files or directories.
    # If not provided, will default to looking for tunes beneath TerminalTunes and any modules that tag TerminalTunes.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('Fullname')]
    [string[]]
    $TunePath,
    # The title of a tune. Can include wildcards.
    [Parameter(ValueFromPipelineByPropertyName)]    
    [ArgumentCompleter({
        param ( $commandName,$parameterName,$wordToComplete,$commandAst,$fakeBoundParameters )    
                                
        $tuneList = @(if ($wordToComplete) {
            $toComplete = $wordToComplete -replace "^['`"]" -replace "['`"]$"
            Get-Tune -Title "$toComplete*"
        } else {
            Get-Tune
        })
        @(foreach ($tune in $tunelist) {
            if ($tune.Title) {
                $tune.Title
            }
        }) -replace '^', "'" -replace '$',"'"
    })]
    [string]
    $Title,
    # If set, will invalidate the cache of tunes and reload any tunes.
    [switch]
    $Force
    )
    begin {
        # If we do not have a tune cache, create one.
        if (-not $script:TuneCache) {
            $script:TuneCache = [Ordered]@{}
        }
        # Tune files can be .clixml, .json, .ps1, .psd1, or .txt
        $tuneExtensions = '.clixml','.json','.ps1', '.psd1', '.txt'
        # create a regex that will match any of them.
        $tuneFilePattern = "\.tune\.(?>$($tuneExtensions -replace '^\.' -join '|'))"
    }
    process {
        # If a -TunePath was not provided
        if (-not $TunePath) {            
            $MyModuleInfo = $MyInvocation.MyCommand.Module
            # We will use the TerminalTunes module path
            $myModulePath = $MyModuleInfo | Split-Path
            # Any the module paths of any modules that Tag 'TerminalTunes'
            $relatedModulePaths = @(
                
                @(
                $MyModuleName, $myModule = 
                    if ($MyModuleInfo -is [string]) {
                        $MyModuleInfo, (Get-Module $MyModuleInfo)
                    } elseif ($MyModuleInfo -is [Management.Automation.PSModuleInfo]) {
                        $MyModuleInfo.Name, $MyModuleInfo
                    } else {
                        Write-Error "$MyModuleInfo must be a [string] or [Management.Automation.PSModuleInfo]"    
                    }
                #region Search for Module Relationships
                if ($myModule -and $MyModuleName) {
                    foreach ($loadedModule in Get-Module) { # Walk over all modules.
                        if ( # If the module has PrivateData keyed to this module
                            $loadedModule.PrivateData.$myModuleName
                        ) {
                            # Determine the root of the module with private data.
                            $relationshipData = $loadedModule.PrivateData.$myModuleName
                            [PSCustomObject][Ordered]@{
                                PSTypeName     = 'Module.Relationship'
                                Module        = $myModule
                                RelatedModule = $loadedModule
                                PrivateData   = $loadedModule.PrivateData.$myModuleName
                            }
                        }
                        elseif ($loadedModule.PrivateData.PSData.Tags -contains $myModuleName) {
                            [PSCustomObject][Ordered]@{
                                PSTypeName     = 'Module.Relationship'
                                Module        = $myModule
                                RelatedModule = $loadedModule
                                PrivateData   = @{}
                            }
                        }
                    }
                }
                #endregion Search for Module Relationships
                )
                
            ) | 
                Select-Object -ExpandProperty RelatedModule | 
                Split-Path
            $TunePath = @($myModulePath) + @($relatedModulePaths)
        }
        
        # Now, create the tune list by walking thru each tune path
        $tunelist = @(foreach ($tp in $TunePath) {
            # get the path item
            $pathItem = Get-Item -Path $tp -ErrorAction Ignore
            if ($pathItem -is [IO.DirectoryInfo]) {
                # If it's a directory, call yourself recursively and cache it.
                if (-not $script:TuneCache.Contains($pathItem.FullName) -or $Force) {
                    $script:TuneCache[$pathItem.FullName] = @(
                        Get-ChildItem -Path $TunePath.Fullname -Recurse -Filter *.tune.* |
                            Where-Object Extension -In $tuneExtensions |
                            Get-Tune
                    )
                }
                if ($script:TuneCache.Contains($pathItem.FullName)) {
                    $script:TuneCache[$pathItem.FullName]
                }
            }
            elseif ($pathItem -is [IO.FileInfo]) {
                # If the path was a file
                # skip it if it doesn't match our pattern.
                if ($pathItem.Name -notmatch $tuneFilePattern) { continue }
                if (-not $script:TuneCache.Contains($pathItem.FullName) -or $Force) {
                    $tuneData = 
                    switch ($pathItem.Extension) {
                        # If it was .clixml, just import-clixml
                        '.clixml' {
                            Import-Clixml -Path $pathItem.FullName
                        }
                        # If it was .json
                        '.json' {                            
                            Get-Content -Path $pathItem.FullName -Raw | 
                                ConvertFrom-Json | # convert from json
                                ForEach-Object {
                                    if ($_.Tune) { # and fix escape sequences
                                        $_.Tune = $_.Tune -replace '[`\\]e', "`e"
                                    }
                                    $_
                                }
                        }
                        # If it was a .ps1
                        '.ps1' {
                            # get the script.
                            $ExecutionContext.SessionState.InvokeCommand.GetCommand($pathItem.FullName, 'ExternalScript')                                
                        }
                        # If it was a .PSD1
                        '.psd1' {
                            # read the file as data language.
                            [PSCustomObject](& ([ScriptBlock]::Create("data {$($(Get-Content -Path $pathItem.FullName -Raw)) }")))
                        }
                        # If it was a .TXT file
                        '.txt' {
                            # Replace the file title and include the tune.
                            [PSCustomObject][Ordered]@{
                                Title = $pathItem.Name -replace '\.tune\.txt$' -replace '[_-]', ' '
                                Tune  = (Get-Content -Path $pathItem.FullName -Raw) -replace '[`\\]e', "`e"
                            }
                        }
                    }
                    
                    # If we had no tune data, clear the cache at this point
                    if (-not $tuneData) {
                        $script:TuneCache[$pathItem.FullName] = $null
                        continue
                    }
                    
                    # Decorate all tune data as a 'TerminalTunes.Tune'
                    $tuneData.pstypenames.clear()
                    $tuneData.pstypenames.add('TerminalTunes.Tune')
                    # If it was a script,
                    if ($tuneData -is [Management.Automation.ExternalScriptInfo]) {
                        # also decorate it to be a 'TerminalTunes.Generator'
                        $tuneData.pstypenames.add('TerminalTunes.Generator')
                    }
                    $script:TuneCache[$pathItem.FullName] = $tuneData
                }
                $tuneData = $script:TuneCache[$pathItem.FullName]
                if ($tuneData) {
                    $tuneData
                }
            }
        })
        # If -Title was provided
        if ($Title) {
            # filter the tunes by title
            $tuneList | Where-Object Title -Like $title
        }
        else {
            # otherwise, output all tunes.
            $tuneList
        }
    }   
}