Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
ShortcodeProcessor
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
10 / 10
13
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 processInput
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getOutput
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 process
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 discoverShortcodes
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addShortcodesFromArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addShortcode
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 expandShortcode
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 discoverSignature
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace Hyde\Framework\Services\Markdown;
4
5use Hyde\Framework\Contracts\MarkdownProcessorContract;
6use Hyde\Framework\Contracts\MarkdownShortcodeContract;
7use Hyde\Framework\Services\Markdown\Shortcodes\AbstractColoredBlockquote;
8
9/**
10 * Handle shortcode processing for Markdown conversions.
11 *
12 * The shortcode system has a few limitations, as it is meant to be simple
13 * by design so that it is easy to understand how the code works, and
14 * what each shortcode does. Shortcodes are expanded on a per-line basis,
15 * and do not support multi-line input. Shortcodes are expected to be
16 * the very first thing on a line. The signature is a static string
17 * that is used to identify the shortcode. The built-in shortcodes
18 * do not use regex, as that would make them harder to read.
19 *
20 * @todo Refactor shortcode manager to singleton as it does not need to be re-instantiated
21 *      for each Markdown conversion.
22 *
23 * @see \Tests\Feature\Services\Markdown\ShortcodeProcessorTest
24 */
25class ShortcodeProcessor implements MarkdownProcessorContract
26{
27    /**
28     * The input Markdown document body.
29     */
30    protected string $input;
31
32    /**
33     * The processed Markdown document body.
34     */
35    protected string $output;
36
37    /**
38     * The activated shortcode instances.
39     */
40    public array $shortcodes;
41
42    public function __construct(string $input)
43    {
44        $this->input = $input;
45
46        $this->discoverShortcodes();
47    }
48
49    public function processInput(): self
50    {
51        $this->output = implode("\n", array_map(function ($line) {
52            return $this->expandShortcode($line);
53        }, explode("\n", $this->input)));
54
55        return $this;
56    }
57
58    public function getOutput(): string
59    {
60        return $this->output;
61    }
62
63    public function run(): string
64    {
65        return $this->processInput()->getOutput();
66    }
67
68    public static function process(string $input): string
69    {
70        return (new static($input))->run();
71    }
72
73    protected function discoverShortcodes(): void
74    {
75        $this->addShortcodesFromArray(array_merge(
76            AbstractColoredBlockquote::get(),
77        ));
78    }
79
80    public function addShortcodesFromArray(array $shortcodes): self
81    {
82        foreach ($shortcodes as $shortcode) {
83            $this->addShortcode($shortcode);
84        }
85
86        return $this;
87    }
88
89    public function addShortcode(MarkdownShortcodeContract $shortcode): self
90    {
91        $this->shortcodes[$shortcode::signature()] = $shortcode;
92
93        return $this;
94    }
95
96    protected function expandShortcode(string $line): string
97    {
98        return array_key_exists($signature = $this->discoverSignature($line), $this->shortcodes)
99            ? $this->shortcodes[$signature]::resolve($line)
100            : $line;
101    }
102
103    protected function discoverSignature(string $line): string
104    {
105        return str_contains($line, ' ') ? substr($line, 0, strpos($line, ' ')) : $line;
106    }
107}