Skip to content

Commit

Permalink
[TASK] Delegate inline node handling to InlineParsers
Browse files Browse the repository at this point in the history
  • Loading branch information
linawolf committed Oct 29, 2023
1 parent 6784187 commit 89bc794
Show file tree
Hide file tree
Showing 13 changed files with 420 additions and 142 deletions.
37 changes: 37 additions & 0 deletions packages/guides-markdown/resources/config/guides-markdown.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,51 @@
declare(strict_types=1);

use phpDocumentor\Guides\Markdown\MarkupLanguageParser;
use phpDocumentor\Guides\Markdown\Parsers\HeaderParser;
use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\EmphasisParser;
use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\LinkParser;
use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\PlainTextParser;
use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\StrongParser;
use phpDocumentor\Guides\Markdown\Parsers\ListBlockParser;
use phpDocumentor\Guides\Markdown\Parsers\ParagraphParser;
use phpDocumentor\Guides\Markdown\Parsers\SeparatorParser;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\String\Slugger\AsciiSlugger;

use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;

return static function (ContainerConfigurator $container): void {
$container->services()
->defaults()
->autowire()
->autoconfigure()

->set(AsciiSlugger::class)

->set(HeaderParser::class)
->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser'))
->tag('phpdoc.guides.markdown.parser.blockParser')
->set(ListBlockParser::class)
->tag('phpdoc.guides.markdown.parser.blockParser')
->set(ParagraphParser::class)
->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser'))
->tag('phpdoc.guides.markdown.parser.blockParser')
->set(SeparatorParser::class)
->tag('phpdoc.guides.markdown.parser.blockParser')

->set(EmphasisParser::class)
->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser'))
->tag('phpdoc.guides.markdown.parser.inlineParser')
->set(LinkParser::class)
->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser'))
->tag('phpdoc.guides.markdown.parser.inlineParser')
->set(PlainTextParser::class)
->tag('phpdoc.guides.markdown.parser.inlineParser')
->set(StrongParser::class)
->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser'))
->tag('phpdoc.guides.markdown.parser.inlineParser')

->set(MarkupLanguageParser::class)
->arg('$parsers', tagged_iterator('phpdoc.guides.markdown.parser.blockParser'))
->tag('phpdoc.guides.parser.markupLanguageParser');
};
95 changes: 12 additions & 83 deletions packages/guides-markdown/src/Markdown/MarkupLanguageParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,15 @@

use League\CommonMark\Environment\Environment as CommonMarkEnvironment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock;
use League\CommonMark\Extension\CommonMark\Node\Inline\Code;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Node\Block\Document;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Node\NodeWalker;
use League\CommonMark\Parser\MarkdownParser;
use phpDocumentor\Guides\Markdown\Parsers\ListBlockParser;
use phpDocumentor\Guides\Markdown\Parsers\ParagraphParser;
use phpDocumentor\Guides\Markdown\Parsers\SeparatorParser;
use phpDocumentor\Guides\MarkupLanguageParser as MarkupLanguageParserInterface;
use phpDocumentor\Guides\Nodes\AnchorNode;
use phpDocumentor\Guides\Nodes\CodeNode;
use phpDocumentor\Guides\Nodes\DocumentNode;
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
use phpDocumentor\Guides\Nodes\Node;
use phpDocumentor\Guides\Nodes\RawNode;
use phpDocumentor\Guides\Nodes\SpanNode;
use phpDocumentor\Guides\Nodes\TitleNode;
use phpDocumentor\Guides\ParserContext;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\String\Slugger\AsciiSlugger;

use function md5;
use function sprintf;
Expand All @@ -42,24 +26,16 @@ final class MarkupLanguageParser implements MarkupLanguageParserInterface

private ParserContext|null $parserContext = null;

/** @var ParserInterface<Node>[] */
private readonly array $parsers;

private DocumentNode|null $document = null;
private readonly AsciiSlugger $idGenerator;

