jit-tree.psm1

. $PSScriptRoot\MessageFunctions.ps1

function Write-Tree
{
    [CmdletBinding()]
    param (
        # Specifies a path to one or more locations.
        [Parameter(Mandatory = $false,
            Position = 0,
            ParameterSetName = "Default",
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Path to one or more locations.")]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path = (Get-Location),

        [Parameter(Mandatory = $false,
            ParameterSetName = "Default",
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Path to one or more locations.")]
        [int]
        $Depth = -1,

        [Parameter(Mandatory = $false,
            ParameterSetName = "Default",
            HelpMessage = "Specifies, as a string array, a property or property that this cmdlet excludes from the operation. The value of this parameter qualifies the Path parameter. Enter a path element or pattern, such as *.txt or A*. Wildcard characters are accepted.")]
        [string[]]$Exclude,

        [Parameter(Mandatory = $false,
            ParameterSetName = "Default",
            HelpMessage = "Gets files and folders with the specified attributes. This parameter supports all attributes and lets you specify complex combinations of attributes.")]
        [System.Management.Automation.FlagsExpression[System.IO.FileAttributes]]$Attributes,

        [Parameter(Mandatory = $false, ParameterSetName = "Default")]
        [switch]$DisplayHint
    )

    # data structure - per pass collect @(depth,bars[],Last/NotLast)
    # [0||Not-Last] ├──.git/
    # [1|0|Last] │ └──refs/
    # [2|0|Not-Last] │ ├──heads/
    # [2|0|Not-Last] │ ├──remotes/
    # [3|0, 2|Last] │ │ └──origin/
    # [2|0|Last] │ └──tags/
    # [3|0|Not-Last] │ ├──jit-psbuild/
    # [3|0|Last] │ └──jit-semver/
    # [0||Last] └──templates/

    function Write-Line($dir, $level, $bars, $isLast, $count)
    {
        $prefix = ""
        for ($i = 0; $i -lt $level; $i++)
        {
            if ($bars -contains $i) { $prefix += "│ " }
            else { $prefix += " " }
        }

        if ($isLast) { $prefix += "└──" }
        else { $prefix += "├──" }
        $itemMsg = "$prefix$($dir.Name)/"

        if ($DebugPreference.value__)
        {
            $lastMsg = ("Not-Last", "Last")
            $debugMsg = "[$level|$($bars -join ', ')|$($lastMsg[$isLast])] ".PadRight(20, ' ')
        }
        if ($DisplayHint)
        {
            $hintMsg = "[$($dir.Mode), $($dir.LastWriteTime.ToString("yyyy-MM-dd hh:mm tt")), $($count.ToString().PadLeft(6, ' '))] "
        }

        Write-Output "${debugMsg}${hintMsg}${itemMsg}"
    }

    function GetChildItem($path)
    {
        return Get-ChildItem $path -Directory -Exclude $Exclude -Attributes $Attributes | Sort-Object -Property Name -Descending
    }

    # Guard path
    if (-Not(Test-Path $Path))
    {
        Write-Information -MessageData (Format-ErrorMessage "Not a valid path $Path") -InformationAction Continue
        break
    }
    # root path
    $currentDir = Resolve-Path -Path $Path
    Write-Output $currentDir.Path

    if ($DisplayHint.IsPresent)
    {
        Write-Output "Columns: [Mode, LastWriteTime, Count, Name]"
    }

    # iterate the tree
    $dir = New-Object -TypeName System.Collections.Stack

    # setup root subdir
    $subDirs = (GetChildItem -path $currentDir)
    $subDirs | Select-Object -First 1 | ForEach-Object { $dir.Push(@( $_, $true, $subDir.Count, [int]$null, [int[]]$null)) }
    $subDirs | Select-Object -Skip 1 | ForEach-Object { $dir.Push(@( $_, $false, $subDir.Count, [int]$null, [int[]]$null)) }

    while ($dir.Count -gt 0)
    {
        ($currentDir, $isLast, $count, $level, $bars) = $dir.Pop()

        if (($Depth -ne -1) -and ($level -gt $Depth - 1)) { continue }

        Write-Line -dir $currentDir -level $level -bars $bars -isLast $isLast -count $count

        $subBar = [int[]]$bars
        $hasChildren = (GetChildItem -path $currentDir.FullName).Count -gt 0
        if (-not($isLast) -and $hasChildren)
        {
            if ($null -eq $subBar)
            {
                $subBar = [int[]]($level)
            }
            else
            {
                $subBar = $subBar + $level
            }
        }

        $subDir = (GetChildItem -path $currentDir.FullName)
        $subDir | Select-Object -First 1 | ForEach-Object { $dir.Push(@( $_, $true, $subDir.Count, [int]($level + 1), [int[]]$subBar)) }
        $subDir | Select-Object -Skip 1 | ForEach-Object { $dir.Push(@( $_, $false, $subDir.Count, [int]($level + 1), [int[]]$subBar)) }
    }
}

$exportModuleMemberParams = @{
    Function = @(
        'Write-Tree'
    )
    Variable = @()
}

Export-ModuleMember @exportModuleMemberParams