chore: complete update
This commit is contained in:
257
src/Framework/Console/Components/InteractiveMenu.php
Normal file
257
src/Framework/Console/Components/InteractiveMenu.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Console\Components;
|
||||
|
||||
use App\Framework\Console\ConsoleColor;
|
||||
use App\Framework\Console\ConsoleOutput;
|
||||
|
||||
/**
|
||||
* Klasse für interaktive Menüs in der Konsole.
|
||||
* Unterstützt sowohl einfache nummerierte Menüs als auch
|
||||
* Menüs mit Pfeiltasten-Navigation.
|
||||
*/
|
||||
final class InteractiveMenu
|
||||
{
|
||||
private ConsoleOutput $output;
|
||||
private array $menuItems = [];
|
||||
private string $title = '';
|
||||
private int $selectedIndex = 0;
|
||||
private bool $showNumbers = true;
|
||||
|
||||
public function __construct(ConsoleOutput $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Titel des Menüs.
|
||||
*/
|
||||
public function setTitle(string $title): self
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt einen Menüpunkt hinzu.
|
||||
*
|
||||
* @param string $label Die Beschriftung des Menüpunkts
|
||||
* @param callable|null $action Eine optionale Aktion, die bei Auswahl ausgeführt wird
|
||||
* @param mixed $value Ein optionaler Wert, der zurückgegeben wird
|
||||
*/
|
||||
public function addItem(string $label, ?callable $action = null, mixed $value = null): self
|
||||
{
|
||||
$this->menuItems[] = [
|
||||
'label' => $label,
|
||||
'action' => $action,
|
||||
'value' => $value ?? $label
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt einen Separator zum Menü hinzu.
|
||||
*/
|
||||
public function addSeparator(): self
|
||||
{
|
||||
$this->menuItems[] = [
|
||||
'label' => '---',
|
||||
'action' => null,
|
||||
'value' => null,
|
||||
'separator' => true
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Steuert, ob Nummern angezeigt werden sollen.
|
||||
*/
|
||||
public function showNumbers(bool $show = true): self
|
||||
{
|
||||
$this->showNumbers = $show;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt ein einfaches Menü mit Nummern-Auswahl.
|
||||
*/
|
||||
public function showSimple(): mixed
|
||||
{
|
||||
// Verwende den ScreenManager anstelle des direkten clearScreen()
|
||||
$this->output->screen()->newMenu();
|
||||
|
||||
if ($this->title) {
|
||||
$this->output->writeLine($this->title, ConsoleColor::BRIGHT_CYAN);
|
||||
$this->output->writeLine(str_repeat('=', strlen($this->title)));
|
||||
$this->output->writeLine('');
|
||||
}
|
||||
|
||||
foreach ($this->menuItems as $index => $item) {
|
||||
if (isset($item['separator'])) {
|
||||
$this->output->writeLine($item['label'], ConsoleColor::GRAY);
|
||||
continue;
|
||||
}
|
||||
|
||||
$number = $this->showNumbers ? ($index + 1) . '. ' : '';
|
||||
$this->output->writeLine($number . $item['label']);
|
||||
}
|
||||
|
||||
$this->output->writeLine('');
|
||||
$this->output->write('Ihre Wahl: ', ConsoleColor::BRIGHT_CYAN);
|
||||
|
||||
$input = trim(fgets(STDIN));
|
||||
|
||||
if (is_numeric($input)) {
|
||||
$selectedIndex = (int)$input - 1;
|
||||
if (isset($this->menuItems[$selectedIndex]) && !isset($this->menuItems[$selectedIndex]['separator'])) {
|
||||
$item = $this->menuItems[$selectedIndex];
|
||||
|
||||
if ($item['action']) {
|
||||
return $item['action']();
|
||||
}
|
||||
|
||||
return $item['value'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->writeError('Ungültige Auswahl!');
|
||||
return $this->showSimple();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt ein interaktives Menü mit Pfeiltasten-Navigation.
|
||||
*/
|
||||
public function showInteractive(): mixed
|
||||
{
|
||||
$this->output->screen()->setInteractiveMode();
|
||||
|
||||
// Terminal in Raw-Modus setzen für Tastatur-Input
|
||||
$this->setRawMode(true);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
$this->renderMenu();
|
||||
$key = $this->readKey();
|
||||
|
||||
switch ($key) {
|
||||
case "\033[A": // Pfeil hoch
|
||||
$this->moveUp();
|
||||
break;
|
||||
case "\033[B": // Pfeil runter
|
||||
$this->moveDown();
|
||||
break;
|
||||
case "\n": // Enter
|
||||
case "\r":
|
||||
return $this->selectCurrentItem();
|
||||
case "\033": // ESC
|
||||
return null;
|
||||
case 'q':
|
||||
case 'Q':
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$this->setRawMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert das Menü auf dem Bildschirm.
|
||||
*/
|
||||
private function renderMenu(): void
|
||||
{
|
||||
// Verwende den ScreenManager anstelle des direkten clearScreen()
|
||||
$this->output->screen()->newMenu();
|
||||
|
||||
if ($this->title) {
|
||||
$this->output->writeLine($this->title, ConsoleColor::BRIGHT_CYAN);
|
||||
$this->output->writeLine(str_repeat('=', strlen($this->title)));
|
||||
$this->output->writeLine('');
|
||||
}
|
||||
|
||||
foreach ($this->menuItems as $index => $item) {
|
||||
if (isset($item['separator'])) {
|
||||
$this->output->writeLine($item['label'], ConsoleColor::GRAY);
|
||||
continue;
|
||||
}
|
||||
|
||||
$isSelected = ($index === $this->selectedIndex);
|
||||
$prefix = $isSelected ? '► ' : ' ';
|
||||
$color = $isSelected ? ConsoleColor::BRIGHT_YELLOW : null;
|
||||
|
||||
$number = $this->showNumbers ? ($index + 1) . '. ' : '';
|
||||
$this->output->writeLine($prefix . $number . $item['label'], $color);
|
||||
}
|
||||
|
||||
$this->output->writeLine('');
|
||||
$this->output->writeLine('Verwenden Sie ↑/↓ zum Navigieren, Enter zum Auswählen, ESC/Q zum Beenden', ConsoleColor::GRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bewegt die Auswahl nach oben.
|
||||
*/
|
||||
private function moveUp(): void
|
||||
{
|
||||
do {
|
||||
$this->selectedIndex = ($this->selectedIndex - 1 + count($this->menuItems)) % count($this->menuItems);
|
||||
} while (isset($this->menuItems[$this->selectedIndex]['separator']) && count($this->menuItems) > 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bewegt die Auswahl nach unten.
|
||||
*/
|
||||
private function moveDown(): void
|
||||
{
|
||||
do {
|
||||
$this->selectedIndex = ($this->selectedIndex + 1) % count($this->menuItems);
|
||||
} while (isset($this->menuItems[$this->selectedIndex]['separator']) && count($this->menuItems) > 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wählt den aktuellen Menüpunkt aus.
|
||||
*/
|
||||
private function selectCurrentItem(): mixed
|
||||
{
|
||||
$item = $this->menuItems[$this->selectedIndex];
|
||||
|
||||
if ($item['action']) {
|
||||
return $item['action']();
|
||||
}
|
||||
|
||||
return $item['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest einen Tastendruck.
|
||||
*/
|
||||
private function readKey(): string
|
||||
{
|
||||
$key = fgetc(STDIN);
|
||||
|
||||
// Behandle Escape-Sequenzen
|
||||
if ($key === "\033") {
|
||||
$key .= fgetc(STDIN);
|
||||
if ($key === "\033[") {
|
||||
$key .= fgetc(STDIN);
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Terminal-Modus.
|
||||
*/
|
||||
private function setRawMode(bool $enable): void
|
||||
{
|
||||
if ($enable) {
|
||||
system('stty -echo -icanon min 1 time 0');
|
||||
} else {
|
||||
system('stty echo icanon');
|
||||
}
|
||||
}
|
||||
|
||||
// Die clearScreen-Methode wurde entfernt, da sie durch den ScreenManager ersetzt wurde.
|
||||
// Verwende stattdessen $this->output->screen()->newScreen() oder $this->output->display()->clear().
|
||||
}
|
||||
205
src/Framework/Console/Components/Table.php
Normal file
205
src/Framework/Console/Components/Table.php
Normal file
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Console\Components;
|
||||
|
||||
use App\Framework\Console\ConsoleStyle;
|
||||
use App\Framework\Console\ConsoleColor;
|
||||
use App\Framework\Console\ConsoleFormat;
|
||||
|
||||
/**
|
||||
* Rendert eine Tabelle in der Konsole.
|
||||
*/
|
||||
final class Table
|
||||
{
|
||||
private array $headers = [];
|
||||
private array $rows = [];
|
||||
private array $columnWidths = [];
|
||||
private int $padding = 1;
|
||||
|
||||
public function __construct(
|
||||
private ?ConsoleStyle $headerStyle = null,
|
||||
private ?ConsoleStyle $rowStyle = null,
|
||||
private ?ConsoleStyle $borderStyle = null,
|
||||
private bool $showBorders = true
|
||||
) {
|
||||
$this->headerStyle ??= ConsoleStyle::create(color: ConsoleColor::BRIGHT_WHITE, format: ConsoleFormat::BOLD);
|
||||
$this->rowStyle ??= ConsoleStyle::create();
|
||||
$this->borderStyle ??= ConsoleStyle::create(color: ConsoleColor::GRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Spaltenüberschriften der Tabelle.
|
||||
*/
|
||||
public function setHeaders(array $headers): self
|
||||
{
|
||||
$this->headers = $headers;
|
||||
$this->calculateColumnWidths();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt eine Zeile zur Tabelle hinzu.
|
||||
*/
|
||||
public function addRow(array $row): self
|
||||
{
|
||||
$this->rows[] = $row;
|
||||
$this->calculateColumnWidths();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt alle Zeilen der Tabelle.
|
||||
*/
|
||||
public function setRows(array $rows): self
|
||||
{
|
||||
$this->rows = $rows;
|
||||
$this->calculateColumnWidths();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Innenabstand der Zellen.
|
||||
*/
|
||||
public function setPadding(int $padding): self
|
||||
{
|
||||
$this->padding = max(0, $padding);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert die Tabelle und gibt den Text zurück.
|
||||
*/
|
||||
public function render(): string
|
||||
{
|
||||
if (empty($this->headers) && empty($this->rows)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$output = '';
|
||||
|
||||
if ($this->showBorders) {
|
||||
$output .= $this->renderBorder('top') . "\n";
|
||||
}
|
||||
|
||||
// Header
|
||||
if (!empty($this->headers)) {
|
||||
$output .= $this->renderRow($this->headers, $this->headerStyle) . "\n";
|
||||
|
||||
if ($this->showBorders) {
|
||||
$output .= $this->renderBorder('middle') . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Zeilen
|
||||
foreach ($this->rows as $index => $row) {
|
||||
$output .= $this->renderRow($row, $this->rowStyle) . "\n";
|
||||
}
|
||||
|
||||
if ($this->showBorders) {
|
||||
$output .= $this->renderBorder('bottom') . "\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert eine Zeile der Tabelle.
|
||||
*/
|
||||
private function renderRow(array $cells, ConsoleStyle $style): string
|
||||
{
|
||||
$output = '';
|
||||
|
||||
if ($this->showBorders) {
|
||||
$output .= $this->borderStyle->apply('│');
|
||||
}
|
||||
|
||||
foreach ($cells as $index => $cell) {
|
||||
$width = $this->columnWidths[$index] ?? 10;
|
||||
$cellText = (string)$cell;
|
||||
|
||||
// Linksbündige Ausrichtung mit exakter Breite
|
||||
$paddedCell = str_pad($cellText, $width, ' ', STR_PAD_RIGHT);
|
||||
|
||||
// Padding hinzufügen
|
||||
$padding = str_repeat(' ', $this->padding);
|
||||
$finalCell = $padding . $paddedCell . $padding;
|
||||
|
||||
$output .= $style->apply($finalCell);
|
||||
|
||||
if ($this->showBorders) {
|
||||
$output .= $this->borderStyle->apply('│');
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert eine Trennlinie der Tabelle.
|
||||
*/
|
||||
private function renderBorder(string $type): string
|
||||
{
|
||||
$left = match($type) {
|
||||
'top' => '┌',
|
||||
'middle' => '├',
|
||||
'row' => '├',
|
||||
'bottom' => '└',
|
||||
default => '│'
|
||||
};
|
||||
|
||||
$right = match($type) {
|
||||
'top' => '┐',
|
||||
'middle' => '┤',
|
||||
'row' => '┤',
|
||||
'bottom' => '┘',
|
||||
default => '│'
|
||||
};
|
||||
|
||||
$horizontal = '─';
|
||||
|
||||
$junction = match($type) {
|
||||
'top' => '┬',
|
||||
'middle' => '┼',
|
||||
'row' => '┼',
|
||||
'bottom' => '┴',
|
||||
default => '│'
|
||||
};
|
||||
|
||||
$border = $left;
|
||||
|
||||
foreach ($this->columnWidths as $index => $width) {
|
||||
$cellWidth = $width + ($this->padding * 2);
|
||||
$border .= str_repeat($horizontal, $cellWidth);
|
||||
|
||||
if ($index < count($this->columnWidths) - 1) {
|
||||
$border .= $junction;
|
||||
}
|
||||
}
|
||||
|
||||
$border .= $right;
|
||||
|
||||
return $this->borderStyle->apply($border);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Breite jeder Spalte basierend auf dem Inhalt.
|
||||
*/
|
||||
private function calculateColumnWidths(): void
|
||||
{
|
||||
$this->columnWidths = [];
|
||||
|
||||
// Header-Breiten
|
||||
foreach ($this->headers as $index => $header) {
|
||||
$this->columnWidths[$index] = mb_strlen((string)$header);
|
||||
}
|
||||
|
||||
// Zeilen-Breiten
|
||||
foreach ($this->rows as $row) {
|
||||
foreach ($row as $index => $cell) {
|
||||
$length = mb_strlen((string)$cell);
|
||||
$this->columnWidths[$index] = max($this->columnWidths[$index] ?? 0, $length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
src/Framework/Console/Components/TextBox.php
Normal file
124
src/Framework/Console/Components/TextBox.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Console\Components;
|
||||
|
||||
use App\Framework\Console\ConsoleStyle;
|
||||
use App\Framework\Console\ConsoleColor;
|
||||
use App\Framework\Console\ConsoleFormat;
|
||||
|
||||
/**
|
||||
* Rendert eine Textbox in der Konsole.
|
||||
*/
|
||||
final readonly class TextBox
|
||||
{
|
||||
public function __construct(
|
||||
private string $content,
|
||||
private int $width = 80,
|
||||
private int $padding = 1,
|
||||
private ?ConsoleStyle $borderStyle = null,
|
||||
private ?ConsoleStyle $contentStyle = null,
|
||||
private ?string $title = null
|
||||
) {}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$borderStyle = $this->borderStyle ?? ConsoleStyle::create(color: ConsoleColor::GRAY);
|
||||
$contentStyle = $this->contentStyle ?? ConsoleStyle::create();
|
||||
|
||||
$contentWidth = $this->width - ($this->padding * 2) - 2;
|
||||
$lines = $this->wrapText($this->content, $contentWidth);
|
||||
$output = '';
|
||||
|
||||
// Obere Linie
|
||||
if ($this->title) {
|
||||
$titleLength = mb_strlen($this->title);
|
||||
$availableSpace = $this->width - $titleLength - 6; // Abzug für '[ ]' und Ränder
|
||||
$leftPadding = max(0, (int)floor($availableSpace / 2));
|
||||
$rightPadding = max(0, $availableSpace - $leftPadding);
|
||||
|
||||
$output .= $borderStyle->apply('┌' . str_repeat('─', $leftPadding) . '[ ' . $this->title . ' ]' . str_repeat('─', $rightPadding) . '┐') . "\n";
|
||||
} else {
|
||||
$output .= $borderStyle->apply('┌' . str_repeat('─', $this->width - 2) . '┐') . "\n";
|
||||
}
|
||||
|
||||
// Padding oben
|
||||
for ($i = 0; $i < $this->padding; $i++) {
|
||||
$output .= $borderStyle->apply('│') . str_repeat(' ', $this->width - 2) . $borderStyle->apply('│') . "\n";
|
||||
}
|
||||
|
||||
// Inhalt
|
||||
foreach ($lines as $line) {
|
||||
$lineLength = mb_strlen($line);
|
||||
$spaces = $contentWidth - $lineLength;
|
||||
$paddedLine = $line . str_repeat(' ', $spaces);
|
||||
|
||||
$output .= $borderStyle->apply('│') .
|
||||
str_repeat(' ', $this->padding) .
|
||||
$contentStyle->apply($paddedLine) .
|
||||
str_repeat(' ', $this->padding) .
|
||||
$borderStyle->apply('│') . "\n";
|
||||
}
|
||||
|
||||
// Padding unten
|
||||
for ($i = 0; $i < $this->padding; $i++) {
|
||||
$output .= $borderStyle->apply('│') . str_repeat(' ', $this->width - 2) . $borderStyle->apply('│') . "\n";
|
||||
}
|
||||
|
||||
// Untere Linie
|
||||
$output .= $borderStyle->apply('└' . str_repeat('─', $this->width - 2) . '┘') . "\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function wrapText(string $text, int $width): array
|
||||
{
|
||||
$lines = explode("\n", $text);
|
||||
$wrapped = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (mb_strlen($line) <= $width) {
|
||||
$wrapped[] = $line;
|
||||
} else {
|
||||
$wrapped = array_merge($wrapped, $this->splitTextIntoLines($line, $width));
|
||||
}
|
||||
}
|
||||
|
||||
// Leere Zeile hinzufügen, falls keine Inhalte vorhanden
|
||||
if (empty($wrapped)) {
|
||||
$wrapped[] = '';
|
||||
}
|
||||
|
||||
return $wrapped;
|
||||
}
|
||||
|
||||
private function splitTextIntoLines(string $text, int $width): array
|
||||
{
|
||||
$lines = [];
|
||||
$words = explode(' ', $text);
|
||||
$currentLine = '';
|
||||
|
||||
foreach ($words as $word) {
|
||||
$testLine = empty($currentLine) ? $word : $currentLine . ' ' . $word;
|
||||
|
||||
if (mb_strlen($testLine) <= $width) {
|
||||
$currentLine = $testLine;
|
||||
} else {
|
||||
if (!empty($currentLine)) {
|
||||
$lines[] = $currentLine;
|
||||
$currentLine = $word;
|
||||
} else {
|
||||
// Wort ist länger als die Zeile - hart umbrechen
|
||||
$lines[] = mb_substr($word, 0, $width);
|
||||
$currentLine = mb_substr($word, $width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($currentLine)) {
|
||||
$lines[] = $currentLine;
|
||||
}
|
||||
|
||||
return $lines ?: [''];
|
||||
}
|
||||
}
|
||||
215
src/Framework/Console/Components/TreeHelper.php
Normal file
215
src/Framework/Console/Components/TreeHelper.php
Normal file
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Console\Components;
|
||||
|
||||
use App\Framework\Console\ConsoleStyle;
|
||||
use App\Framework\Console\ConsoleColor;
|
||||
use App\Framework\Console\ConsoleFormat;
|
||||
use App\Framework\Console\ConsoleOutput;
|
||||
|
||||
/**
|
||||
* TreeHelper zum Anzeigen hierarchischer Baumstrukturen in der Konsole.
|
||||
* Ähnlich dem Symfony TreeHelper, aber angepasst an unser Styling-System.
|
||||
*/
|
||||
final class TreeHelper
|
||||
{
|
||||
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 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(): void
|
||||
{
|
||||
if (!empty($this->title)) {
|
||||
$this->output->writeLine($this->title, $this->nodeStyle);
|
||||
}
|
||||
|
||||
$this->displayTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(): 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'];
|
||||
|
||||
$this->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user