Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
Tree
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
11 / 11
33
100.00% covered (success)
100.00%
1 / 1
 walk
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 map_structure_values
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 map_structure_types
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 isEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 containsNode
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 getCurrentNode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCurrentNode
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addNode
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 addValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 render
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php declare(strict_types=1);
2
3namespace Computator\FrameworkUtils\PHPTemplate\RenderTree;
4
5use WeakMap;
6use ValueError;
7
8use function get_class;
9
10class Tree {
11    public readonly Node $root;
12    protected Node $current_node;
13    protected WeakMap $in_tree_weakmap;
14
15    /**
16     * @param callable(Node $node): mixed|false $callback
17     *     return false from the callback to abort walking the rest of the tree
18     * @param ?callable(Node $node): bool $filter
19     *     return false from the optional filter callback to skip the node and it's children
20     */
21    public static function walk(Node $start, callable $callback, ?callable $filter = null): bool {
22        if ($filter && !$filter($start))
23            return true;
24        if ($callback($start) === false)
25            return false;
26        if (!$start->isLeaf()) {
27            foreach ($start as $n) {
28                if (static::walk($n, $callback, $filter) === false)
29                    return false;
30            }
31        }
32        return true;
33    }
34
35    /** @return array<array|Renderable|Null>|Renderable|null */
36    public static function map_structure_values(Node $start): array|Renderable|null {
37        if ($start->isLeaf())
38            return $start->getValue();
39        return array_map(static::map_structure_values(...), [...$start]);
40    }
41
42    /** @return array<array|string|null> */
43    public static function map_structure_types(Node $start): array {
44        if ($start->isLeaf())
45            return [get_class($start) => (($v = $start->getValue()) ? get_class($v) : null)];
46        return array_map(
47            fn ($n) => [
48                get_class($n) => $n->isLeaf()
49                    ? (($v = $n->getValue()) ? get_class($v) : null)
50                    : static::map_structure_types($n),
51            ],
52            [...$start],
53        );
54    }
55
56    public function __construct(Node $root_node) {
57        $this->root = $root_node;
58        $this->current_node = $this->root;
59
60        $this->in_tree_weakmap = new WeakMap();
61        static::walk($this->root, fn (Node $n) => $this->in_tree_weakmap[$n] = true);
62    }
63
64    public function isEmpty(): bool {
65        return $this->root->isLeaf();
66    }
67
68    public function containsNode(Node $node): bool {
69        if (isset($this->in_tree_weakmap[$node]))
70            return true;
71        $found = false;
72        static::walk($this->root, function ($curr) use ($node, &$found) {
73            $this->in_tree_weakmap[$curr] = true;
74            if ($curr === $node) {
75                $found = true;
76                return false;
77            }
78        });
79        return $found;
80    }
81
82    public function getCurrentNode(): Node {
83        return $this->current_node;
84    }
85
86    public function setCurrentNode(Node $node): void {
87        if (!$this->containsNode($node))
88            throw new ValueError("node not found in tree");
89        $this->current_node = $node;
90    }
91
92    public function addNode(?Node $node = null): Node {
93        if ($node === null)
94            $node = Node::withValue(null);
95        elseif ($this->containsNode($node))
96            throw new ValueError("node cannot be added to tree in more than one place");
97        $this->current_node->appendChildren($node);
98        if (!$node->isLeaf())
99            static::walk($node, fn($n) => $this->in_tree_weakmap[$n] = true);
100        else
101            $this->in_tree_weakmap[$node] = true;
102        return $node;
103    }
104
105    public function addValue(Renderable $value): void {
106        if (!$this->isEmpty() && $this->current_node->isLeaf() && !$this->current_node->hasValue())
107            $this->current_node->setValue($value);
108        else
109            $this->current_node->appendChildren(Node::withValue($value));
110    }
111
112    public function render(): void {
113        static::walk($this->root,
114            fn ($n) => ($n->isLeaf() && $n->getValue()?->render()) || true,
115            fn ($n) => !($n instanceof IgnoredNode),
116        );
117    }
118}