Skip to content

Commit

Permalink
Icon generator from configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
Spomky committed Jan 27, 2024
1 parent b983496 commit 26387fa
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 2 deletions.
40 changes: 40 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,46 @@ parameters:
count: 1
path: src/Command/CreateIconsCommand.php

-
message: "#^Cannot access property \\$sourcePath on Symfony\\\\Component\\\\AssetMapper\\\\MappedAsset\\|null\\.$#"
count: 1
path: src/Command/CreateIconsFromConfigurationCommand.php

-
message: "#^Cannot call method process\\(\\) on SpomkyLabs\\\\PwaBundle\\\\ImageProcessor\\\\ImageProcessor\\|null\\.$#"
count: 1
path: src/Command/CreateIconsFromConfigurationCommand.php

-
message: "#^Method SpomkyLabs\\\\PwaBundle\\\\Command\\\\CreateIconsFromConfigurationCommand\\:\\:__construct\\(\\) has parameter \\$configuration with no value type specified in iterable type array\\.$#"
count: 1
path: src/Command/CreateIconsFromConfigurationCommand.php

-
message: "#^Method SpomkyLabs\\\\PwaBundle\\\\Command\\\\CreateIconsFromConfigurationCommand\\:\\:generateIcons\\(\\) has parameter \\$generatedIcons with no value type specified in iterable type array\\.$#"
count: 1
path: src/Command/CreateIconsFromConfigurationCommand.php

-
message: "#^Method SpomkyLabs\\\\PwaBundle\\\\Command\\\\CreateIconsFromConfigurationCommand\\:\\:generateIcons\\(\\) has parameter \\$icon with no value type specified in iterable type array\\.$#"
count: 1
path: src/Command/CreateIconsFromConfigurationCommand.php

-
message: "#^Method SpomkyLabs\\\\PwaBundle\\\\Command\\\\CreateIconsFromConfigurationCommand\\:\\:generateIcons\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Command/CreateIconsFromConfigurationCommand.php

-
message: "#^Parameter \\#1 \\$data of static method SpomkyLabs\\\\PwaBundle\\\\Command\\\\CreateIconsFromConfigurationCommand\\:\\:cleanup\\(\\) expects array\\<int\\|string, mixed\\>, array given\\.$#"
count: 1
path: src/Command/CreateIconsFromConfigurationCommand.php

-
message: "#^Parameter \\#4 \\$outputDirectory of method SpomkyLabs\\\\PwaBundle\\\\Command\\\\CreateIconsFromConfigurationCommand\\:\\:generateIcons\\(\\) expects string, mixed given\\.$#"
count: 3
path: src/Command/CreateIconsFromConfigurationCommand.php

-
message: "#^Cannot cast mixed to int\\.$#"
count: 4
Expand Down
175 changes: 175 additions & 0 deletions src/Command/CreateIconsFromConfigurationCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Command;

use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessor;
use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Yaml\Yaml;
use function count;
use function is_array;

