Import-TypeView.ps1

function Import-TypeView
{
    <#
    .Synopsis
        Imports a Type View
    .Description
        Imports a Type View, defined in a external file .method or .property file
    .Link
        Write-TypeView
    .Example
        Import-TypeView .\Types
    #>

    param(
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
    [Alias('FullName')]
    [string[]]
    $FilePath
    )

    process {

        <#
        In order to make something like inheritance work,
        we want to be able to define properties and methods a few places:
 
        * In the -FilePath directory (these apply to all types)
        * In a directory (these apply to that a type sharing the name of the directory)
        * In any nested subdirectory (these should apply like inherited types)
        #>



        $membersByType = @{}
        foreach ($fp in $FilePath) {
            $filePathRoot = Get-Item -Path $fp
            $filesBeneathRoot = Get-ChildItem -Recurse -Path $fp -Force



            foreach ($fbr in $filesBeneathRoot) {
                if ($fbr -is [IO.DirectoryInfo]) { continue }
                if ($fbr.Directory.FullName -eq $filePathRoot.FullName) {
                    # Files directly beneath the root become methods / properties shared by all typenames
                    if (-not $membersByType['*']) {
                        $membersByType['*'] = @()
                    }
                    $membersByType['*'] += $fbr
                } else {
                    $subTypeNames = @($fbr.FullName.Substring(
                        $filePathRoot.FullName.Length
                    ).TrimStart(
                        [IO.Path]::DirectorySeparatorChar
                    ).Split([IO.Path]::DirectorySeparatorChar))

                    foreach ($subType in $subTypeNames) {
                        if ($subType -eq $subTypeNames[-1]) { continue }
                        if (-not $membersByType[$subType]) {
                            $membersByType[$subType] = @()
                        }
                        $membersByType[$subType] += $fbr
                    }
                }
            }



        }

        if ($membersByType['*']) # If any members applied to all types
        {
            foreach ($k in @($membersByType.Keys)) { # Apply them to each member
                if ($k -ne '*') {
                    $membersByType[$k] += $membersByType['*']
                }
            }
            $membersByType.Remove('*') # then remove it.
        }

        foreach ($mt in $membersByType.GetEnumerator()) { # Walk thru the members by type
            $WriteTypeViewSplat = @{TypeName = $mt.Key}   # and create a hashtable to splat.
            # Then, sort the values by name and by if it comes from this directory.
            $sortedValues = $mt.Value | Sort-Object Name, { $_.Directory.Name -ne $mt.Key }
            $scriptMethods = [Ordered]@{}
            $scriptPropertyGet = [Ordered]@{}
            $scriptPropertySet = [Ordered]@{}
            $noteProperty = [Ordered]@{}
            $hideProperty = [Collections.Generic.List[string]]::new()
            foreach ($item in $sortedValues) {
                $itemName =
                    $item.Name.Substring(0, $item.Name.Length - $item.Extension.Length)

                if ($item.Extension -eq '.ps1') {
                    $isScript = $true
                    $scriptBlock =
                        $ExecutionContext.SessionState.InvokeCommand.GetCommand(
                            $item.Fullname, 'ExternalScript'
                        ).ScriptBlock
                } else {
                    $isScript = $false
                }

                if (-not $isScript -and $itemName -eq $item.Directory.Name) {
                    $itemName = 'Default'
                    $hideProperty += $itemName
                } elseif ($itemName.StartsWith('.')) {
                    $itemName = $itemName.TrimStart('.')
                    $hideProperty += $itemName
                }

                if ($isScript -and $itemName -match '(?<GetSet>get|set)_')
                {
                    $propertyName = $itemName.Substring(4)
                    if ($matches.GetSet -eq 'get' -and -not $scriptPropertyGet.Contains($propertyName)) {
                        if ($scriptPropertyGet.Contains($propertyName)) {
                            continue
                        }
                        $scriptPropertyGet[$propertyName] = $scriptBlock

                    } elseif (-not $scriptPropertySet.Contains($propertyName)) {
                        if ($scriptPropertySet.Contains($propertyName)) {
                            continue
                        }
                        $scriptPropertySet[$propertyName] = $scriptBlock
                    }
                }
                elseif ($isScript)
                {
                    $methodName =$itemName
                    if ($scriptMethods.Contains($methodName)) {
                        continue
                    }
                    $scriptMethods[$methodName] = $scriptBlock
                }
                else
                {
                    $fileText = [IO.File]::ReadAllText($item.FullName)
                    if ($item.Extension -in '.psd1', '.xml','.json','.clixml' -and
                        $scriptPropertyGet[$itemName])
                    {
                        continue
                    }
                    switch ($item.Extension)
                    {
                        .txt {
                            if (-not $noteProperty.Contains($itemName)) {
                                $noteProperty[$itemName] = $fileText
                            }
                        }
                        .psd1 {
                            $scriptPropertyGet[$itemName] = [ScriptBlock]::Create(@"
data { $fileText }
"@
)

                        }
                        .xml {
                            $scriptPropertyGet[$itemName] = [ScriptBlock]::Create(@"
[xml]@'
$fileText
'@
"@
)
                        }
                        .json {
                            $scriptPropertyGet[$itemName] = [ScriptBlock]::Create(@"
@'
$fileText
'@ | ConvertFrom-Json
"@
)

                        }
                        .clixml {
                            $scriptPropertyGet[$itemName] = [ScriptBlock]::Create(@"
[Management.Automation.PSSerializer]::Deserialize(@'
$fileText
'@)
"@
)
                        }
                        default {
                            $fileBytes = [IO.File]::ReadAllBytes($item.FullName)
                            $ms = [IO.MemoryStream]::new()
                            $gzipStream = [IO.Compression.GZipStream]::new($ms, [Io.Compression.CompressionMode]"Compress")
                            $null = $gzipStream.Write($fileBytes, 0, $fileBytes.Length)
                            $null = $gzipStream.Close()
                            $itemName = $item.Name
                            if ($itemName.StartsWith('.')) {
                                $itemName = $itemName.TrimStart('.')
                                $hideProperty += $itemName
                            }
                            if (-not $scriptPropertyGet.Contains($itemName)) {
                                $scriptPropertyGet[$itemName] = [ScriptBlock]::Create(@"
 
`$stream =
    [IO.Compression.GZipStream]::new(
        [IO.MemoryStream]::new(
            [Convert]::FromBase64String(@'
$(
[Convert]::ToBase64String($ms.ToArray(), "InsertLineBreaks")
)
'@)
        ),
        [IO.Compression.CompressionMode]'Decompress'
    );
"@
 + @'
 
$BufferSize = 1kb
$buffer = [Byte[]]::new($BufferSize)
$bytes =
    do {
        $bytesRead= $stream.Read($buffer, 0, $BufferSize )
        $buffer[0..($bytesRead - 1)]
        if ($bytesRead -lt $BufferSize ) {
            break
        }
    } while ($bytesRead -eq $BufferSize )
$bytes -as [byte[]]
$stream.Close()
$stream.Dispose()
'@
)

                            }
                        }
                    }
                }



            }

            if ($scriptMethods.Count) {
                $WriteTypeViewSplat.ScriptMethod = $scriptMethods
            }
            if ($scriptPropertyGet.Count -or $scriptPropertySet.Count) {
                $scriptProperties = [Ordered]@{}
                foreach ($k in $scriptPropertyGet.Keys) {
                    $scriptProperties[$k] = $scriptPropertyGet[$k]
                }
                foreach ($k in $scriptPropertySet.Keys) {
                    if (-not $scriptProperties[$k]) {
                        $scriptProperties[$k] = {}, $scriptPropertySet[$k]
                    } else {
                        $scriptProperties[$k] = $scriptProperties[$k], $scriptPropertySet[$k]
                    }
                }
                $WriteTypeViewSplat.ScriptProperty = $scriptProperties
            }

            if ($noteProperty.Count) {
                $WriteTypeViewSplat.NoteProperty = $noteProperty
            }

            if ($WriteTypeViewSplat.Count -gt 1) {
                $WriteTypeViewSplat.HideProperty = $hideProperty
                Write-TypeView @WriteTypeViewSplat
            }
        }

        $null = $null
    }
}