diff --git a/psalm.xml b/psalm.xml
index 3cb03e6..8b58482 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -28,6 +28,8 @@
+
+
diff --git a/src/Configurables/BlockTypes.php b/src/Configurables/BlockTypes.php
new file mode 100644
index 0000000..7b3bcc2
--- /dev/null
+++ b/src/Configurables/BlockTypes.php
@@ -0,0 +1,123 @@
+[]> */
+ private static $defaultBlockTypes = [
+ '#' => [Header::class],
+ '*' => [Rule::class, TList::class],
+ '+' => [TList::class],
+ '-' => [SetextHeader::class, Table::class, Rule::class, TList::class],
+ '0' => [TList::class],
+ '1' => [TList::class],
+ '2' => [TList::class],
+ '3' => [TList::class],
+ '4' => [TList::class],
+ '5' => [TList::class],
+ '6' => [TList::class],
+ '7' => [TList::class],
+ '8' => [TList::class],
+ '9' => [TList::class],
+ ':' => [Table::class],
+ '<' => [Comment::class, BlockMarkup::class],
+ '=' => [SetextHeader::class],
+ '>' => [BlockQuote::class],
+ '[' => [Reference::class],
+ '_' => [Rule::class],
+ '`' => [FencedCode::class],
+ '|' => [Table::class],
+ '~' => [FencedCode::class],
+ ];
+
+ /** @var class-string[] */
+ private static $defaultUnmarkedBlockTypes = [
+ IndentedCode::class,
+ ];
+
+ /** @var array[]> */
+ private $blockTypes;
+
+ /** @var class-string[] */
+ private $unmarkedBlockTypes;
+
+ /**
+ * @param array[]> $blockTypes
+ * @param class-string[] $unmarkedBlockTypes
+ */
+ public function __construct(array $blockTypes, array $unmarkedBlockTypes)
+ {
+ $this->blockTypes = $blockTypes;
+ $this->unmarkedBlockTypes = $unmarkedBlockTypes;
+ }
+
+ /** @return self */
+ public static function initial()
+ {
+ return new self(
+ self::$defaultBlockTypes,
+ self::$defaultUnmarkedBlockTypes
+ );
+ }
+
+ /**
+ * @param string $marker
+ * @param class-string[] $newBlockTypes
+ * @return self
+ */
+ public function settingMarked($marker, array $newBlockTypes)
+ {
+ $blockTypes = $this->blockTypes;
+ $blockTypes[$marker] = $newBlockTypes;
+
+ return new self($blockTypes, $this->unmarkedBlockTypes);
+ }
+
+ /**
+ * @param class-string[] $newUnmarkedBlockTypes
+ * @return self
+ */
+ public function settingUnmarked(array $newUnmarkedBlockTypes)
+ {
+ return new self($this->blockTypes, $newUnmarkedBlockTypes);
+ }
+
+ /**
+ * @param string $marker
+ * @return class-string[]
+ */
+ public function for($marker)
+ {
+ if (isset($this->blockTypes[$marker])) {
+ return $this->blockTypes[$marker];
+ }
+
+ return [];
+ }
+
+ /**
+ * @return class-string[]
+ */
+ public function unmarked()
+ {
+ return $this->unmarkedBlockTypes;
+ }
+}
diff --git a/src/Configurables/InlineTypes.php b/src/Configurables/InlineTypes.php
new file mode 100644
index 0000000..bca3411
--- /dev/null
+++ b/src/Configurables/InlineTypes.php
@@ -0,0 +1,80 @@
+[]> */
+ private static $defaultInlineTypes = [
+ '!' => [Image::class],
+ '*' => [Emphasis::class],
+ '_' => [Emphasis::class],
+ '&' => [SpecialCharacter::class],
+ '[' => [Link::class],
+ ':' => [Url::class],
+ '<' => [UrlTag::class, Email::class, InlineMarkup::class],
+ '`' => [Code::class],
+ '~' => [Strikethrough::class],
+ '\\' => [EscapeSequence::class],
+ "\n" => [HardBreak::class, SoftBreak::class],
+ ];
+
+ /** @var array[]> */
+ private $inlineTypes;
+
+ /** @var string */
+ private $inlineMarkers;
+
+ /**
+ * @param array[]> $inlineTypes
+ */
+ public function __construct(array $inlineTypes)
+ {
+ $this->inlineTypes = $inlineTypes;
+ $this->inlineMarkers = \implode('', \array_keys($inlineTypes));
+ }
+
+ /** @return self */
+ public static function initial()
+ {
+ return new self(self::$defaultInlineTypes);
+ }
+
+ /**
+ * @param string $marker
+ * @return class-string[]
+ */
+ public function for($marker)
+ {
+ if (isset($this->inlineTypes[$marker])) {
+ return $this->inlineTypes[$marker];
+ }
+
+ return [];
+ }
+
+ /** @return string */
+ public function markers()
+ {
+ return $this->inlineMarkers;
+ }
+}
diff --git a/src/Parsedown.php b/src/Parsedown.php
index 44f1cee..8a2daa5 100644
--- a/src/Parsedown.php
+++ b/src/Parsedown.php
@@ -4,35 +4,13 @@ namespace Erusev\Parsedown;
use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Block;
-use Erusev\Parsedown\Components\Blocks\BlockQuote;
-use Erusev\Parsedown\Components\Blocks\Comment;
-use Erusev\Parsedown\Components\Blocks\FencedCode;
-use Erusev\Parsedown\Components\Blocks\Header;
-use Erusev\Parsedown\Components\Blocks\IndentedCode;
-use Erusev\Parsedown\Components\Blocks\Markup as BlockMarkup;
use Erusev\Parsedown\Components\Blocks\Paragraph;
-use Erusev\Parsedown\Components\Blocks\Reference;
-use Erusev\Parsedown\Components\Blocks\Rule;
-use Erusev\Parsedown\Components\Blocks\SetextHeader;
-use Erusev\Parsedown\Components\Blocks\Table;
-use Erusev\Parsedown\Components\Blocks\TList;
use Erusev\Parsedown\Components\ContinuableBlock;
use Erusev\Parsedown\Components\Inline;
-use Erusev\Parsedown\Components\Inlines\Code;
-use Erusev\Parsedown\Components\Inlines\Email;
-use Erusev\Parsedown\Components\Inlines\Emphasis;
-use Erusev\Parsedown\Components\Inlines\EscapeSequence;
-use Erusev\Parsedown\Components\Inlines\HardBreak;
-use Erusev\Parsedown\Components\Inlines\Image;
-use Erusev\Parsedown\Components\Inlines\Link;
-use Erusev\Parsedown\Components\Inlines\Markup as InlineMarkup;
use Erusev\Parsedown\Components\Inlines\PlainText;
-use Erusev\Parsedown\Components\Inlines\SoftBreak;
-use Erusev\Parsedown\Components\Inlines\SpecialCharacter;
-use Erusev\Parsedown\Components\Inlines\Strikethrough;
-use Erusev\Parsedown\Components\Inlines\Url;
-use Erusev\Parsedown\Components\Inlines\UrlTag;
use Erusev\Parsedown\Components\StateUpdatingBlock;
+use Erusev\Parsedown\Configurables\BlockTypes;
+use Erusev\Parsedown\Configurables\InlineTypes;
use Erusev\Parsedown\Html\Renderable;
use Erusev\Parsedown\Html\Renderables\Invisible;
use Erusev\Parsedown\Html\Renderables\Text;
@@ -48,61 +26,15 @@ final class Parsedown
/** @var State */
private $State;
- /** @var array[]> */
- private $BlockTypes = [
- '#' => [Header::class],
- '*' => [Rule::class, TList::class],
- '+' => [TList::class],
- '-' => [SetextHeader::class, Table::class, Rule::class, TList::class],
- '0' => [TList::class],
- '1' => [TList::class],
- '2' => [TList::class],
- '3' => [TList::class],
- '4' => [TList::class],
- '5' => [TList::class],
- '6' => [TList::class],
- '7' => [TList::class],
- '8' => [TList::class],
- '9' => [TList::class],
- ':' => [Table::class],
- '<' => [Comment::class, BlockMarkup::class],
- '=' => [SetextHeader::class],
- '>' => [BlockQuote::class],
- '[' => [Reference::class],
- '_' => [Rule::class],
- '`' => [FencedCode::class],
- '|' => [Table::class],
- '~' => [FencedCode::class],
- ];
-
- /** @var class-string[] */
- private $unmarkedBlockTypes = [
- IndentedCode::class,
- ];
-
- /** @var array[]> */
- private $InlineTypes = [
- '!' => [Image::class],
- '*' => [Emphasis::class],
- '_' => [Emphasis::class],
- '&' => [SpecialCharacter::class],
- '[' => [Link::class],
- ':' => [Url::class],
- '<' => [UrlTag::class, Email::class, InlineMarkup::class],
- '`' => [Code::class],
- '~' => [Strikethrough::class],
- '\\' => [EscapeSequence::class],
- "\n" => [HardBreak::class, SoftBreak::class],
- ];
-
- /** @var string */
- private $inlineMarkers;
-
public function __construct(State $State = null)
{
$this->State = $State ?: new State;
- $this->inlineMarkers = \implode('', \array_keys($this->InlineTypes));
+ // ensure we cache the initial value if these weren't explicitly set
+ $this->State = $this->State->mergingWith(new State([
+ $this->State->get(BlockTypes::class),
+ $this->State->get(InlineTypes::class),
+ ]));
}
/**
@@ -174,18 +106,15 @@ final class Parsedown
# ~
- $blockTypes = $this->unmarkedBlockTypes;
-
- if (isset($this->BlockTypes[$marker])) {
- foreach ($this->BlockTypes[$marker] as $blockType) {
- $blockTypes []= $blockType;
- }
- }
+ $potentialBlockTypes = \array_merge(
+ $this->State->get(BlockTypes::class)->unmarked(),
+ $this->State->get(BlockTypes::class)->for($marker)
+ );
#
# ~
- foreach ($blockTypes as $blockType) {
+ foreach ($potentialBlockTypes as $blockType) {
$Block = $blockType::build($Context, $CurrentBlock, $this->State);
if (isset($Block)) {
@@ -264,14 +193,17 @@ final class Parsedown
# $excerpt is based on the first occurrence of a marker
+ $InlineTypes = $this->State->get(InlineTypes::class);
+ $markerMask = $InlineTypes->markers();
+
for (
- $Excerpt = (new Excerpt($text, 0))->pushingOffsetTo($this->inlineMarkers);
+ $Excerpt = (new Excerpt($text, 0))->pushingOffsetTo($markerMask);
$Excerpt->text() !== '';
- $Excerpt = $Excerpt->pushingOffsetTo($this->inlineMarkers)
+ $Excerpt = $Excerpt->pushingOffsetTo($markerMask)
) {
$marker = \substr($Excerpt->text(), 0, 1);
- foreach ($this->InlineTypes[$marker] as $inlineType) {
+ foreach ($InlineTypes->for($marker) as $inlineType) {
# check to see if the current inline type is nestable in the current context
$Inline = $inlineType::build($Excerpt, $this->State);