Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
MarkdownConverterService
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
8 / 8
17
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
 parse
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 addExtension
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 initializeExtension
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setupConverter
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 runPreprocessing
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 runPostProcessing
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 getExtensions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Hyde\Framework\Services;
4
5use Hyde\Framework\Concerns\Markdown\HasConfigurableMarkdownFeatures;
6use Hyde\Framework\Concerns\Markdown\HasTorchlightIntegration;
7use Hyde\Framework\Services\Markdown\BladeDownProcessor;
8use Hyde\Framework\Services\Markdown\CodeblockFilepathProcessor;
9use Hyde\Framework\Services\Markdown\ShortcodeProcessor;
10use League\CommonMark\CommonMarkConverter;
11use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
12use Torchlight\Commonmark\V2\TorchlightExtension;
13
14/**
15 * Interface for the CommonMarkConverter,
16 * allowing for easy configuration of extensions.
17 *
18 * @see \Tests\Feature\MarkdownConverterServiceTest
19 * @see \Tests\Feature\Services\HasConfigurableMarkdownFeaturesTest
20 */
21class MarkdownConverterService
22{
23    use HasConfigurableMarkdownFeatures;
24    use HasTorchlightIntegration;
25
26    public string $markdown;
27    public ?string $sourceModel = null;
28
29    protected array $config = [];
30    protected array $extensions = [];
31    protected CommonMarkConverter $converter;
32
33    protected string $html;
34
35    public function __construct(string $markdown, ?string $sourceModel = null)
36    {
37        $this->sourceModel = $sourceModel;
38        $this->markdown = $markdown;
39    }
40
41    public function parse(): string
42    {
43        $this->setupConverter();
44
45        $this->runPreprocessing();
46
47        $this->html = $this->converter->convert($this->markdown);
48
49        $this->runPostProcessing();
50
51        return $this->html;
52    }
53
54    public function addExtension(string $extensionClassName): void
55    {
56        if (! in_array($extensionClassName, $this->extensions)) {
57            $this->extensions[] = $extensionClassName;
58        }
59    }
60
61    public function initializeExtension(string $extensionClassName): void
62    {
63        $this->converter->getEnvironment()->addExtension(new $extensionClassName());
64    }
65
66    protected function setupConverter(): void
67    {
68        // Determine what dynamic extensions to enable
69
70        if ($this->canEnablePermalinks()) {
71            $this->addExtension(HeadingPermalinkExtension::class);
72
73            $this->config = array_merge([
74                'heading_permalink' =>[
75                    'id_prefix' => '',
76                    'fragment_prefix' => '',
77                    'symbol' => '#',
78                    'insert' => 'after',
79                    'min_heading_level' => 2,
80                ],
81            ], $this->config);
82        }
83
84        if ($this->canEnableTorchlight()) {
85            $this->addExtension(TorchlightExtension::class);
86        }
87
88        // Add any custom extensions defined in config
89        foreach (config('markdown.extensions', []) as $extensionClassName) {
90            $this->addExtension($extensionClassName);
91        }
92
93        // Merge any custom configuration options
94        $this->config = array_merge(config('markdown.config', []), $this->config);
95
96        $this->converter = new CommonMarkConverter($this->config);
97
98        foreach ($this->extensions as $extension) {
99            $this->initializeExtension($extension);
100        }
101    }
102
103    protected function runPreprocessing(): void
104    {
105        if (config('markdown.enable_blade', false)) {
106            $this->markdown = BladeDownProcessor::preprocess($this->markdown);
107        }
108
109        $this->markdown = ShortcodeProcessor::process($this->markdown);
110
111        $this->markdown = CodeblockFilepathProcessor::preprocess($this->markdown);
112    }
113
114    protected function runPostProcessing(): void
115    {
116        if ($this->determineIfTorchlightAttributionShouldBeInjected()) {
117            $this->html .= $this->injectTorchlightAttribution();
118        }
119
120        if (config('markdown.enable_blade', false)) {
121            $this->html = BladeDownProcessor::process($this->html);
122        }
123
124        if (config('markdown.features.codeblock_filepaths', true)) {
125            $this->html = CodeblockFilepathProcessor::process($this->html);
126        }
127
128        // Remove any Hyde annotations (everything between `// HYDE!` and `HYDE! //`) (must be done last)
129        $this->html = preg_replace('/ \/\/ HYDE!.*HYDE! \/\//s', '', $this->html);
130    }
131
132    public function getExtensions(): array
133    {
134        return $this->extensions;
135    }
136}