Skip to content

Commit d5ab7e6

Browse files
authored
Add markdown parser extension system via MarkdownExtensionRecipe (#7)
1 parent b0babf6 commit d5ab7e6

File tree

9 files changed

+469
-25
lines changed

9 files changed

+469
-25
lines changed

packages/sprinkle-core/app/src/Core.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
use Lcharette\WebpackEncoreTwig\EntrypointsTwigExtension;
1616
use Lcharette\WebpackEncoreTwig\VersionedAssetsTwigExtension;
17+
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
18+
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
19+
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
1720
use UserFrosting\Event\AppInitiatedEvent;
1821
use UserFrosting\Event\BakeryInitiatedEvent;
1922
use UserFrosting\Event\EventListenerRecipe;
@@ -90,6 +93,7 @@
9093
use UserFrosting\Sprinkle\Core\ServicesProvider\VersionsService;
9194
use UserFrosting\Sprinkle\Core\ServicesProvider\ViteService;
9295
use UserFrosting\Sprinkle\Core\ServicesProvider\WebpackService;
96+
use UserFrosting\Sprinkle\Core\Sprinkle\Recipe\MarkdownExtensionRecipe;
9397
use UserFrosting\Sprinkle\Core\Sprinkle\Recipe\MigrationRecipe;
9498
use UserFrosting\Sprinkle\Core\Sprinkle\Recipe\TwigExtensionRecipe;
9599
use UserFrosting\Sprinkle\Core\Twig\Extensions\AlertsExtension;
@@ -106,6 +110,7 @@
106110
class Core implements
107111
SprinkleRecipe,
108112
TwigExtensionRecipe,
113+
MarkdownExtensionRecipe,
109114
MigrationRecipe,
110115
EventListenerRecipe,
111116
MiddlewareRecipe,
@@ -271,6 +276,20 @@ public function getTwigExtensions(): array
271276
];
272277
}
273278

