diff --git a/CHANGELOG.md b/CHANGELOG.md index 39dd3ac..c0121de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * add CircleCI for build and tests * add changelog * configure and run php-cs-fixer with new code style roles +* add setting custom badge style from query string * add psalm ### Changed @@ -20,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * refactoring library to full typed * upgrade readme * use class notation in tests +* badge style separated from file format ### Deprecated * this version works only with php version >= 7.4 diff --git a/README.md b/README.md index 8cc2ec3..66c41a9 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,16 @@ $poser = new Poser($render); echo $poser->generate('license', 'MIT', '428F7E', 'plastic'); // or -echo $poser->generateFromURI('license-MIT-428F7E.plastic'); +echo $poser->generateFromURI('license-MIT-428F7E.svg?style=plastic'); +// or +echo $poser->generateFromURI('license-MIT-428F7E?style=plastic'); // or $image = $poser->generate('license', 'MIT', '428F7E', 'plastic'); -echo $image->getFormat(); +echo $image->getStyle(); ``` -The allowed format are: `plastic`, `flat` and `flat-square`. +The allowed styles are: `plastic`, `flat` and `flat-square`. ## Encoding diff --git a/features/ui_command_creating_image_file.feature b/features/ui_command_creating_image_file.feature index e7a385e..8b70c08 100644 --- a/features/ui_command_creating_image_file.feature +++ b/features/ui_command_creating_image_file.feature @@ -5,7 +5,7 @@ Feature: Generation of an image I want to use the poser script Scenario: Create the image running a script with plastic format - When I run "poser license MIT blue -p /tmp/img.svg -f plastic" + When I run "poser license MIT blue -p /tmp/img.svg -s plastic" Then it should pass And the content of "/tmp/img.svg" should be equal to "bootstrap/fixtures/license-MIT-blue_plastic.svg" diff --git a/features/ui_command_echo_image.feature b/features/ui_command_echo_image.feature index 4a093aa..4434775 100644 --- a/features/ui_command_echo_image.feature +++ b/features/ui_command_echo_image.feature @@ -5,7 +5,7 @@ Feature: Generation of an image by echo-ing the content I want to use the poser script Scenario: Echo the image running a script with plastic format - When I run "poser license MIT blue -f plastic" + When I run "poser license MIT blue -s plastic" Then it should pass And the same output should be like the content of "bootstrap/fixtures/license-MIT-blue_plastic.svg" diff --git a/spec/PUGX/Poser/BadgeSpec.php b/spec/PUGX/Poser/BadgeSpec.php index 2bb936a..b0c4d28 100644 --- a/spec/PUGX/Poser/BadgeSpec.php +++ b/spec/PUGX/Poser/BadgeSpec.php @@ -10,13 +10,13 @@ class BadgeSpec extends ObjectBehavior { public function it_is_initializable(): void { - $this->beConstructedWith('a', 'b', '97CA00', 'svg'); + $this->beConstructedWith('a', 'b', '97CA00', 'flat'); $this->shouldHaveType(Badge::class); } public function it_should_be_constructed_by_fromURI_factory_method(): void { - $this->beConstructedWith('a', 'b', '97CA00', 'svg'); + $this->beConstructedWith('a', 'b', '97CA00', 'flat'); $assert = 'version-stable-97CA00.svg'; $it = Badge::fromURI($assert); @@ -27,7 +27,7 @@ public function it_should_be_constructed_by_fromURI_factory_method(): void public function it_should_be_constructed_by_fromURI_factory_method_escaping_correctly_underscores(): void { - $this->beConstructedWith('a', 'b', '97CA00', 'svg'); + $this->beConstructedWith('a', 'b', '97CA00', 'flat'); $input = 'I__m__liugg__io-b-97CA00.svg'; $assertInput = 'I_m_liugg_io-b-97CA00.svg'; $it = Badge::fromURI($input); @@ -39,7 +39,7 @@ public function it_should_be_constructed_by_fromURI_factory_method_escaping_corr public function it_should_be_constructed_by_fromURI_factory_method_escaping_correctly_with_single_underscore(): void { - $this->beConstructedWith('a', 'b', '97CA00', 'svg'); + $this->beConstructedWith('a', 'b', '97CA00', 'flat'); $input = 'I_m_liuggio-b-97CA00.svg'; $assertInput = 'I m liuggio-b-97CA00.svg'; $it = Badge::fromURI($input); @@ -51,7 +51,7 @@ public function it_should_be_constructed_by_fromURI_factory_method_escaping_corr public function it_should_be_constructed_by_fromURI_factory_method_escaping_correctly_with_dashes(): void { - $this->beConstructedWith('a', 'b', '97CA00', 'svg'); + $this->beConstructedWith('a', 'b', '97CA00', 'flat'); $input = 'I--m--liuggio-b-97CA00.svg'; $assertInput = 'I-m-liuggio-b-97CA00.svg'; $it = Badge::fromURI($input); @@ -66,7 +66,7 @@ public function it_should_be_constructed_by_fromURI_factory_method_escaping_corr */ public function it_should_validate_available_color_schemes($colorName, $expectedValue): void { - $this->beConstructedWith('a', 'b', $colorName, 'svg'); + $this->beConstructedWith('a', 'b', $colorName, 'flat'); $this->getHexColor()->shouldBeString(); } diff --git a/spec/PUGX/Poser/PoserSpec.php b/spec/PUGX/Poser/PoserSpec.php index d207797..d3bf7bb 100644 --- a/spec/PUGX/Poser/PoserSpec.php +++ b/spec/PUGX/Poser/PoserSpec.php @@ -5,13 +5,16 @@ use PhpSpec\ObjectBehavior; use PUGX\Poser\Poser; use PUGX\Poser\Render\SvgFlatRender; +use PUGX\Poser\Render\SvgFlatSquareRender; class PoserSpec extends ObjectBehavior { public function let(): void { - $render = new SvgFlatRender(); - $this->beConstructedWith([$render]); + $this->beConstructedWith([ + new SvgFlatRender(), + new SvgFlatSquareRender(), + ]); } public function it_is_initializable(): void @@ -24,9 +27,10 @@ public function it_should_be_able_to_generate_an_svg_image(): void $subject = 'stable'; $status = 'v2.0'; $color = '97CA00'; + $style = 'flat'; $format = 'svg'; - $this->generate($subject, $status, $color, $format)->shouldBeAValidSVGImageContaining($subject, $status); + $this->generate($subject, $status, $color, $style, $format)->shouldBeAValidSVGImageContaining($subject, $status); } public function it_should_be_able_to_generate_an_svg_image_from_URI(): void @@ -36,6 +40,48 @@ public function it_should_be_able_to_generate_an_svg_image_from_URI(): void $this->generateFromURI($subject)->shouldBeAValidSVGImageContaining('stable', 'v2.0'); } + public function it_should_be_able_to_generate_an_svg_image_from_URI_without_file_extension(): void + { + $subject = 'stable-v2.0-97CA00'; + + $this->generateFromURI($subject)->shouldBeAValidSVGImageContaining('stable', 'v2.0'); + } + + public function it_should_be_able_to_generate_an_svg_image_from_URI_with_style(): void + { + $subject = 'stable-v2.0-97CA00.svg?style=flat-square'; + + $this->generateFromURI($subject)->shouldBeAValidSVGImageContaining('stable', 'v2.0'); + } + + public function it_should_be_able_to_generate_an_svg_image_from_URI_with_empty_style(): void + { + $subject = 'stable-v2.0-97CA00.svg?style='; + + $this->generateFromURI($subject)->shouldBeAValidSVGImageContaining('stable', 'v2.0'); + } + + public function it_should_be_able_to_generate_an_svg_image_from_URI_with_empty_query(): void + { + $subject = 'stable-v2.0-97CA00.svg?'; + + $this->generateFromURI($subject)->shouldBeAValidSVGImageContaining('stable', 'v2.0'); + } + + public function it_should_be_able_to_generate_an_svg_image_from_URI_without_file_extension_with_style(): void + { + $subject = 'stable-v2.0-97CA00?style=flat-square'; + + $this->generateFromURI($subject)->shouldBeAValidSVGImageContaining('stable', 'v2.0'); + } + + public function it_should_throw_exception_on_generate_an_svg_image_with_bad_uri(): void + { + $subject = 'stable-v2.0-'; + + $this->shouldThrow(\InvalidArgumentException::class)->during('generateFromURI', [$subject]); + } + public function getMatchers(): array { return [ diff --git a/src/Badge.php b/src/Badge.php index e6aa7f7..bb3c4ae 100644 --- a/src/Badge.php +++ b/src/Badge.php @@ -4,6 +4,7 @@ class Badge { + public const DEFAULT_STYLE = 'flat'; public const DEFAULT_FORMAT = 'svg'; private static array $colorScheme = [ @@ -21,14 +22,16 @@ class Badge private string $subject; private string $status; private string $color; + private string $style; private string $format; - public function __construct(string $subject, string $status, string $color, string $format = self::DEFAULT_FORMAT) + public function __construct(string $subject, string $status, string $color, string $style = self::DEFAULT_STYLE, string $format = self::DEFAULT_FORMAT) { $this->subject = $this->escapeValue($subject); $this->status = $this->escapeValue($status); - $this->format = $this->escapeValue($format); $this->color = $this->getColorHex($color); + $this->style = $this->escapeValue($style); + $this->format = $this->escapeValue($format); if (!$this->isValidColorHex($this->color)) { throw new \InvalidArgumentException(\sprintf('Color not valid %s', $this->color)); @@ -51,19 +54,23 @@ public static function getColorNamesAvailable(): array */ public static function fromURI(string $URI): self { - $regex = '/^(([^-]|--)+)-(([^-]|--)+)-(([^-]|--)+)\.(svg|png|gif|jpg)$/'; + $parsedURI = \parse_url($URI); + $path = $parsedURI['path']; + \parse_str($parsedURI['query'] ?? '', $query); + + $regex = '/^(([^-]|--)+)-(([^-]|--)+)-(([^-.]|--)+)(\.(svg|png|gif|jpg))?$/'; $match = []; - if (1 !== \preg_match($regex, $URI, $match) && (7 !== \count($match))) { + if (1 !== \preg_match($regex, $path, $match) && (6 > \count($match))) { throw new \InvalidArgumentException('The URI given is not a valid URI' . $URI); } - $subject = $match[1]; $status = $match[3]; $color = $match[5]; - $format = $match[7]; + $style = isset($query['style']) && '' !== $query['style'] ? $query['style'] : self::DEFAULT_STYLE; + $format = $match[8] ?? self::DEFAULT_FORMAT; - return new self($subject, $status, $color, $format); + return new self($subject, $status, $color, $style, $format); } /** @@ -74,6 +81,14 @@ public function getHexColor(): string return '#' . $this->color; } + /** + * @return string the style of the image eg. `flat`. + */ + public function getStyle(): string + { + return $this->style; + } + /** * @return string the format of the image eg. `svg`. */ diff --git a/src/Image.php b/src/Image.php index c26148b..88e58e8 100644 --- a/src/Image.php +++ b/src/Image.php @@ -21,12 +21,12 @@ class Image { private string $content; - private string $format; + private string $style; - private function __construct(string $content, string $format) + private function __construct(string $content, string $style) { $this->content = $content; - $this->format = $format; + $this->style = $style; } /** @@ -40,13 +40,13 @@ public function __toString(): string /** * Factory method. */ - public static function createFromString(string $content, string $format): self + public static function createFromString(string $content, string $style): self { - return new self($content, $format); + return new self($content, $style); } - public function getFormat(): string + public function getStyle(): string { - return $this->format; + return $this->style; } } diff --git a/src/Poser.php b/src/Poser.php index 9cb8e25..78c6a57 100644 --- a/src/Poser.php +++ b/src/Poser.php @@ -21,23 +21,24 @@ public function __construct($renders) } foreach ($renders as $render) { - $this->addFormatRender($render); + $this->addStyleRender($render); } } /** - * Generate and Render a badge according to the format. + * Generate and Render a badge according to the style. * * @param $subject * @param $status * @param $color + * @param $style * @param $format */ - public function generate(string $subject, string $status, string $color, string $format): Image + public function generate(string $subject, string $status, string $color, string $style, string $format): Image { - $badge = new Badge($subject, $status, $color, $format); + $badge = new Badge($subject, $status, $color, $style, $format); - return $this->getRenderFor($badge->getFormat())->render($badge); + return $this->getRenderFor($badge->getStyle())->render($badge); } /** @@ -50,30 +51,28 @@ public function generateFromURI(string $string): Image { $badge = Badge::fromURI($string); - return $this->getRenderFor($badge->getFormat())->render($badge); + return $this->getRenderFor($badge->getStyle())->render($badge); } /** - * All the formats available. + * All the styles available. */ - public function validFormats(): array + public function validStyles(): array { return \array_keys($this->renders); } - private function addFormatRender(RenderInterface $render): void + private function addStyleRender(RenderInterface $render): void { - foreach ($render->supportedFormats() as $format) { - $this->renders[$format] = $render; - } + $this->renders[$render->getBadgeStyle()] = $render; } - private function getRenderFor(string $format): RenderInterface + private function getRenderFor(string $style): RenderInterface { - if (!isset($this->renders[$format])) { - throw new \InvalidArgumentException(\sprintf('No render founds for this format [%s]', $format)); + if (!isset($this->renders[$style])) { + throw new \InvalidArgumentException(\sprintf('No render founds for this style [%s]', $style)); } - return $this->renders[$format]; + return $this->renders[$style]; } } diff --git a/src/Render/LocalSvgRenderer.php b/src/Render/LocalSvgRenderer.php index ff3ec77..2cd45c2 100644 --- a/src/Render/LocalSvgRenderer.php +++ b/src/Render/LocalSvgRenderer.php @@ -49,7 +49,7 @@ public function render(Badge $badge): Image $template = $this->getTemplate($this->getTemplateName()); $parameters = $this->buildParameters($badge); - return $this->renderSvg($template, $parameters, $badge->getFormat()); + return $this->renderSvg($template, $parameters, $badge->getStyle()); } abstract protected function getTemplateName(): string; @@ -57,12 +57,12 @@ abstract protected function getTemplateName(): string; /** * @return string SVG content of the template */ - private function getTemplate(string $format): string + private function getTemplate(string $style): string { - $filepath = \sprintf('%s/%s.svg', $this->templatesDirectory, $format); + $filepath = \sprintf('%s/%s.svg', $this->templatesDirectory, $style); if (!\file_exists($filepath)) { - throw new \InvalidArgumentException(\sprintf('No template for format %s', $format)); + throw new \InvalidArgumentException(\sprintf('No template for style %s', $style)); } return \file_get_contents($filepath); @@ -73,7 +73,7 @@ private function stringWidth(string $text): float return $this->textSizeCalculator->calculateWidth($text); } - private function renderSvg(string $render, array $parameters, string $format): Image + private function renderSvg(string $render, array $parameters, string $style): Image { foreach ($parameters as $key => $variable) { $render = \str_replace(\sprintf('{{ %s }}', $key), $variable, $render); @@ -88,7 +88,7 @@ private function renderSvg(string $render, array $parameters, string $format): I throw new \RuntimeException('Generated xml is not a SVG'); } - return Image::createFromString($render, $format); + return Image::createFromString($render, $style); } private function buildParameters(Badge $badge): array diff --git a/src/Render/RenderInterface.php b/src/Render/RenderInterface.php index a0b5e20..3f5b422 100644 --- a/src/Render/RenderInterface.php +++ b/src/Render/RenderInterface.php @@ -22,7 +22,7 @@ interface RenderInterface public function render(Badge $badge): Image; /** - * @return array the list of the supported format eg array('svg') + * @return string the style of the badge image eg. `flat`. */ - public function supportedFormats(): array; + public function getBadgeStyle(): string; } diff --git a/src/Render/SvgFlatRender.php b/src/Render/SvgFlatRender.php index 3119138..b387566 100644 --- a/src/Render/SvgFlatRender.php +++ b/src/Render/SvgFlatRender.php @@ -12,24 +12,19 @@ namespace PUGX\Poser\Render; /** - * Class SvgFlatGenerator. + * Class SvgFlatRender. * * @author Giulio De Donato */ class SvgFlatRender extends LocalSvgRenderer { - /** - * A list of all supported formats. - * - * @return array|string[] - */ - public function supportedFormats(): array + public function getBadgeStyle(): string { - return ['flat', 'svg']; + return 'flat'; } protected function getTemplateName(): string { - return 'flat'; + return $this->getBadgeStyle(); } } diff --git a/src/Render/SvgFlatSquareRender.php b/src/Render/SvgFlatSquareRender.php index 7531bfb..979bb41 100644 --- a/src/Render/SvgFlatSquareRender.php +++ b/src/Render/SvgFlatSquareRender.php @@ -12,24 +12,19 @@ namespace PUGX\Poser\Render; /** - * Class SvgFlatGenerator. + * Class SvgFlatSquareRender. * * @author Giulio De Donato */ class SvgFlatSquareRender extends LocalSvgRenderer { - /** - * A list of all supported formats. - * - * @return array|string[] - */ - public function supportedFormats(): array + public function getBadgeStyle(): string { - return ['flat-square']; + return 'flat-square'; } protected function getTemplateName(): string { - return 'flat-square'; + return $this->getBadgeStyle(); } } diff --git a/src/Render/SvgPlasticRender.php b/src/Render/SvgPlasticRender.php index 5530c4f..69b7112 100644 --- a/src/Render/SvgPlasticRender.php +++ b/src/Render/SvgPlasticRender.php @@ -12,25 +12,20 @@ namespace PUGX\Poser\Render; /** - * Class SvgPlasticGenerator. + * Class SvgPlasticRender. * * @author Claudio D'Alicandro * @author Giulio De Donato */ class SvgPlasticRender extends LocalSvgRenderer { - /** - * A list of all supported formats. - * - * @return array|string[] - */ - public function supportedFormats(): array + public function getBadgeStyle(): string { - return ['plastic']; + return 'plastic'; } protected function getTemplateName(): string { - return 'plastic'; + return $this->getBadgeStyle(); } } diff --git a/src/UI/Command.php b/src/UI/Command.php index 4b6a5fa..0545285 100644 --- a/src/UI/Command.php +++ b/src/UI/Command.php @@ -25,8 +25,6 @@ class Command extends BaseCommand private Poser $poser; - protected string $format; - protected string $header; private function init(): void @@ -36,7 +34,6 @@ private function init(): void new SvgFlatRender(), new SvgFlatSquareRender(), ]); - $this->format = 'flat'; $this->header = self::HEADER; } @@ -62,11 +59,19 @@ protected function configure(): void InputArgument::OPTIONAL, 'The hexadecimal color eg. `97CA00` or the name [' . \implode(', ', Badge::getColorNamesAvailable()) . ']' ) + ->addOption( + 'style', + 's', + InputOption::VALUE_REQUIRED, + 'The style of the image eg. `flat`, styles available [' . \implode(', ', $this->poser->validStyles()) . ']', + Badge::DEFAULT_STYLE + ) ->addOption( 'format', 'f', InputOption::VALUE_REQUIRED, - 'The format of the image eg. `svg`, formats available [' . \implode(', ', $this->poser->validFormats()) . ']' + 'The format of the image eg. `svg`, formats available [' . \implode(', ', $this->poser->validStyles()) . ']', + Badge::DEFAULT_FORMAT ) ->addOption( 'path', @@ -84,13 +89,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $subject = $input->getArgument('subject'); $status = $input->getArgument('status'); $color = $input->getArgument('color'); - - if ($input->getOption('format')) { - $this->format = (string) $input->getOption('format'); - } + $style = (string) $input->getOption('style'); + $format = (string) $input->getOption('format'); try { - $imageContent = $this->poser->generate($subject, $status, $color, $this->format); + $imageContent = $this->poser->generate($subject, $status, $color, $style, $format); if ($input->getOption('path')) { $this->storeImage($output, $input->getOption('path'), $imageContent);