jit-tree.psm1
. $PSScriptRoot\MessageFunctions.ps1 . $PSScriptRoot\ColorTheme.ps1 $JitTreeSettings = @{ ColorTheme = Get-JitTreeDarkTheme } function Write-Tree { [CmdletBinding()] param ( [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 = "Specifies, as an int, the depth to list, default -1 (all depths).")] [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 = "List files in output")] [switch] $File, [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", HelpMessage = "Display filesystem entry metadata.")] [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-DisplayHintHeader() { if ($DisplayHint.IsPresent) { $text = "[$("Mode".PadRight(5, ' ')), $("LastWriteTime".PadRight(19, ' ')), $("Length".PadRight(8, ' '))] Name" $data = Format-HostInformationMessage -MessageData $text -ForegroundColor $JitTreeSettings.ColorTheme.DisplayHintForeground Write-Information -MessageData $data -InformationAction Continue } } function Write-Line($dir, $level, $bars, $isLast, $count, $isDir) { $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 ($isDir) { $itemMsg += [IO.Path]::DirectorySeparatorChar } 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(8, ' '))] " } $fg = [System.ConsoleColor]($JitTreeSettings.ColorTheme.FileForeground, $JitTreeSettings.ColorTheme.DirectoryForeground)[$isDir -or $false] $msg = Format-HostInformationMessage -MessageData "${debugMsg}${hintMsg}" -NoNewline -fg $JitTreeSettings.ColorTheme.DisplayHintForeground $itemMsg = Format-HostInformationMessage -MessageData "${itemMsg}" -fg $fg Write-Information -MessageData $msg -OutBuffer 25 -InformationAction Continue Write-Information -MessageData $itemMsg -OutBuffer 25 -InformationAction Continue } function GetChildDirectory { [OutputType([System.IO.FileSystemInfo[]])] param( [string]$path ) Get-ChildItem $path -Directory -Exclude $Exclude -Attributes $Attributes | Sort-Object -Property Name -Descending } function GetChildFile { [OutputType([System.IO.FileSystemInfo[]])] param( [string]$path ) return Get-ChildItem $path -File -Depth 0 -Exclude $Exclude -Attributes $Attributes | Sort-Object -Property Name -Descending } function GetChildItemCountOrFileLength { [OutputType([int])] param( [string]$path ) if ($_.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) { return $_.GetDirectories().Count } return $_.Length } function LoadFSItems { param ( [string]$path, [int]$level, [int[]]$subBar ) $fsitems = @() if ($File.IsPresent) { $fsitems += (GetChildFile -path $path) } $fsitems += (GetChildDirectory -path $path) $rtn = @($fsitems | ForEach-Object { @{ dir = $_; isLast = $false; childDirCount = (GetChildItemCountOrFileLength $_); treeLevel = $level; treeBarLevel = $subBar; isDir = $_.Attributes.HasFlag([System.IO.FileAttributes]::Directory) } }) # set $isLast to true for 1st item (note: decending order) if ($rtn.Length -gt 0) { $rtn[0].isLast = $true } return $rtn } # Guard path if (-Not(Test-Path $Path)) { Write-Information -MessageData (Format-ErrorMessage "Not a valid path $Path") -InformationAction Continue break } # root path $rootPath = Resolve-Path -Path $Path Write-Output $rootPath.Path Write-DisplayHintHeader # iterate the tree $dir = New-Object -TypeName System.Collections.Stack # init root LoadFSItems -path $rootPath | ForEach-Object { $dir.Push($_) } while ($dir.Count -gt 0) { $dirEntry = $dir.Pop() $currentDir = $dirEntry.dir $isLast = $dirEntry.isLast $count = $dirEntry.childDirCount $level = $dirEntry.treeLevel $bars = $dirEntry.treeBarLevel $isDir = $dirEntry.isDir # adhere to depth if (($Depth -ne -1) -and ($level -gt $Depth - 1)) { continue } Write-Line -dir $currentDir -level $level -bars $bars -isLast $isLast -count $count -isDir $isDir if ($isDir) { $subBar = [int[]]$bars if (-not($isLast) ) { if ($null -eq $subBar) { $subBar = [int[]]($level) } else { $subBar = $subBar + $level } } LoadFSItems -path $currentDir -level ($level + 1) -subBar $subBar | ForEach-Object { $dir.Push($_) } } } } $exportModuleMemberParams = @{ Function = @( 'Write-Tree' ) Variable = @( 'JitTreeSettings' ) } Export-ModuleMember @exportModuleMemberParams |