diff --git a/src/Composer/DefaultStrategy.php b/src/Composer/DefaultStrategy.php index 0fbc6cc..5531f21 100644 --- a/src/Composer/DefaultStrategy.php +++ b/src/Composer/DefaultStrategy.php @@ -7,12 +7,12 @@ */ namespace Bartlett\BoxManifest\Composer; -use Bartlett\BoxManifest\Helper\ManifestFile; -use Bartlett\BoxManifest\Helper\ManifestFormat; - use InvalidArgumentException; use function class_exists; +use function is_array; use function sprintf; +use function str_ends_with; +use function str_starts_with; /** * This is the default strategy used to build your manifest(s). @@ -26,40 +26,66 @@ public function __construct(private ManifestFactory $factory) { } - public function build(ManifestOptions $options): ?string + public function getCallable(string $outputFormat, ?string $resourceFile): callable { - $factory = $this->factory; + if ('auto' == $outputFormat) { + if (null === $resourceFile) { + return [$this->factory, 'toConsole']; + } + + $recognizedFilePatternsRules = [ + 'sbom.json' => [$this->factory, 'toSbomJson'], + 'sbom.xml' => [$this->factory, 'toSbomXml'], + '.cdx.json' => [$this->factory, 'toSbomJson'], + '.cdx.xml' => [$this->factory, 'toSbomXml'], + 'manifest.txt' => [$this->factory, 'toText'], + 'plain.txt' => [$this->factory, 'toText'], + 'ansi.txt' => [$this->factory, 'toHighlight'], + 'console.txt' => [$this->factory, 'toConsole'], + 'custom.bin' => [$this->factory, 'fromClass'], + ]; + + foreach ($recognizedFilePatternsRules as $extension => $callable) { + if (str_ends_with($resourceFile, $extension)) { + return $callable; + } + } + + throw new InvalidArgumentException(sprintf('Cannot auto-detect format for "%s" resource file', $resourceFile)); + } + + $recognizedOutputFormatRules = [ + 'console' => [$this->factory, 'toConsole'], + 'ansi' => [$this->factory, 'toHighlight'], + 'plain' => [$this->factory, 'toText'], + 'sbom-json' => [$this->factory, 'toSbomJson'], + 'sbom-xml' => [$this->factory, 'toSbomXml'], + ]; + + foreach ($recognizedOutputFormatRules as $format => $callable) { + if ($outputFormat === $format) { + return $callable; + } + } + if (!class_exists($outputFormat)) { + throw new InvalidArgumentException(sprintf('Format "%s" is not supported', $outputFormat)); + } + + return [$this->factory, 'fromClass']; + } + + public function build(ManifestOptions $options): ?string + { /** @var string $rawFormat */ $rawFormat = $options->getFormat(true); - $format = $options->getFormat(); - $outputFile = $options->getOutputFile(); - $sbomSpec = $options->getSbomSpec(); - $output = $outputFile ? ManifestFile::tryFrom(basename($outputFile)) : null; + $callable = $this->getCallable($rawFormat, $options->getOutputFile()); - return match ($format) { - ManifestFormat::auto => match ($output) { - null, ManifestFile::ansi => $factory->toHighlight(), - ManifestFile::consoleTable => $factory->toConsole(), - ManifestFile::txt => $factory->toText(), - ManifestFile::sbomXml => $factory->toSbom('xml', $sbomSpec), - ManifestFile::sbomJson => $factory->toSbom('json', $sbomSpec), - default => match (pathinfo($outputFile ? : '', PATHINFO_EXTENSION)) { - 'xml' => $factory->toSbom('xml', $sbomSpec), - 'json' => $factory->toSbom('json', $sbomSpec), - '', 'txt' => $factory->toText(), - default => throw new InvalidArgumentException('Cannot auto-detect format with such output file') - } - }, - ManifestFormat::plain => $factory->toText(), - ManifestFormat::ansi => $factory->toHighlight(), - ManifestFormat::console => $factory->toConsole(), - ManifestFormat::sbomXml => $factory->toSbom('xml', $sbomSpec), - ManifestFormat::sbomJson => $factory->toSbom('json', $sbomSpec), - default => class_exists($rawFormat) - ? $factory->fromClass($rawFormat) - : throw new InvalidArgumentException(sprintf('Format "%s" is not supported', $rawFormat)) - }; + if (is_array($callable) && str_starts_with($callable[1], 'toSbom')) { + return $callable($options->getSbomSpec()); + } + + return $callable(); } } diff --git a/src/Helper/ManifestFile.php b/src/Helper/ManifestFile.php index 0c65832..3707784 100644 --- a/src/Helper/ManifestFile.php +++ b/src/Helper/ManifestFile.php @@ -18,6 +18,7 @@ enum ManifestFile: string case custom = 'custom.bin'; case ansi = 'ansi.txt'; case txt = 'manifest.txt'; + case plain = 'plain.txt'; case xml = 'manifest.xml'; case sbomXml = 'sbom.xml'; case sbomJson = 'sbom.json'; diff --git a/tests/unit/DefaultStrategyTest.php b/tests/unit/DefaultStrategyTest.php new file mode 100644 index 0000000..cf6097b --- /dev/null +++ b/tests/unit/DefaultStrategyTest.php @@ -0,0 +1,97 @@ +testAutoDetection($outputFormat, $resource, $expectedException); + } + + /** + * @dataProvider dpRecognizedOutputFormat + */ + public function testRecognizedOutputFormat(string $outputFormat, ?string $resource, bool $expectedException): void + { + $this->testAutoDetection($outputFormat, $resource, $expectedException); + } + + private function testAutoDetection(string $outputFormat, ?string $resource, bool $expectedException): void + { + if ($expectedException) { + $this->expectException(InvalidArgumentException::class); + } + + $configFilePath = __DIR__ . '/../fixtures/phario-manifest-2.0.x-dev/box.json'; + + $raw = new stdClass(); + $main = 'main'; + $raw->{$main} = false; + $config = Configuration::create($configFilePath, $raw); + + $factory = new ManifestFactory($config, true, '4.x-dev', '4.x-dev'); + $strategy = new DefaultStrategy($factory); + + $callable = $strategy->getCallable($outputFormat, $resource); + $this->assertIsCallable($callable); + } + + public static function dpRecognizedFilePatterns(): iterable + { + yield ['auto', null, false]; + yield ['auto', 'console.txt', false]; + yield ['auto', 'plain.txt', false]; + yield ['auto', 'manifest.txt', false]; + yield ['auto', 'ansi.txt', false]; + yield ['auto', 'sbom.json', false]; + yield ['auto', 'sbom.xml', false]; + yield ['auto', 'sbom.cdx.json', false]; + yield ['auto', 'sbom.cdx.xml', false]; + yield ['auto', 'custom.bin', false]; + yield ['auto', 'custom.txt', true]; + } + + public static function dpRecognizedOutputFormat(): iterable + { + yield ['console', null, false]; + yield ['console', 'whatever.you.want', false]; + + yield ['plain', null, false]; + yield ['plain', 'whatever.you.want', false]; + + yield ['ansi', null, false]; + yield ['ansi', 'whatever.you.want', false]; + + yield ['sbom-json', null, false]; + yield ['sbom-json', 'whatever.you.want', false]; + + yield ['sbom-xml', null, false]; + yield ['sbom-xml', 'whatever.you.want', false]; + + yield ['\My\Space\ClassNotFound', null, true]; + yield ['\My\Space\ClassNotFound', 'whatever.you.want', true]; + } +}