Compare commits

..

10 Commits

Author SHA1 Message Date
440053bf4f Throw more specific exception if an svg file's contents is invalid svg code 2024-01-11 16:50:48 +00:00
a8af463c5c Add invalid svg exception 2024-01-11 16:49:32 +00:00
3f27e50c32 Add invalid svg 2024-01-11 16:49:14 +00:00
3da5e00361 Throw specific error if file is found to be empty 2024-01-11 16:22:14 +00:00
a19f1f4438 Add empty file exception 2024-01-11 16:08:18 +00:00
6e2228b835 Add empty file 2024-01-11 16:08:05 +00:00
15ee51b884 Add docblock 2024-01-11 16:07:57 +00:00
6ab2baeb4e Add a changelog 2024-01-11 16:01:50 +00:00
33892f81aa Update readme 2024-01-11 15:57:48 +00:00
bb7862c6ac Allow using a space-separated string as the value for additional classes 2024-01-11 15:57:42 +00:00
8 changed files with 81 additions and 15 deletions

7
CHANGELOG.md Normal file
View File

@@ -0,0 +1,7 @@
# Changelog
## [X.X.X] - XXXX-XX-XX
- Allow passing in a space-separated string to the `classes` option
## [1.0.0] - 2023-06-27
- First major version release

View File

@@ -1,5 +1,3 @@
# DEPRECATED in favour of Symfony UX Icon
# PCM Icon Bundle # PCM Icon Bundle
Use icons inside of Twig templates with ease! Project must be using Tailwind for the colours to work properly. Use icons inside of Twig templates with ease! Project must be using Tailwind for the colours to work properly.
@@ -64,7 +62,7 @@ COLOUR:
`hover (string)` Name of the colour to use when the icon is hovered over `hover (string)` Name of the colour to use when the icon is hovered over
`classes (string[])` Additional classes to add to the icon. This can cause Tailwind class collisions, so only use it as a last resort. `classes (string[]|string)` Additional classes to add to the icon as either an array of strings or a space-separated string. This can cause Tailwind class collisions, so it is not recommended to use.
## Reminders ## Reminders
Remember to add `./config/packages/*.yaml` as a value in your Tailwind config content array if it does not already exist. This will ensure Tailwind classes inside of the config file get compiled. Remember to add `./config/packages/*.yaml` as a value in your Tailwind config content array if it does not already exist. This will ensure Tailwind classes inside of the config file get compiled.

View File

@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace Pcm\IconBundle\Exception;
class EmptyFileException extends \Exception {};

View File

@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace Pcm\IconBundle\Exception;
class InvalidSvgException extends \Exception {};

View File

@@ -5,7 +5,9 @@ declare(strict_types=1);
namespace Pcm\IconBundle\Twig\Runtime; namespace Pcm\IconBundle\Twig\Runtime;
use Pcm\IconBundle\Exception\ColourNotFound; use Pcm\IconBundle\Exception\ColourNotFound;
use Pcm\IconBundle\Exception\EmptyFileException;
use Pcm\IconBundle\Exception\IconNotFound; use Pcm\IconBundle\Exception\IconNotFound;
use Pcm\IconBundle\Exception\InvalidSvgException;
use Twig\Extension\RuntimeExtensionInterface; use Twig\Extension\RuntimeExtensionInterface;
final class IconRuntime implements RuntimeExtensionInterface final class IconRuntime implements RuntimeExtensionInterface
@@ -16,7 +18,7 @@ final class IconRuntime implements RuntimeExtensionInterface
'size' => null, 'size' => null,
'colour' => null, 'colour' => null,
'hover' => null, 'hover' => null,
'classes' => [], 'classes' => "",
]; ];
public function __construct(private array $defaultOptions, private array $directories, private array $colours) public function __construct(private array $defaultOptions, private array $directories, private array $colours)
@@ -31,7 +33,8 @@ final class IconRuntime implements RuntimeExtensionInterface
* 'size' => (int) Height and width in px * 'size' => (int) Height and width in px
* 'colour' => (string) Main colour * 'colour' => (string) Main colour
* 'hover' => (?string) Hover colour * 'hover' => (?string) Hover colour
* 'classes' => (array) Additional classes to add to the icon. Not recommended. * 'classes' => (string[]|string) Additional classes to add to the icon, given as
* an array of strings or a space-separated string.
* ] * ]
* ``` * ```
* *
@@ -43,6 +46,14 @@ final class IconRuntime implements RuntimeExtensionInterface
$svg = $this->getSvg($options['icon']); $svg = $this->getSvg($options['icon']);
if (empty($svg)) {
throw new EmptyFileException(\sprintf("The file %s.svg was found, but it was empty!", $options['icon']));
}
if (!$this->isValidXml($svg)) {
throw new InvalidSvgException(\sprintf("The file %s.svg was found, but it does not contain valid SVG code!", $options['icon']));
}
$this->sanitiseSvg($svg); $this->sanitiseSvg($svg);
$colourClasses = $this->getColourClasses($options['colour'], $options['hover']); $colourClasses = $this->getColourClasses($options['colour'], $options['hover']);
@@ -60,9 +71,9 @@ final class IconRuntime implements RuntimeExtensionInterface
return $svg; return $svg;
} }
private function getExtraClasses(array $extraClasses): string private function getExtraClasses(array|string $extraClasses): string
{ {
return implode(' ', $extraClasses); return \is_array($extraClasses) ? implode(' ', $extraClasses) : $extraClasses;
} }
private function mergeWithDefaultOptions(array $userOptions): array private function mergeWithDefaultOptions(array $userOptions): array
@@ -103,6 +114,9 @@ final class IconRuntime implements RuntimeExtensionInterface
$this->removeBlackFillAttributes($svg); $this->removeBlackFillAttributes($svg);
} }
/**
* @throws IconNotFound when an svg file with the passed in name cannot be found
*/
private function findSvgFilepath(string $iconName): string private function findSvgFilepath(string $iconName): string
{ {
foreach ($this->directories as $directory) { foreach ($this->directories as $directory) {
@@ -202,4 +216,15 @@ final class IconRuntime implements RuntimeExtensionInterface
$svg = $this->removeXMLDeclaration($svgAsXml->saveXML()); $svg = $this->removeXMLDeclaration($svgAsXml->saveXML());
} }
private function isValidXml(string $input): bool
{
try {
new \SimpleXMLElement($input);
} catch (\Exception) {
return false;
}
return true;
}
} }

