Skip to content

Commit 2b63fb7

Browse files
committed
feat(icons): add ux:icons:lock command to "mass import" used icons
1 parent c9dbea1 commit 2b63fb7

File tree

6 files changed

+226
-2
lines changed

6 files changed

+226
-2
lines changed

src/Icons/config/iconify.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
use Symfony\UX\Icons\Command\ImportIconCommand;
15+
use Symfony\UX\Icons\Command\LockIconsCommand;
1516
use Symfony\UX\Icons\Iconify;
1617
use Symfony\UX\Icons\Registry\IconifyOnDemandRegistry;
1718

@@ -36,5 +37,13 @@
3637
service('.ux_icons.local_svg_icon_registry'),
3738
])
3839
->tag('console.command')
40+
41+
->set('.ux_icons.command.lock', LockIconsCommand::class)
42+
->args([
43+
service('.ux_icons.iconify'),
44+
service('.ux_icons.local_svg_icon_registry'),
45+
service('.ux_icons.icon_finder'),
46+
])
47+
->tag('console.command')
3948
;
4049
};

src/Icons/doc/index.rst

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ to fetch the icon and always use the *latest version* of the icon. It's possible
109109
that the icon could change or be removed in the future. Additionally, the cache
110110
warming process will take significantly longer if using many *on-demand* icons.
111111

112-
That's why this package provices a command to download the open source icons into
112+
That's why this package provides a command to download the open source icons into
113113
the ``assets/icons/`` directory. You can think of importing an icon as *locking it*
114114
(similar to how ``composer.lock`` *locks* your dependencies):
115115

@@ -126,6 +126,23 @@ the ``assets/icons/`` directory. You can think of importing an icon as *locking
126126

127127
Imported icons must be committed to your repository.
128128

129+
Locking On-Demand Icons
130+
~~~~~~~~~~~~~~~~~~~~~~~
131+
132+
You can *lock* (import) all the *on-demand* icons you're using in your project by
133+
running the following command:
134+
135+
.. code-block:: terminal
136+
137+
$ php bin/console ux:icons:lock
138+
139+
This command only imports icons that do not already exist locally. You can force
140+
the report to overwrite existing icons by using the ``--force`` option:
141+
142+
.. code-block:: terminal
143+
144+
$ php bin/console ux:icons:lock --force
145+
129146
Caching
130147
-------
131148

src/Icons/src/Command/ImportIconCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ protected function configure(): void
4444
->addArgument(
4545
'names',
4646
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
47-
'Icon name from iconify.design (suffix with "@<name>" to rename locally)',
47+
'Icon name from ux.symfony.com/icons (e.g. "mdi:home")',
4848
)
4949
;
5050
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Icons\Command;
13+
14+
use Symfony\Component\Console\Attribute\AsCommand;
15+
use Symfony\Component\Console\Command\Command;
16+
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Input\InputOption;
18+
use Symfony\Component\Console\Output\OutputInterface;
19+
use Symfony\Component\Console\Style\SymfonyStyle;
20+
use Symfony\UX\Icons\Exception\IconNotFoundException;
21+
use Symfony\UX\Icons\Iconify;
22+
use Symfony\UX\Icons\Registry\LocalSvgIconRegistry;
23+
use Symfony\UX\Icons\Twig\IconFinder;
24+
25+
/**
26+
* @author Kevin Bond <[email protected]>
27+
*
28+
* @internal
29+
*/
30+
#[AsCommand(
31+
name: 'ux:icons:lock',
32+
description: 'Scan project and import icon(s) from iconify.design',
33+
)]
34+
final class LockIconsCommand extends Command
35+
{
36+
public function __construct(
37+
private Iconify $iconify,
38+
private LocalSvgIconRegistry $registry,
39+
private IconFinder $iconFinder,
40+
) {
41+
parent::__construct();
42+
}
43+
44+
protected function configure(): void
45+
{
46+
$this
47+
->addOption(
48+
name: 'force',
49+
mode: InputOption::VALUE_NONE,
50+
description: 'Force re-import of all found icons'
51+
)
52+
;
53+
}
54+
55+
protected function execute(InputInterface $input, OutputInterface $output): int
56+
{
57+
$io = new SymfonyStyle($input, $output);
58+
$force = $input->getOption('force');
59+
$count = 0;
60+
61+
$io->comment('Scanning project for icons...');
62+
63+
foreach ($this->iconFinder->icons() as $icon) {
64+
if (2 !== \count($parts = explode(':', $icon))) {
65+
continue;
66+
}
67+
68+
if (!$force && $this->registry->has($icon)) {
69+
// icon already imported
70+
continue;
71+
}
72+
73+
[$prefix, $name] = $parts;
74+
75+
try {
76+
$svg = $this->iconify->fetchSvg($prefix, $name);
77+
} catch (IconNotFoundException) {
78+
// icon not found on iconify
79+
continue;
80+
}
81+
82+
$this->registry->add(sprintf('%s/%s', $prefix, $name), $svg);
83+
84+
$license = $this->iconify->metadataFor($prefix)['license'];
85+
++$count;
86+
87+
$io->text(sprintf(
88+
" <fg=bright-green;options=bold>✓</> Imported <fg=bright-white;bg=black>%s:</><fg=bright-magenta;bg=black;options>%s</> (License: <href=%s>%s</>). Render with: <comment>{{ ux_icon('%s') }}</comment>",
89+
$prefix,
90+
$name,
91+
$license['url'],
92+
$license['title'],
93+
$icon,
94+
));
95+
}
96+
97+
$io->success(sprintf('Imported %d icons.', $count));
98+
99+
return Command::SUCCESS;
100+
}
101+
}

