LoginSignup
1
5

More than 5 years have passed since last update.

PowerShellで木構造の整形表示コマンドを作った

Last updated at Posted at 2017-07-31

下記記事が面白そうなのでPowerShellで似たようなのを作ってみた。

実装はCodeの節を参照のこと。
なお、class構文を使っているのでv5以降でないと動かない。

挙動

入力ファイル

sample1.txt
pattern1_root
pattern1_root/pattern2_last_leaf
root_node
root_node/pattern3_non-last_leaf
root_node/pattern3_non-last_leaf/sample
root_node/pattern3_non-last_leaf/pattern4_non-last_node's_child
root_node/leaf_node
sample2.txt
サ
サン
サンプル
forest
forest doesn't specify any characters
forest doesn't specify any characters as separater.

実行結果

通常実行

PS C:\demo\tree> cat .\sample1.txt | ConvertTo-Tree | Format-Tree
pattern1_root
└─/pattern2_last_leaf
root_node
├─/pattern3_non-last_leaf
│ ├─/sample
│ └─/pattern4_non-last_node's_child
└─/leaf_node

PS C:\demo\tree> cat .\sample2.txt | ConvertTo-Tree | Format-Tree
サ
└─ン
  └─プル
forest
└─ doesn't specify any characters
  └─ as separater.

PS C:\demo\tree> 

枝の表示を変えて実行

PS C:\demo\tree> cat .\sample1.txt | ConvertTo-Tree | Format-Tree -e ([EdgeString]::New('+---','`---','|   ','    '))
pattern1_root
`---/pattern2_last_leaf
root_node
+---/pattern3_non-last_leaf
|   +---/sample
|   `---/pattern4_non-last_node's_child
`---/leaf_node

PS C:\demo\tree> 

空のRootに集約して実行

PS C:\demo\tree> cat .\sample1.txt | ConvertTo-Tree | % {$r = [TreeNode]::New('')} {$r.AddChild($_)} {$r} | Format-Tree

├─pattern1_root
│ └─/pattern2_last_leaf
└─root_node
  ├─/pattern3_non-last_leaf
  │ ├─/sample
  │ └─/pattern4_non-last_node's_child
  └─/leaf_node

PS C:\demo\tree> cat .\sample1.txt | ConvertTo-Tree | % {$r = [TreeNode]::New('')} {$r.AddChild($_)} {$r} | Format-Tree -e ([EdgeString]::New('+ ','+ ',"`t","`t"))

+ pattern1_root
    + /pattern2_last_leaf
+ root_node
    + /pattern3_non-last_leaf
        + /sample
        + /pattern4_non-last_node's_child
    + /leaf_node

PS C:\demo\tree> 

Code

下記パラメータに適当なスクリプトブロックを与えると、親子判定、Rootの表示、Root以外のNodeの表示を好きにイジれる。

  • ConvertTo-Tree -IsChild {...}
  • Format-Tree -RootFromat {...}
  • Format-Tree -ChildFromat {...}
class TreeNode
{
    [System.Object] $Data
    [System.Collections.ArrayList] $Children

    TreeNode ([System.Object] $Data)
    {
        $this.Data = $Data
        $this.Children = New-Object System.Collections.ArrayList
    }

    [void] AddChild ([TreeNode] $Child)
        {[void] $this.Children.Add($Child)}


}

class EdgeString {
    [System.String] $Child
    [System.String] $LastChild
    [System.String] $Descendant
    [System.String] $LastDescendant

    EdgeString (
        [System.String] $Child,
        [System.String] $LastChild,
        [System.String] $Descendant,
        [System.String] $LastDescendant
    )
    {
        $this.Child          = $Child
        $this.LastChild      = $LastChild
        $this.Descendant     = $Descendant
        $this.LastDescendant = $LastDescendant
    }
}

function ConvertTo-Tree
{
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        $InputObject,

        [ValidateNotNullOrEmpty()]
        [scriptblock] $IsChild = {$_[1].ToString().StartsWith($_[0].ToString())}
    )

    Begin
    {
        $stack = New-Object System.Collections.Stack
    }

    Process
    {
        $node = [TreeNode]::New($InputObject)

        while ($true)
        {
            if ($stack.Count -eq 0)
            {
                $stack.Push($node)
                break
            }

            $peek = $stack.Peek()
            $isC = ,@($peek.Data, $node.Data) | ForEach-Object $IsChild
            if ($isC)
            {
                $peek.AddChild($node)
                $stack.Push($node)
                break
            }

            if ($stack.Count -gt 1)
                {[void] $stack.Pop()}
            else
                {$stack.Pop()}
        }

    }

    End
    {
        while ($stack.Count -gt 1)
            {[void] $stack.Pop()}

        if ($stack.Count -eq 1)
            {return $stack.Pop()}
    }
}

function Format-Tree
{
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [TreeNode] $Tree,

        [ValidateNotNullOrEmpty()]
        [scriptblock]$RootFromat = {$_.ToString()},

        [ValidateNotNullOrEmpty()]
        [scriptblock]$ChildFromat = {$_[1].ToString().SubString($_[0].ToString().Length)},

        [ValidateNotNullOrEmpty()]
        [EdgeString] $EdgeString = [EdgeString]::New("├─", "└─", "│ ", "  ")
    )

    Begin { 
        function writeChild ($stack)
        {
            $str = New-Object "System.Collections.Generic.Stack[string]" 

            $child, $parent = $stack | ForEach-Object {$_[0].Data} | Select-Object -First 2
            $str.Push((,@($parent, $child) | ForEach-Object $ChildFromat))

            $stack |
                Select-Object -Skip 1 -First 1 |
                ForEach-Object {
                    $node, $count = $_
                    if ($node.Children.Count -eq $count)
                        {$str.Push(($EdgeString.LastChild))}
                    else
                        {$str.Push(($EdgeString.Child))}
                }

            $stack |
                Select-Object -Skip 2 |
                ForEach-Object {
                    $node, $count = $_
                    if ($node.Children.Count -eq $count)
                        {$str.Push(($EdgeString.LastDescendant))}
                    else
                        {$str.Push(($EdgeString.Descendant))}
                }

            -join $str
        }
    }

    Process
    {
        $stack = New-Object "System.Collections.Stack"

        $stack.Push(@($Tree, 0))
        $Tree.Data | ForEach-Object $RootFromat

        while ($stack.Count -gt 0)
        {
            $node, $count = $stack.Pop()
            if ($node.Children.Count -le $count)
                {continue}

            $stack.Push(@($node, ($count + 1)))
            $stack.Push(@($node.Children[$count], 0))
            writeChild $stack
        }
    }
}
1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5