/** @param iterable<ParserInterface<Node>> $parsers */
public function __construct(
private readonly LoggerInterface $logger,
private readonly iterable $parsers,
) {
$cmEnvironment = new CommonMarkEnvironment(['html_input' => 'strip']);
$cmEnvironment->addExtension(new CommonMarkCoreExtension());
$this->markdownParser = new MarkdownParser($cmEnvironment);
$this->idGenerator = new AsciiSlugger();
$this->parsers = [
new ParagraphParser($logger),
new ListBlockParser($logger),
new SeparatorParser(),
];
}

public function supports(string $inputFormat): bool
Expand All @@ -82,72 +58,25 @@ private function parseDocument(NodeWalker $walker, string $hash): DocumentNode
$this->document = $document;

while ($event = $walker->next()) {
$node = $event->getNode();

foreach ($this->parsers as $parser) {
if (!$parser->supports($event)) {
continue;
}
$commonMarkNode = $event->getNode();

$document->addChildNode($parser->parse($this, $walker));
}

// ignore all Entering events; these are only used to switch to another context and context switching
// is defined above
if ($event->isEntering()) {
continue;
}

if ($node instanceof Document) {
return $document;
}

if ($node instanceof Heading) {
$content = $node->firstChild();
if ($content instanceof Text === false) {
continue;
// Use entering events for context switching
foreach ($this->parsers as $parser) {
if ($parser->supports($event)) {
$document->addChildNode($parser->parse($this, $walker, $commonMarkNode));
break;
}
}

$title = new TitleNode(
InlineCompoundNode::getPlainTextInlineNode($content->getLiteral()),
$node->getLevel(),
$this->idGenerator->slug($content->getLiteral())->lower()->toString(),
);
$document->addChildNode($title);
continue;
}

if ($node instanceof Text) {
$spanNode = new SpanNode($node->getLiteral(), []);
$document->addChildNode($spanNode);
continue;
}

if ($node instanceof Code) {
$spanNode = new CodeNode([$node->getLiteral()]);
$document->addChildNode($spanNode);
continue;
}

if ($node instanceof Link) {
$spanNode = new AnchorNode($node->getUrl());
$document->addChildNode($spanNode);
continue;
}

if ($node instanceof FencedCode) {
$spanNode = new CodeNode([$node->getLiteral()]);
$document->addChildNode($spanNode);
continue;
}

if ($node instanceof HtmlBlock) {
$spanNode = new RawNode($node->getLiteral());
$document->addChildNode($spanNode);
continue;
if ($commonMarkNode instanceof Document) {
return $document;
}

$this->logger->warning(sprintf('DOCUMENT CONTEXT: I am leaving a %s node', $node::class));
$this->logger->warning(sprintf('DOCUMENT CONTEXT: I am leaving a %s node', $commonMarkNode::class));
}

return $document;
Expand Down
3 changes: 2 additions & 1 deletion packages/guides-markdown/src/Markdown/ParserInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace phpDocumentor\Guides\Markdown;

use League\CommonMark\Node\Node as CommonMarkNode;
use League\CommonMark\Node\NodeWalker;
use League\CommonMark\Node\NodeWalkerEvent;
use phpDocumentor\Guides\MarkupLanguageParser as GuidesParser;
Expand All @@ -13,7 +14,7 @@
interface ParserInterface
{
/** @return TValue */
public function parse(GuidesParser $parser, NodeWalker $walker): Node;
public function parse(GuidesParser $parser, NodeWalker $walker, CommonMarkNode $current): Node;

public function supports(NodeWalkerEvent $event): bool;
}
71 changes: 71 additions & 0 deletions packages/guides-markdown/src/Markdown/Parsers/HeaderParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Guides\Markdown\Parsers;

use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
use League\CommonMark\Node\Node as CommonMarkNode;
use League\CommonMark\Node\NodeWalker;
use League\CommonMark\Node\NodeWalkerEvent;
use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\AbstractInlineParser;
use phpDocumentor\Guides\MarkupLanguageParser;
use phpDocumentor\Guides\Nodes\Inline\InlineNode;
use phpDocumentor\Guides\Nodes\InlineCompoundNode;
use phpDocumentor\Guides\Nodes\Node;
use phpDocumentor\Guides\Nodes\TitleNode;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\String\Slugger\AsciiSlugger;

use function sprintf;

/** @extends AbstractBlockParser<TitleNode> */
final class HeaderParser extends AbstractBlockParser
{
/** @param iterable<AbstractInlineParser<InlineNode>> $inlineParsers */
public function __construct(
private readonly iterable $inlineParsers,
private readonly LoggerInterface $logger,
private readonly AsciiSlugger $idGenerator,
) {
}

public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): Node
{
$content = [];

while ($event = $walker->next()) {
$commonMarkNode = $event->getNode();

if ($event->isEntering()) {
foreach ($this->inlineParsers as $subParser) {
if ($subParser->supports($event)) {
$content[] = $subParser->parse($parser, $walker, $commonMarkNode);
break;
}
}

continue;
}

// leaving the heading node
if ($commonMarkNode instanceof Heading) {
return new TitleNode(
new InlineCompoundNode($content),
$commonMarkNode->getLevel(),
$this->idGenerator->slug($content[0]->toString() ?? '')->lower()->toString(),
);
}

$this->logger->warning(sprintf('Header CONTEXT: I am leaving a %s node', $commonMarkNode::class));
}

throw new RuntimeException('Unexpected end of NodeWalker');
}

public function supports(NodeWalkerEvent $event): bool
{
return $event->isEntering() && $event->getNode() instanceof Heading;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Guides\Markdown\Parsers\InlineParsers;

use League\CommonMark\Node\Node as CommonMarkNode;
use League\CommonMark\Node\NodeWalker;
use phpDocumentor\Guides\Markdown\ParserInterface;
use phpDocumentor\Guides\MarkupLanguageParser;
use phpDocumentor\Guides\Nodes\Inline\InlineNode;

/**
* @template TValue as InlineNode
* @implements ParserInterface<TValue>
*/
abstract class AbstractInlineParser implements ParserInterface
{
abstract public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): InlineNode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace phpDocumentor\Guides\Markdown\Parsers\InlineParsers;

use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
use League\CommonMark\Node\Node as CommonMarkNode;
use League\CommonMark\Node\NodeWalker;
use League\CommonMark\Node\NodeWalkerEvent;
use phpDocumentor\Guides\MarkupLanguageParser;
use phpDocumentor\Guides\Nodes\Inline\EmphasisInlineNode;
use phpDocumentor\Guides\Nodes\Inline\InlineNode;
use phpDocumentor\Guides\Nodes\Inline\PlainTextInlineNode;
use Psr\Log\LoggerInterface;
use RuntimeException;

use function count;
use function sprintf;
use function var_export;

/** @extends AbstractInlineParser<EmphasisInlineNode> */
final class EmphasisParser extends AbstractInlineParser
{
/** @param iterable<AbstractInlineParser<InlineNode>> $inlineParsers */
public function __construct(
private readonly iterable $inlineParsers,
private readonly LoggerInterface $logger,
) {
}

public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): InlineNode
{
$content = [];

while ($event = $walker->next()) {
$commonMarkNode = $event->getNode();

if ($event->isEntering()) {
foreach ($this->inlineParsers as $subParser) {
if (!$subParser->supports($event)) {
continue;
}

$content[] = $subParser->parse($parser, $walker, $commonMarkNode);
}

continue;
}

if ($commonMarkNode instanceof Emphasis) {
if (count($content) > 0 && $content[0] instanceof PlainTextInlineNode) {
return new EmphasisInlineNode($content[0]->getValue());
}

$this->logger->warning(sprintf('Emphasis CONTEXT: Content of emphasis could not be interpreted: %s', var_export($content, true)));

return new EmphasisInlineNode('');
}

$this->logger->warning(sprintf('Emphasis CONTEXT: I am leaving a %s node', $commonMarkNode::class));
}

throw new RuntimeException('Unexpected end of NodeWalker');
}

public function supports(NodeWalkerEvent $event): bool
{
return $event->isEntering() && $event->getNode() instanceof Emphasis;
}
}
Loading

0 comments on commit 89bc794

Please sign in to comment.