Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
57 / 57 |
|
100.00% |
11 / 11 |
CRAP | |
100.00% |
1 / 1 |
| Tree | |
100.00% |
57 / 57 |
|
100.00% |
11 / 11 |
33 | |
100.00% |
1 / 1 |
| walk | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
7 | |||
| map_structure_values | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| map_structure_types | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
| __construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| isEmpty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| containsNode | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
| getCurrentNode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setCurrentNode | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| addNode | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
| addValue | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
4 | |||
| render | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
| 1 | <?php declare(strict_types=1); |
| 2 | |
| 3 | namespace Computator\FrameworkUtils\PHPTemplate\RenderTree; |
| 4 | |
| 5 | use WeakMap; |
| 6 | use ValueError; |
| 7 | |
| 8 | use function get_class; |
| 9 | |
| 10 | class 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 | } |