Skip to main content
Glama

CTX: Context as Code (CaC) tool

by context-hub
MIT License
235
  • Apple
  • Linux
DocumentCompiler.php8.53 kB
<?php declare(strict_types=1); namespace Butschster\ContextGenerator\Document\Compiler; use Butschster\ContextGenerator\Application\FSPath; use Butschster\ContextGenerator\Application\Logger\LoggerPrefix; use Butschster\ContextGenerator\Document\Compiler\Error\ErrorCollection; use Butschster\ContextGenerator\Document\Compiler\Error\SourceError; use Butschster\ContextGenerator\Document\Document; use Butschster\ContextGenerator\Lib\Content\Block\TextBlock; use Butschster\ContextGenerator\Lib\Content\ContentBuilderFactory; use Butschster\ContextGenerator\Lib\Variable\VariableResolver; use Butschster\ContextGenerator\Modifier\ModifiersApplier; use Butschster\ContextGenerator\Modifier\SourceModifierRegistry; use Butschster\ContextGenerator\SourceParserInterface; use Psr\Log\LoggerInterface; use Spiral\Files\FilesInterface; /** * Handles compiling documents */ final readonly class DocumentCompiler { /** * @param string $basePath Base path for relative file references * @param SourceModifierRegistry $modifierRegistry Registry for source modifiers * @param LoggerInterface|null $logger PSR Logger instance */ public function __construct( private FilesInterface $files, private SourceParserInterface $parser, private string $basePath, private SourceModifierRegistry $modifierRegistry, private VariableResolver $variables, private ContentBuilderFactory $builderFactory = new ContentBuilderFactory(), #[LoggerPrefix(prefix: 'document-compiler')] private ?LoggerInterface $logger = null, ) {} /** * Compile a document with all its sources */ public function compile(Document $document): CompiledDocument { $outputPath = $this->variables->resolve($document->outputPath); $this->logger?->info('Starting document compilation', [ 'outputPath' => $outputPath, ]); $errors = new ErrorCollection($document->getErrors()); $resultPath = (string) FSPath::create($this->basePath)->join($outputPath); if (!$document->overwrite && $this->files->exists($resultPath)) { $this->logger?->notice('Document already exists and overwrite is disabled', [ 'path' => $resultPath, ]); return new CompiledDocument( content: '', errors: $errors, ); } $this->logger?->debug('Building document content'); $compiledDocument = $this->buildContent($errors, $document); $directory = \dirname($resultPath); $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, $finalContent); $errorCount = \count($errors); if ($errorCount > 0) { $this->logger?->warning('Document compiled with errors', ['errorCount' => $errorCount]); } else { $this->logger?->info('Document compiled successfully', [ 'path' => $resultPath, 'contentLength' => \strlen($finalContent), 'fileSize' => $this->files->size($resultPath), ]); } return $compiledDocument->withOutputPath($this->basePath, $outputPath); } /** * Build document content from all sources */ public function buildContent(ErrorCollection $errors, Document $document): CompiledDocument { $description = $this->variables->resolve($document->description); $this->logger?->debug('Creating content builder'); $builder = $this->builderFactory->create(); $this->logger?->debug('Adding document title', ['title' => $description]); $builder->addTitle($description); // Add document tags if present if ($document->hasTags()) { $tags = \implode(', ', $document->getTags()); $this->logger?->debug('Adding document tags', ['tags' => $tags]); $builder->addBlock(new TextBlock($tags, 'DOCUMENT_TAGS')); } $sources = $document->getSources(); $sourceCount = \count($sources); $this->logger?->info('Processing document sources', [ 'sourceCount' => $sourceCount, 'hasDocumentModifiers' => $document->hasModifiers(), ]); // Create a modifiers applier with document-level modifiers if present $modifiersApplier = ModifiersApplier::empty($this->modifierRegistry, $this->logger) ->withModifiers($document->getModifiers()); // Process all sources foreach ($sources as $index => $source) { $sourceType = $source::class; $this->logger?->debug('Processing source', [ 'index' => $index + 1, 'total' => $sourceCount, 'type' => $sourceType, ]); try { if ($source->hasDescription()) { $description = $source->getDescription(); $this->logger?->debug('Adding source description', ['description' => $description]); $builder->addDescription("SOURCE: {$description}"); } $this->logger?->debug('Parsing source content'); $content = $source->parseContent($this->parser, $modifiersApplier); $this->logger?->debug('Adding parsed content to builder', [ 'contentLength' => \strlen($content), ]); $builder->addText($content); $this->logger?->debug('Source processed successfully'); } catch (\Throwable $e) { $this->logger?->error('Error processing source', [ 'sourceType' => $sourceType, 'error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), ]); $errors->add( new SourceError( source: $source, exception: $e, ), ); } } $this->logger?->debug('Content building completed'); // Remove empty lines at the beginning of each line return new CompiledDocument( content: $builder, 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; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/context-hub/generator'

If you have feedback or need assistance with the MCP directory API, please join our Discord server