#[AsCommand(name: 'pwa:create:icons-from-config', description: 'Generate icons for your PWA from configuration')]
final class CreateIconsFromConfigurationCommand extends Command
{
private readonly MimeTypes $mime;

public function __construct(
#[Autowire('%spomky_labs_pwa.config%')]
private readonly array $configuration,
private readonly Filesystem $filesystem,
private readonly null|ImageProcessor $imageProcessor,
private readonly AssetMapperInterface $assetMapper,
#[Autowire('%kernel.project_dir%')]
private readonly string $projectDir,
) {
parent::__construct();
$this->mime = MimeTypes::getDefault();
}

public function isEnabled(): bool
{
return $this->imageProcessor !== null;
}

protected function configure(): void
{
$this
->addArgument('output', null, 'Output directory', $this->projectDir . '/assets/icons');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('PWA Icons Generator');
if ($this->imageProcessor === null) {
$io->error('The image processor is not enabled.');
return self::FAILURE;
}
$configuration = $this->configuration;

$outputDirectory = $input->getArgument('output');
$applicationIcons = [];
foreach ($configuration['icons'] ?? [] as $icon) {
$applicationIcons = $this->generateIcons($icon, $io, $applicationIcons, $outputDirectory);
}
$configuration['icons'] = $applicationIcons;
foreach ($configuration['shortcuts'] ?? [] as $key => $shortcut) {
$shortcutIcons = [];
foreach ($shortcut['icons'] ?? [] as $icon) {
$shortcutIcons = $this->generateIcons($icon, $io, $shortcutIcons, $outputDirectory);
}
$configuration['shortcuts'][$key]['icons'] = $shortcutIcons;
}
foreach ($configuration['widgets'] ?? [] as $key => $shortcut) {
$shortcutIcons = [];
foreach ($shortcut['icons'] ?? [] as $icon) {
$shortcutIcons = $this->generateIcons($icon, $io, $shortcutIcons, $outputDirectory);
}
$configuration['widgets'][$key]['icons'] = $shortcutIcons;
}
$configuration = self::cleanup($configuration);

$io->success('Icons have been generated. You can now use them in your application configuration file.');
$io->writeln(Yaml::dump([
'pwa' => $configuration,
], 10, 2));

return self::SUCCESS;
}

private function generateIcons(array $icon, SymfonyStyle $io, array $generatedIcons, string $outputDirectory): array
{
$sourcePath = null;
if (! str_starts_with((string) $icon['src'], '/')) {
$asset = $this->assetMapper->getAsset($icon['src']);
$sourcePath = $asset->sourcePath;
}
if ($sourcePath === null) {
$sourcePath = $icon['src'];
}
$content = file_get_contents($sourcePath);
if ($content === false) {
$io->info(sprintf('Unable to read the file "%s". Skipping', $icon['src']));
return [];
}

$pathinfo = pathinfo((string) $sourcePath);
$filenameRoot = $outputDirectory . '/' . $pathinfo['filename'];

foreach ($icon['sizes'] as $size) {
$size = (int) $size;
if ($size !== 0) {
$newIcon = $this->imageProcessor->process($content, $size, $size, $icon['format'] ?? null);
} else {
$newIcon = $content;
}
$tmpFilename = $this->filesystem->tempnam('', 'pwa');
$this->filesystem->dumpFile($tmpFilename, $newIcon);
$result = $this->getFormat($tmpFilename);
if ($result === null) {
$io->info(sprintf('Unable to guess the format for the file "%s". Skipping', $icon['src']));
continue;
}
['extension' => $extension, 'format' => $format] = $result;
$filename = sprintf('%s-%sx%s.%s', $filenameRoot, $size, $size, $extension);
if (! $this->filesystem->exists($filename)) {
$this->filesystem->copy($tmpFilename, $filename);
}
$this->filesystem->remove($tmpFilename);
$asset = $this->assetMapper->getAssetFromSourcePath($filename);

$iconStatement = [
'src' => $asset === null ? $filename : $asset->logicalPath,
'sizes' => $size,
'purpose' => $icon['purpose'] ?? null,
];
$iconStatement = array_filter($iconStatement, fn (mixed $value) => $value !== null);
$generatedIcons[] = $iconStatement;
}
return $generatedIcons;
}

/**
* @return array{extension: string, format: string}|null
*/
private function getFormat(string $sourcePath): ?array
{
$mimeType = $this->mime->guessMimeType($sourcePath);
if ($mimeType === null) {
return null;
}
$extensions = $this->mime->getExtensions($mimeType);
if (count($extensions) === 0) {
return null;
}

return [
'extension' => current($extensions),
'format' => $mimeType,
];
}

/**
* @param array<int|string, mixed> $data
* @return array<int|string, mixed>
*/
private static function cleanup(array $data): array
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = self::cleanup($value);
}
}
return array_filter($data, fn (mixed $value) => ($value !== null && $value !== [] && $value !== ''));
}
}
4 changes: 2 additions & 2 deletions src/DependencyInjection/SpomkyLabsPwaExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ public function load(array $configs, ContainerBuilder $container): void
$container->setParameter('spomky_labs_pwa.manifest_public_url', $config['manifest_public_url']);
$container->setParameter('spomky_labs_pwa.sw_public_url', $config['serviceworker']['dest'] ?? null);

unset(
/*unset(
$config['image_processor'],
$config['web_client'],
$config['path_type_reference'],
$config['manifest_public_url'],
);
);*/
$container->setParameter('spomky_labs_pwa.config', $config);
if (! in_array($container->getParameter('kernel.environment'), ['dev', 'test'], true)) {
$container->removeDefinition(PwaDevServerSubscriber::class);
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Facebook\WebDriver\WebDriverDimension;
use SpomkyLabs\PwaBundle\Command\CreateIconsCommand;
use SpomkyLabs\PwaBundle\Command\CreateIconsFromConfigurationCommand;
use SpomkyLabs\PwaBundle\Command\CreateScreenshotCommand;
use SpomkyLabs\PwaBundle\Command\CreateServiceWorkerCommand;
use SpomkyLabs\PwaBundle\Dto\Manifest;
Expand Down Expand Up @@ -45,6 +46,7 @@
}
if (class_exists(MimeTypes::class)) {
$container->set(CreateIconsCommand::class);
$container->set(CreateIconsFromConfigurationCommand::class);
}

$container->load('SpomkyLabs\\PwaBundle\\Normalizer\\', '../../Normalizer/*')
Expand Down
35 changes: 35 additions & 0 deletions tests/Functional/GenerateIconsFromConfigurationCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace SpomkyLabs\PwaBundle\Tests\Functional;

use PHPUnit\Framework\Attributes\Test;
use Symfony\Component\Console\Tester\CommandTester;

/**
* @internal
*/
final class GenerateIconsFromConfigurationCommandTest extends AbstractPwaTestCase
{
#[Test]
public static function iconsAreCorrectlyCreated(): void
{
// Given
$output = sprintf('%s/samples/icons', self::$kernel->getCacheDir());
$command = self::$application->find('pwa:create:icons-from-config');
$commandTester = new CommandTester($command);

// When
$commandTester->execute([
'output' => $output,
]);

// Then
$commandTester->assertCommandIsSuccessful();
static::assertStringContainsString(
'Icons have been generated. You can now use them in your application',
$commandTester->getDisplay()
);
}
}

0 comments on commit 26387fa

Please sign in to comment.