From ae858044e212c5190e391d1e5649243131a846ca Mon Sep 17 00:00:00 2001 From: Mistralys Date: Thu, 28 May 2026 08:28:40 +0200 Subject: [PATCH 1/2] Document: Add showFileStats toggle to suppress File Statistics section Introduces a \showFileStats\ boolean (default: true) on Document. Can be set per-document or globally via \settings.showFileStats\; per-document always wins. Implemented across the domain model, YAML parser, JSON schema, and Markdown front-matter transformer. Includes full test coverage and docs. --- README.md | 3 + docs/config/config-system-guide.md | 29 ++++- docs/document-compilation-guide.md | 48 +++++++- docs/markdown-import/README.md | 1 + json-schema.json | 10 ++ .../Local/MarkdownToResourceTransformer.php | 5 + src/Document/Document.php | 7 ++ src/Document/DocumentsParserPlugin.php | 15 +++ .../MarkdownToResourceTransformerTest.php | 96 ++++++++++++++++ tests/src/Unit/Document/DocumentTest.php | 42 +++++++ .../Document/DocumentsParserPluginTest.php | 107 ++++++++++++++++++ 11 files changed, 356 insertions(+), 7 deletions(-) create mode 100644 tests/src/Unit/Config/Import/Source/Local/MarkdownToResourceTransformerTest.php create mode 100644 tests/src/Unit/Document/DocumentsParserPluginTest.php diff --git a/README.md b/README.md index e2985616..e309fdc1 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ structures it into clean markdown documents: documents: - description: User Authentication System outputPath: auth.md + showFileStats: false # Optional — omit the auto-generated File Statistics section sources: - type: file sourcePaths: [ src/Auth ] @@ -115,6 +116,8 @@ documents: commit: "last-week" ``` +Set `showFileStats: false` per document or globally under `settings.showFileStats: false` to suppress the auto-generated File Statistics section. Per-document takes precedence over the global default (`true`). + ### 📐 Declarative Config with JSON Schema Everything is configured through `context.yaml` with full JSON Schema support. Your AI assistant can generate and modify diff --git a/docs/config/config-system-guide.md b/docs/config/config-system-guide.md index 631cdcaa..fcda89b4 100644 --- a/docs/config/config-system-guide.md +++ b/docs/config/config-system-guide.md @@ -192,6 +192,7 @@ final class Document implements \JsonSerializable public readonly string $description, public readonly string $outputPath, public readonly bool $overwrite = true, + public readonly bool $showFileStats = true, private array $modifiers = [], private array $tags = [], SourceInterface ...$sources, @@ -232,9 +233,10 @@ import: documents: - description: API Documentation outputPath: docs/api.md - overwrite: true # Whether to overwrite existing file - tags: [ api, docs ] # Optional tags - modifiers: [ ... ] # Optional modifiers + overwrite: true # Optional, default: true — overwrite existing file + showFileStats: true # Optional, default: true — append the File Statistics section + tags: [ api, docs ] # Optional tags + modifiers: [ ... ] # Optional modifiers sources: - type: file # Source type sourcePaths: # Paths to source files @@ -242,6 +244,27 @@ documents: - src/Controllers ``` +### Global Default via `settings.showFileStats` + +The `showFileStats` flag can also be set globally for all documents using the `settings` block at the root of +`context.yaml`. The per-document value always takes precedence; when neither level is set the default is `true`. + +```yaml +settings: + showFileStats: false # Disable File Statistics for all documents by default + +documents: + - description: API Documentation + outputPath: docs/api.md + # Inherits showFileStats: false from settings + + - description: Reference + outputPath: docs/reference.md + showFileStats: true # Per-document override — stats shown for this document only +``` + +**Precedence:** per-document `showFileStats` > `settings.showFileStats` > default (`true`). + ## 5. Usage Examples ### Basic Configuration Loading diff --git a/docs/document-compilation-guide.md b/docs/document-compilation-guide.md index ab499899..faa95b45 100644 --- a/docs/document-compilation-guide.md +++ b/docs/document-compilation-guide.md @@ -37,6 +37,7 @@ final class Document implements \JsonSerializable public readonly string $description, public readonly string $outputPath, public readonly bool $overwrite = true, + public readonly bool $showFileStats = true, private array $modifiers = [], private array $tags = [], SourceInterface ...$sources, @@ -165,7 +166,8 @@ A document definition in configuration follows this structure: documents: - description: "API Documentation" outputPath: "docs/api.md" - overwrite: true # Optional, default: true + overwrite: true # Optional, default: true + showFileStats: true # Optional, default: true — set to false to omit the auto-generated File Statistics section tags: # Optional tags for categorization - api - reference @@ -187,6 +189,42 @@ documents: This is custom markdown content. ``` +### Global Default via `settings.showFileStats` + +The `showFileStats` flag can also be set globally in the `settings` block at the root of `context.yaml`, providing a +project-wide default for all documents: + +```yaml +settings: + showFileStats: false # Disable File Statistics section for all documents by default + +documents: + - description: "API Documentation" + outputPath: "docs/api.md" + sources: + - type: file + sourcePaths: ["src/Api"] + # Inherits showFileStats: false from settings + + - description: "Reference Guide" + outputPath: "docs/reference.md" + showFileStats: true # Per-document override — stats section is shown for this document only + sources: + - type: file + sourcePaths: ["src/Reference"] +``` + +**Precedence rule:** the per-document `showFileStats` value always wins over the global `settings.showFileStats`. +When neither is set, both default to `true`. + +| Setting | Per-document | Global default | Effective value | +|---------|-------------|----------------|-----------------| +| Neither set | — | — | `true` | +| Global only | — | `false` | `false` | +| Per-document only | `true` | — | `true` | +| Both set | `true` | `false` | `true` (per-document wins) | +| Both set | `false` | `true` | `false` (per-document wins) | + ## 5. Compilation Process The document compilation process follows these steps: @@ -202,9 +240,10 @@ The document compilation process follows these steps: - Parse source content - Apply modifiers (source-specific and document-level) - Add content to the builder -5. **Error Collection**: Collect any errors that occur during compilation -6. **Directory Creation**: Ensure the output directory exists -7. **File Writing**: Write the compiled content to the output file +5. **File Statistics**: If `showFileStats` is `true` (the default), append the auto-generated File Statistics section summarising each included source file. If `showFileStats` is `false`, the compiled content is written as-is — no statistics block is appended and the prior version of the output file is not read. +6. **Error Collection**: Collect any errors that occur during compilation +7. **Directory Creation**: Ensure the output directory exists +8. **File Writing**: Write the compiled content to the output file ## 6. Usage Examples @@ -216,6 +255,7 @@ $document = Document::create( description: 'API Documentation', outputPath: 'docs/api.md', overwrite: true, + showFileStats: true, // Set to false to omit the File Statistics section tags: ['api', 'reference'] ); diff --git a/docs/markdown-import/README.md b/docs/markdown-import/README.md index 5cedfd91..a0977cff 100644 --- a/docs/markdown-import/README.md +++ b/docs/markdown-import/README.md @@ -82,6 +82,7 @@ The system uses this priority order for determining titles and descriptions: - `title`/`description`: Document description - `outputPath`: Where to save the generated document - `overwrite`: Whether to overwrite existing files +- `showFileStats`: Whether to append the auto-generated File Statistics section (default: `true`). When absent from front-matter, the global `settings.showFileStats` value is used; per-document takes precedence when set. - `tags`: Array or comma-separated string of tags - `sources`: Array of source configurations (auto-generated from content if not provided) diff --git a/json-schema.json b/json-schema.json index 779fc02e..a1f39cef 100644 --- a/json-schema.json +++ b/json-schema.json @@ -452,6 +452,11 @@ } } } + }, + "showFileStats": { + "type": "boolean", + "description": "Global default for whether to append the auto-generated File Statistics section to compiled documents", + "default": true } } }, @@ -925,6 +930,11 @@ "description": "Whether to overwrite existing files", "default": true }, + "showFileStats": { + "type": "boolean", + "description": "Whether to append the auto-generated File Statistics section to this document", + "default": true + }, "sources": { "type": "array", "description": "List of content sources for this document", diff --git a/src/Config/Import/Source/Local/MarkdownToResourceTransformer.php b/src/Config/Import/Source/Local/MarkdownToResourceTransformer.php index bebc98bd..20bf1733 100644 --- a/src/Config/Import/Source/Local/MarkdownToResourceTransformer.php +++ b/src/Config/Import/Source/Local/MarkdownToResourceTransformer.php @@ -180,6 +180,11 @@ private function createDocumentResource(string $id, array $metadata, string $con $document['overwrite'] = (bool) $metadata['overwrite']; } + // Add showFileStats flag if specified + if (isset($metadata['showFileStats'])) { + $document['showFileStats'] = (bool) $metadata['showFileStats']; + } + // Add tags if present if (!empty($metadata['tags'])) { $document['tags'] = $this->normalizeTagsArray($metadata['tags']); diff --git a/src/Document/Document.php b/src/Document/Document.php index 71ba48ba..8a0ff7e8 100644 --- a/src/Document/Document.php +++ b/src/Document/Document.php @@ -19,6 +19,7 @@ final class Document implements \JsonSerializable * @param string $description Human-readable description * @param string $outputPath Path where to write the output * @param bool $overwrite Whether to overwrite the file if it already exists + * @param bool $showFileStats Whether to append the auto-generated File Statistics section * @param array $modifiers Modifiers to apply to all sources * @param array $tags Tags to apply to the document */ @@ -26,6 +27,7 @@ public function __construct( public readonly string $description, public readonly string $outputPath, public readonly bool $overwrite = true, + public readonly bool $showFileStats = true, private array $modifiers = [], private array $tags = [], SourceInterface ...$sources, @@ -35,12 +37,14 @@ public function __construct( /** * @param bool $overwrite Whether to overwrite the file if it already exists + * @param bool $showFileStats Whether to append the auto-generated File Statistics section * @param array $tags Tags to apply to the document */ public static function create( string $description, string $outputPath, bool $overwrite = true, + bool $showFileStats = true, array $modifiers = [], array $tags = [], ): self { @@ -48,6 +52,7 @@ public static function create( description: $description, outputPath: $outputPath, overwrite: $overwrite, + showFileStats: $showFileStats, modifiers: $modifiers, tags: $tags, ); @@ -152,12 +157,14 @@ public function getSources(): array return \array_values($this->sources); } + #[\Override] public function jsonSerialize(): array { return \array_filter([ 'description' => $this->description, 'outputPath' => $this->outputPath, 'overwrite' => $this->overwrite, + 'showFileStats' => $this->showFileStats, 'sources' => $this->getSources(), 'modifiers' => $this->getModifiers(), 'tags' => $this->getTags(), diff --git a/src/Document/DocumentsParserPlugin.php b/src/Document/DocumentsParserPlugin.php index 8bd4b07c..c4f3cb4e 100644 --- a/src/Document/DocumentsParserPlugin.php +++ b/src/Document/DocumentsParserPlugin.php @@ -40,6 +40,18 @@ public function updateConfig(array $config, string $rootPath): array return $config; } + /** + * Parse the "documents" section of the configuration and build a DocumentRegistry. + * + * **`showFileStats` resolution order (highest priority first):** + * 1. Per-document `showFileStats` key — when present on a document entry it always takes + * precedence over the global setting. + * 2. Global `settings.showFileStats` — when the per-document key is absent, the value from + * `$config['settings']['showFileStats']` is used. + * 3. Default `true` — when neither the per-document key nor `settings.showFileStats` is + * present, the flag defaults to `true` so the File Statistics section is appended + * (backward-compatible behaviour). + */ public function parse(array $config, string $rootPath): ?RegistryInterface { if (!$this->supports($config)) { @@ -48,6 +60,8 @@ public function parse(array $config, string $rootPath): ?RegistryInterface $registry = new DocumentRegistry(); + $globalShowFileStats = (bool) ($config['settings']['showFileStats'] ?? true); + foreach ($config['documents'] as $index => $docData) { if (!isset($docData['description'], $docData['outputPath'])) { throw new \RuntimeException( @@ -71,6 +85,7 @@ public function parse(array $config, string $rootPath): ?RegistryInterface description: (string) $docData['description'], outputPath: (string) $docData['outputPath'], overwrite: (bool) ($docData['overwrite'] ?? true), + showFileStats: (bool) ($docData['showFileStats'] ?? $globalShowFileStats), modifiers: $documentModifiers, tags: $documentTags, ); diff --git a/tests/src/Unit/Config/Import/Source/Local/MarkdownToResourceTransformerTest.php b/tests/src/Unit/Config/Import/Source/Local/MarkdownToResourceTransformerTest.php new file mode 100644 index 00000000..f118cc4c --- /dev/null +++ b/tests/src/Unit/Config/Import/Source/Local/MarkdownToResourceTransformerTest.php @@ -0,0 +1,96 @@ + [ + [ + 'name' => 'test.md', + 'relativePath' => 'test.md', + 'content' => 'Hello', + 'metadata' => [ + 'description' => 'Test doc', + 'outputPath' => 'out.md', + 'showFileStats' => false, + ], + ], + ], + ]; + + $config = $this->transformer->transform($data); + + $this->assertArrayHasKey('documents', $config); + $this->assertCount(1, $config['documents']); + $this->assertArrayHasKey('showFileStats', $config['documents'][0]); + $this->assertFalse($config['documents'][0]['showFileStats']); + } + + #[Test] + public function it_omits_showFileStats_key_when_absent_from_front_matter(): void + { + $data = [ + 'markdownFiles' => [ + [ + 'name' => 'test.md', + 'relativePath' => 'test.md', + 'content' => 'Hello', + 'metadata' => [ + 'description' => 'Test doc', + 'outputPath' => 'out.md', + ], + ], + ], + ]; + + $config = $this->transformer->transform($data); + + $this->assertArrayHasKey('documents', $config); + $this->assertArrayNotHasKey('showFileStats', $config['documents'][0]); + } + + #[Test] + public function it_leaves_overwrite_handling_unaffected(): void + { + $data = [ + 'markdownFiles' => [ + [ + 'name' => 'test.md', + 'relativePath' => 'test.md', + 'content' => 'Hello', + 'metadata' => [ + 'description' => 'Test doc', + 'outputPath' => 'out.md', + 'overwrite' => false, + 'showFileStats' => false, + ], + ], + ], + ]; + + $config = $this->transformer->transform($data); + + $doc = $config['documents'][0]; + $this->assertFalse($doc['overwrite']); + $this->assertFalse($doc['showFileStats']); + } + + protected function setUp(): void + { + $this->transformer = new MarkdownToResourceTransformer(); + } +} diff --git a/tests/src/Unit/Document/DocumentTest.php b/tests/src/Unit/Document/DocumentTest.php index c817bcc9..34c5c2d0 100644 --- a/tests/src/Unit/Document/DocumentTest.php +++ b/tests/src/Unit/Document/DocumentTest.php @@ -109,11 +109,53 @@ public function it_should_json_serialize_correctly(): void $this->assertArrayHasKey('description', $json); $this->assertArrayHasKey('outputPath', $json); $this->assertArrayHasKey('overwrite', $json); + $this->assertArrayHasKey('showFileStats', $json); $this->assertArrayHasKey('sources', $json); $this->assertArrayHasKey('modifiers', $json); $this->assertArrayHasKey('tags', $json); } + #[Test] + public function it_should_default_show_file_stats_to_true(): void + { + $document = Document::create( + description: 'Test Description', + outputPath: '/path/to/output.txt', + ); + + $this->assertTrue($document->showFileStats); + } + + #[Test] + public function it_should_accept_show_file_stats_false(): void + { + $document = Document::create( + description: 'Test Description', + outputPath: '/path/to/output.txt', + showFileStats: false, + ); + + $this->assertFalse($document->showFileStats); + } + + #[Test] + public function it_should_include_show_file_stats_in_json_serialize(): void + { + $documentTrue = Document::create( + description: 'Test', + outputPath: '/path/to/output.txt', + showFileStats: true, + ); + $documentFalse = Document::create( + description: 'Test', + outputPath: '/path/to/output.txt', + showFileStats: false, + ); + + $this->assertTrue($documentTrue->jsonSerialize()['showFileStats']); + $this->assertFalse($documentFalse->jsonSerialize()['showFileStats']); + } + #[Test] public function it_should_handle_modifiers_with_context(): void { diff --git a/tests/src/Unit/Document/DocumentsParserPluginTest.php b/tests/src/Unit/Document/DocumentsParserPluginTest.php new file mode 100644 index 00000000..93d502c7 --- /dev/null +++ b/tests/src/Unit/Document/DocumentsParserPluginTest.php @@ -0,0 +1,107 @@ + ['showFileStats' => false], + 'documents' => [ + ['description' => 'Doc', 'outputPath' => '/out.md'], + ], + ]; + + $registry = $this->plugin->parse($config, '/root'); + $documents = \iterator_to_array($registry); + + $this->assertCount(1, $documents); + $this->assertFalse($documents[0]->showFileStats); + } + + #[Test] + public function it_allows_per_document_to_override_global_false_with_true(): void + { + $config = [ + 'settings' => ['showFileStats' => false], + 'documents' => [ + ['description' => 'Doc', 'outputPath' => '/out.md', 'showFileStats' => true], + ], + ]; + + $registry = $this->plugin->parse($config, '/root'); + $documents = \iterator_to_array($registry); + + $this->assertCount(1, $documents); + $this->assertTrue($documents[0]->showFileStats); + } + + #[Test] + public function it_defaults_to_true_when_neither_global_nor_per_document_key_is_set(): void + { + $config = [ + 'documents' => [ + ['description' => 'Doc', 'outputPath' => '/out.md'], + ], + ]; + + $registry = $this->plugin->parse($config, '/root'); + $documents = \iterator_to_array($registry); + + $this->assertCount(1, $documents); + $this->assertTrue($documents[0]->showFileStats); + } + + #[Test] + public function it_applies_per_doc_show_file_stats_false_when_no_global(): void + { + $config = [ + 'documents' => [ + ['description' => 'Doc', 'outputPath' => '/out.md', 'showFileStats' => false], + ], + ]; + + $registry = $this->plugin->parse($config, '/root'); + $documents = \iterator_to_array($registry); + + $this->assertCount(1, $documents); + $this->assertFalse($documents[0]->showFileStats); + } + + #[Test] + public function it_does_not_emit_notice_when_settings_is_absent(): void + { + $config = [ + 'documents' => [ + ['description' => 'Doc', 'outputPath' => '/out.md'], + ], + ]; + + // If PHP emitted a notice/warning, PHPUnit would report it as a risky test + // (or error, depending on configuration). A clean parse confirms no notice. + $registry = $this->plugin->parse($config, '/root'); + $documents = \iterator_to_array($registry); + + $this->assertTrue($documents[0]->showFileStats); + } + + protected function setUp(): void + { + $this->sources = $this->createMock(SourceProviderInterface::class); + $this->plugin = new DocumentsParserPlugin($this->sources); + } +} From 64633647c100127d8d4945d6e9239e6788ef41f3 Mon Sep 17 00:00:00 2001 From: Mistralys Date: Thu, 28 May 2026 08:28:46 +0200 Subject: [PATCH 2/2] Document Compiler: Fix file stats computed from stale disk file Rewrites addFileStatistics() to compute byte count and line count directly from the in-memory content string via strlen/substr_count, removing all disk I/O. Stats are now accurate on the very first run. Also drops the redundant post-write files->size() call from the success log. --- src/Document/Compiler/DocumentCompiler.php | 62 ++++----- .../Compiler/DocumentCompilerTest.php | 122 ++++++++++++++---- 2 files changed, 124 insertions(+), 60 deletions(-) diff --git a/src/Document/Compiler/DocumentCompiler.php b/src/Document/Compiler/DocumentCompiler.php index 35515459..d24f7038 100644 --- a/src/Document/Compiler/DocumentCompiler.php +++ b/src/Document/Compiler/DocumentCompiler.php @@ -72,7 +72,9 @@ public function compile(Document $document): CompiledDocument $this->files->ensureDirectory($directory); // Add file statistics to the generated content before writing - $finalContent = $this->addFileStatistics($compiledDocument->content, $resultPath, $outputPath); + $finalContent = $document->showFileStats + ? $this->addFileStatistics($compiledDocument->content, $outputPath) + : (string) $compiledDocument->content; $this->logger?->debug('Writing compiled document to file', ['path' => $resultPath]); $this->files->write($resultPath, $finalContent); @@ -83,8 +85,7 @@ public function compile(Document $document): CompiledDocument } else { $this->logger?->info('Document compiled successfully', [ 'path' => $resultPath, - 'contentLength' => \strlen($finalContent), - 'fileSize' => $this->files->size($resultPath), + 'fileSize' => \strlen($finalContent), ]); } @@ -173,47 +174,36 @@ public function buildContent(ErrorCollection $errors, Document $document): Compi } /** - * Add file statistics to the generated content + * Add file statistics to the generated content. + * + * Statistics are computed directly from the in-memory content string — no disk I/O is + * performed. Byte count and line count therefore describe the content as written in the + * current run, including on the very first compilation where the output file does not yet + * exist. * * @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 + * @param string $outputPath The configured output path (used as the display path in the stats block) * @return string The content with file statistics added */ - private function addFileStatistics(string|\Stringable $content, string $resultPath, string $outputPath): string + private function addFileStatistics(string|\Stringable $content, 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 + $contentStr = (string) $content; + $fileSize = \strlen($contentStr); + $lineCount = \substr_count($contentStr, "\n") + 1; - // Create a new content builder with the original content - $builder = $this->builderFactory->create(); - $builder->addText((string) $content); + // Create a new content builder with the original content + $builder = $this->builderFactory->create(); + $builder->addText($contentStr); - // Add file statistics - $this->logger?->debug('Adding file statistics', [ - 'fileSize' => $fileSize, - 'lineCount' => $lineCount, - 'filePath' => $outputPath, - ]); + // Add file statistics + $this->logger?->debug('Adding file statistics', [ + 'fileSize' => $fileSize, + 'lineCount' => $lineCount, + 'filePath' => $outputPath, + ]); - $builder->addFileStats($fileSize, $lineCount, $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; - } + return $builder->build(); } } diff --git a/tests/src/Unit/Document/Compiler/DocumentCompilerTest.php b/tests/src/Unit/Document/Compiler/DocumentCompilerTest.php index 672a5001..93d06a8e 100644 --- a/tests/src/Unit/Document/Compiler/DocumentCompilerTest.php +++ b/tests/src/Unit/Document/Compiler/DocumentCompilerTest.php @@ -35,14 +35,9 @@ public function it_should_compile_document_with_basic_content(): void description: 'Test Document', outputPath: 'output.txt', overwrite: true, + showFileStats: false, ); - $this->files - ->expects($this->once()) - ->method('exists') - ->with('/base/path/output.txt') - ->willReturn(false); - $this->files ->expects($this->once()) ->method('ensureDirectory') @@ -103,12 +98,6 @@ public function it_should_handle_source_errors(): void $document = $document->addSource($source); - $this->files - ->expects($this->once()) - ->method('exists') - ->with('/base/path/output.txt') - ->willReturn(false); - $compiled = $this->compiler->compile($document); $this->assertEquals("# Test Document", (string) $compiled->content); @@ -123,14 +112,9 @@ public function it_should_include_document_tags(): void description: 'Test Document', outputPath: 'output.txt', overwrite: true, + showFileStats: false, )->addTag(...['tag1', 'tag2']); - $this->files - ->expects($this->once()) - ->method('exists') - ->with('/base/path/output.txt') - ->willReturn(false); - $this->files ->expects($this->once()) ->method('write') @@ -152,6 +136,7 @@ public function it_should_process_multiple_sources(): void description: 'Test Document', outputPath: 'output.txt', overwrite: true, + showFileStats: false, ); $source1 = $this->createMock(SourceInterface::class); @@ -166,12 +151,6 @@ public function it_should_process_multiple_sources(): void $document = $document->addSource($source1)->addSource($source2); - $this->files - ->expects($this->once()) - ->method('exists') - ->with('/base/path/output.txt') - ->willReturn(false); - $this->files ->expects($this->once()) ->method('write') @@ -193,6 +172,7 @@ public function it_should_include_source_description(): void description: 'Test Document', outputPath: 'output.txt', overwrite: true, + showFileStats: false, ); $source = $this->createMock(SourceInterface::class); @@ -255,6 +235,7 @@ public function it_should_compile_empty_document(): void description: 'Empty Document', outputPath: 'empty.txt', overwrite: true, + showFileStats: false, ); $this->files @@ -271,6 +252,99 @@ public function it_should_compile_empty_document(): void $this->assertEquals(0, \count($compiled->errors)); } + #[Test] + public function it_should_skip_file_stats_when_showFileStats_is_false(): void + { + $document = Document::create( + description: 'Doc', + outputPath: 'output.txt', + overwrite: true, + showFileStats: false, + ); + + // exists() should NOT be called for statistics (addFileStatistics is bypassed) + $this->files + ->expects($this->never()) + ->method('exists'); + + $writtenContent = null; + $this->files + ->expects($this->once()) + ->method('write') + ->willReturnCallback(static function (string $path, string $content) use (&$writtenContent): bool { + $writtenContent = $content; + return true; + }); + + $this->compiler->compile($document); + + $this->assertNotNull($writtenContent); + $this->assertStringNotContainsString('File Statistics', $writtenContent); + } + + #[Test] + public function it_should_include_file_stats_when_showFileStats_is_true(): void + { + $document = Document::create( + description: 'Doc', + outputPath: 'output.txt', + overwrite: true, + showFileStats: true, + ); + + $writtenContent = null; + $this->files + ->expects($this->once()) + ->method('write') + ->willReturnCallback(static function (string $path, string $content) use (&$writtenContent): bool { + $writtenContent = $content; + return true; + }); + + $this->compiler->compile($document); + + $this->assertNotNull($writtenContent); + $this->assertStringContainsString('File Statistics', $writtenContent); + } + + #[Test] + public function it_should_include_file_stats_on_first_run(): void + { + $document = Document::create( + description: 'Doc', + outputPath: 'output.txt', + overwrite: true, + showFileStats: true, + ); + + // These filesystem methods must NOT be called — stats are computed from in-memory content + $this->files + ->expects($this->never()) + ->method('exists'); + + $this->files + ->expects($this->never()) + ->method('size'); + + $this->files + ->expects($this->never()) + ->method('read'); + + $writtenContent = null; + $this->files + ->expects($this->once()) + ->method('write') + ->willReturnCallback(static function (string $path, string $content) use (&$writtenContent): bool { + $writtenContent = $content; + return true; + }); + + $this->compiler->compile($document); + + $this->assertNotNull($writtenContent); + $this->assertStringContainsString('File Statistics', $writtenContent); + } + protected function setUp(): void { parent::setUp();