diff --git a/src/IsRule.php b/src/IsRule.php new file mode 100644 index 0000000..4fc3301 --- /dev/null +++ b/src/IsRule.php @@ -0,0 +1,43 @@ +, bool> */ + private(set) array $tokenSupport = []; + + public function supportsToken(Token $token): bool + { + return ( + $this->tokenSupport[$token::class] ?? in_array( + RuleContext::INLINE, + $this->contexts, + strict: true, + ) + ); + } + + /** @param class-string<\Tempest\Markdown\Token> $token */ + public function addTokenSupport(string $token): static + { + $this->tokenSupport[$token] = true; + + return $this; + } + + /** @param class-string<\Tempest\Markdown\Token> $token */ + public function removeTokenSupport(string $token): static + { + $this->tokenSupport[$token] = false; + + return $this; + } +} diff --git a/src/Markdown.php b/src/Markdown.php index f40bf71..342edba 100644 --- a/src/Markdown.php +++ b/src/Markdown.php @@ -53,4 +53,15 @@ public function removeRules(string ...$rules): self return $this; } + + /** + * The registered instance of $rule, to reconfigure it or to change the + * tokens it supports. + * + * @param class-string<\Tempest\Markdown\Rule> $rule + */ + public function getRule(string $rule): ?Rule + { + return $this->parser->getRule($rule); + } } diff --git a/src/Parser.php b/src/Parser.php index 26ce0ba..2487580 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -4,11 +4,17 @@ use Tempest\Highlight\Highlighter; use Tempest\Markdown\Exceptions\MaximumNestingDepthWasExceeded; +use Tempest\Markdown\Rules\BoldAndItalicRule; +use Tempest\Markdown\Rules\BoldRule; +use Tempest\Markdown\Rules\CodeRule; use Tempest\Markdown\Rules\DivRule; use Tempest\Markdown\Rules\FrontMatterRule; use Tempest\Markdown\Rules\HeadingRule; use Tempest\Markdown\Rules\HtmlCommentRule; use Tempest\Markdown\Rules\HtmlRule; +use Tempest\Markdown\Rules\ImageRule; +use Tempest\Markdown\Rules\ItalicRule; +use Tempest\Markdown\Rules\LinkRule; use Tempest\Markdown\Rules\ListRule; use Tempest\Markdown\Rules\NewLineRule; use Tempest\Markdown\Rules\OrderedListRule; @@ -16,7 +22,10 @@ use Tempest\Markdown\Rules\PreRule; use Tempest\Markdown\Rules\QuoteRule; use Tempest\Markdown\Rules\RawRule; +use Tempest\Markdown\Rules\SocialHandleRule; +use Tempest\Markdown\Rules\StrikethroughRule; use Tempest\Markdown\Rules\TableRule; +use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Rules\ThickRulerRule; use Tempest\Markdown\Rules\ThinRulerRule; use Tempest\Markdown\Tokens\FrontMatterToken; @@ -33,6 +42,7 @@ final class Parser private(set) ?string $current; private(set) string $content; private(set) ?Token $lastToken = null; + /** Every registered rule, whether or not it runs here. */ /** @var \Tempest\Markdown\Rule[] */ private(set) array $rules = []; /** @var \Tempest\Markdown\Rule[] */ @@ -45,6 +55,7 @@ final class Parser private static int $depth = 0; + /** @param \Tempest\Markdown\Rule[] $rules */ public function __construct( public ?Highlighter $highlighter = new Highlighter(), public ?ResponsiveImageFactory $imageFactory = null, @@ -65,12 +76,27 @@ public function __construct( new HtmlRule(), new TableRule(), new ParagraphRule(), + new BoldAndItalicRule(), + new BoldRule(), + new ItalicRule(), + new StrikethroughRule(), + new CodeRule(), + new LinkRule(), + new SocialHandleRule(), + new ImageRule(), + new TextRule(), ], ) { $this->setRules($rules); } - public function forToken(Token $token, array $rules): self + /** + * A parser for the content of $token, running every registered rule that + * supports it. Rules are never rebuilt here: they are the configured + * instances, so adding, removing or reconfiguring a rule reaches nested + * content too. + */ + public function forToken(Token $token): self { if (isset($this->cache[$token::class])) { return $this->cache[$token::class]; @@ -78,13 +104,36 @@ public function forToken(Token $token, array $rules): self $clone = clone $this; - $clone->setRules($rules); + $clone->activateRules(array_values(array_filter( + $this->rules, + static fn (Rule $rule) => $rule->supportsToken($token), + ))); $this->cache[$token::class] = $clone; return $clone; } + /** + * The registered instance of $rule, to reconfigure it or to change the + * tokens it supports. Sub-parsers are derived from the rules, so handing + * one out drops the ones already built from it. + * + * @param class-string<\Tempest\Markdown\Rule> $rule + */ + public function getRule(string $rule): ?Rule + { + $this->cache = []; + + foreach ($this->rules as $registered) { + if ($registered::class === $rule) { + return $registered; + } + } + + return null; + } + public function withRules(Rule ...$rules): self { $clone = clone $this; @@ -125,24 +174,41 @@ public function removeRules(string ...$rules): self return $clone; } + /** @param \Tempest\Markdown\Rule[] $rules */ private function setRules(array $rules): self { - /** @var \Tempest\Markdown\NeedsStopChars[] $needsStopChars */ - $needsStopChars = []; + $this->rules = $rules; + + return $this->activateRules($rules); + } + + /** @param \Tempest\Markdown\Rule[] $rules */ + private function activateRules(array $rules): self + { $providedStopChars = ''; foreach ($rules as $rule) { - if ($rule instanceof ProvidesStopChar) { - $providedStopChars .= $rule->stopChar; + if (! $rule instanceof ProvidesStopChar) { + continue; } - if ($rule instanceof NeedsStopChars) { - $needsStopChars[] = $rule; - } + $providedStopChars .= $rule->stopChar; } - foreach ($needsStopChars as $rule) { - $rule->stopChars .= $providedStopChars; + foreach ($rules as $key => $rule) { + if (! $rule instanceof NeedsStopChars) { + continue; + } + + if ($rule->stopChars === $providedStopChars) { + continue; + } + + // Stop chars belong to a rule set, not to a rule: the registered + // instance is shared between sets, so this set gets its own copy + // instead of overwriting the stop chars of the previous one. + $rules[$key] = clone $rule; + $rules[$key]->stopChars = $providedStopChars; } $perCharRules = []; @@ -167,12 +233,11 @@ private function setRules(array $rules): self } } - /** @var \Tempest\Markdown\Rule[] $rules */ /** @var \Tempest\Markdown\Rule[] $defaultRules */ /** @var \Tempest\Markdown\Rule[][] $perCharRules */ - $this->rules = $rules; $this->defaultRules = $defaultRules; $this->perCharRules = $perCharRules; + $this->cache = []; return $this; } diff --git a/src/Rule.php b/src/Rule.php index 420dc88..1de81de 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -7,4 +7,13 @@ interface Rule public function shouldParse(Parser $parser): bool; public function parse(Parser $parser): ?Token; + + /** Whether this rule runs on the content of $token. */ + public function supportsToken(Token $token): bool; + + /** @param class-string<\Tempest\Markdown\Token> $token */ + public function addTokenSupport(string $token): static; + + /** @param class-string<\Tempest\Markdown\Token> $token */ + public function removeTokenSupport(string $token): static; } diff --git a/src/RuleContext.php b/src/RuleContext.php new file mode 100644 index 0000000..54e64e9 --- /dev/null +++ b/src/RuleContext.php @@ -0,0 +1,17 @@ +contexts = [RuleContext::INLINE]; + + $this->removeTokenSupport(BoldToken::class); + $this->removeTokenSupport(ItalicToken::class); + $this->removeTokenSupport(BoldAndItalicToken::class); + } + public function shouldParse(Parser $parser): bool { return ( diff --git a/src/Rules/BoldRule.php b/src/Rules/BoldRule.php index de2a7ad..5b4ac88 100644 --- a/src/Rules/BoldRule.php +++ b/src/Rules/BoldRule.php @@ -2,18 +2,31 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; +use Tempest\Markdown\RuleContext; use Tempest\Markdown\Token; +use Tempest\Markdown\Tokens\BoldAndItalicToken; use Tempest\Markdown\Tokens\BoldToken; final class BoldRule implements Rule, ProvidesFirstChar, ProvidesStopChar { + use IsRule; + private(set) string $firstChar = '*_'; private(set) string $stopChar = '*_'; + public function __construct() + { + $this->contexts = [RuleContext::INLINE]; + + $this->removeTokenSupport(BoldToken::class); + $this->removeTokenSupport(BoldAndItalicToken::class); + } + public function shouldParse(Parser $parser): bool { $stopToken = $parser->lookaheadUntil('*_')[0] ?? null; diff --git a/src/Rules/CodeRule.php b/src/Rules/CodeRule.php index 4a1b6cc..9f1966d 100644 --- a/src/Rules/CodeRule.php +++ b/src/Rules/CodeRule.php @@ -2,18 +2,27 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; +use Tempest\Markdown\RuleContext; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\CodeToken; final class CodeRule implements Rule, ProvidesFirstChar, ProvidesStopChar { + use IsRule; + private(set) string $firstChar = '`'; private(set) string $stopChar = '`'; + public function __construct() + { + $this->contexts = [RuleContext::INLINE]; + } + public function shouldParse(Parser $parser): bool { return $parser->comesNext('`', 1); diff --git a/src/Rules/DivRule.php b/src/Rules/DivRule.php index 556640c..73d86c4 100644 --- a/src/Rules/DivRule.php +++ b/src/Rules/DivRule.php @@ -2,18 +2,27 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\DivToken; +use Tempest\Markdown\Tokens\ParagraphToken; final class DivRule implements Rule, ProvidesFirstChar, ProvidesStopChar { + use IsRule; + public string $stopChar = ':'; public string $firstChar = ':'; + public function __construct() + { + $this->addTokenSupport(ParagraphToken::class); + } + public function shouldParse(Parser $parser): bool { return $parser->comesNext(':::', 3); diff --git a/src/Rules/FrontMatterRule.php b/src/Rules/FrontMatterRule.php index 232835e..af0afb5 100644 --- a/src/Rules/FrontMatterRule.php +++ b/src/Rules/FrontMatterRule.php @@ -7,6 +7,7 @@ use Tempest\Markdown\Exceptions\FrontMatterCouldNotBeParsed; use Tempest\Markdown\Exceptions\FrontMatterShouldBeAnArray; use Tempest\Markdown\Exceptions\FrontMatterWasNotProperlyClosed; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -15,6 +16,8 @@ final class FrontMatterRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '-'; public function shouldParse(Parser $parser): bool diff --git a/src/Rules/HeadingRule.php b/src/Rules/HeadingRule.php index edc05e7..3f8ca01 100644 --- a/src/Rules/HeadingRule.php +++ b/src/Rules/HeadingRule.php @@ -2,14 +2,18 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; use Tempest\Markdown\Token; +use Tempest\Markdown\Tokens\DivToken; use Tempest\Markdown\Tokens\HeadingToken; final class HeadingRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '#'; public function __construct( @@ -18,7 +22,9 @@ public function __construct( * content. An id written as `## Title ## id` is kept either way. */ public bool $generateIds = true, - ) {} + ) { + $this->addTokenSupport(DivToken::class); + } public function shouldParse(Parser $parser): bool { diff --git a/src/Rules/HtmlCommentRule.php b/src/Rules/HtmlCommentRule.php index 3513afc..625c022 100644 --- a/src/Rules/HtmlCommentRule.php +++ b/src/Rules/HtmlCommentRule.php @@ -2,6 +2,7 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -10,6 +11,8 @@ final class HtmlCommentRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '<'; public function shouldParse(Parser $parser): bool diff --git a/src/Rules/HtmlRule.php b/src/Rules/HtmlRule.php index a56e0cc..51e47a3 100644 --- a/src/Rules/HtmlRule.php +++ b/src/Rules/HtmlRule.php @@ -2,6 +2,7 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -10,6 +11,8 @@ final class HtmlRule implements Rule, ProvidesFirstChar { + use IsRule; + /** * Elements whose content is raw text rather than Markdown. * diff --git a/src/Rules/ImageRule.php b/src/Rules/ImageRule.php index 7bd2bdc..bf28b35 100644 --- a/src/Rules/ImageRule.php +++ b/src/Rules/ImageRule.php @@ -4,18 +4,27 @@ use Tempest\Markdown\Exceptions\ImageSourceWasMissing; use Tempest\Markdown\Exceptions\ImageSourceWasNotClosed; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; +use Tempest\Markdown\RuleContext; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\ImageToken; final class ImageRule implements Rule, ProvidesFirstChar, ProvidesStopChar { + use IsRule; + private(set) string $firstChar = '!'; private(set) string $stopChar = '!'; + public function __construct() + { + $this->contexts = [RuleContext::INLINE]; + } + public function shouldParse(Parser $parser): bool { return $parser->comesNext('![', 2); diff --git a/src/Rules/ItalicRule.php b/src/Rules/ItalicRule.php index 539970b..c7edc36 100644 --- a/src/Rules/ItalicRule.php +++ b/src/Rules/ItalicRule.php @@ -2,18 +2,31 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; +use Tempest\Markdown\RuleContext; use Tempest\Markdown\Token; +use Tempest\Markdown\Tokens\BoldAndItalicToken; use Tempest\Markdown\Tokens\ItalicToken; final class ItalicRule implements Rule, ProvidesFirstChar, ProvidesStopChar { + use IsRule; + private(set) string $firstChar = '*_'; private(set) string $stopChar = '*_'; + public function __construct() + { + $this->contexts = [RuleContext::INLINE]; + + $this->removeTokenSupport(ItalicToken::class); + $this->removeTokenSupport(BoldAndItalicToken::class); + } + public function shouldParse(Parser $parser): bool { $stopToken = $parser->lookaheadUntil('*_')[0] ?? null; diff --git a/src/Rules/LinkRule.php b/src/Rules/LinkRule.php index 3c16b96..2c3f6d2 100644 --- a/src/Rules/LinkRule.php +++ b/src/Rules/LinkRule.php @@ -2,18 +2,29 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; +use Tempest\Markdown\RuleContext; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\LinkToken; final class LinkRule implements Rule, ProvidesFirstChar, ProvidesStopChar { + use IsRule; + private(set) string $firstChar = '['; private(set) string $stopChar = '['; + public function __construct() + { + $this->contexts = [RuleContext::INLINE]; + + $this->removeTokenSupport(LinkToken::class); + } + public function shouldParse(Parser $parser): bool { return $parser->comesNext('[', 1); diff --git a/src/Rules/ListRule.php b/src/Rules/ListRule.php index 73704c0..65e0e71 100644 --- a/src/Rules/ListRule.php +++ b/src/Rules/ListRule.php @@ -2,6 +2,7 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -11,6 +12,8 @@ final class ListRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '-*+'; public function shouldParse(Parser $parser): bool diff --git a/src/Rules/NewLineRule.php b/src/Rules/NewLineRule.php index 8bf42f5..a8d582b 100644 --- a/src/Rules/NewLineRule.php +++ b/src/Rules/NewLineRule.php @@ -2,6 +2,7 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -10,6 +11,8 @@ final class NewLineRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = "\n\r"; public function shouldParse(Parser $parser): bool diff --git a/src/Rules/OrderedListRule.php b/src/Rules/OrderedListRule.php index 588673b..4a1245c 100644 --- a/src/Rules/OrderedListRule.php +++ b/src/Rules/OrderedListRule.php @@ -2,6 +2,7 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -11,6 +12,8 @@ final class OrderedListRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '0123456789'; public function shouldParse(Parser $parser): bool diff --git a/src/Rules/ParagraphRule.php b/src/Rules/ParagraphRule.php index 707fb3f..239fd29 100644 --- a/src/Rules/ParagraphRule.php +++ b/src/Rules/ParagraphRule.php @@ -2,14 +2,17 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\Rule; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\HeadingToken; use Tempest\Markdown\Tokens\ParagraphToken; -final readonly class ParagraphRule implements Rule +final class ParagraphRule implements Rule { + use IsRule; + public function shouldParse(Parser $parser): bool { return true; diff --git a/src/Rules/PreRule.php b/src/Rules/PreRule.php index 7c8cc8d..ef6bc11 100644 --- a/src/Rules/PreRule.php +++ b/src/Rules/PreRule.php @@ -2,16 +2,27 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; use Tempest\Markdown\Token; +use Tempest\Markdown\Tokens\DivToken; +use Tempest\Markdown\Tokens\ParagraphToken; use Tempest\Markdown\Tokens\PreToken; final class PreRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '`~'; + public function __construct() + { + $this->addTokenSupport(DivToken::class); + $this->addTokenSupport(ParagraphToken::class); + } + public function shouldParse(Parser $parser): bool { return $parser->comesNext('```', 3) || $parser->comesNext('~~~', 3); diff --git a/src/Rules/QuoteRule.php b/src/Rules/QuoteRule.php index 90321ba..52f4fb2 100644 --- a/src/Rules/QuoteRule.php +++ b/src/Rules/QuoteRule.php @@ -2,18 +2,28 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; use Tempest\Markdown\Token; +use Tempest\Markdown\Tokens\DivToken; use Tempest\Markdown\Tokens\QuoteToken; final class QuoteRule implements Rule, ProvidesFirstChar, ProvidesStopChar { + use IsRule; + private(set) string $firstChar = '>'; private(set) string $stopChar = '>'; + public function __construct() + { + $this->addTokenSupport(DivToken::class); + $this->addTokenSupport(QuoteToken::class); + } + public function shouldParse(Parser $parser): bool { if (! $parser->comesNext('>', 1)) { diff --git a/src/Rules/RawRule.php b/src/Rules/RawRule.php index bc1ed13..f913411 100644 --- a/src/Rules/RawRule.php +++ b/src/Rules/RawRule.php @@ -2,13 +2,16 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\Rule; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\RawToken; -final readonly class RawRule implements Rule +final class RawRule implements Rule { + use IsRule; + public function shouldParse(Parser $parser): bool { return $parser->comesNext('@@', length: 2); diff --git a/src/Rules/SocialHandleRule.php b/src/Rules/SocialHandleRule.php index fbcf9a1..29d8245 100644 --- a/src/Rules/SocialHandleRule.php +++ b/src/Rules/SocialHandleRule.php @@ -4,10 +4,12 @@ use Tempest\Markdown\Exceptions\SocialHandlePlatformWasUnknown; use Tempest\Markdown\Exceptions\SocialHandleWasInvalid; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; +use Tempest\Markdown\RuleContext; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\LinkToken; @@ -20,9 +22,18 @@ final class SocialHandleRule implements ProvidesFirstChar, ProvidesStopChar { + use IsRule; + private(set) string $firstChar = '{'; private(set) string $stopChar = '{'; + public function __construct() + { + $this->contexts = [RuleContext::INLINE]; + + $this->removeTokenSupport(LinkToken::class); + } + public function shouldParse(Parser $parser): bool { return ( diff --git a/src/Rules/StrikethroughRule.php b/src/Rules/StrikethroughRule.php index 2e9adc3..faf0955 100644 --- a/src/Rules/StrikethroughRule.php +++ b/src/Rules/StrikethroughRule.php @@ -2,10 +2,12 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\ProvidesStopChar; use Tempest\Markdown\Rule; +use Tempest\Markdown\RuleContext; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\StrikethroughToken; use Tempest\Markdown\Tokens\TextToken; @@ -15,9 +17,18 @@ final class StrikethroughRule implements ProvidesFirstChar, ProvidesStopChar { + use IsRule; + private(set) string $firstChar = '~'; private(set) string $stopChar = '~'; + public function __construct() + { + $this->contexts = [RuleContext::INLINE]; + + $this->removeTokenSupport(StrikethroughToken::class); + } + public function shouldParse(Parser $parser): bool { return $parser->comesNext('~', 1); diff --git a/src/Rules/TableRule.php b/src/Rules/TableRule.php index c5e16f9..dd96ec9 100644 --- a/src/Rules/TableRule.php +++ b/src/Rules/TableRule.php @@ -2,6 +2,7 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -11,6 +12,8 @@ final class TableRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '|'; public function shouldParse(Parser $parser): bool diff --git a/src/Rules/TextRule.php b/src/Rules/TextRule.php index e78f1e7..0c73735 100644 --- a/src/Rules/TextRule.php +++ b/src/Rules/TextRule.php @@ -2,17 +2,23 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\NeedsStopChars; use Tempest\Markdown\Parser; use Tempest\Markdown\Rule; +use Tempest\Markdown\RuleContext; use Tempest\Markdown\Token; use Tempest\Markdown\Tokens\TextToken; final class TextRule implements Rule, NeedsStopChars { + use IsRule; + public function __construct( public string $stopChars = '', - ) {} + ) { + $this->contexts = [RuleContext::INLINE]; + } public function shouldParse(Parser $parser): bool { diff --git a/src/Rules/ThickRulerRule.php b/src/Rules/ThickRulerRule.php index 881e8ec..fc4e648 100644 --- a/src/Rules/ThickRulerRule.php +++ b/src/Rules/ThickRulerRule.php @@ -2,6 +2,7 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -11,6 +12,8 @@ final class ThickRulerRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '='; public function shouldParse(Parser $parser): bool diff --git a/src/Rules/ThinRulerRule.php b/src/Rules/ThinRulerRule.php index 53f0b86..8e71a6d 100644 --- a/src/Rules/ThinRulerRule.php +++ b/src/Rules/ThinRulerRule.php @@ -2,6 +2,7 @@ namespace Tempest\Markdown\Rules; +use Tempest\Markdown\IsRule; use Tempest\Markdown\Parser; use Tempest\Markdown\ProvidesFirstChar; use Tempest\Markdown\Rule; @@ -11,6 +12,8 @@ final class ThinRulerRule implements Rule, ProvidesFirstChar { + use IsRule; + public string $firstChar = '-'; public function shouldParse(Parser $parser): bool diff --git a/src/Tokens/BoldAndItalicToken.php b/src/Tokens/BoldAndItalicToken.php index 271ee18..7773ace 100644 --- a/src/Tokens/BoldAndItalicToken.php +++ b/src/Tokens/BoldAndItalicToken.php @@ -3,12 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\LinkRule; -use Tempest\Markdown\Rules\SocialHandleRule; -use Tempest\Markdown\Rules\StrikethroughRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class BoldAndItalicToken implements Token @@ -20,14 +14,7 @@ public function __construct( public function parse(Parser $parser): string { $content = $parser - ->forToken($this, [ - new StrikethroughRule(), - new CodeRule(), - new LinkRule(), - new SocialHandleRule(), - new ImageRule(), - new TextRule(), - ]) + ->forToken($this) ->parse($this->content); return "{$content}"; diff --git a/src/Tokens/BoldToken.php b/src/Tokens/BoldToken.php index cd2a8a0..2614c12 100644 --- a/src/Tokens/BoldToken.php +++ b/src/Tokens/BoldToken.php @@ -3,13 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\ItalicRule; -use Tempest\Markdown\Rules\LinkRule; -use Tempest\Markdown\Rules\SocialHandleRule; -use Tempest\Markdown\Rules\StrikethroughRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class BoldToken implements Token @@ -21,15 +14,7 @@ public function __construct( public function parse(Parser $parser): string { $content = $parser - ->forToken($this, [ - new ItalicRule(), - new StrikethroughRule(), - new CodeRule(), - new LinkRule(), - new SocialHandleRule(), - new ImageRule(), - new TextRule(), - ]) + ->forToken($this) ->parse($this->content); return "{$content}"; diff --git a/src/Tokens/DivToken.php b/src/Tokens/DivToken.php index 0ea7470..2f19efd 100644 --- a/src/Tokens/DivToken.php +++ b/src/Tokens/DivToken.php @@ -3,18 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\BoldAndItalicRule; -use Tempest\Markdown\Rules\BoldRule; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\HeadingRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\ItalicRule; -use Tempest\Markdown\Rules\LinkRule; -use Tempest\Markdown\Rules\PreRule; -use Tempest\Markdown\Rules\QuoteRule; -use Tempest\Markdown\Rules\SocialHandleRule; -use Tempest\Markdown\Rules\StrikethroughRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class DivToken implements Token @@ -27,20 +15,7 @@ public function __construct( public function parse(Parser $parser): string { $content = $parser - ->forToken($this, [ - new HeadingRule(), - new QuoteRule(), - new BoldAndItalicRule(), - new BoldRule(), - new ItalicRule(), - new StrikethroughRule(), - new LinkRule(), - new SocialHandleRule(), - new ImageRule(), - new PreRule(), - new CodeRule(), - new TextRule(), - ]) + ->forToken($this) ->parse($this->content); $class = $this->class diff --git a/src/Tokens/HeadingToken.php b/src/Tokens/HeadingToken.php index 385f3b2..96c38e2 100644 --- a/src/Tokens/HeadingToken.php +++ b/src/Tokens/HeadingToken.php @@ -3,15 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\BoldAndItalicRule; -use Tempest\Markdown\Rules\BoldRule; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\ItalicRule; -use Tempest\Markdown\Rules\LinkRule; -use Tempest\Markdown\Rules\SocialHandleRule; -use Tempest\Markdown\Rules\StrikethroughRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class HeadingToken implements Token @@ -33,17 +24,7 @@ public function parse(Parser $parser): string } $content = $parser - ->forToken($this, [ - new BoldAndItalicRule(), - new BoldRule(), - new ItalicRule(), - new StrikethroughRule(), - new LinkRule(), - new SocialHandleRule(), - new ImageRule(), - new CodeRule(), - new TextRule(), - ]) + ->forToken($this) ->parse($this->content); return "<{$tag}{$id}>{$content}{$tag}>"; diff --git a/src/Tokens/HtmlToken.php b/src/Tokens/HtmlToken.php index 5663e04..a1ecfe3 100644 --- a/src/Tokens/HtmlToken.php +++ b/src/Tokens/HtmlToken.php @@ -3,15 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\BoldAndItalicRule; -use Tempest\Markdown\Rules\BoldRule; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\ItalicRule; -use Tempest\Markdown\Rules\LinkRule; -use Tempest\Markdown\Rules\SocialHandleRule; -use Tempest\Markdown\Rules\StrikethroughRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class HtmlToken implements Token @@ -30,17 +21,7 @@ public function parse(Parser $parser): string } return $parser - ->forToken($this, [ - new BoldAndItalicRule(), - new BoldRule(), - new ItalicRule(), - new StrikethroughRule(), - new LinkRule(), - new SocialHandleRule(), - new ImageRule(), - new CodeRule(), - new TextRule(), - ]) + ->forToken($this) ->parse($this->html) ->html; } diff --git a/src/Tokens/ItalicToken.php b/src/Tokens/ItalicToken.php index dceeb9e..aabbea1 100644 --- a/src/Tokens/ItalicToken.php +++ b/src/Tokens/ItalicToken.php @@ -3,13 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\BoldRule; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\LinkRule; -use Tempest\Markdown\Rules\SocialHandleRule; -use Tempest\Markdown\Rules\StrikethroughRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class ItalicToken implements Token @@ -21,15 +14,7 @@ public function __construct( public function parse(Parser $parser): string { $content = $parser - ->forToken($this, [ - new BoldRule(), - new StrikethroughRule(), - new CodeRule(), - new LinkRule(), - new SocialHandleRule(), - new ImageRule(), - new TextRule(), - ]) + ->forToken($this) ->parse($this->content); return "{$content}"; diff --git a/src/Tokens/LinkToken.php b/src/Tokens/LinkToken.php index 7e8c856..785aee1 100644 --- a/src/Tokens/LinkToken.php +++ b/src/Tokens/LinkToken.php @@ -3,13 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\BoldAndItalicRule; -use Tempest\Markdown\Rules\BoldRule; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\ItalicRule; -use Tempest\Markdown\Rules\StrikethroughRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class LinkToken implements Token @@ -28,15 +21,7 @@ public function parse(Parser $parser): string if ($this->parseContent) { $content = $parser - ->forToken($this, [ - new CodeRule(), - new BoldAndItalicRule(), - new BoldRule(), - new ItalicRule(), - new StrikethroughRule(), - new ImageRule(), - new TextRule(), - ]) + ->forToken($this) ->parse($this->content); } diff --git a/src/Tokens/ListToken.php b/src/Tokens/ListToken.php index e228ed3..3597f66 100644 --- a/src/Tokens/ListToken.php +++ b/src/Tokens/ListToken.php @@ -3,15 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\BoldAndItalicRule; -use Tempest\Markdown\Rules\BoldRule; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\ItalicRule; -use Tempest\Markdown\Rules\LinkRule; -use Tempest\Markdown\Rules\SocialHandleRule; -use Tempest\Markdown\Rules\StrikethroughRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class ListToken implements Token @@ -23,17 +14,7 @@ public function __construct( public function parse(Parser $parser): string { - $parser = $parser->forToken($this, [ - new BoldAndItalicRule(), - new BoldRule(), - new ItalicRule(), - new StrikethroughRule(), - new LinkRule(), - new SocialHandleRule(), - new ImageRule(), - new CodeRule(), - new TextRule(), - ]); + $parser = $parser->forToken($this); $list = '
{$content}"; diff --git a/src/Tokens/StrikethroughToken.php b/src/Tokens/StrikethroughToken.php index 8b68180..d6e75c7 100644 --- a/src/Tokens/StrikethroughToken.php +++ b/src/Tokens/StrikethroughToken.php @@ -3,14 +3,6 @@ namespace Tempest\Markdown\Tokens; use Tempest\Markdown\Parser; -use Tempest\Markdown\Rules\BoldAndItalicRule; -use Tempest\Markdown\Rules\BoldRule; -use Tempest\Markdown\Rules\CodeRule; -use Tempest\Markdown\Rules\ImageRule; -use Tempest\Markdown\Rules\ItalicRule; -use Tempest\Markdown\Rules\LinkRule; -use Tempest\Markdown\Rules\SocialHandleRule; -use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Token; final class StrikethroughToken implements Token @@ -22,16 +14,7 @@ public function __construct( public function parse(Parser $parser): string { $content = $parser - ->forToken($this, [ - new BoldAndItalicRule(), - new BoldRule(), - new ItalicRule(), - new CodeRule(), - new LinkRule(), - new SocialHandleRule(), - new ImageRule(), - new TextRule(), - ]) + ->forToken($this) ->parse($this->content); return "
~~struck~~
', + $markdown->parse('~~struck~~')->html, + ); + $this->assertSame( + '~~struck~~
', + $markdown->parse('**~~struck~~**')->html, + ); + } + + #[Test] + public function an_added_inline_rule_reaches_nested_content(): void + { + $markdown = new Markdown(highlighter: null)->prependRules( + new HighlightRule(), + ); + + $this->assertSame( + 'look here now
', + $markdown->parse('look ==here== now')->html, + ); + $this->assertSame( + 'here', + $markdown->parse('> ==here==')->html, + ); + $this->assertSame( + '
here
', + $markdown->parse('**==here==**')->html, + ); + } + + #[Test] + public function a_custom_token_gets_the_registered_inline_rules(): void + { + $markdown = new Markdown(highlighter: null)->prependRules( + new AsideRule(), + ); + + $this->assertSame( + '', + $markdown->parse('!! **bold** and ~~struck~~')->html, + ); + } + + #[Test] + public function token_support_can_be_added_on_a_registered_rule(): void + { + $markdown = new Markdown(highlighter: null); + + $this->assertSame( + '', + $markdown->parse('[a [c](/d) e](/b)')->html, + ); + + $markdown->getRule(LinkRule::class)?->addTokenSupport(LinkToken::class); + + $this->assertSame( + '', + $markdown->parse('[a [c](/d) e](/b)')->html, + ); + } + + #[Test] + public function token_support_can_be_removed_on_a_registered_rule(): void + { + $markdown = new Markdown(highlighter: null); + + $markdown + ->getRule(ImageRule::class) + ?->removeTokenSupport( + HeadingToken::class, + ); + + $this->assertSame( + '
bold
', + $markdown->parse('**bold**')->html, + ); + $this->assertSame( + 'bold
', + $markdown->parse('**bold**')->html, + ); + } +} + +final class HighlightRule implements Rule, ProvidesFirstChar, ProvidesStopChar +{ + use IsRule; + + private(set) string $firstChar = '='; + private(set) string $stopChar = '='; + + public function __construct() + { + $this->contexts = [RuleContext::INLINE]; + } + + public function shouldParse(Parser $parser): bool + { + return $parser->comesNext('==', 2) && $parser->hasNext('==', "\n"); + } + + public function parse(Parser $parser): Token + { + $parser->consume(2); + + $content = $parser->consumeUntilString('=='); + + $parser->consume(2); + + return new HighlightToken($content); + } +} + +final class HighlightToken implements Token +{ + public function __construct( + public string $content, + ) {} + + public function parse(Parser $parser): string + { + return "{$this->content}"; + } +} + +final class AsideRule implements Rule, ProvidesFirstChar +{ + use IsRule; + + private(set) string $firstChar = '!'; + + public function shouldParse(Parser $parser): bool + { + return $parser->comesNext('!! ', 3); + } + + public function parse(Parser $parser): Token + { + $parser->consume(3); + + return new AsideToken($parser->consumeUntil(Parser::NEW_LINE)); + } +} + +final class AsideToken implements Token +{ + public function __construct( + public string $content, + ) {} + + public function parse(Parser $parser): string + { + $content = $parser->forToken($this)->parse($this->content); + + return ""; + } +} diff --git a/tests/Rules/BoldAndItalicRuleTest.php b/tests/Rules/BoldAndItalicRuleTest.php index 1f51f5d..9e20dea 100644 --- a/tests/Rules/BoldAndItalicRuleTest.php +++ b/tests/Rules/BoldAndItalicRuleTest.php @@ -7,6 +7,7 @@ use Tempest\Markdown\Rules\BoldAndItalicRule; use Tempest\Markdown\Rules\BoldRule; use Tempest\Markdown\Rules\ItalicRule; +use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Tests\ParserTestCase; class BoldAndItalicRuleTest extends ParserTestCase @@ -18,6 +19,7 @@ public function test_triple_asterisk_bold_and_italic(): void new BoldAndItalicRule(), new BoldRule(), new ItalicRule(), + new TextRule(), ])->parse('***text***'); $this->assertSame('text', $html); @@ -30,6 +32,7 @@ public function test_triple_underscore_bold_and_italic(): void new BoldAndItalicRule(), new BoldRule(), new ItalicRule(), + new TextRule(), ])->parse('___text___'); $this->assertSame('text', $html); @@ -42,6 +45,7 @@ public function test_single_underscore_double_asterisk(): void new BoldAndItalicRule(), new BoldRule(), new ItalicRule(), + new TextRule(), ])->parse('_**text**_'); $this->assertSame('text', $html); @@ -54,6 +58,7 @@ public function test_single_asterisk_double_underscore(): void new BoldAndItalicRule(), new BoldRule(), new ItalicRule(), + new TextRule(), ])->parse('*__text__*'); $this->assertSame('text', $html); @@ -66,6 +71,7 @@ public function test_double_underscore_single_asterisk(): void new BoldAndItalicRule(), new BoldRule(), new ItalicRule(), + new TextRule(), ])->parse('__*text*__'); $this->assertSame('text', $html); @@ -78,6 +84,7 @@ public function test_double_asterisk_single_underscore(): void new BoldAndItalicRule(), new BoldRule(), new ItalicRule(), + new TextRule(), ])->parse('**_text_**'); $this->assertSame('text', $html); @@ -86,33 +93,39 @@ public function test_double_asterisk_single_underscore(): void #[Test] public function test_does_not_lex_double_asterisk(): void { - $html = - (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule()])->parse( - '**text**', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new BoldAndItalicRule(), + new TextRule(), + ])->parse( + '**text**', + ); - $this->assertSame('', $html); + $this->assertSame('**text**', $html); } #[Test] public function test_does_not_lex_single_asterisk(): void { - $html = - (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule()])->parse( - '*text*', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new BoldAndItalicRule(), + new TextRule(), + ])->parse( + '*text*', + ); - $this->assertSame('', $html); + $this->assertSame('*text*', $html); } #[Test] public function test_does_not_lex_double_underscore(): void { - $html = - (string) new Parser(highlighter: null, rules: [new BoldAndItalicRule()])->parse( - '__text__', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new BoldAndItalicRule(), + new TextRule(), + ])->parse( + '__text__', + ); - $this->assertSame('', $html); + $this->assertSame('__text__', $html); } } diff --git a/tests/Rules/BoldRuleTest.php b/tests/Rules/BoldRuleTest.php index 9e59e9b..5204ed6 100644 --- a/tests/Rules/BoldRuleTest.php +++ b/tests/Rules/BoldRuleTest.php @@ -8,6 +8,7 @@ use Tempest\Markdown\Rules\ItalicRule; use Tempest\Markdown\Rules\NewLineRule; use Tempest\Markdown\Rules\ParagraphRule; +use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Tests\ParserTestCase; class BoldRuleTest extends ParserTestCase @@ -15,10 +16,12 @@ class BoldRuleTest extends ParserTestCase #[Test] public function test_lex_double_asterisk(): void { - $html = - (string) new Parser(highlighter: null, rules: [new BoldRule()])->parse( - '**bold**', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new BoldRule(), + new TextRule(), + ])->parse( + '**bold**', + ); $this->assertSame('bold', $html); } @@ -30,6 +33,7 @@ public function test_double_asterisk_must_be_terminated(): void new NewLineRule(), new BoldRule(), new ParagraphRule(), + new TextRule(), ])->parse("Hello**world\n\nHi"); $this->assertSame("Hello**world
\n\nHi
", $html); @@ -41,6 +45,7 @@ public function test_lex_asterisk_with_underscore(): void $html = (string) new Parser(highlighter: null, rules: [ new BoldRule(), new ItalicRule(), + new TextRule(), ])->parse('**_bold_**'); $this->assertSame('bold', $html); @@ -49,21 +54,25 @@ public function test_lex_asterisk_with_underscore(): void #[Test] public function test_does_not_lex_single_asterisk(): void { - $html = - (string) new Parser(highlighter: null, rules: [new BoldRule()])->parse( - '*bold*', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new BoldRule(), + new TextRule(), + ])->parse( + '*bold*', + ); - $this->assertSame('', $html); + $this->assertSame('*bold*', $html); } #[Test] public function test_lex_double_underscore(): void { - $html = - (string) new Parser(highlighter: null, rules: [new BoldRule()])->parse( - '__bold__', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new BoldRule(), + new TextRule(), + ])->parse( + '__bold__', + ); $this->assertSame('bold', $html); } @@ -75,6 +84,7 @@ public function test_double_underscore_must_be_terminated(): void new NewLineRule(), new BoldRule(), new ParagraphRule(), + new TextRule(), ])->parse("Hello__world\n\nHi"); $this->assertSame("Hello__world
\n\nHi
", $html); @@ -83,21 +93,24 @@ public function test_double_underscore_must_be_terminated(): void #[Test] public function test_does_not_lex_single_underscore(): void { - $html = - (string) new Parser(highlighter: null, rules: [new BoldRule()])->parse( - '_bold_', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new BoldRule(), + new TextRule(), + ])->parse( + '_bold_', + ); - $this->assertSame('', $html); + $this->assertSame('_bold_', $html); } #[Test] public function test_underscore_with_asterisk(): void { - $html = - (string) new Parser(highlighter: null, rules: [new BoldRule()])->parse( - '__*bold*__', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new BoldRule(), + new ItalicRule(), + new TextRule(), + ])->parse('__*bold*__'); $this->assertSame('bold', $html); } diff --git a/tests/Rules/CodeRuleTest.php b/tests/Rules/CodeRuleTest.php index 8138522..36fa855 100644 --- a/tests/Rules/CodeRuleTest.php +++ b/tests/Rules/CodeRuleTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\Attributes\Test; use Tempest\Markdown\Parser; use Tempest\Markdown\Rules\CodeRule; +use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Tests\ParserTestCase; class CodeRuleTest extends ParserTestCase @@ -12,10 +13,12 @@ class CodeRuleTest extends ParserTestCase #[Test] public function test_lex(): void { - $html = - (string) new Parser(highlighter: null, rules: [new CodeRule()])->parse( - '`code`', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new CodeRule(), + new TextRule(), + ])->parse( + '`code`', + ); $this->assertSame('code', $html);
}
@@ -23,10 +26,12 @@ public function test_lex(): void
#[Test]
public function test_lex_with_language(): void
{
- $html =
- (string) new Parser(highlighter: null, rules: [new CodeRule()])->parse(
- '`{php}code`',
- );
+ $html = (string) new Parser(highlighter: null, rules: [
+ new CodeRule(),
+ new TextRule(),
+ ])->parse(
+ '`{php}code`',
+ );
$this->assertSame('code', $html);
}
@@ -34,10 +39,12 @@ public function test_lex_with_language(): void
#[Test]
public function test_with_custom_hl_token(): void
{
- $html =
- (string) new Parser(highlighter: null, rules: [new CodeRule()])->parse(
- '`{:hl-class:code:}`',
- );
+ $html = (string) new Parser(highlighter: null, rules: [
+ new CodeRule(),
+ new TextRule(),
+ ])->parse(
+ '`{:hl-class:code:}`',
+ );
$this->assertSame('{:hl-class:code:}', $html);
}
diff --git a/tests/Rules/DivRuleTest.php b/tests/Rules/DivRuleTest.php
index 634aa46..a98dcd9 100644
--- a/tests/Rules/DivRuleTest.php
+++ b/tests/Rules/DivRuleTest.php
@@ -5,6 +5,7 @@
use PHPUnit\Framework\Attributes\Test;
use Tempest\Markdown\Parser;
use Tempest\Markdown\Rules\DivRule;
+use Tempest\Markdown\Rules\TextRule;
use Tempest\Markdown\Tests\ParserTestCase;
class DivRuleTest extends ParserTestCase
@@ -12,10 +13,12 @@ class DivRuleTest extends ParserTestCase
#[Test]
public function test_lex_without_class(): void
{
- $html =
- (string) new Parser(highlighter: null, rules: [new DivRule()])->parse(
- ":::\nHello\n:::\n",
- );
+ $html = (string) new Parser(highlighter: null, rules: [
+ new DivRule(),
+ new TextRule(),
+ ])->parse(
+ ":::\nHello\n:::\n",
+ );
$this->assertSame("#titre
', (string) $parser->parse('#titre')); @@ -135,6 +153,7 @@ public function lex_more_than_six_markers_is_not_a_heading(): void $html = (string) new Parser(highlighter: null, rules: [ new HeadingRule(), new ParagraphRule(), + new TextRule(), ])->parse('####### seven'); $this->assertSame('####### seven
', $html); @@ -143,7 +162,10 @@ public function lex_more_than_six_markers_is_not_a_heading(): void #[Test] public function lex_marker_alone_is_an_empty_heading(): void { - $parser = new Parser(highlighter: null, rules: [new HeadingRule()]); + $parser = new Parser(highlighter: null, rules: [ + new HeadingRule(), + new TextRule(), + ]); $this->assertSame('', (string) $parser->parse('#')); $this->assertSame('', (string) $parser->parse('##')); diff --git a/tests/Rules/HtmlCommentRuleTest.php b/tests/Rules/HtmlCommentRuleTest.php index 4ee0bd8..dc11999 100644 --- a/tests/Rules/HtmlCommentRuleTest.php +++ b/tests/Rules/HtmlCommentRuleTest.php @@ -7,6 +7,7 @@ use Tempest\Markdown\Rules\HtmlCommentRule; use Tempest\Markdown\Rules\NewLineRule; use Tempest\Markdown\Rules\ParagraphRule; +use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Tests\ParserTestCase; class HtmlCommentRuleTest extends ParserTestCase @@ -14,10 +15,12 @@ class HtmlCommentRuleTest extends ParserTestCase #[Test] public function test_lex(): void { - $html = - (string) new Parser(highlighter: null, rules: [new HtmlCommentRule()])->parse( - '', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new HtmlCommentRule(), + new TextRule(), + ])->parse( + '', + ); $this->assertSame('', $html); } @@ -27,10 +30,12 @@ public function test_lex_multiline(): void { $comment = ""; - $html = - (string) new Parser(highlighter: null, rules: [new HtmlCommentRule()])->parse( - $comment, - ); + $html = (string) new Parser(highlighter: null, rules: [ + new HtmlCommentRule(), + new TextRule(), + ])->parse( + $comment, + ); $this->assertSame($comment, $html); } @@ -42,6 +47,7 @@ public function test_lex_with_surrounding_content(): void new NewLineRule(), new HtmlCommentRule(), new ParagraphRule(), + new TextRule(), ])->parse("Hello\n\n\n\nWorld"); $this->assertSame( diff --git a/tests/Rules/HtmlRuleTest.php b/tests/Rules/HtmlRuleTest.php index 5f8208d..316c5fc 100644 --- a/tests/Rules/HtmlRuleTest.php +++ b/tests/Rules/HtmlRuleTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\Attributes\Test; use Tempest\Markdown\Parser; +use Tempest\Markdown\Rules\BoldRule; use Tempest\Markdown\Rules\HtmlRule; use Tempest\Markdown\Rules\NewLineRule; use Tempest\Markdown\Rules\ParagraphRule; @@ -15,10 +16,12 @@ class HtmlRuleTest extends ParserTestCase #[Test] public function test_lex(): void { - $html = - (string) new Parser(highlighter: null, rules: [new HtmlRule()])->parse( - 'Hi
', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new HtmlRule(), + new TextRule(), + ])->parse( + 'Hi
', + ); $this->assertSame('Hi
', $html); } @@ -26,10 +29,12 @@ public function test_lex(): void #[Test] public function test_lex_nested(): void { - $html = - (string) new Parser(highlighter: null, rules: [new HtmlRule()])->parse( - 'Hello
', $html); @@ -78,6 +84,7 @@ public function test_void_tags_are_case_insensitive(): void new HtmlRule(), new NewLineRule(), new ParagraphRule(), + new TextRule(), ])->parse("Hello
", $html); @@ -86,10 +93,12 @@ public function test_void_tags_are_case_insensitive(): void #[Test] public function lex_keeps_script_content_raw(): void { - $html = - (string) new Parser(highlighter: null, rules: [new HtmlRule()])->parse( - '', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new HtmlRule(), + new TextRule(), + ])->parse( + '', + ); $this->assertSame('', $html); } @@ -97,10 +106,12 @@ public function lex_keeps_script_content_raw(): void #[Test] public function lex_keeps_style_content_raw(): void { - $html = - (string) new Parser(highlighter: null, rules: [new HtmlRule()])->parse( - '', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new HtmlRule(), + new TextRule(), + ])->parse( + '', + ); $this->assertSame('', $html); } @@ -108,7 +119,10 @@ public function lex_keeps_style_content_raw(): void #[Test] public function lex_keeps_pre_and_textarea_content_raw(): void { - $parser = new Parser(highlighter: null, rules: [new HtmlRule()]); + $parser = new Parser(highlighter: null, rules: [ + new HtmlRule(), + new TextRule(), + ]); $this->assertSame( '**a** _b_', @@ -124,10 +138,12 @@ public function lex_keeps_pre_and_textarea_content_raw(): void #[Test] public function lex_detects_raw_text_tags_case_insensitively(): void { - $html = - (string) new Parser(highlighter: null, rules: [new HtmlRule()])->parse( - '', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new HtmlRule(), + new TextRule(), + ])->parse( + '', + ); $this->assertSame('', $html); } @@ -135,10 +151,11 @@ public function lex_detects_raw_text_tags_case_insensitively(): void #[Test] public function lex_still_parses_markdown_in_other_elements(): void { - $html = - (string) new Parser(highlighter: null, rules: [new HtmlRule()])->parse( - '
Hello_world
\n\nHi
", $html); @@ -38,10 +42,12 @@ public function test_underscore_must_be_terminated(): void #[Test] public function test_lex_with_asterisk(): void { - $html = - (string) new Parser(highlighter: null, rules: [new ItalicRule()])->parse( - '*italic*', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new ItalicRule(), + new TextRule(), + ])->parse( + '*italic*', + ); $this->assertSame('italic', $html); } @@ -53,6 +59,7 @@ public function test_asterisk_must_be_terminated(): void new NewLineRule(), new ItalicRule(), new ParagraphRule(), + new TextRule(), ])->parse("Hello*world\n\nHi"); $this->assertSame("Hello*world
\n\nHi
", $html); @@ -64,6 +71,7 @@ public function test_multiple_in_a_row(): void $html = (string) new Parser(highlighter: null, rules: [ new BoldRule(), new ItalicRule(), + new TextRule(), ])->parse('_a__b_'); $this->assertSame('ab', $html); diff --git a/tests/Rules/LinkRuleTest.php b/tests/Rules/LinkRuleTest.php index 4e0a285..f8a2369 100644 --- a/tests/Rules/LinkRuleTest.php +++ b/tests/Rules/LinkRuleTest.php @@ -4,7 +4,9 @@ use PHPUnit\Framework\Attributes\Test; use Tempest\Markdown\Parser; +use Tempest\Markdown\Rules\ImageRule; use Tempest\Markdown\Rules\LinkRule; +use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Tests\ParserTestCase; class LinkRuleTest extends ParserTestCase @@ -12,10 +14,12 @@ class LinkRuleTest extends ParserTestCase #[Test] public function test_lex(): void { - $html = - (string) new Parser(highlighter: null, rules: [new LinkRule()])->parse( - '[click here](#)', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new LinkRule(), + new TextRule(), + ])->parse( + '[click here](#)', + ); $this->assertSame('click here', $html); } @@ -23,10 +27,12 @@ public function test_lex(): void #[Test] public function test_lex_without_href(): void { - $html = - (string) new Parser(highlighter: null, rules: [new LinkRule()])->parse( - '[click here]', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new LinkRule(), + new TextRule(), + ])->parse( + '[click here]', + ); $this->assertSame('click here', $html); } @@ -34,10 +40,11 @@ public function test_lex_without_href(): void #[Test] public function test_lex_with_image_content(): void { - $html = - (string) new Parser(highlighter: null, rules: [new LinkRule()])->parse( - '[](/link)', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new LinkRule(), + new ImageRule(), + new TextRule(), + ])->parse('[](/link)'); $this->assertSame( '
',
@@ -48,10 +55,12 @@ public function test_lex_with_image_content(): void
#[Test]
public function lex_with_parenthesis_content(): void
{
- $html =
- (string) new Parser(highlighter: null, rules: [new LinkRule()])->parse(
- '[.NET best practices](https://learn.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms229043(v=vs.100)?redirectedfrom=MSDN)',
- );
+ $html = (string) new Parser(highlighter: null, rules: [
+ new LinkRule(),
+ new TextRule(),
+ ])->parse(
+ '[.NET best practices](https://learn.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms229043(v=vs.100)?redirectedfrom=MSDN)',
+ );
$this->assertSame(
'.NET best practices',
@@ -62,10 +71,12 @@ public function lex_with_parenthesis_content(): void
#[Test]
public function lex_with_end_parenthesis_without_start_parenthesis(): void
{
- $html =
- (string) new Parser(highlighter: null, rules: [new LinkRule()])->parse(
- '[.NET best practices](https://learn.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms229043v=vs.100\)?redirectedfrom=MSDN)',
- );
+ $html = (string) new Parser(highlighter: null, rules: [
+ new LinkRule(),
+ new TextRule(),
+ ])->parse(
+ '[.NET best practices](https://learn.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms229043v=vs.100\)?redirectedfrom=MSDN)',
+ );
$this->assertSame(
'.NET best practices',
diff --git a/tests/Rules/ListRuleTest.php b/tests/Rules/ListRuleTest.php
index 06a3b16..a108a75 100644
--- a/tests/Rules/ListRuleTest.php
+++ b/tests/Rules/ListRuleTest.php
@@ -13,10 +13,12 @@ class ListRuleTest extends ParserTestCase
#[Test]
public function test_lex(): void
{
- $html =
- (string) new Parser(highlighter: null, rules: [new ListRule()])->parse(
- "- item\n",
- );
+ $html = (string) new Parser(highlighter: null, rules: [
+ new ListRule(),
+ new TextRule(),
+ ])->parse(
+ "- item\n",
+ );
$this->assertSame('Hello, world!\n
", $html); } @@ -24,10 +27,12 @@ public function test_single_line(): void #[Test] public function test_multi_line_paragraph(): void { - $html = - (string) new Parser(highlighter: null, rules: [new ParagraphRule()])->parse( - "First line\nSecond line\n", - ); + $html = (string) new Parser(highlighter: null, rules: [ + new ParagraphRule(), + new TextRule(), + ])->parse( + "First line\nSecond line\n", + ); $this->assertSame("First line\nSecond line\n
", $html); } @@ -38,6 +43,7 @@ public function test_stops_at_blank_line(): void $html = (string) new Parser(highlighter: null, rules: [ new NewLineRule(), new ParagraphRule(), + new TextRule(), ])->parse("First\nSecond\n\nThird"); $this->assertSame("First\nSecond
\n\nThird
", $html); @@ -46,10 +52,12 @@ public function test_stops_at_blank_line(): void #[Test] public function test_paragraph_without_trailing_newline(): void { - $html = - (string) new Parser(highlighter: null, rules: [new ParagraphRule()])->parse( - 'Hello, world!', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new ParagraphRule(), + new TextRule(), + ])->parse( + 'Hello, world!', + ); $this->assertSame('Hello, world!
', $html); } @@ -57,7 +65,10 @@ public function test_paragraph_without_trailing_newline(): void #[Test] public function test_setext_headings(): void { - $parser = new Parser(highlighter: null, rules: [new ParagraphRule()]); + $parser = new Parser(highlighter: null, rules: [ + new ParagraphRule(), + new TextRule(), + ]); $this->assertSame( 'echo "hi";', @@ -32,14 +34,16 @@ public function test_lex_with_language(): void #[Test] public function test_lex_with_language_and_title(): void { - $html = - (string) new Parser(highlighter: null, rules: [new PreRule()])->parse( - <<<'MD' - ```php file.php - echo "hi"; - ``` - MD, - ); + $html = (string) new Parser(highlighter: null, rules: [ + new PreRule(), + new TextRule(), + ])->parse( + <<<'MD' + ```php file.php + echo "hi"; + ``` + MD, + ); $this->assertSame( '
echo "hi";', @@ -50,14 +54,16 @@ public function test_lex_with_language_and_title(): void #[Test] public function test_lex_without_language(): void { - $html = - (string) new Parser(highlighter: null, rules: [new PreRule()])->parse( - <<<'MD' - ``` - echo "hi"; - ``` - MD, - ); + $html = (string) new Parser(highlighter: null, rules: [ + new PreRule(), + new TextRule(), + ])->parse( + <<<'MD' + ``` + echo "hi"; + ``` + MD, + ); $this->assertSame('
echo "hi";', $html); } @@ -65,10 +71,12 @@ public function test_lex_without_language(): void #[Test] public function test_lex_preserves_significant_whitespace(): void { - $html = - (string) new Parser(highlighter: null, rules: [new PreRule()])->parse( - "```\n keep \n```", - ); + $html = (string) new Parser(highlighter: null, rules: [ + new PreRule(), + new TextRule(), + ])->parse( + "```\n keep \n```", + ); $this->assertSame('
keep', $html); } @@ -76,14 +84,16 @@ public function test_lex_preserves_significant_whitespace(): void #[Test] public function test_lex_with_backtick_in_content(): void { - $html = - (string) new Parser(highlighter: null, rules: [new PreRule()])->parse( - <<<'MD' - ```php - echo `uname`; - ``` - MD, - ); + $html = (string) new Parser(highlighter: null, rules: [ + new PreRule(), + new TextRule(), + ])->parse( + <<<'MD' + ```php + echo `uname`; + ``` + MD, + ); $this->assertSame( '
echo `uname`;', diff --git a/tests/Rules/QuoteRuleTest.php b/tests/Rules/QuoteRuleTest.php index 1bb6bd0..a8a20d9 100644 --- a/tests/Rules/QuoteRuleTest.php +++ b/tests/Rules/QuoteRuleTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\Attributes\Test; use Tempest\Markdown\Parser; use Tempest\Markdown\Rules\QuoteRule; +use Tempest\Markdown\Rules\TextRule; use Tempest\Markdown\Tests\ParserTestCase; class QuoteRuleTest extends ParserTestCase @@ -12,10 +13,12 @@ class QuoteRuleTest extends ParserTestCase #[Test] public function test_lex(): void { - $html = - (string) new Parser(highlighter: null, rules: [new QuoteRule()])->parse( - '> quote', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new QuoteRule(), + new TextRule(), + ])->parse( + '> quote', + ); $this->assertSame('
quote', $html); } @@ -23,14 +26,16 @@ public function test_lex(): void #[Test] public function test_lex_multiline(): void { - $html = - (string) new Parser(highlighter: null, rules: [new QuoteRule()])->parse( - <<<'MD' - > line 1 - > > line 2 - > line 3 - MD, - ); + $html = (string) new Parser(highlighter: null, rules: [ + new QuoteRule(), + new TextRule(), + ])->parse( + <<<'MD' + > line 1 + > > line 2 + > line 3 + MD, + ); $this->assertSame( "
line 1\n", @@ -41,11 +46,13 @@ public function test_lex_multiline(): void #[Test] public function test_lex_only_at_start_of_line(): void { - $html = - (string) new Parser(highlighter: null, rules: [new QuoteRule()])->parse( - 'two > one', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new QuoteRule(), + new TextRule(), + ])->parse( + 'two > one', + ); - $this->assertSame('', $html); + $this->assertSame('two > one', $html); } } diff --git a/tests/Rules/RawRuleTest.php b/tests/Rules/RawRuleTest.php index ed003eb..a2d0ba4 100644 --- a/tests/Rules/RawRuleTest.php +++ b/tests/Rules/RawRuleTest.php @@ -13,10 +13,12 @@ class RawRuleTest extends ParserTestCase #[Test] public function test_raw_content_is_passed_through(): void { - $html = - (string) new Parser(highlighter: null, rules: [new RawRule()])->parse( - '@@raw@@', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new RawRule(), + new TextRule(), + ])->parse( + '@@raw@@', + ); $this->assertSame('raw', $html); } @@ -24,10 +26,12 @@ public function test_raw_content_is_passed_through(): void #[Test] public function test_raw_html_is_not_escaped(): void { - $html = - (string) new Parser(highlighter: null, rules: [new RawRule()])->parse( - '@@@@', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new RawRule(), + new TextRule(), + ])->parse( + '@@@@', + ); $this->assertSame('', $html); } @@ -59,10 +63,12 @@ public function test_raw_multiline_content(): void { $input = "@@line1\nline2@@"; - $html = - (string) new Parser(highlighter: null, rules: [new RawRule()])->parse( - $input, - ); + $html = (string) new Parser(highlighter: null, rules: [ + new RawRule(), + new TextRule(), + ])->parse( + $input, + ); $this->assertSame("line1\nline2", $html); } diff --git a/tests/Rules/SocialHandleRuleTest.php b/tests/Rules/SocialHandleRuleTest.php index b7a8c76..91c5b30 100644 --- a/tests/Rules/SocialHandleRuleTest.php +++ b/tests/Rules/SocialHandleRuleTest.php @@ -73,8 +73,10 @@ public function unclosed_social_handle_preserves_remaining_text(): void #[Test] public function social_handle_preserves_underscores_in_default_label(): void { - $parser = - new Parser(highlighter: null, rules: [new SocialHandleRule()]); + $parser = new Parser(highlighter: null, rules: [ + new SocialHandleRule(), + new TextRule(), + ]); $html = $parser->parse('{x:my_test_account}')->html; diff --git a/tests/Rules/StrikethroughRuleTest.php b/tests/Rules/StrikethroughRuleTest.php index 88f55ff..cc27837 100644 --- a/tests/Rules/StrikethroughRuleTest.php +++ b/tests/Rules/StrikethroughRuleTest.php @@ -15,10 +15,12 @@ class StrikethroughRuleTest extends ParserTestCase #[Test] public function test_lex(): void { - $html = - (string) new Parser(highlighter: null, rules: [new StrikethroughRule()])->parse( - '~~strikethrough~~', - ); + $html = (string) new Parser(highlighter: null, rules: [ + new StrikethroughRule(), + new TextRule(), + ])->parse( + '~~strikethrough~~', + ); $this->assertSame('line 2line 3
| A | B |
|---|
| A | B |
|---|---|
| 1 | 2 |
| A | B |
|---|---|
| 1 | 2 |
| 3 | 4 |
| A | B | C |
|---|---|---|
| 1 | 2 | 3 |
| A | B | C |
|---|---|---|
| 3 |
| A | B |
|---|---|