Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
284 lines
8.2 KiB
PHP
284 lines
8.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Display\Components\Console;
|
|
|
|
use App\Framework\Console\ConsoleColor;
|
|
use App\Framework\Console\ConsoleFormat;
|
|
use App\Framework\Console\ConsoleOutput;
|
|
use App\Framework\Console\ConsoleStyle;
|
|
use App\Framework\Display\Components\DisplayComponentInterface;
|
|
use App\Framework\Display\ValueObjects\ArrayStructure;
|
|
use App\Framework\Display\ValueObjects\ObjectStructure;
|
|
use App\Framework\Display\ValueObjects\OutputFormat;
|
|
|
|
/**
|
|
* TreeHelper zum Anzeigen hierarchischer Baumstrukturen in der Konsole.
|
|
* Ähnlich dem Symfony TreeHelper, aber angepasst an unser Styling-System.
|
|
*/
|
|
final class TreeHelper implements DisplayComponentInterface
|
|
{
|
|
private string $prefix = '';
|
|
|
|
private bool $isLastElement = true;
|
|
|
|
private ?ConsoleStyle $nodeStyle = null;
|
|
|
|
private ?ConsoleStyle $leafStyle = null;
|
|
|
|
private ?ConsoleStyle $lineStyle = null;
|
|
|
|
/**
|
|
* @var array<array{title: string, node: ?self, isLeaf: bool}>
|
|
*/
|
|
private array $nodes = [];
|
|
|
|
public function __construct(
|
|
private string $title = '',
|
|
#private readonly ConsoleOutput $output = new ConsoleOutput(),
|
|
) {
|
|
$this->nodeStyle = ConsoleStyle::create(color: ConsoleColor::BRIGHT_YELLOW, format: ConsoleFormat::BOLD);
|
|
$this->leafStyle = ConsoleStyle::create(color: ConsoleColor::WHITE);
|
|
$this->lineStyle = ConsoleStyle::create(color: ConsoleColor::GRAY);
|
|
}
|
|
|
|
/**
|
|
* Setzt den Stil für Knotentitel (Verzeichnisse/Kategorien).
|
|
*/
|
|
public function setNodeStyle(?ConsoleStyle $style): self
|
|
{
|
|
$this->nodeStyle = $style;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Setzt den Stil für Blätter/Endpunkte.
|
|
*/
|
|
public function setLeafStyle(?ConsoleStyle $style): self
|
|
{
|
|
$this->leafStyle = $style;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Setzt den Stil für Baumlinien.
|
|
*/
|
|
public function setLineStyle(?ConsoleStyle $style): self
|
|
{
|
|
$this->lineStyle = $style;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Setzt den Haupttitel des Baums.
|
|
*/
|
|
public function setTitle(string $title): self
|
|
{
|
|
$this->title = $title;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Fügt einen Unterknoten (z.B. Unterverzeichnis) hinzu.
|
|
*/
|
|
public function addNode(string $title): self
|
|
{
|
|
$node = new self($title);
|
|
$node->nodeStyle = $this->nodeStyle;
|
|
$node->leafStyle = $this->leafStyle;
|
|
$node->lineStyle = $this->lineStyle;
|
|
|
|
$this->nodes[] = [
|
|
'title' => $title,
|
|
'node' => $node,
|
|
'isLeaf' => false,
|
|
];
|
|
|
|
return $node;
|
|
}
|
|
|
|
/**
|
|
* Fügt einen Endpunkt (z.B. Datei) hinzu.
|
|
*/
|
|
public function addLeaf(string $title): self
|
|
{
|
|
$this->nodes[] = [
|
|
'title' => $title,
|
|
'node' => null,
|
|
'isLeaf' => true,
|
|
];
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Zeigt die vollständige Baumstruktur an.
|
|
*/
|
|
public function display(ConsoleOutput $output): void
|
|
{
|
|
if (! empty($this->title)) {
|
|
$output->writeLine($this->title, $this->nodeStyle);
|
|
}
|
|
|
|
$this->displayTree($output);
|
|
}
|
|
|
|
/**
|
|
* Rendert die Baumstruktur und gibt den Text zurück.
|
|
*/
|
|
public function render(): string
|
|
{
|
|
$output = '';
|
|
|
|
if (! empty($this->title)) {
|
|
$output .= $this->nodeStyle->apply($this->title) . "\n";
|
|
}
|
|
|
|
$output .= $this->renderTree();
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Setzt den Präfix für die aktuelle Ebene.
|
|
* (Interne Methode für rekursives Rendern)
|
|
*/
|
|
private function setPrefix(string $prefix, bool $isLastElement): self
|
|
{
|
|
$this->prefix = $prefix;
|
|
$this->isLastElement = $isLastElement;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Zeigt die Baumstruktur mit dem aktuellen Präfix an.
|
|
* (Interne Methode für rekursives Rendern)
|
|
*/
|
|
private function displayTree(ConsoleOutput $output): void
|
|
{
|
|
$count = count($this->nodes);
|
|
|
|
foreach ($this->nodes as $index => $item) {
|
|
$isLast = ($index === $count - 1);
|
|
$nodePrefix = $this->prefix . ($this->isLastElement ? ' ' : '│ ');
|
|
|
|
// Baumlinien und Verbindungen
|
|
$connector = $isLast ? '└── ' : '├── ';
|
|
$linePrefix = $this->prefix . $connector;
|
|
|
|
// Titel anzeigen
|
|
$style = $item['isLeaf'] ? $this->leafStyle : $this->nodeStyle;
|
|
$title = $linePrefix . $item['title'];
|
|
|
|
$output->writeLine(
|
|
$this->lineStyle->apply($linePrefix) .
|
|
$style->apply($item['title'])
|
|
);
|
|
|
|
// Unterelemente rekursiv anzeigen
|
|
if (! $item['isLeaf'] && $item['node'] !== null) {
|
|
$item['node']
|
|
->setPrefix($nodePrefix, $isLast)
|
|
->displayTree($output);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rendert die Baumstruktur mit dem aktuellen Präfix und gibt den Text zurück.
|
|
* (Interne Methode für rekursives Rendern)
|
|
*/
|
|
private function renderTree(): string
|
|
{
|
|
$output = '';
|
|
$count = count($this->nodes);
|
|
|
|
foreach ($this->nodes as $index => $item) {
|
|
$isLast = ($index === $count - 1);
|
|
$nodePrefix = $this->prefix . ($this->isLastElement ? ' ' : '│ ');
|
|
|
|
// Baumlinien und Verbindungen
|
|
$connector = $isLast ? '└── ' : '├── ';
|
|
$linePrefix = $this->prefix . $connector;
|
|
|
|
// Titel formatieren
|
|
$style = $item['isLeaf'] ? $this->leafStyle : $this->nodeStyle;
|
|
$title = $item['title'];
|
|
|
|
$output .= $this->lineStyle->apply($linePrefix) .
|
|
$style->apply($title) . "\n";
|
|
|
|
// Unterelemente rekursiv rendern
|
|
if (! $item['isLeaf'] && $item['node'] !== null) {
|
|
$childOutput = $item['node']
|
|
->setPrefix($nodePrefix, $isLast)
|
|
->renderTree();
|
|
|
|
$output .= $childOutput;
|
|
}
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
public function getOutputFormat(): OutputFormat
|
|
{
|
|
return OutputFormat::CONSOLE;
|
|
}
|
|
|
|
/**
|
|
* Build tree from Display ArrayStructure
|
|
*/
|
|
public function fromArrayStructure(ArrayStructure $structure, string $separator = ': '): self
|
|
{
|
|
foreach ($structure->items as $key => $value) {
|
|
if (isset($structure->nestedArrays[$key])) {
|
|
$nested = $structure->nestedArrays[$key];
|
|
if ($nested->isCircularReference) {
|
|
$this->addLeaf((string) $key . $separator . '[circular reference]');
|
|
} else {
|
|
$node = $this->addNode((string) $key);
|
|
$node->fromArrayStructure($nested, $separator);
|
|
}
|
|
} else {
|
|
$valueStr = is_scalar($value) ? (string) $value : get_debug_type($value);
|
|
$this->addLeaf((string) $key . $separator . $valueStr);
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Build tree from Display ObjectStructure
|
|
*/
|
|
public function fromObjectStructure(ObjectStructure $structure, string $separator = ': '): self
|
|
{
|
|
foreach ($structure->properties as $property) {
|
|
if (isset($structure->nestedObjects[$property->name])) {
|
|
$nested = $structure->nestedObjects[$property->name];
|
|
if ($nested->isCircularReference) {
|
|
$this->addLeaf($property->name . $separator . '[circular reference]');
|
|
} else {
|
|
$node = $this->addNode($property->name . $separator . $nested->className);
|
|
$node->fromObjectStructure($nested, $separator);
|
|
}
|
|
} else {
|
|
$valueStr = $property->isValueObject && is_object($property->value)
|
|
? (string) $property->value
|
|
: (is_scalar($property->value) ? (string) $property->value : get_debug_type($property->value));
|
|
$this->addLeaf($property->name . $separator . $valueStr);
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
}
|
|
|