ComposerSourceFetcher.php•8.81 kB
<?php
declare(strict_types=1);
namespace Butschster\ContextGenerator\Source\Composer;
use Butschster\ContextGenerator\Application\Logger\LoggerPrefix;
use Butschster\ContextGenerator\Lib\Content\ContentBuilderFactory;
use Butschster\ContextGenerator\Lib\Variable\VariableResolver;
use Butschster\ContextGenerator\Modifier\ModifiersApplierInterface;
use Butschster\ContextGenerator\Source\Composer\Provider\ComposerProviderInterface;
use Butschster\ContextGenerator\Source\Fetcher\SourceFetcherInterface;
use Butschster\ContextGenerator\Source\File\FileSource;
use Butschster\ContextGenerator\Source\File\FileSourceFetcher;
use Butschster\ContextGenerator\Source\File\SymfonyFinder;
use Butschster\ContextGenerator\Source\SourceInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
/**
 * Fetcher for Composer package sources
 * @implements SourceFetcherInterface<ComposerSource>
 */
final readonly class ComposerSourceFetcher implements SourceFetcherInterface
{
    private FileSourceFetcher $fileSourceFetcher;
    public function __construct(
        private ComposerProviderInterface $provider,
        SymfonyFinder $finder,
        private string $basePath = '.',
        private ContentBuilderFactory $builderFactory = new ContentBuilderFactory(),
        private VariableResolver $variableResolver = new VariableResolver(),
        #[LoggerPrefix(prefix: 'composer-source-fetcher')]
        private ?LoggerInterface $logger = null,
    ) {
        // Create a FileSourceFetcher to handle the actual file fetching
        $this->fileSourceFetcher = new FileSourceFetcher(
            basePath: $this->basePath,
            finder: $finder,
            builderFactory: $this->builderFactory,
            logger: $this->logger instanceof LoggerInterface ? $this->logger : new NullLogger(),
        );
    }
    public function supports(SourceInterface $source): bool
    {
        $isSupported = $source instanceof ComposerSource;
        $this->logDebug('Checking if source is supported', [
            'sourceType' => $source::class,
            'isSupported' => $isSupported,
        ]);
        return $isSupported;
    }
    public function fetch(SourceInterface $source, ModifiersApplierInterface $modifiersApplier): string
    {
        if (!$source instanceof ComposerSource) {
            $errorMessage = 'Source must be an instance of ComposerSource';
            $this->logError($errorMessage, [
                'sourceType' => $source::class,
            ]);
            throw new \InvalidArgumentException($errorMessage);
        }
        $description = $this->variableResolver->resolve($source->getDescription());
        $this->logInfo('Fetching Composer source content', [
            'description' => $description,
            'composerPath' => $source->composerPath,
            'includeDevDependencies' => $source->includeDevDependencies,
        ]);
        // Create a content builder
        $builder = $this->builderFactory
            ->create()
            ->addTitle($description);
        // Get packages from the provider
        $packages = $this->provider->getPackages(
            $source->composerPath,
            $source->includeDevDependencies,
        );
        // Filter packages if packages is set
        $packages = $packages->filter($source->packages);
        if ($packages->count() === 0) {
            $this->logWarning('No matching packages found', [
                'composerPath' => $source->composerPath,
                'packages' => $source->packages,
            ]);
            $builder->addText('No matching packages found.');
            return $builder->build();
        }
        $this->logInfo('Found matching packages', [
            'count' => $packages->count(),
            'packages' => \array_keys($packages->all()),
        ]);
        // Generate a tree view of selected packages if requested
        if ($source->treeView->enabled) {
            $this->logDebug('Generating package tree view');
            $builder->addTreeView($packages->generateTree());
        }
        // For each package, fetch its source code
        foreach ($packages as $package) {
            $this->logInfo('Processing package', [
                'name' => $package->name,
                'version' => $package->version,
                'path' => $package->path,
            ]);
            $builder->addTitle(\sprintf('%s (%s)', $package->name, $package->version), 2);
            // Add package description and metadata if available
            if ($package->getDescription()) {
                $builder->addDescription($package->getDescription());
            }
            // Create a metadata section with authors, license, homepage, etc.
            $metadata = [];
            if ($authors = $package->getFormattedAuthors()) {
                $metadata[] = "**Authors:** {$authors}";
            }
            if ($license = $package->getFormattedLicense()) {
                $metadata[] = "**License:** {$license}";
            }
            if ($homepage = $package->getHomepage()) {
                $metadata[] = "**Homepage:** {$homepage}";
            }
            if (!empty($metadata)) {
                $builder->addText(\implode("\n", $metadata));
            }
            // Get source directories for this package
            $sourceDirs = $package->getSourceDirectories();
            $this->logDebug('Found source directories', [
                'package' => $package->name,
                'directories' => $sourceDirs,
            ]);
            // For each source directory, create a FileSource and fetch its content
            foreach ($sourceDirs as $dir) {
                $sourceDir = $package->path . '/' . $dir;
                if (!\is_dir($sourceDir)) {
                    $this->logWarning('Source directory not found', [
                        'package' => $package->name,
                        'directory' => $sourceDir,
                    ]);
                    continue;
                }
                $this->logDebug('Creating FileSource for directory', [
                    'package' => $package->name,
                    'directory' => $sourceDir,
                ]);
                // Create a FileSource for this directory
                $fileSource = new FileSource(
                    sourcePaths: $sourceDir,
                    filePattern: $source->filePattern,
                    notPath: $source->notPath,
                    path: $source->path,
                    contains: $source->contains,
                    notContains: $source->notContains,
                    treeView: $source->treeView, // Show tree view for individual directories if requested
                    modifiers: $source->modifiers,
                );
                // Use the FileSourceFetcher to fetch the content
                try {
                    $content = $this->fileSourceFetcher->fetch($fileSource, $modifiersApplier);
                    $builder->addText($content);
                } catch (\Throwable $e) {
                    $this->logError('Error fetching package source', [
                        'package' => $package->name,
                        'directory' => $sourceDir,
                        'error' => $e->getMessage(),
                    ]);
                    $builder->addText(
                        \sprintf(
                            "Error fetching source for %s in directory %s: %s",
                            $package->name,
                            $sourceDir,
                            $e->getMessage(),
                        ),
                    );
                }
            }
            $builder->addSeparator();
        }
        $content = $builder->build();
        $this->logInfo('Composer source content fetched successfully', [
            'packageCount' => $packages->count(),
            'contentLength' => \strlen($content),
        ]);
        return $content;
    }
    /**
     * Log a debug message if a logger is available
     */
    private function logDebug(string $message, array $context = []): void
    {
        $this->logger?->debug($message, $context);
    }
    /**
     * Log an info message if a logger is available
     */
    private function logInfo(string $message, array $context = []): void
    {
        $this->logger?->info($message, $context);
    }
    /**
     * Log a warning message if a logger is available
     */
    private function logWarning(string $message, array $context = []): void
    {
        $this->logger?->warning($message, $context);
    }
    /**
     * Log an error message if a logger is available
     */
    private function logError(string $message, array $context = []): void
    {
        $this->logger?->error($message, $context);
    }
}