src/Icons/src/Registry/LocalSvgIconRegistry.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ public function get(string $name): Icon
3636
return Icon::fromFile($filename);
3737
}
3838

39+
public function has(string $name): bool
40+
{
41+
try {
42+
$this->get($name);
43+
44+
return true;
45+
} catch (IconNotFoundException) {
46+
return false;
47+
}
48+
}
49+
3950
public function add(string $name, string $svg): void
4051
{
4152
$filename = sprintf('%s/%s.svg', $this->iconDir, $name);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\Icons\Tests\Integration\Command;
13+
14+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
15+
use Symfony\Component\Filesystem\Filesystem;
16+
use Zenstruck\Console\Test\InteractsWithConsole;
17+
18+
/**
19+
* @author Kevin Bond <[email protected]>
20+
*/
21+
final class LockIconsCommandTest extends KernelTestCase
22+
{
23+
use InteractsWithConsole;
24+
25+
private const ICONS = [
26+
__DIR__.'/../../Fixtures/icons/iconamoon/3d-duotone.svg',
27+
__DIR__.'/../../Fixtures/icons/flag/eu-4x3.svg',
28+
];
29+
30+
/**
31+
* @before
32+
*
33+
* @after
34+
*/
35+
public static function cleanup(): void
36+
{
37+
$fs = new Filesystem();
38+
39+
foreach (self::ICONS as $icon) {
40+
$fs->remove($icon);
41+
}
42+
}
43+
44+
public function testImportFoundIcons(): void
45+
{
46+
foreach (self::ICONS as $icon) {
47+
$this->assertFileDoesNotExist($icon);
48+
}
49+
50+
$this->executeConsoleCommand('ux:icons:lock')
51+
->assertSuccessful()
52+
->assertOutputContains('Scanning project for icons...')
53+
->assertOutputContains('Imported flag:eu-4x3')
54+
->assertOutputContains('Imported iconamoon:3d-duotone')
55+
->assertOutputContains('Imported 2 icons')
56+
;
57+
58+
foreach (self::ICONS as $icon) {
59+
$this->assertFileExists($icon);
60+
}
61+
62+
$this->executeConsoleCommand('ux:icons:lock')
63+
->assertSuccessful()
64+
->assertOutputContains('Imported 0 icons')
65+
;
66+
}
67+
68+
public function testForceImportFoundIcons(): void
69+
{
70+
$this->executeConsoleCommand('ux:icons:lock')
71+
->assertSuccessful()
72+
->assertOutputContains('Scanning project for icons...')
73+
->assertOutputContains('Imported flag:eu-4x3')
74+
->assertOutputContains('Imported iconamoon:3d-duotone')
75+
->assertOutputContains('Imported 2 icons')
76+
;
77+
78+
$this->executeConsoleCommand('ux:icons:lock --force')
79+
->assertSuccessful()
80+
->assertOutputContains('Scanning project for icons...')
81+
->assertOutputContains('Imported flag:eu-4x3')
82+
->assertOutputContains('Imported iconamoon:3d-duotone')
83+
->assertOutputContains('Imported 2 icons')
84+
;
85+
}
86+
}

0 commit comments

Comments
 (0)