View File

@@ -6,7 +6,9 @@ namespace Pcm\IconBundle\Tests\Twig\Functions;
use Pcm\IconBundle\DependencyInjection\Configuration; use Pcm\IconBundle\DependencyInjection\Configuration;
use Pcm\IconBundle\Exception\ColourNotFound; use Pcm\IconBundle\Exception\ColourNotFound;
use Pcm\IconBundle\Exception\EmptyFileException;
use Pcm\IconBundle\Exception\IconNotFound; use Pcm\IconBundle\Exception\IconNotFound;
use Pcm\IconBundle\Exception\InvalidSvgException;
use Pcm\IconBundle\Twig\Runtime\IconRuntime; use Pcm\IconBundle\Twig\Runtime\IconRuntime;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@@ -50,6 +52,18 @@ class IconRuntimeTest extends TestCase
$this->icon->renderIcon(['icon' => random_bytes(8)]); $this->icon->renderIcon(['icon' => random_bytes(8)]);
} }
public function testThrowsIfPassedInAnEmptyFile(): void
{
$this->expectException(EmptyFileException::class);
$this->icon->renderIcon(['icon' => 'empty']);
}
public function testThrowsIfContentsIsNotValidSvg(): void
{
$this->expectException(InvalidSvgException::class);
$this->icon->renderIcon(['icon' => 'invalid']);
}
public function testNoTitleExistsIfNotPassedIn(): void public function testNoTitleExistsIfNotPassedIn(): void
{ {
$content = $this->icon->renderIcon(['icon' => self::ICON]); $content = $this->icon->renderIcon(['icon' => self::ICON]);
@@ -225,19 +239,26 @@ class IconRuntimeTest extends TestCase
$this->assertMatchesRegularExpression('/<svg.+class=".*group-hover:stroke-white.*".*>/', $contents); $this->assertMatchesRegularExpression('/<svg.+class=".*group-hover:stroke-white.*".*>/', $contents);
} }
public function testExtraClassesThrowsIfNotAnArray(): void public function testExtraClassesThrowsIfNotAnArrayOrString(): void
{ {
$this->expectException(\TypeError::class); $this->expectException(\TypeError::class);
$this->icon->renderIcon(['icon' => self::ICON, 'classes' => 'string_value']); $this->icon->renderIcon(['icon' => self::ICON, 'classes' => 1]);
} }
public function testExtraClassesGetAdded(): void public function testExtraClassesGetAddedFromArray(): void
{ {
$contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => ['abc', 'def']]); $contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => ['abc', 'def']]);
$this->assertMatchesRegularExpression('/<svg.+class=".*abc.*".*>/', $contents); $this->assertMatchesRegularExpression('/<svg.+class=".*abc.*".*>/', $contents);
$this->assertMatchesRegularExpression('/<svg.+class=".*def.*".*>/', $contents); $this->assertMatchesRegularExpression('/<svg.+class=".*def.*".*>/', $contents);
} }
public function testExtraClassesGetAddedFromString(): void
{
$contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => 'ghi jkl']);
$this->assertMatchesRegularExpression('/<svg.+class=".*ghi.*".*>/', $contents);
$this->assertMatchesRegularExpression('/<svg.+class=".*jkl.*".*>/', $contents);
}
public function testAddingExtraClassesDoesntStripAwayColourClasses(): void public function testAddingExtraClassesDoesntStripAwayColourClasses(): void
{ {
$contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => ['abc']]); $contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => ['abc']]);

0
tests/icons/empty.svg Normal file
View File

1
tests/icons/invalid.svg Normal file
View File

@@ -0,0 +1 @@
<svg><svg>

After

Width:  |  Height:  |  Size: 11 B