diff --git a/src/Console/GenerateCommand.php b/src/Console/GenerateCommand.php index f1ddf6e2..48ac09c0 100644 --- a/src/Console/GenerateCommand.php +++ b/src/Console/GenerateCommand.php @@ -11,6 +11,7 @@ use Butschster\ContextGenerator\Console\Renderer\GenerateCommandRenderer; use Butschster\ContextGenerator\DirectoriesInterface; use Butschster\ContextGenerator\Document\Compiler\DocumentCompiler; +use Spiral\Files\FilesInterface; use Spiral\Console\Attribute\Option; use Spiral\Core\Container; use Spiral\Core\Scope; @@ -88,6 +89,7 @@ public function __invoke(Container $container, DirectoriesInterface $dirs): int DocumentCompiler $compiler, ConfigurationProvider $configProvider, DirectoriesInterface $dirs, + FilesInterface $files, ): int { try { // Get the appropriate loader based on options provided @@ -120,7 +122,11 @@ public function __invoke(Container $container, DirectoriesInterface $dirs): int } // Create the renderer for consistent output formatting - $renderer = new GenerateCommandRenderer($this->output); + $renderer = new GenerateCommandRenderer( + output: $this->output, + files: $files, + basePath: $dirs->getOutputPath()->toString(), + ); // Display summary header $this->output->writeln(''); diff --git a/src/Console/Renderer/GenerateCommandRenderer.php b/src/Console/Renderer/GenerateCommandRenderer.php index 11712154..a943e48a 100644 --- a/src/Console/Renderer/GenerateCommandRenderer.php +++ b/src/Console/Renderer/GenerateCommandRenderer.php @@ -7,6 +7,7 @@ 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; @@ -37,6 +38,8 @@ public function __construct( private OutputInterface $output, + private ?FilesInterface $files = null, + private ?string $basePath = null, ) {} public function renderImports(ImportRegistry $imports): void @@ -78,6 +81,7 @@ public function renderCompilationResult(Document $document, CompiledDocument $co $outputPath = $document->outputPath; // Calculate padding to align the document descriptions + $stats = $this->getFileStatistics($outputPath); $padding = $this->calculatePadding($description, $outputPath); if ($hasErrors) { @@ -99,17 +103,87 @@ public function renderCompilationResult(Document $document, CompiledDocument $co $this->output->newLine(); } else { - // Render success line with document info - $this->output->writeln( - \sprintf( - ' %s %s [%s]%s', - $this->padRight(self::SUCCESS_SYMBOL, 2), - $description, - $outputPath, - $padding, - ), - ); + // 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(' (%s, %d lines)', $stats['size'], $stats['lines']); + } + + $this->output->writeln( + \sprintf( + ' %s %s [%s]%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( + '%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]; } /** diff --git a/src/Document/Compiler/DocumentCompiler.php b/src/Document/Compiler/DocumentCompiler.php index c2c85d8d..35515459 100644 --- a/src/Document/Compiler/DocumentCompiler.php +++ b/src/Document/Compiler/DocumentCompiler.php @@ -71,8 +71,11 @@ public function compile(Document $document): CompiledDocument $this->logger?->debug('Ensuring directory exists', ['directory' => $directory]); $this->files->ensureDirectory($directory); + // Add file statistics to the generated content before writing + $finalContent = $this->addFileStatistics($compiledDocument->content, $resultPath, $outputPath); + $this->logger?->debug('Writing compiled document to file', ['path' => $resultPath]); - $this->files->write($resultPath, (string) $compiledDocument->content); + $this->files->write($resultPath, $finalContent); $errorCount = \count($errors); if ($errorCount > 0) { @@ -80,7 +83,8 @@ public function compile(Document $document): CompiledDocument } else { $this->logger?->info('Document compiled successfully', [ 'path' => $resultPath, - 'contentLength' => \strlen((string) $compiledDocument->content), + 'contentLength' => \strlen($finalContent), + 'fileSize' => $this->files->size($resultPath), ]); } @@ -167,4 +171,49 @@ public function buildContent(ErrorCollection $errors, Document $document): Compi errors: $errors, ); } + + /** + * Add file statistics to the generated content + * + * @param string|\Stringable $content The original content + * @param string $resultPath The actual file path where content was written + * @param string $outputPath The configured output path + * @return string The content with file statistics added + */ + private function addFileStatistics(string|\Stringable $content, string $resultPath, string $outputPath): string + { + try { + // Check if file exists before attempting to read it + if (!$this->files->exists($resultPath)) { + $this->logger?->debug('File does not exist, skipping statistics', ['path' => $resultPath]); + return (string) $content; + } + + $fileSize = $this->files->size($resultPath); + $fileContent = $this->files->read($resultPath); + $lineCount = \substr_count($fileContent, "\n") + 1; // Count lines including the last line + + // Create a new content builder with the original content + $builder = $this->builderFactory->create(); + $builder->addText((string) $content); + + // Add file statistics + $this->logger?->debug('Adding file statistics', [ + 'fileSize' => $fileSize, + 'lineCount' => $lineCount, + 'filePath' => $outputPath, + ]); + + $builder->addFileStats($fileSize, $lineCount, $outputPath); + + return $builder->build(); + } catch (\Throwable $e) { + $this->logger?->warning('Failed to add file statistics', [ + 'path' => $resultPath, + 'error' => $e->getMessage(), + ]); + // Return original content if statistics calculation fails + return (string) $content; + } + } } diff --git a/src/Lib/Content/Block/FileStatsBlock.php b/src/Lib/Content/Block/FileStatsBlock.php new file mode 100644 index 00000000..5e624570 --- /dev/null +++ b/src/Lib/Content/Block/FileStatsBlock.php @@ -0,0 +1,58 @@ +renderFileStatsBlock($this); + } + + public function getFileSize(): int + { + return $this->fileSize; + } + + public function getLineCount(): int + { + return $this->lineCount; + } + + public function getFilePath(): ?string + { + return $this->filePath; + } + + public 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, 2) . ' ' . $units[$i]; + } +} diff --git a/src/Lib/Content/ContentBuilder.php b/src/Lib/Content/ContentBuilder.php index 54d9877b..301e4081 100644 --- a/src/Lib/Content/ContentBuilder.php +++ b/src/Lib/Content/ContentBuilder.php @@ -12,6 +12,7 @@ use Butschster\ContextGenerator\Lib\Content\Block\TextBlock; use Butschster\ContextGenerator\Lib\Content\Block\TitleBlock; use Butschster\ContextGenerator\Lib\Content\Block\TreeViewBlock; +use Butschster\ContextGenerator\Lib\Content\Block\FileStatsBlock; use Butschster\ContextGenerator\Lib\Content\Renderer\MarkdownRenderer; use Butschster\ContextGenerator\Lib\Content\Renderer\RendererInterface; @@ -123,6 +124,18 @@ public function addTreeView(string $treeView): self return $this->addBlock(new TreeViewBlock($treeView)); } + /** + * Add a file stats block + * + * @param int $fileSize The file size in bytes + * @param int $lineCount The number of lines in the file + * @param string|null $filePath The file path (optional) + */ + public function addFileStats(int $fileSize, int $lineCount, ?string $filePath = null): self + { + return $this->addBlock(new FileStatsBlock('', $fileSize, $lineCount, $filePath)); + } + /** * Add a separator block * diff --git a/src/Lib/Content/Renderer/MarkdownRenderer.php b/src/Lib/Content/Renderer/MarkdownRenderer.php index cc23c515..0dee2bb6 100644 --- a/src/Lib/Content/Renderer/MarkdownRenderer.php +++ b/src/Lib/Content/Renderer/MarkdownRenderer.php @@ -11,6 +11,7 @@ use Butschster\ContextGenerator\Lib\Content\Block\TextBlock; use Butschster\ContextGenerator\Lib\Content\Block\TitleBlock; use Butschster\ContextGenerator\Lib\Content\Block\TreeViewBlock; +use Butschster\ContextGenerator\Lib\Content\Block\FileStatsBlock; /** * Renderer for Markdown format @@ -77,6 +78,22 @@ public function renderTreeViewBlock(TreeViewBlock $block): string return \sprintf("```\n// Structure of documents\n%s\n```\n\n", $content); } + public function renderFileStatsBlock(FileStatsBlock $block): string + { + $filePath = $block->getFilePath() ? "File: `{$block->getFilePath()}`\n" : ''; + $size = $block->formatSize($block->getFileSize()); + $lineCount = $block->getLineCount(); + + return <<getLength()) . "\n\n"; diff --git a/src/Lib/Content/Renderer/RendererInterface.php b/src/Lib/Content/Renderer/RendererInterface.php index bf57a041..53614054 100644 --- a/src/Lib/Content/Renderer/RendererInterface.php +++ b/src/Lib/Content/Renderer/RendererInterface.php @@ -11,6 +11,7 @@ use Butschster\ContextGenerator\Lib\Content\Block\TextBlock; use Butschster\ContextGenerator\Lib\Content\Block\TitleBlock; use Butschster\ContextGenerator\Lib\Content\Block\TreeViewBlock; +use Butschster\ContextGenerator\Lib\Content\Block\FileStatsBlock; /** * Interface for content renderers @@ -42,6 +43,11 @@ public function renderDescriptionBlock(DescriptionBlock $block): string; */ public function renderTreeViewBlock(TreeViewBlock $block): string; + /** + * Render a file stats block + */ + public function renderFileStatsBlock(FileStatsBlock $block): string; + /** * Render a separator block */ diff --git a/tests/src/Unit/Document/Compiler/DocumentCompilerTest.php b/tests/src/Unit/Document/Compiler/DocumentCompilerTest.php index abb4bc93..672a5001 100644 --- a/tests/src/Unit/Document/Compiler/DocumentCompilerTest.php +++ b/tests/src/Unit/Document/Compiler/DocumentCompilerTest.php @@ -38,8 +38,10 @@ public function it_should_compile_document_with_basic_content(): void ); $this->files - ->expects($this->never()) - ->method('exists'); + ->expects($this->once()) + ->method('exists') + ->with('/base/path/output.txt') + ->willReturn(false); $this->files ->expects($this->once()) @@ -102,8 +104,10 @@ public function it_should_handle_source_errors(): void $document = $document->addSource($source); $this->files - ->expects($this->never()) - ->method('exists'); + ->expects($this->once()) + ->method('exists') + ->with('/base/path/output.txt') + ->willReturn(false); $compiled = $this->compiler->compile($document); @@ -122,7 +126,7 @@ public function it_should_include_document_tags(): void )->addTag(...['tag1', 'tag2']); $this->files - ->expects($this->never()) + ->expects($this->once()) ->method('exists') ->with('/base/path/output.txt') ->willReturn(false); @@ -163,8 +167,10 @@ public function it_should_process_multiple_sources(): void $document = $document->addSource($source1)->addSource($source2); $this->files - ->expects($this->never()) - ->method('exists'); + ->expects($this->once()) + ->method('exists') + ->with('/base/path/output.txt') + ->willReturn(false); $this->files ->expects($this->once())