Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
ShortcodeProcessor
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
11 / 11
15
100.00% covered (success)
100.00%
1 / 1
 preprocess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
2 / 2
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
 getShortcodes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addShortcodesFromArray
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 addShortcode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 discoverShortcodes
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getOutput
100.00% covered (success)
100.00%
1 / 1
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
 processInput
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Hyde\Markdown\Processing;
6
7use Hyde\Markdown\Contracts\MarkdownPreProcessorContract;
8use Hyde\Markdown\Contracts\MarkdownShortcodeContract;
9
10use function array_key_exists;
11use function array_map;
12use function explode;
13use function implode;
14use function str_contains;
15use function strpos;
16use function substr;
17
18/**
19 * Handle all shortcode processing for a Markdown conversion.
20 *
21 * The shortcode system has a few limitations, as it is meant to be simple
22 * by design so that it is easy to understand how the code works, and
23 * what each shortcode does. Shortcodes are expanded on a per-line basis,
24 * and do not support multi-line input. Shortcodes are expected to be
25 * the very first thing on a line. The signature is a static string
26 * that is used to identify the shortcode. The built-in shortcodes
27 * do not use regex, as that would make them harder to read.
28 *
29 *
30 * @phpstan-consistent-constructor
31 */
32class ShortcodeProcessor implements MarkdownPreProcessorContract
33{
34    /**
35     * The input Markdown document body.
36     */
37    protected string $input;
38
39    /**
40     * The processed Markdown document body.
41     */
42    protected string $output;
43
44    /**
45     * The activated shortcode instances.
46     *
47     * @var array<string, MarkdownShortcodeContract>
48     */
49    protected array $shortcodes;
50
51    public static function preprocess(string $markdown): string
52    {
53        return (new static($markdown))->run();
54    }
55
56    /** @internal This class may be converted to a singleton. Thus, this constructor should not be relied upon. Use preprocess instead.  */
57    public function __construct(string $input)
58    {
59        $this->input = $input;
60
61        $this->discoverShortcodes();
62    }
63
64    /** @internal Use the preprocess method */
65    public function run(): string
66    {
67        return $this->processInput()->getOutput();
68    }
69
70    /**
71     * @internal As the shortcodes are currently added per-instance, this method is not useful outside of this class.
72     *
73     * @return array<string, MarkdownShortcodeContract>
74     */
75    public function getShortcodes(): array
76    {
77        return $this->shortcodes;
78    }
79
80    /**
81     * @internal As the shortcodes are currently added per-instance, this method is not useful outside of this class.
82     *
83     * @param  array<MarkdownShortcodeContract>  $shortcodes
84     */
85    public function addShortcodesFromArray(array $shortcodes): void
86    {
87        foreach ($shortcodes as $shortcode) {
88            $this->addShortcode($shortcode);
89        }
90    }
91
92    /** @internal As the shortcodes are currently added per-instance, this method is not useful outside of this class. */
93    public function addShortcode(MarkdownShortcodeContract $shortcode): void
94    {
95        $this->shortcodes[$shortcode::signature()] = $shortcode;
96    }
97
98    protected function discoverShortcodes(): void
99    {
100        // Add the built-in shortcodes.
101
102        foreach (ColoredBlockquotes::getSignatures() as $signature) {
103            $this->shortcodes[$signature] = new ColoredBlockquotes();
104        }
105
106        $this->addShortcodesFromArray([
107            //
108        ]);
109    }
110
111    protected function getOutput(): string
112    {
113        return $this->output;
114    }
115
116    protected function expandShortcode(string $line): string
117    {
118        return array_key_exists($signature = $this->discoverSignature($line), $this->shortcodes)
119            ? $this->shortcodes[$signature]::resolve($line)
120            : $line;
121    }
122
123    protected function discoverSignature(string $line): string
124    {
125        return str_contains($line, ' ') ? substr($line, 0, strpos($line, ' ')) : $line;
126    }
127
128    protected function processInput(): static
129    {
130        $this->output = implode("\n", array_map(function (string $line): string {
131            return $this->expandShortcode($line);
132        }, explode("\n", $this->input)));
133
134        return $this;
135    }
136}