279+
/**
280+
* Return an array of all registered Markdown Extensions.
281+
*
282+
* {@inheritDoc}
283+
*/
284+
public function getMarkdownExtensions(): array
285+
{
286+
return [
287+
CommonMarkCoreExtension::class,
288+
FrontMatterExtension::class,
289+
GithubFlavoredMarkdownExtension::class,
290+
];
291+
}
292+
274293
/**
275294
* Return an array of all registered Migrations.
276295
*
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* UserFrosting Core Sprinkle (http://www.userfrosting.com)
7+
*
8+
* @link https://github.com/userfrosting/sprinkle-core
9+
* @copyright Copyright (c) 2013-2024 Alexander Weissman & Louis Charette
10+
* @license https://github.com/userfrosting/sprinkle-core/blob/master/LICENSE.md (MIT License)
11+
*/
12+
13+
namespace UserFrosting\Sprinkle\Core\Markdown;
14+
15+
use League\CommonMark\Extension\ExtensionInterface;
16+
use UserFrosting\Support\ClassRepositoryInterface;
17+
18+
/**
19+
* Markdown Extensions Repository Interface.
20+
*
21+
* @extends ClassRepositoryInterface<ExtensionInterface>
22+
*/
23+
interface MarkdownRepositoryInterface extends ClassRepositoryInterface
24+
{
25+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* UserFrosting Core Sprinkle (http://www.userfrosting.com)
7+
*
8+
* @link https://github.com/userfrosting/sprinkle-core
9+
* @copyright Copyright (c) 2013-2024 Alexander Weissman & Louis Charette
10+
* @license https://github.com/userfrosting/sprinkle-core/blob/master/LICENSE.md (MIT License)
11+
*/
12+
13+
namespace UserFrosting\Sprinkle\Core\Markdown;
14+
15+
use League\CommonMark\Extension\ExtensionInterface;
16+
use Psr\Container\ContainerInterface;
17+
use UserFrosting\Sprinkle\Core\Sprinkle\Recipe\MarkdownExtensionRecipe;
18+
use UserFrosting\Sprinkle\SprinkleManager;
19+
use UserFrosting\Support\ClassRepository;
20+
use UserFrosting\Support\Exception\BadClassNameException;
21+
use UserFrosting\Support\Exception\BadInstanceOfException;
22+
23+
/**
24+
* Find and returns all registered ExtensionInterface across all sprinkles, using MarkdownExtensionRecipe.
25+
*
26+
* @extends ClassRepository<ExtensionInterface>
27+
*/
28+
final class SprinkleMarkdownRepository extends ClassRepository implements MarkdownRepositoryInterface
29+
{
30+
/**
31+
* @param SprinkleManager $sprinkleManager
32+
* @param ContainerInterface $ci
33+
*/
34+
public function __construct(
35+
protected SprinkleManager $sprinkleManager,
36+
protected ContainerInterface $ci
37+
) {
38+
}
39+
40+
/**
41+
* {@inheritDoc}
42+
*/
43+
public function all(): array
44+
{
45+
$instances = [];
46+
47+
foreach ($this->sprinkleManager->getSprinkles() as $sprinkle) {
48+
if (!$sprinkle instanceof MarkdownExtensionRecipe) {
49+
continue;
50+
}
51+
foreach ($sprinkle->getMarkdownExtensions() as $extensionClass) {
52+
if (!class_exists($extensionClass)) {
53+
throw new BadClassNameException("Extension class `$extensionClass` not found.");
54+
}
55+
$instance = $this->ci->get($extensionClass);
56+
if (!is_object($instance) || !is_subclass_of($instance, ExtensionInterface::class)) {
57+
throw new BadInstanceOfException("Extension class `$extensionClass` doesn't implement " . ExtensionInterface::class . '.');
58+
}
59+
$instances[] = $instance;
60+
}
61+
}
62+
63+
return $instances;
64+
}
65+
}

packages/sprinkle-core/app/src/ServicesProvider/MarkdownService.php

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,53 @@
1414

1515
use League\CommonMark\ConverterInterface;
1616
use League\CommonMark\Environment\Environment;
17-
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
18-
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
19-
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
2017
use League\CommonMark\MarkdownConverter;
2118
use UserFrosting\Config\Config;
2219
use UserFrosting\ServicesProvider\ServicesProviderInterface;
20+
use UserFrosting\Sprinkle\Core\Markdown\MarkdownRepositoryInterface;
21+
use UserFrosting\Sprinkle\Core\Markdown\SprinkleMarkdownRepository;
2322

2423
/**
2524
* Markdown service. Add CommonMark markdown parser with GitHub Flavored Markdown frontmatter support.
2625
*
27-
* @see https://commonmark.thephpleague.com
26+
* Sprinkles can register custom markdown extensions by implementing the MarkdownExtensionRecipe interface.
2827
*
29-
* TODO : Should have a way to extend the markdown parser with custom extensions.
28+
* @see https://commonmark.thephpleague.com
3029
*/
3130
class MarkdownService implements ServicesProviderInterface
3231
{
3332
public function register(): array
3433
{
3534
return [
36-
ConverterInterface::class => function (Config $config) {
35+
ConverterInterface::class => function (Config $config, MarkdownRepositoryInterface $extensionLoader) {
3736
// Get markdown configuration from config service
3837
$markdownConfig = $config->get('markdown', []);
3938

4039
$environment = new Environment($markdownConfig);
41-
$environment->addExtension(new CommonMarkCoreExtension());
42-
$environment->addExtension(new FrontMatterExtension());
43-
$environment->addExtension(new GithubFlavoredMarkdownExtension());
40+
41+
// Register markdown extensions from sprinkles
42+
$this->registerMarkdownExtensions($environment, $extensionLoader);
4443

4544
// Instantiate the converter engine and start converting some Markdown!
4645
$converter = new MarkdownConverter($environment);
4746

4847
return $converter;
4948
},
49+
50+
MarkdownRepositoryInterface::class => \DI\autowire(SprinkleMarkdownRepository::class),
5051
];
5152
}
53+
54+
/**
55+
* Register all Markdown Extensions defined in Sprinkles MarkdownExtensionRecipe.
56+
*
57+
* @param Environment $environment
58+
* @param MarkdownRepositoryInterface $extensionLoader
59+
*/
60+
protected function registerMarkdownExtensions(Environment $environment, MarkdownRepositoryInterface $extensionLoader): void
61+
{
62+
foreach ($extensionLoader as $extension) {
63+
$environment->addExtension($extension);
64+
}
65+
}
5266
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* UserFrosting Core Sprinkle (http://www.userfrosting.com)
7+
*
8+
* @link https://github.com/userfrosting/sprinkle-core
9+
* @copyright Copyright (c) 2013-2024 Alexander Weissman & Louis Charette
10+
* @license https://github.com/userfrosting/sprinkle-core/blob/master/LICENSE.md (MIT License)
11+
*/
12+
13+
namespace UserFrosting\Sprinkle\Core\Sprinkle\Recipe;
14+
15+
/**
16+
* Sprinkle Markdown Extensions definition Interface.
17+
*/
18+
interface MarkdownExtensionRecipe
19+
{
20+
/**
21+
* Return an array of all registered Markdown Extensions.
22+
*
23+
* @return class-string<\League\CommonMark\Extension\ExtensionInterface>[]
24+
*/
25+
public function getMarkdownExtensions(): array;
26+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* UserFrosting Core Sprinkle (http://www.userfrosting.com)
7+
*
8+
* @link https://github.com/userfrosting/sprinkle-core
9+
* @copyright Copyright (c) 2013-2024 Alexander Weissman & Louis Charette
10+
* @license https://github.com/userfrosting/sprinkle-core/blob/master/LICENSE.md (MIT License)
11+
*/
12+
13+
namespace UserFrosting\Sprinkle\Core\Tests\Integration\Markdown\Fixtures;
14+
15+
use League\CommonMark\Environment\EnvironmentBuilderInterface;
16+
use League\CommonMark\Extension\ExtensionInterface;
17+
18+
/**
19+
* Dummy markdown extension for testing purposes.
20+
*/
21+
class DummyMarkdownExtension implements ExtensionInterface
22+
{
23+
public function register(EnvironmentBuilderInterface $environment): void
24+
{
25+
// This is a dummy extension for testing - it doesn't actually add any functionality
26+
}
27+
}

0 commit comments

Comments
 (0)