<?php
declare(strict_types=1);
namespace Butschster\ContextGenerator\Console\Renderer;
use Butschster\ContextGenerator\Config\Import\ImportRegistry;
use Butschster\ContextGenerator\Document\Compiler\CompiledDocument;
use Butschster\ContextGenerator\Document\Document;
use Spiral\Files\FilesInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Renderer for the generate command output
*/
final readonly class GenerateCommandRenderer
{
/**
* Maximum line width for consistent display
*/
private const int MAX_LINE_WIDTH = 100;
/**
* Success indicator symbol
*/
private const string SUCCESS_SYMBOL = '✓';
/**
* Warning indicator symbol
*/
private const string WARNING_SYMBOL = '!';
/**
* Error indicator symbol
*/
private const string ERROR_SYMBOL = '✗';
public function __construct(
private OutputInterface $output,
private ?FilesInterface $files = null,
private ?string $basePath = null,
) {}
public function renderImports(ImportRegistry $imports): void
{
\assert($this->output instanceof SymfonyStyle);
foreach ($imports as $item) {
$description = $item->getPath();
if (\strlen($description) > self::MAX_LINE_WIDTH - 40) {
$halfLength = (self::MAX_LINE_WIDTH - 40) / 2;
$description = \substr($description, 0, $halfLength) . '...' . \substr($description, -$halfLength);
}
// Calculate padding to align the document descriptions
$padding = $this->calculatePadding($item->getType(), $description, 12);
$this->output->writeln(
\sprintf(
' <fg=yellow>%s</> Import %s <fg=yellow>[%s]</><fg=gray>%s</>',
$this->padRight(self::SUCCESS_SYMBOL, 1),
$item->getType(),
$description,
$padding,
),
);
}
$this->output->newLine();
}
/**
* Render the compilation result for a document
*/
public function renderCompilationResult(Document $document, CompiledDocument $compiledDocument): void
{
\assert($this->output instanceof SymfonyStyle);
$hasErrors = $compiledDocument->errors->hasErrors();
$description = $document->description;
$outputPath = $document->outputPath;
// Calculate padding to align the document descriptions
$stats = $this->getFileStatistics($outputPath);
$padding = $this->calculatePadding($description, $outputPath);
if ($hasErrors) {
// Render warning line with document info
$this->output->writeln(
\sprintf(
' <fg=yellow>%s</> %s <fg=yellow>[%s]</><fg=gray>%s</>',
$this->padRight(self::WARNING_SYMBOL, 1),
$description,
$outputPath,
$padding,
),
);
// Render errors
foreach ($compiledDocument->errors as $error) {
$this->output->writeln(\sprintf(' <fg=red>%s</> %s', self::ERROR_SYMBOL, $error));
}
$this->output->newLine();
} else {
// Render success line with document info and file statistics
$this->renderSuccessWithStats($description, $outputPath, $stats, $padding);
}
}
/**
* Render success message with file statistics
*/
private function renderSuccessWithStats(string $description, string $outputPath, ?array $stats, string $padding): void
{
$statsInfo = '';
if ($stats !== null) {
$statsInfo = \sprintf(' <fg=gray>(%s, %d lines)</>', $stats['size'], $stats['lines']);
}
$this->output->writeln(
\sprintf(
' <fg=green>%s</> %s <fg=cyan>[%s]</><fg=gray>%s</>%s',
$this->padRight(self::SUCCESS_SYMBOL, 2),
$description,
$outputPath,
$padding,
$statsInfo,
),
);
}
/**
* Get file statistics for a given output path
*/
private function getFileStatistics(string $outputPath): ?array
{
if ($this->files === null || $this->basePath === null) {
return null;
}
try {
$fullPath = $this->basePath . '/' . $outputPath;
$fullPath = \str_replace('//', '/', $fullPath);
if (!$this->files->exists($fullPath)) {
return null;
}
$fileSize = $this->files->size($fullPath);
$fileContent = $this->files->read($fullPath);
$lineCount = \substr_count($fileContent, "\n") + 1;
return [
'size' => $this->formatSize($fileSize),
'lines' => $lineCount,
];
} catch (\Throwable $e) {
// If we can't read the file, return null to avoid errors
$this->output->writeln(\sprintf(
'<fg=yellow>%s Warning:</> Could not read file statistics for %s: %s',
self::WARNING_SYMBOL,
$outputPath,
$e->getMessage(),
));
return null;
}
}
/**
* Format file size in human-readable format
*/
private function formatSize(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$i = 0;
while ($bytes >= 1024 && $i < \count($units) - 1) {
$bytes = (float) $bytes / 1024.0;
$i++;
}
// Ensure $i is within bounds
$i = \min($i, \count($units) - 1);
return (string) \round($bytes, 1) . ' ' . $units[$i];
}
/**
* Calculate padding to align the document information
*/
private function calculatePadding(string $description, string $outputPath, int $additional = 5): string
{
$totalLength = \strlen($description) + \strlen($outputPath) + $additional;
$padding = \max(0, self::MAX_LINE_WIDTH - $totalLength);
return \str_repeat('.', $padding);
}
/**
* Pad a string on the right with spaces
*/
private function padRight(string $text, int $length): string
{
return \str_pad($text, $length, ' ', \STR_PAD_RIGHT);
}
}