From 9fd3d58a35e7e0b9f9a6842d0f24571cb7fd3654 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Mon, 30 Dec 2019 22:32:57 +0000
Subject: [PATCH 01/28] Fix version compare
---
ParsedownExtra.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ParsedownExtra.php b/ParsedownExtra.php
index 45f4f83..ecd6aa7 100644
--- a/ParsedownExtra.php
+++ b/ParsedownExtra.php
@@ -23,7 +23,7 @@ class ParsedownExtra extends Parsedown
function __construct()
{
- if (version_compare(parent::version, '1.5.0') >= 0)
+ if (version_compare(parent::version, '1.5.0') < 0)
{
throw new Exception('ParsedownExtra requires a later version of Parsedown');
}
From 4c22769c2eb49baa996b6effb7b7943a493b65eb Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Mon, 30 Dec 2019 22:33:20 +0000
Subject: [PATCH 02/28] Check for null parse result
---
ParsedownExtra.php | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/ParsedownExtra.php b/ParsedownExtra.php
index ecd6aa7..995ee03 100644
--- a/ParsedownExtra.php
+++ b/ParsedownExtra.php
@@ -206,6 +206,10 @@ protected function blockHeader($Line)
{
$Block = parent::blockHeader($Line);
+ if (! isset($Block)) {
+ return null;
+ }
+
if (preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
@@ -238,6 +242,10 @@ protected function blockSetextHeader($Line, array $Block = null)
{
$Block = parent::blockSetextHeader($Line, $Block);
+ if (! isset($Block)) {
+ return null;
+ }
+
if (preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
@@ -302,6 +310,10 @@ protected function inlineLink($Excerpt)
{
$Link = parent::inlineLink($Excerpt);
+ if (! isset($Link)) {
+ return null;
+ }
+
$remainder = substr($Excerpt['text'], $Link['extent']);
if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
From d1208f60db969e7207348db59e819c57c3a8c9b9 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Mon, 30 Dec 2019 22:35:34 +0000
Subject: [PATCH 03/28] Utilise optional rawHtml to permit footnotes
---
ParsedownExtra.php | 4 ++--
composer.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/ParsedownExtra.php b/ParsedownExtra.php
index 995ee03..bfea96d 100644
--- a/ParsedownExtra.php
+++ b/ParsedownExtra.php
@@ -23,7 +23,7 @@ class ParsedownExtra extends Parsedown
function __construct()
{
- if (version_compare(parent::version, '1.5.0') < 0)
+ if (version_compare(parent::version, '1.7.4') < 0)
{
throw new Exception('ParsedownExtra requires a later version of Parsedown');
}
@@ -432,7 +432,7 @@ protected function buildFootnoteElement()
$Element['text'][1]['text'] []= array(
'name' => 'li',
'attributes' => array('id' => 'fn:'.$definitionId),
- 'text' => "\n".$text."\n",
+ 'rawHtml' => "\n".$text."\n",
);
}
diff --git a/composer.json b/composer.json
index a8103e9..317d147 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,7 @@
}
],
"require": {
- "erusev/parsedown": "~1.4"
+ "erusev/parsedown": "^1.7.4"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
From d8845590c7d1c8cf93992fc66cdfc765e28de27c Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Mon, 30 Dec 2019 23:02:40 +0000
Subject: [PATCH 04/28] Bump version for dev
---
ParsedownExtra.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ParsedownExtra.php b/ParsedownExtra.php
index 995ee03..3f9abf3 100644
--- a/ParsedownExtra.php
+++ b/ParsedownExtra.php
@@ -17,7 +17,7 @@ class ParsedownExtra extends Parsedown
{
# ~
- const version = '0.7.0';
+ const version = '0.8.1-dev';
# ~
From e9c25361cd262368e5ae6e4a3c56546448a79a0d Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Mon, 30 Dec 2019 23:08:47 +0000
Subject: [PATCH 05/28] Drop hhvm
---
.travis.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 29bfea9..e1fd72d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,8 +13,6 @@ matrix:
- php: 7.0
- php: 7.1
- php: nightly
- - php: hhvm
- - php: hhvm-nightly
fast_finish: true
allow_failures:
- php: nightly
From ba0ae88eae1871a116707ffd0fc0433adbb80f14 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Mon, 30 Dec 2019 23:09:15 +0000
Subject: [PATCH 06/28] Test on modern versions of PHP
---
.travis.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.travis.yml b/.travis.yml
index e1fd72d..d0b48f8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,6 +12,9 @@ matrix:
- php: 5.6
- php: 7.0
- php: 7.1
+ - php: 7.2
+ - php: 7.3
+ - php: 7.4
- php: nightly
fast_finish: true
allow_failures:
From 91ac3ff98f0cea243bdccc688df43810f044dcef Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Mon, 30 Dec 2019 23:20:37 +0000
Subject: [PATCH 07/28] Bump version for release
---
ParsedownExtra.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ParsedownExtra.php b/ParsedownExtra.php
index c6a71e6..632ba84 100644
--- a/ParsedownExtra.php
+++ b/ParsedownExtra.php
@@ -17,7 +17,7 @@ class ParsedownExtra extends Parsedown
{
# ~
- const version = '0.8.1-dev';
+ const version = '0.8.1';
# ~
From 8296157b62bc7f6ebaa623ab8c4432d5a4759503 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sun, 10 May 2020 14:51:44 +0100
Subject: [PATCH 08/28] Namespace for psr-4
---
composer.json | 35 ++++++++++++++------
ParsedownExtra.php => src/ParsedownExtra.php | 28 ++--------------
2 files changed, 28 insertions(+), 35 deletions(-)
rename ParsedownExtra.php => src/ParsedownExtra.php (96%)
diff --git a/composer.json b/composer.json
index 317d147..f24ccf1 100644
--- a/composer.json
+++ b/composer.json
@@ -13,21 +13,36 @@
}
],
"require": {
- "erusev/parsedown": "^1.7.4"
+ "erusev/parsedown": "^2.0.0|2.0.x@dev",
+ "php": "^7.1",
+ "ext-mbstring": "*"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35"
+ "phpunit/phpunit": "^7.4||^6.5.13||^5.7.27||^4.8.36",
+ "vimeo/psalm": "^3.8.3",
+ "friendsofphp/php-cs-fixer": "^2.13",
+ "infection/infection": "^0.12.0"
},
"autoload": {
- "psr-0": {"ParsedownExtra": ""}
+ "psr-4": {"Erusev\\ParsedownExtra\\": "src/"}
},
"autoload-dev": {
- "psr-0": {
- "TestParsedown": "test/",
- "ParsedownExtraTest": "test/",
- "ParsedownTest": "vendor/erusev/parsedown/test/",
- "CommonMarkTest": "vendor/erusev/parsedown/test/",
- "CommonMarkTestWeak": "vendor/erusev/parsedown/test/"
- }
+ "psr-4": {"Erusev\\ParsedownExtra\\Tests\\": "tests/"}
+ },
+ "scripts": {
+ "test": [
+ "@test-static",
+ "@test-formatting",
+ "@test-dead-code",
+ "@test-units"
+ ],
+ "test-static": "vendor/bin/psalm",
+ "test-dead-code": "vendor/bin/psalm --find-dead-code",
+ "test-units": "vendor/bin/phpunit",
+ "test-commonmark": "vendor/bin/phpunit tests/CommonMarkTestStrict.php",
+ "test-commonmark-weak": "vendor/bin/phpunit tests/CommonMarkTestWeak.php",
+ "test-formatting": "@composer fix -- --dry-run",
+
+ "fix": "vendor/bin/php-cs-fixer fix --verbose --show-progress=dots --diff"
}
}
diff --git a/ParsedownExtra.php b/src/ParsedownExtra.php
similarity index 96%
rename from ParsedownExtra.php
rename to src/ParsedownExtra.php
index 632ba84..4c1869b 100644
--- a/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -1,33 +1,11 @@
BlockTypes[':'] []= 'DefinitionList';
$this->BlockTypes['*'] []= 'Abbreviation';
From 774ee75c312655f11621772f41ddd667d5b13d27 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sun, 10 May 2020 14:55:45 +0100
Subject: [PATCH 09/28] Move test dir, add test config
---
.gitignore | 3 +
.php_cs.dist | 37 ++++++++++++
.travis.yml | 60 ++++++++++++-------
psalm.xml | 34 +++++++++++
{test => tests}/ParsedownExtraTest.php | 0
{test => tests}/TestParsedown.php | 0
{test => tests}/data/abbreviation.html | 0
{test => tests}/data/abbreviation.md | 0
{test => tests}/data/compound_footnote.html | 0
{test => tests}/data/compound_footnote.md | 0
{test => tests}/data/definition_list.html | 0
{test => tests}/data/definition_list.md | 0
{test => tests}/data/footnote.html | 0
{test => tests}/data/footnote.md | 0
.../data/markdown_inside_markup.html | 0
.../data/markdown_inside_markup.md | 0
{test => tests}/data/special_attributes.html | 0
{test => tests}/data/special_attributes.md | 0
tests/infection.json.dist | 18 ++++++
19 files changed, 131 insertions(+), 21 deletions(-)
create mode 100644 .gitignore
create mode 100644 .php_cs.dist
create mode 100644 psalm.xml
rename {test => tests}/ParsedownExtraTest.php (100%)
rename {test => tests}/TestParsedown.php (100%)
rename {test => tests}/data/abbreviation.html (100%)
rename {test => tests}/data/abbreviation.md (100%)
rename {test => tests}/data/compound_footnote.html (100%)
rename {test => tests}/data/compound_footnote.md (100%)
rename {test => tests}/data/definition_list.html (100%)
rename {test => tests}/data/definition_list.md (100%)
rename {test => tests}/data/footnote.html (100%)
rename {test => tests}/data/footnote.md (100%)
rename {test => tests}/data/markdown_inside_markup.html (100%)
rename {test => tests}/data/markdown_inside_markup.md (100%)
rename {test => tests}/data/special_attributes.html (100%)
rename {test => tests}/data/special_attributes.md (100%)
create mode 100644 tests/infection.json.dist
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..725b855
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+vendor/
+infection.log
diff --git a/.php_cs.dist b/.php_cs.dist
new file mode 100644
index 0000000..f2b25f9
--- /dev/null
+++ b/.php_cs.dist
@@ -0,0 +1,37 @@
+in(__DIR__ . '/src/')
+ ->in(__DIR__ . '/tests/')
+;
+$rules = [
+ '@PSR2' => true,
+ 'array_syntax' => [
+ 'syntax' => 'short',
+ ],
+ 'braces' => [
+ 'allow_single_line_closure' => true,
+ ],
+ 'logical_operators' => true,
+ 'native_constant_invocation' => [
+ 'fix_built_in' => true,
+ ],
+ 'native_function_invocation' => [
+ 'include' => ['@all'],
+ ],
+ 'no_unused_imports' => true,
+ 'ordered_imports' => [
+ 'sort_algorithm' => 'alpha',
+ ],
+ 'single_blank_line_before_namespace' => true,
+ 'strict_comparison' => true,
+ 'strict_param' => true,
+ 'whitespace_after_comma_in_array' => true,
+];
+return Config::create()
+ ->setRules($rules)
+ ->setFinder($finder)
+ ->setUsingCache(false)
+ ->setRiskyAllowed(true)
+;
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index d0b48f8..3ae8263 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,28 +3,46 @@ language: php
dist: trusty
sudo: false
-matrix:
- include:
- - php: 5.3
- dist: precise
- - php: 5.4
- - php: 5.5
- - php: 5.6
- - php: 7.0
- - php: 7.1
- - php: 7.2
- - php: 7.3
- - php: 7.4
- - php: nightly
+stages:
+ - Code Format and Static Analysis
+ - Units
+
+cache:
+ directories:
+ - $HOME/.composer/cache
+
+jobs:
fast_finish: true
allow_failures:
- - php: nightly
- - php: hhvm-nightly
+ - env: ALLOW_FAILURE
+
+ include:
+ - stage: Code Format and Static Analysis
+ php: 7.4
+ install: composer install --prefer-dist --no-interaction --no-progress
+ script:
+ - '[ -z "$TRAVIS_TAG" ] || [ "$TRAVIS_TAG" == "$(php -r "require(\"vendor/autoload.php\"); echo Erusev\Parsedown\Parsedown::version;")" ]'
+ - composer test-static -- --shepherd
+ - composer test-formatting
+ - composer test-dead-code
+
+ - &MUTATION_AND_UNIT_TEST
+ stage: Units
+ php: 7.1
+ install:
+ - composer update --prefer-dist --no-interaction --no-progress
+ script:
+ - composer test-units
+ - vendor/bin/infection --show-mutations --threads=4 --min-msi=90 --min-covered-msi=90
-install:
- - composer install --prefer-source
+ - <<: *MUTATION_AND_UNIT_TEST
+ php: 7.2
+ - <<: *MUTATION_AND_UNIT_TEST
+ php: 7.3
+ - <<: *MUTATION_AND_UNIT_TEST
+ php: 7.4
-script:
- - vendor/bin/phpunit
- - vendor/bin/phpunit vendor/erusev/parsedown/test/CommonMarkTestWeak.php || true
- - '[ -z "$TRAVIS_TAG" ] || [ "$TRAVIS_TAG" == "$(php -r "require(\"ParsedownExtra.php\"); echo ParsedownExtra::version;")" ]'
+ - <<: *MUTATION_AND_UNIT_TEST
+ php: nightly
+ env: ALLOW_FAILURE
+ script: composer test-units
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..22601f8
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/ParsedownExtraTest.php b/tests/ParsedownExtraTest.php
similarity index 100%
rename from test/ParsedownExtraTest.php
rename to tests/ParsedownExtraTest.php
diff --git a/test/TestParsedown.php b/tests/TestParsedown.php
similarity index 100%
rename from test/TestParsedown.php
rename to tests/TestParsedown.php
diff --git a/test/data/abbreviation.html b/tests/data/abbreviation.html
similarity index 100%
rename from test/data/abbreviation.html
rename to tests/data/abbreviation.html
diff --git a/test/data/abbreviation.md b/tests/data/abbreviation.md
similarity index 100%
rename from test/data/abbreviation.md
rename to tests/data/abbreviation.md
diff --git a/test/data/compound_footnote.html b/tests/data/compound_footnote.html
similarity index 100%
rename from test/data/compound_footnote.html
rename to tests/data/compound_footnote.html
diff --git a/test/data/compound_footnote.md b/tests/data/compound_footnote.md
similarity index 100%
rename from test/data/compound_footnote.md
rename to tests/data/compound_footnote.md
diff --git a/test/data/definition_list.html b/tests/data/definition_list.html
similarity index 100%
rename from test/data/definition_list.html
rename to tests/data/definition_list.html
diff --git a/test/data/definition_list.md b/tests/data/definition_list.md
similarity index 100%
rename from test/data/definition_list.md
rename to tests/data/definition_list.md
diff --git a/test/data/footnote.html b/tests/data/footnote.html
similarity index 100%
rename from test/data/footnote.html
rename to tests/data/footnote.html
diff --git a/test/data/footnote.md b/tests/data/footnote.md
similarity index 100%
rename from test/data/footnote.md
rename to tests/data/footnote.md
diff --git a/test/data/markdown_inside_markup.html b/tests/data/markdown_inside_markup.html
similarity index 100%
rename from test/data/markdown_inside_markup.html
rename to tests/data/markdown_inside_markup.html
diff --git a/test/data/markdown_inside_markup.md b/tests/data/markdown_inside_markup.md
similarity index 100%
rename from test/data/markdown_inside_markup.md
rename to tests/data/markdown_inside_markup.md
diff --git a/test/data/special_attributes.html b/tests/data/special_attributes.html
similarity index 100%
rename from test/data/special_attributes.html
rename to tests/data/special_attributes.html
diff --git a/test/data/special_attributes.md b/tests/data/special_attributes.md
similarity index 100%
rename from test/data/special_attributes.md
rename to tests/data/special_attributes.md
diff --git a/tests/infection.json.dist b/tests/infection.json.dist
new file mode 100644
index 0000000..43325e8
--- /dev/null
+++ b/tests/infection.json.dist
@@ -0,0 +1,18 @@
+{
+ "timeout": 10,
+ "source": {
+ "directories": [
+ "src"
+ ]
+ },
+ "logs": {
+ "text": "infection.log"
+ },
+ "mutators": {
+ "@default": true,
+ "@cast": false,
+ "This": false,
+ "FunctionCall": false,
+ "NewObject": false
+ }
+}
From 6d82e0338e1dd8e959b61d75d87f1f13cbd42a45 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sun, 10 May 2020 15:09:27 +0100
Subject: [PATCH 10/28] Implement abbreviation block
---
src/Components/Blocks/Abbreviation.php | 63 ++++++++++++++++++++++++++
src/Configurables/AbbreviationBook.php | 40 ++++++++++++++++
src/ParsedownExtra.php | 21 ---------
3 files changed, 103 insertions(+), 21 deletions(-)
create mode 100644 src/Components/Blocks/Abbreviation.php
create mode 100644 src/Configurables/AbbreviationBook.php
diff --git a/src/Components/Blocks/Abbreviation.php b/src/Components/Blocks/Abbreviation.php
new file mode 100644
index 0000000..8cde0ef
--- /dev/null
+++ b/src/Components/Blocks/Abbreviation.php
@@ -0,0 +1,63 @@
+State = $State;
+ }
+
+ /**
+ * @param Context $Context
+ * @param State $State
+ * @param Block|null $Block
+ * @return static|null
+ */
+ public static function build(
+ Context $Context,
+ State $State,
+ Block $Block = null
+ ) {
+ if (\preg_match(
+ '/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/',
+ $Context->line()->text(),
+ $matches
+ )) {
+ $short = $matches[1];
+ $long = $matches[2];
+
+ $State->get(AbbreviationBook::class)->mutatingSet($short, $long);
+
+ return new self($State);
+ }
+
+ return null;
+ }
+
+ /** @return State */
+ public function latestState()
+ {
+ return $this->State;
+ }
+
+ /**
+ * @return Invisible
+ */
+ public function stateRenderable()
+ {
+ return new Invisible;
+ }
+}
diff --git a/src/Configurables/AbbreviationBook.php b/src/Configurables/AbbreviationBook.php
new file mode 100644
index 0000000..08f4f8f
--- /dev/null
+++ b/src/Configurables/AbbreviationBook.php
@@ -0,0 +1,40 @@
+ */
+ private $book;
+
+ /**
+ * @param array $book
+ */
+ public function __construct(array $book = [])
+ {
+ $this->book = $book;
+ }
+
+ /** @return self */
+ public static function initial()
+ {
+ return new self;
+ }
+
+ public function mutatingSet(string $abbreviation, string $definition): void
+ {
+ $this->book[$abbreviation] = $definition;
+ }
+
+ public function lookup(string $abbreviation): ?string
+ {
+ return $this->book[$abbreviation] ?? null;
+ }
+
+ public function isolatedCopy(): self
+ {
+ return new self($this->book);
+ }
+}
diff --git a/src/ParsedownExtra.php b/src/ParsedownExtra.php
index 4c1869b..1047c64 100644
--- a/src/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -39,27 +39,6 @@ function text($text)
return $markup;
}
- #
- # Blocks
- #
-
- #
- # Abbreviation
-
- protected function blockAbbreviation($Line)
- {
- if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
- {
- $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
-
- $Block = array(
- 'hidden' => true,
- );
-
- return $Block;
- }
- }
-
#
# Footnote
From f08ccc337060dbd4bd695f2d47203807fc580007 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sun, 10 May 2020 20:25:06 +0100
Subject: [PATCH 11/28] Implement DefinitionList
---
src/Components/Blocks/DefinitionList.php | 218 +++++++++++++++++++++++
src/ParsedownExtra.php | 94 ----------
2 files changed, 218 insertions(+), 94 deletions(-)
create mode 100644 src/Components/Blocks/DefinitionList.php
diff --git a/src/Components/Blocks/DefinitionList.php b/src/Components/Blocks/DefinitionList.php
new file mode 100644
index 0000000..c7a8c87
--- /dev/null
+++ b/src/Components/Blocks/DefinitionList.php
@@ -0,0 +1,218 @@
+StartingBlock = $StartingBlock;
+ $this->Lis = $Lis;
+ $this->isLoose = $isLoose;
+ $this->requiredIndent = $requiredIndent;
+ }
+
+ /**
+ * @param Context $Context
+ * @param Block|null $Block
+ * @param State|null $State
+ * @return static|null
+ */
+ public static function build(
+ Context $Context,
+ State $State = null,
+ Block $Block = null
+ ) {
+ if (! isset($Block) || ! $Block instanceof Paragraph || $Context->precedingEmptyLines() > 0) {
+ return null;
+ }
+
+ $text = $Context->line()->text();
+
+ if (\substr($text, 0, 1) !== ':' || $Context->line()->indent() > 3) {
+ return null;
+ }
+
+ $secondChar = \substr($text, 1, 2);
+
+ if ($secondChar !== ' ' && $secondChar !== "\t") {
+ return null;
+ }
+
+ $preAfterMarkerSpacesIndentOffset = $Context->line()->indentOffset() + $Context->line()->indent() + 1;
+
+ $LineWithMarkerIndent = new Line(\substr($text, 2), $preAfterMarkerSpacesIndentOffset);
+ $indentAfterMarker = $LineWithMarkerIndent->indent();
+
+ if ($indentAfterMarker > 4) {
+ $perceivedIndent = $indentAfterMarker -1;
+ $afterMarkerSpaces = 1;
+ } else {
+ $perceivedIndent = 0;
+ $afterMarkerSpaces = $indentAfterMarker;
+ }
+
+ $indentOffset = $preAfterMarkerSpacesIndentOffset + $afterMarkerSpaces;
+ $text = \str_repeat(' ', $perceivedIndent) . $LineWithMarkerIndent->text();
+
+
+ return new self(
+ $Block,
+ [!empty($text) ? Lines::fromTextLines($text, $indentOffset) : Lines::none()],
+ false,
+ $Context->line()->indent() + 1 + $afterMarkerSpaces
+ );
+ }
+
+ /** @return bool */
+ public function acquiredPrevious()
+ {
+ return true;
+ }
+
+ /**
+ * @param Context $Context
+ * @return self|null
+ */
+ public function advance(Context $Context, State $State)
+ {
+ if ($Context->precedingEmptyLines() > 0 && \end($this->Lis)->isEmpty()) {
+ return null;
+ }
+
+ $indent = $Context->line()->indent();
+ $offset = $Context->line()->indentOffset();
+
+ $newLines = '';
+
+ if ($Context->precedingEmptyLines() > 0) {
+ foreach (\explode("\n", $Context->precedingEmptyLinesText()) as $line) {
+ $newLines .= (new Line($line, $offset))->ltrimBodyUpto($this->requiredIndent) . "\n";
+ }
+
+ $newLines = \substr($newLines, 0, -1);
+ }
+
+ if ($indent >= $this->requiredIndent) {
+ $newLines .= $Context->line()->ltrimBodyUpto($this->requiredIndent) . "\n";
+
+ $Lis = $this->Lis;
+ $Lis[] = Lines::fromTextLines($newLines, $offset + $this->requiredIndent);
+
+ return new self(
+ $this->StartingBlock,
+ $Lis,
+ $this->isLoose,
+ $this->requiredIndent
+ );
+ } elseif ($NewDefinitionList = self::build($Context, $State, $this->StartingBlock)) {
+ return new self(
+ $this->StartingBlock,
+ \array_merge($this->Lis, $NewDefinitionList->Lis),
+ $this->isLoose || $Context->precedingEmptyLines() > 0,
+ $NewDefinitionList->requiredIndent
+ );
+ } elseif (! $Context->precedingEmptyLines() > 0) {
+ $Lis = $this->Lis;
+ $text = $Context->line()->ltrimBodyUpto($this->requiredIndent);
+
+ $Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingTextLines(
+ $newLines . \str_repeat(' ', $Context->line()->indent()) . $text,
+ $Context->line()->indentOffset() + $Context->line()->indent()
+ );
+
+ return new self(
+ $this->StartingBlock,
+ $Lis,
+ $this->isLoose,
+ $this->requiredIndent
+ );
+ }
+
+ return null;
+ }
+
+ /**
+ * @return Handler
+ */
+ public function stateRenderable()
+ {
+ return new Handler(
+ /** @return Element */
+ function (State $State) {
+ return new Element(
+ 'dl',
+ [],
+ \array_merge(
+ \array_map(
+ /**
+ * @param string $term
+ * @return Element
+ */
+ function ($term) {
+ return new Element('dt', [], [new Text($term)]);
+ },
+ \explode("\n", $this->StartingBlock->text())
+ ),
+ \array_map(
+ /** @return Element */
+ function (Lines $Lines) use ($State) {
+ list($StateRenderables, $State) = Parsedown::lines(
+ $Lines,
+ $State
+ );
+
+ $Renderables = $State->applyTo($StateRenderables);
+
+ if (! $this->isLoose
+ && isset($Renderables[0])
+ && $Renderables[0] instanceof Element
+ && $Renderables[0]->name() === 'p'
+ ) {
+ $Contents = $Renderables[0]->contents();
+ unset($Renderables[0]);
+ $Renderables = \array_merge($Contents ?: [], $Renderables);
+ }
+
+ return new Element('dd', [], $Renderables);
+ },
+ $this->Lis
+ )
+ )
+ );
+ }
+ );
+ }
+}
diff --git a/src/ParsedownExtra.php b/src/ParsedownExtra.php
index 1047c64..228815c 100644
--- a/src/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -91,71 +91,6 @@ protected function blockFootnoteComplete($Block)
return $Block;
}
- #
- # Definition List
-
- protected function blockDefinitionList($Line, $Block)
- {
- if ( ! isset($Block) or isset($Block['type']))
- {
- return;
- }
-
- $Element = array(
- 'name' => 'dl',
- 'handler' => 'elements',
- 'text' => array(),
- );
-
- $terms = explode("\n", $Block['element']['text']);
-
- foreach ($terms as $term)
- {
- $Element['text'] []= array(
- 'name' => 'dt',
- 'handler' => 'line',
- 'text' => $term,
- );
- }
-
- $Block['element'] = $Element;
-
- $Block = $this->addDdElement($Line, $Block);
-
- return $Block;
- }
-
- protected function blockDefinitionListContinue($Line, array $Block)
- {
- if ($Line['text'][0] === ':')
- {
- $Block = $this->addDdElement($Line, $Block);
-
- return $Block;
- }
- else
- {
- if (isset($Block['interrupted']) and $Line['indent'] === 0)
- {
- return;
- }
-
- if (isset($Block['interrupted']))
- {
- $Block['dd']['handler'] = 'text';
- $Block['dd']['text'] .= "\n\n";
-
- unset($Block['interrupted']);
- }
-
- $text = substr($Line['body'], min($Line['indent'], 4));
-
- $Block['dd']['text'] .= "\n" . $text;
-
- return $Block;
- }
- }
-
#
# Header
@@ -304,35 +239,6 @@ protected function unmarkedText($text)
return $text;
}
- #
- # Util Methods
- #
-
- protected function addDdElement(array $Line, array $Block)
- {
- $text = substr($Line['text'], 1);
- $text = trim($text);
-
- unset($Block['dd']);
-
- $Block['dd'] = array(
- 'name' => 'dd',
- 'handler' => 'line',
- 'text' => $text,
- );
-
- if (isset($Block['interrupted']))
- {
- $Block['dd']['handler'] = 'text';
-
- unset($Block['interrupted']);
- }
-
- $Block['element']['text'] []= & $Block['dd'];
-
- return $Block;
- }
-
protected function buildFootnoteElement()
{
$Element = array(
From 6f42ced4a1d7fb90a23d728e55ad4a8fab83ae61 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 22:33:48 +0100
Subject: [PATCH 12/28] Drop custom markdown support
---
tests/data/markdown_inside_markup.html | 25 --------------------
tests/data/markdown_inside_markup.md | 32 --------------------------
2 files changed, 57 deletions(-)
delete mode 100644 tests/data/markdown_inside_markup.html
delete mode 100644 tests/data/markdown_inside_markup.md
diff --git a/tests/data/markdown_inside_markup.html b/tests/data/markdown_inside_markup.html
deleted file mode 100644
index 27da259..0000000
--- a/tests/data/markdown_inside_markup.html
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
markdown
-
This is another paragraph. It contains inline markup.
-
-_no markdown_
-
-
-
-
-
-
-
-
-_no markdown_
-
\ No newline at end of file
diff --git a/tests/data/markdown_inside_markup.md b/tests/data/markdown_inside_markup.md
deleted file mode 100644
index dbb85d6..0000000
--- a/tests/data/markdown_inside_markup.md
+++ /dev/null
@@ -1,32 +0,0 @@
-
-_markdown_
-
-This is another paragraph. It contains
inline markup.
-
-_no markdown_
-
-
-
----
-
-
-_markdown_
-
-_markdown_
-
-
-
----
-
-
-_no markdown_
-
-_markdown_
-
-
-
----
-
-
-_no markdown_
-
\ No newline at end of file
From 3f402cc9525149442212fe3ed28a09815e1c4ee0 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 22:35:37 +0100
Subject: [PATCH 13/28] Adjust formatting for new renderer
---
tests/data/definition_list.html | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/tests/data/definition_list.html b/tests/data/definition_list.html
index 2567dc1..ea7a42e 100644
--- a/tests/data/definition_list.html
+++ b/tests/data/definition_list.html
@@ -3,15 +3,24 @@
one
two
extra line
+
+
- Term 2
-lazy
-line
-multiple
-paragraphs
-nested
-code block
+-
+
lazy
+line
+
+-
+
multiple
+paragraphs
+
+-
+
nested
+code block
+
quote
block
-
+
+
\ No newline at end of file
From c0f1dda30189af4be2ffb232388b9bc9dd5593b6 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 22:42:13 +0100
Subject: [PATCH 14/28] Test config files
---
.gitignore | 1 +
composer.json | 14 +++++++++-----
phpunit.xml.dist | 2 +-
psalm.xml | 6 +++---
4 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/.gitignore b/.gitignore
index 725b855..d8c0ab9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
composer.lock
vendor/
infection.log
+.phpunit.result.cache
diff --git a/composer.json b/composer.json
index f24ccf1..fc615f7 100644
--- a/composer.json
+++ b/composer.json
@@ -14,20 +14,24 @@
],
"require": {
"erusev/parsedown": "^2.0.0|2.0.x@dev",
- "php": "^7.1",
+ "php": "^7.1||^8.0",
"ext-mbstring": "*"
},
"require-dev": {
- "phpunit/phpunit": "^7.4||^6.5.13||^5.7.27||^4.8.36",
- "vimeo/psalm": "^3.8.3",
+ "phpunit/phpunit": "^9.3.11||^8.5.21||^7.5.20",
+ "vimeo/psalm": "^4.10.0",
"friendsofphp/php-cs-fixer": "^2.13",
- "infection/infection": "^0.12.0"
+ "infection/infection": "^0.25.0",
+ "roave/infection-static-analysis-plugin": "^1.10.0"
},
"autoload": {
"psr-4": {"Erusev\\ParsedownExtra\\": "src/"}
},
"autoload-dev": {
- "psr-4": {"Erusev\\ParsedownExtra\\Tests\\": "tests/"}
+ "psr-4": {
+ "Erusev\\ParsedownExtra\\Tests\\": "tests/",
+ "Erusev\\Parsedown\\Tests\\": "vendor/erusev/parsedown/tests/"
+ }
},
"scripts": {
"test": [
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 29880b5..49736c9 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -2,7 +2,7 @@
- test/ParsedownExtraTest.php
+ tests/ParsedownExtraTest.php
diff --git a/psalm.xml b/psalm.xml
index 22601f8..ae8cdcd 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -22,13 +22,13 @@
-
+
-
+
-
+
From 9f99f5215141ab23fbbfe6f210ccb4e7f1d57cac Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 22:43:05 +0100
Subject: [PATCH 15/28] Fix tests
---
tests/ParsedownExtraTest.php | 11 +++++++++++
tests/TestParsedown.php | 9 ---------
2 files changed, 11 insertions(+), 9 deletions(-)
delete mode 100644 tests/TestParsedown.php
diff --git a/tests/ParsedownExtraTest.php b/tests/ParsedownExtraTest.php
index d2a6d23..9df57d2 100644
--- a/tests/ParsedownExtraTest.php
+++ b/tests/ParsedownExtraTest.php
@@ -1,5 +1,11 @@
textLevelElements;
- }
-}
From 99c7dd303620b48327d144cf16117f508303c7b9 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 22:43:34 +0100
Subject: [PATCH 16/28] Implement ParsedownExtra extension
---
src/Components/Blocks/Abbreviation.php | 2 +-
src/Components/Blocks/DefinitionList.php | 29 +-
src/Components/Blocks/Footnote.php | 119 ++++++
src/Components/Blocks/Header.php | 161 +++++++
src/Components/Blocks/SetextHeader.php | 168 ++++++++
src/Components/Inlines/Footnote.php | 101 +++++
src/Components/Inlines/Image.php | 133 ++++++
src/Components/Inlines/Link.php | 200 +++++++++
src/Configurables/AbbreviationBook.php | 7 +
src/Configurables/FootnoteBook.php | 84 ++++
src/ParsedownExtra.php | 523 +++++++----------------
11 files changed, 1132 insertions(+), 395 deletions(-)
create mode 100644 src/Components/Blocks/Footnote.php
create mode 100644 src/Components/Blocks/Header.php
create mode 100644 src/Components/Blocks/SetextHeader.php
create mode 100644 src/Components/Inlines/Footnote.php
create mode 100644 src/Components/Inlines/Image.php
create mode 100644 src/Components/Inlines/Link.php
create mode 100644 src/Configurables/FootnoteBook.php
diff --git a/src/Components/Blocks/Abbreviation.php b/src/Components/Blocks/Abbreviation.php
index 8cde0ef..4c6f01c 100644
--- a/src/Components/Blocks/Abbreviation.php
+++ b/src/Components/Blocks/Abbreviation.php
@@ -5,10 +5,10 @@
use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\StateUpdatingBlock;
-use Erusev\ParsedownExtra\Configurables\AbbreviationBook;
use Erusev\Parsedown\Html\Renderables\Invisible;
use Erusev\Parsedown\Parsing\Context;
use Erusev\Parsedown\State;
+use Erusev\ParsedownExtra\Configurables\AbbreviationBook;
final class Abbreviation implements StateUpdatingBlock
{
diff --git a/src/Components/Blocks/DefinitionList.php b/src/Components/Blocks/DefinitionList.php
index c7a8c87..92f6927 100644
--- a/src/Components/Blocks/DefinitionList.php
+++ b/src/Components/Blocks/DefinitionList.php
@@ -4,9 +4,9 @@
use Erusev\Parsedown\AST\Handler;
use Erusev\Parsedown\AST\StateRenderable;
+use Erusev\Parsedown\Components\AcquisitioningBlock;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\Blocks\Paragraph;
-use Erusev\Parsedown\Components\AcquisitioningBlock;
use Erusev\Parsedown\Components\ContinuableBlock;
use Erusev\Parsedown\Html\Renderables\Element;
use Erusev\Parsedown\Html\Renderables\Text;
@@ -55,7 +55,7 @@ public static function build(
State $State = null,
Block $Block = null
) {
- if (! isset($Block) || ! $Block instanceof Paragraph || $Context->precedingEmptyLines() > 0) {
+ if (! isset($Block) || ! $Block instanceof Paragraph || $Context->precedingEmptyLines() > 1) {
return null;
}
@@ -65,7 +65,7 @@ public static function build(
return null;
}
- $secondChar = \substr($text, 1, 2);
+ $secondChar = \substr($text, 1, 1);
if ($secondChar !== ' ' && $secondChar !== "\t") {
return null;
@@ -92,7 +92,7 @@ public static function build(
$Block,
[!empty($text) ? Lines::fromTextLines($text, $indentOffset) : Lines::none()],
false,
- $Context->line()->indent() + 1 + $afterMarkerSpaces
+ $Context->line()->indent() + 2 + $afterMarkerSpaces
);
}
@@ -126,10 +126,13 @@ public function advance(Context $Context, State $State)
}
if ($indent >= $this->requiredIndent) {
- $newLines .= $Context->line()->ltrimBodyUpto($this->requiredIndent) . "\n";
+ $newLines .= $Context->line()->ltrimBodyUpto($this->requiredIndent);
$Lis = $this->Lis;
- $Lis[] = Lines::fromTextLines($newLines, $offset + $this->requiredIndent);
+ $Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingTextLines(
+ $newLines,
+ $offset
+ );
return new self(
$this->StartingBlock,
@@ -144,7 +147,7 @@ public function advance(Context $Context, State $State)
$this->isLoose || $Context->precedingEmptyLines() > 0,
$NewDefinitionList->requiredIndent
);
- } elseif (! $Context->precedingEmptyLines() > 0) {
+ } elseif (! ($Context->precedingEmptyLines() > 0)) {
$Lis = $this->Lis;
$text = $Context->line()->ltrimBodyUpto($this->requiredIndent);
@@ -170,25 +173,19 @@ public function advance(Context $Context, State $State)
public function stateRenderable()
{
return new Handler(
- /** @return Element */
- function (State $State) {
+ function (State $State): Element {
return new Element(
'dl',
[],
\array_merge(
\array_map(
- /**
- * @param string $term
- * @return Element
- */
- function ($term) {
+ function (string $term): Element {
return new Element('dt', [], [new Text($term)]);
},
\explode("\n", $this->StartingBlock->text())
),
\array_map(
- /** @return Element */
- function (Lines $Lines) use ($State) {
+ function (Lines $Lines) use ($State): Element {
list($StateRenderables, $State) = Parsedown::lines(
$Lines,
$State
diff --git a/src/Components/Blocks/Footnote.php b/src/Components/Blocks/Footnote.php
new file mode 100644
index 0000000..148cfdd
--- /dev/null
+++ b/src/Components/Blocks/Footnote.php
@@ -0,0 +1,119 @@
+State = $State;
+ $this->title = $title;
+ $this->Lines = $Lines;
+ }
+
+ /**
+ * @param Context $Context
+ * @param State $State
+ * @param Block|null $Block
+ * @return static|null
+ */
+ public static function build(
+ Context $Context,
+ State $State,
+ Block $Block = null
+ ) {
+ if (\preg_match(
+ '/^\[\^(.+?)\]:(.*+)$/',
+ $Context->line()->text(),
+ $matches
+ )) {
+ $indentOffset = $Context->line()->indentOffset() + $Context->line()->indent() + \strlen($matches[1]) + 4;
+
+ $title = $matches[1];
+ $text = $matches[2];
+
+ $Footnote = new self(
+ $State,
+ $title,
+ Lines::fromTextLines($text, $indentOffset)
+ );
+
+ $State->get(FootnoteBook::class)->mutatingSetBlock($Footnote);
+
+ return $Footnote;
+ }
+
+ return null;
+ }
+
+ /** @return self|null */
+ public function advance(Context $Context, State $State): ?self
+ {
+ if ($Context->line()->indent() < 4) {
+ return null;
+ }
+
+ $Lines = $this->Lines;
+
+ $offset = $Context->line()->indentOffset();
+
+ if ($Context->precedingEmptyLines() > 0) {
+ foreach (\explode("\n", $Context->precedingEmptyLinesText()) as $line) {
+ $Lines = $Lines->appendingTextLines((new Line($line, $offset))->ltrimBodyUpto(4), $offset);
+ }
+ }
+
+ $indentOffset = $Context->line()->indentOffset() + $Context->line()->indent();
+ $Lines = $Lines->appendingTextLines($Context->line()->text(), $indentOffset);
+
+ $Footnote = new self($State, $this->title, $Lines);
+
+ $State->get(FootnoteBook::class)->mutatingSetBlock($Footnote);
+
+ return $Footnote;
+ }
+
+ /** @return State */
+ public function latestState()
+ {
+ return $this->State;
+ }
+
+ public function title(): string
+ {
+ return $this->title;
+ }
+
+ public function lines(): Lines
+ {
+ return $this->Lines;
+ }
+
+ /**
+ * @return Invisible
+ */
+ public function stateRenderable()
+ {
+ return new Invisible;
+ }
+}
diff --git a/src/Components/Blocks/Header.php b/src/Components/Blocks/Header.php
new file mode 100644
index 0000000..a88fa1a
--- /dev/null
+++ b/src/Components/Blocks/Header.php
@@ -0,0 +1,161 @@
+ */
+ private $classes;
+
+ /**
+ * @param string $text
+ * @param 1|2|3|4|5|6 $level
+ * @param string|null $id
+ * @param list $classes
+ */
+ private function __construct($text, $level, $id, $classes)
+ {
+ $this->text = $text;
+ $this->level = $level;
+ $this->id = $id;
+ $this->classes = $classes;
+ }
+
+ /**
+ * @param Context $Context
+ * @param State $State
+ * @param Block|null $Block
+ * @return static|null
+ */
+ public static function build(
+ Context $Context,
+ State $State,
+ Block $Block = null
+ ) {
+ $BaseHeader = BaseHeader::build($Context, $State, $Block);
+
+ if (! isset($BaseHeader)) {
+ return null;
+ }
+
+ $text = $BaseHeader->text();
+ $level = $BaseHeader->level();
+ $id = null;
+ $classes = [];
+
+ if (\preg_match('/[ #]*{('.self::ATT_REGEX.'+)}[ ]*$/', $text, $matches, \PREG_OFFSET_CAPTURE)) {
+ /** @var array{0: array{string, int}, 1: array{string, int}} $matches */
+ $attributeString = $matches[1][0];
+
+ ['id' => $id, 'classes' => $classes] = self::parseAttributeData($attributeString);
+
+ $text = \substr($text, 0, $matches[0][1]);
+ }
+
+ return new self($text, $level, $id, $classes);
+ }
+
+ /** @return string */
+ public function text()
+ {
+ return $this->text;
+ }
+
+ /** @return 1|2|3|4|5|6 */
+ public function level()
+ {
+ return $this->level;
+ }
+
+ public function id(): ?string
+ {
+ return $this->id;
+ }
+
+ /** @return list */
+ public function classes()
+ {
+ return $this->classes;
+ }
+
+ /**
+ * @return array{id:string|null,classes:list}
+ */
+ private static function parseAttributeData(string $attributeString)
+ {
+ $data = [
+ 'id' => null,
+ 'classes' => [],
+ ];
+
+ $attributes = \preg_split('/[ ]+/', $attributeString, - 1, \PREG_SPLIT_NO_EMPTY);
+
+ foreach ($attributes as $attribute) {
+ if ($attribute[0] === '#') {
+ $data['id'] = \substr($attribute, 1);
+ } else { # "."
+ $data['classes'][]= \substr($attribute, 1);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return Handler
+ */
+ public function stateRenderable()
+ {
+ return new Handler(
+ /** @return Element */
+ function (State $State) {
+ $HeaderSlug = $State->get(HeaderSlug::class);
+ $Register = $State->get(SlugRegister::class);
+
+ $id = $this->id();
+ $classes = $this->classes();
+
+ $attributes = \array_merge(
+ isset($id)
+ ? ['id' => $id]
+ : (
+ $HeaderSlug->isEnabled()
+ ? ['id' => $HeaderSlug->transform($Register, $this->text())]
+ : []
+ ),
+ !empty($classes)
+ ? ['class' => \implode(' ', $classes)]
+ : []
+ );
+
+ return new Element(
+ 'h' . \strval($this->level()),
+ $attributes,
+ $State->applyTo(Parsedown::line($this->text(), $State))
+ );
+ }
+ );
+ }
+}
diff --git a/src/Components/Blocks/SetextHeader.php b/src/Components/Blocks/SetextHeader.php
new file mode 100644
index 0000000..c2b3459
--- /dev/null
+++ b/src/Components/Blocks/SetextHeader.php
@@ -0,0 +1,168 @@
+ */
+ private $classes;
+
+ /**
+ * @param string $text
+ * @param 1|2 $level
+ * @param string|null $id
+ * @param list $classes
+ */
+ private function __construct($text, $level, $id, $classes)
+ {
+ $this->text = $text;
+ $this->level = $level;
+ $this->id = $id;
+ $this->classes = $classes;
+ }
+
+ /**
+ * @param Context $Context
+ * @param State $State
+ * @param Block|null $Block
+ * @return static|null
+ */
+ public static function build(
+ Context $Context,
+ State $State,
+ Block $Block = null
+ ) {
+ $BaseHeader = BaseSetextHeader::build($Context, $State, $Block);
+
+ if (! isset($BaseHeader)) {
+ return null;
+ }
+
+ $text = $BaseHeader->text();
+ $level = $BaseHeader->level();
+ $id = null;
+ $classes = [];
+
+ if (\preg_match('/[ ]*{('.self::ATT_REGEX.'+)}[ ]*$/', $text, $matches, \PREG_OFFSET_CAPTURE)) {
+ /** @var array{0: array{string, int}, 1: array{string, int}} $matches */
+ $attributeString = $matches[1][0];
+
+ ['id' => $id, 'classes' => $classes] = self::parseAttributeData($attributeString);
+
+ $text = \substr($text, 0, $matches[0][1]);
+ }
+
+ return new self($text, $level, $id, $classes);
+ }
+
+ /** @return bool */
+ public function acquiredPrevious()
+ {
+ return true;
+ }
+
+ /** @return string */
+ public function text()
+ {
+ return $this->text;
+ }
+
+ /** @return 1|2 */
+ public function level()
+ {
+ return $this->level;
+ }
+
+ public function id(): ?string
+ {
+ return $this->id;
+ }
+
+ /** @return list */
+ public function classes()
+ {
+ return $this->classes;
+ }
+
+ /**
+ * @return array{id:string|null,classes:list}
+ */
+ private static function parseAttributeData(string $attributeString)
+ {
+ $data = [
+ 'id' => null,
+ 'classes' => [],
+ ];
+
+ $attributes = \preg_split('/[ ]+/', $attributeString, - 1, \PREG_SPLIT_NO_EMPTY);
+
+ foreach ($attributes as $attribute) {
+ if ($attribute[0] === '#') {
+ $data['id'] = \substr($attribute, 1);
+ } else { # "."
+ $data['classes'][]= \substr($attribute, 1);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return Handler
+ */
+ public function stateRenderable()
+ {
+ return new Handler(
+ /** @return Element */
+ function (State $State) {
+ $HeaderSlug = $State->get(HeaderSlug::class);
+ $Register = $State->get(SlugRegister::class);
+
+ $id = $this->id();
+ $classes = $this->classes();
+
+ $attributes = \array_merge(
+ isset($id)
+ ? ['id' => $id]
+ : (
+ $HeaderSlug->isEnabled()
+ ? ['id' => $HeaderSlug->transform($Register, $this->text())]
+ : []
+ ),
+ !empty($classes)
+ ? ['class' => \implode(' ', $classes)]
+ : []
+ );
+
+ return new Element(
+ 'h' . \strval($this->level()),
+ $attributes,
+ $State->applyTo(Parsedown::line($this->text(), $State))
+ );
+ }
+ );
+ }
+}
diff --git a/src/Components/Inlines/Footnote.php b/src/Components/Inlines/Footnote.php
new file mode 100644
index 0000000..a4d112c
--- /dev/null
+++ b/src/Components/Inlines/Footnote.php
@@ -0,0 +1,101 @@
+title = $title;
+ $this->number = $number;
+ $this->count = $count;
+ $this->width = \strlen($title) + 3;
+ }
+
+ /**
+ * @param Excerpt $Excerpt
+ * @param State $State
+ * @return static|null
+ */
+ public static function build(Excerpt $Excerpt, State $State = null)
+ {
+ $State = $State ?: new State;
+
+ $FootnoteBook = $State->get(FootnoteBook::class);
+
+ if (\preg_match('/^\[\^(.+?)\]/', $Excerpt->text(), $matches)) {
+ $title = $matches[1];
+
+ $numbers = $FootnoteBook->mutatingGetNextInlineNumbers($title);
+
+ if (! isset($numbers)) {
+ return null;
+ }
+
+ ['num' => $num, 'count' => $count] = $numbers;
+
+ return new self($title, $num, $count);
+ }
+
+ return null;
+ }
+
+ public function number(): int
+ {
+ return $this->number;
+ }
+
+ public function count(): int
+ {
+ return $this->count;
+ }
+
+ public function title(): string
+ {
+ return $this->title;
+ }
+
+ /**
+ * @return Element
+ */
+ public function stateRenderable()
+ {
+ return new Element(
+ 'sup',
+ ['id' => 'fnref'.\strval($this->count()).':'.$this->title()],
+ [new Element(
+ 'a',
+ ['href' => '#fn:'.$this->title(), 'class' => 'footnote-ref'],
+ [new Text(\strval($this->number()))]
+ )]
+ );
+ }
+
+ /**
+ * @return Text
+ */
+ public function bestPlaintext()
+ {
+ return new Text($this->title());
+ }
+}
diff --git a/src/Components/Inlines/Image.php b/src/Components/Inlines/Image.php
new file mode 100644
index 0000000..1466444
--- /dev/null
+++ b/src/Components/Inlines/Image.php
@@ -0,0 +1,133 @@
+Link = $Link;
+ $this->width = $Link->width() + 1;
+ }
+
+ /**
+ * @param Excerpt $Excerpt
+ * @param State $State
+ * @return static|null
+ */
+ public static function build(Excerpt $Excerpt, State $State)
+ {
+ if (\substr($Excerpt->text(), 0, 1) !== '!') {
+ return null;
+ }
+
+ $Excerpt = $Excerpt->addingToOffset(1);
+
+ $Link = Link::build($Excerpt, $State);
+
+ if (! isset($Link)) {
+ return null;
+ }
+
+ return new self($Link);
+ }
+
+ /** @return string */
+ public function label()
+ {
+ return $this->Link->label();
+ }
+
+ /** @return string */
+ public function url()
+ {
+ return $this->Link->url();
+ }
+
+ /** @return string|null */
+ public function title()
+ {
+ return $this->Link->title();
+ }
+
+ /**
+ * @return Handler
+ */
+ public function stateRenderable()
+ {
+ return new Handler(
+ /** @return Element */
+ function (State $State) {
+ $attributes = [
+ 'src' => $this->url(),
+ 'alt' => \array_reduce(
+ Parsedown::inlines($this->label(), $State),
+ /**
+ * @param string $text
+ * @return string
+ */
+ function ($text, Inline $Inline) {
+ return (
+ $text
+ . $Inline->bestPlaintext()->getStringBacking()
+ );
+ },
+ ''
+ ),
+ ];
+
+ $title = $this->title();
+
+ if (isset($title)) {
+ $attributes['title'] = $title;
+ }
+
+ if ($State->get(SafeMode::class)->isEnabled()) {
+ $attributes['src'] = UrlSanitiser::filter($attributes['src']);
+ }
+
+ $id = $this->Link->id();
+
+ if (isset($id)) {
+ $attributes['id'] = $id;
+ }
+
+ $classes = $this->Link->classes();
+
+ if (!empty($classes)) {
+ $attributes['class'] = \implode(' ', $classes);
+ }
+
+ return Element::selfClosing('img', $attributes);
+ }
+ );
+ }
+
+ /**
+ * @return Text
+ */
+ public function bestPlaintext()
+ {
+ return new Text($this->label());
+ }
+}
diff --git a/src/Components/Inlines/Link.php b/src/Components/Inlines/Link.php
new file mode 100644
index 0000000..3d87e5e
--- /dev/null
+++ b/src/Components/Inlines/Link.php
@@ -0,0 +1,200 @@
+ */
+ private $classes;
+
+ /**
+ * @param string $label
+ * @param string $url
+ * @param string|null $title
+ * @param string|null $id
+ * @param list $classes
+ * @param int $width
+ */
+ private function __construct($label, $url, $title, $id, $classes, $width)
+ {
+ $this->label = $label;
+ $this->url = $url;
+ $this->title = $title;
+ $this->id = $id;
+ $this->classes = $classes;
+ $this->width = $width;
+ }
+
+ /**
+ * @param Excerpt $Excerpt
+ * @param State $State
+ * @return static|null
+ */
+ public static function build(Excerpt $Excerpt, State $State)
+ {
+ $Base = BaseLink::build($Excerpt, $State);
+
+ if (! isset($Base)) {
+ return null;
+ }
+
+ $id = null;
+ $classes = [];
+ $extraWidth = 0;
+
+ $remainder = $Excerpt->addingToOffset($Base->width())->text();
+
+ if (\preg_match('/^[ ]*{('.self::ATT_REGEX.'+)}/', $remainder, $matches)) {
+ $attributeString = $matches[1];
+
+ ['id' => $id, 'classes' => $classes] = self::parseAttributeData($attributeString);
+
+ $extraWidth = \strlen($matches[0]);
+ }
+
+ return new self(
+ $Base->label(),
+ $Base->url(),
+ $Base->title(),
+ $id,
+ $classes,
+ $Base->width() + $extraWidth
+ );
+ }
+
+ /** @return string */
+ public function label()
+ {
+ return $this->label;
+ }
+
+ /** @return string */
+ public function url()
+ {
+ return $this->url;
+ }
+
+ /** @return string|null */
+ public function title()
+ {
+ return $this->title;
+ }
+
+ public function id(): ?string
+ {
+ return $this->id;
+ }
+
+ /** @return list */
+ public function classes()
+ {
+ return $this->classes;
+ }
+
+ /**
+ * @return array{id:string|null,classes:list}
+ */
+ private static function parseAttributeData(string $attributeString)
+ {
+ $data = [
+ 'id' => null,
+ 'classes' => [],
+ ];
+
+ $attributes = \preg_split('/[ ]+/', $attributeString, - 1, \PREG_SPLIT_NO_EMPTY);
+
+ foreach ($attributes as $attribute) {
+ if ($attribute[0] === '#') {
+ $data['id'] = \substr($attribute, 1);
+ } else { # "."
+ $data['classes'][]= \substr($attribute, 1);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return Handler
+ */
+ public function stateRenderable()
+ {
+ return new Handler(
+ /** @return Element */
+ function (State $State) {
+ $attributes = ['href' => $this->url()];
+
+ $title = $this->title();
+
+ if (isset($title)) {
+ $attributes['title'] = $title;
+ }
+
+ if ($State->get(SafeMode::class)->isEnabled()) {
+ $attributes['href'] = UrlSanitiser::filter($attributes['href']);
+ }
+
+ $id = $this->id();
+
+ if (isset($id)) {
+ $attributes['id'] = $id;
+ }
+
+ $classes = $this->classes();
+
+ if (!empty($classes)) {
+ $attributes['class'] = \implode(' ', $classes);
+ }
+
+ $State = $State->setting(
+ $State->get(InlineTypes::class)->removing([Url::class])
+ );
+
+ return new Element(
+ 'a',
+ $attributes,
+ $State->applyTo(Parsedown::line($this->label(), $State))
+ );
+ }
+ );
+ }
+
+ /**
+ * @return Text
+ */
+ public function bestPlaintext()
+ {
+ return new Text($this->label());
+ }
+}
diff --git a/src/Configurables/AbbreviationBook.php b/src/Configurables/AbbreviationBook.php
index 08f4f8f..1c123e0 100644
--- a/src/Configurables/AbbreviationBook.php
+++ b/src/Configurables/AbbreviationBook.php
@@ -33,6 +33,13 @@ public function lookup(string $abbreviation): ?string
return $this->book[$abbreviation] ?? null;
}
+ /** @return array */
+ public function all()
+ {
+ return $this->book;
+ }
+
+ /** @return self */
public function isolatedCopy(): self
{
return new self($this->book);
diff --git a/src/Configurables/FootnoteBook.php b/src/Configurables/FootnoteBook.php
new file mode 100644
index 0000000..2ab429b
--- /dev/null
+++ b/src/Configurables/FootnoteBook.php
@@ -0,0 +1,84 @@
+ */
+ private $Footnotes;
+
+ /** @var array */
+ private $inlineRecord;
+
+ /** @var int */
+ private $inlineCount;
+
+ /**
+ * @param array $Footnotes
+ * @param array $inlineRecord
+ */
+ public function __construct(array $Footnotes = [], array $inlineRecord = [])
+ {
+ $this->Footnotes = $Footnotes;
+ $this->inlineRecord = $inlineRecord;
+
+ $this->inlineCount = \array_reduce(
+ $inlineRecord,
+ /** @param array{count:int,num:int} $record */
+ function (int $sum, $record): int {
+ return $sum + $record['count'];
+ },
+ 0
+ );
+ }
+
+ /** @return self */
+ public static function initial()
+ {
+ return new self;
+ }
+
+ public function mutatingSetBlock(Footnote $Footnote): void
+ {
+ $this->Footnotes[$Footnote->title()] = $Footnote;
+ }
+
+ /** @return array{num:int,count:int}|null */
+ public function mutatingGetNextInlineNumbers(string $title): ?array
+ {
+ if (! isset($this->Footnotes[$title])) {
+ return null;
+ }
+
+ if (! isset($this->inlineRecord[$title])) {
+ $this->inlineRecord[$title] = [
+ 'count' => 0,
+ 'num' => ++$this->inlineCount,
+ ];
+ }
+
+ ++$this->inlineRecord[$title]['count'];
+
+ return $this->inlineRecord[$title];
+ }
+
+ /** @return array */
+ public function all()
+ {
+ return $this->Footnotes;
+ }
+
+ public function inlineRecord(string $title): int
+ {
+ return $this->inlineRecord[$title]['count'] ?? 0;
+ }
+
+ /** @return self */
+ public function isolatedCopy(): self
+ {
+ return new self($this->Footnotes, $this->inlineRecord);
+ }
+}
diff --git a/src/ParsedownExtra.php b/src/ParsedownExtra.php
index 228815c..91585c8 100644
--- a/src/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -2,400 +2,167 @@
namespace Erusev\ParsedownExtra;
-final class ParsedownExtra
+use Erusev\Parsedown\Components\Blocks\Header as CoreHeader;
+use Erusev\Parsedown\Components\Blocks\SetextHeader as CoreSetextHeader;
+use Erusev\Parsedown\Components\Inlines\Image as CoreImage;
+use Erusev\Parsedown\Components\Inlines\Link as CoreLink;
+use Erusev\Parsedown\Configurables\BlockTypes;
+use Erusev\Parsedown\Configurables\InlineTypes;
+use Erusev\Parsedown\Configurables\RenderStack;
+use Erusev\Parsedown\Html\Renderable;
+use Erusev\Parsedown\Html\Renderables\Container;
+use Erusev\Parsedown\Html\Renderables\Element;
+use Erusev\Parsedown\Html\Renderables\RawHtml;
+use Erusev\Parsedown\Html\Renderables\Text;
+use Erusev\Parsedown\Html\TransformableRenderable;
+use Erusev\Parsedown\Parsedown;
+use Erusev\Parsedown\State;
+use Erusev\Parsedown\StateBearer;
+use Erusev\ParsedownExtra\Components\Blocks\Abbreviation;
+use Erusev\ParsedownExtra\Components\Blocks\DefinitionList;
+use Erusev\ParsedownExtra\Components\Blocks\Footnote;
+use Erusev\ParsedownExtra\Components\Blocks\Header;
+use Erusev\ParsedownExtra\Components\Blocks\SetextHeader;
+use Erusev\ParsedownExtra\Components\Inlines\Footnote as InlineFootnote;
+use Erusev\ParsedownExtra\Components\Inlines\Image;
+use Erusev\ParsedownExtra\Components\Inlines\Link;
+use Erusev\ParsedownExtra\Configurables\AbbreviationBook;
+use Erusev\ParsedownExtra\Configurables\FootnoteBook;
+
+final class ParsedownExtra implements StateBearer
{
- function __construct()
- {
- $this->BlockTypes[':'] []= 'DefinitionList';
- $this->BlockTypes['*'] []= 'Abbreviation';
-
- # identify footnote definitions before reference definitions
- array_unshift($this->BlockTypes['['], 'Footnote');
-
- # identify footnote markers before before links
- array_unshift($this->InlineTypes['['], 'FootnoteMarker');
- }
-
- #
- # ~
-
- function text($text)
- {
- $markup = parent::text($text);
-
- # merge consecutive dl elements
-
- $markup = preg_replace('/<\/dl>\s+\s+/', '', $markup);
-
- # add footnotes
-
- if (isset($this->DefinitionData['Footnote']))
- {
- $Element = $this->buildFootnoteElement();
-
- $markup .= "\n" . $this->element($Element);
- }
-
- return $markup;
- }
-
- #
- # Footnote
-
- protected function blockFootnote($Line)
- {
- if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
- {
- $Block = array(
- 'label' => $matches[1],
- 'text' => $matches[2],
- 'hidden' => true,
- );
-
- return $Block;
- }
- }
-
- protected function blockFootnoteContinue($Line, $Block)
- {
- if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
- {
- return;
- }
-
- if (isset($Block['interrupted']))
- {
- if ($Line['indent'] >= 4)
- {
- $Block['text'] .= "\n\n" . $Line['text'];
-
- return $Block;
- }
- }
- else
- {
- $Block['text'] .= "\n" . $Line['text'];
-
- return $Block;
- }
- }
-
- protected function blockFootnoteComplete($Block)
- {
- $this->DefinitionData['Footnote'][$Block['label']] = array(
- 'text' => $Block['text'],
- 'count' => null,
- 'number' => null,
- );
-
- return $Block;
- }
-
- #
- # Header
-
- protected function blockHeader($Line)
- {
- $Block = parent::blockHeader($Line);
-
- if (! isset($Block)) {
- return null;
- }
-
- if (preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
- {
- $attributeString = $matches[1][0];
-
- $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
-
- $Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
- }
-
- return $Block;
- }
-
- #
- # Markup
-
- protected function blockMarkupComplete($Block)
- {
- if ( ! isset($Block['void']))
- {
- $Block['markup'] = $this->processTag($Block['markup']);
- }
-
- return $Block;
- }
-
- #
- # Setext
-
- protected function blockSetextHeader($Line, array $Block = null)
- {
- $Block = parent::blockSetextHeader($Line, $Block);
-
- if (! isset($Block)) {
- return null;
- }
-
- if (preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
- {
- $attributeString = $matches[1][0];
-
- $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
-
- $Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
- }
-
- return $Block;
- }
-
- #
- # Inline Elements
- #
-
- #
- # Footnote Marker
-
- protected function inlineFootnoteMarker($Excerpt)
- {
- if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
- {
- $name = $matches[1];
-
- if ( ! isset($this->DefinitionData['Footnote'][$name]))
- {
- return;
- }
-
- $this->DefinitionData['Footnote'][$name]['count'] ++;
+ /** @var State */
+ private $State;
- if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
- {
- $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # ยป &
- }
-
- $Element = array(
- 'name' => 'sup',
- 'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
- 'handler' => 'element',
- 'text' => array(
- 'name' => 'a',
- 'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
- 'text' => $this->DefinitionData['Footnote'][$name]['number'],
- ),
- );
-
- return array(
- 'extent' => strlen($matches[0]),
- 'element' => $Element,
- );
- }
- }
-
- private $footnoteCount = 0;
-
- #
- # Link
-
- protected function inlineLink($Excerpt)
+ public function __construct(StateBearer $StateBearer = null)
{
- $Link = parent::inlineLink($Excerpt);
-
- if (! isset($Link)) {
- return null;
- }
-
- $remainder = substr($Excerpt['text'], $Link['extent']);
-
- if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
- {
- $Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
-
- $Link['extent'] += strlen($matches[0]);
- }
-
- return $Link;
+ $State = ($StateBearer ?? new State)->state();
+
+ $BlockTypes = $State->get(BlockTypes::class)
+ ->addingMarkedLowPrecedence(':', [DefinitionList::class])
+ ->addingMarkedLowPrecedence('*', [Abbreviation::class])
+ ->addingMarkedHighPrecedence('[', [Footnote::class])
+ ->replacing(CoreHeader::class, Header::class)
+ ->replacing(CoreSetextHeader::class, SetextHeader::class)
+ ;
+
+ $InlineTypes = $State->get(InlineTypes::class)
+ ->addingHighPrecedence('[', [InlineFootnote::class])
+ ->replacing(CoreLink::class, Link::class)
+ ->replacing(CoreImage::class, Image::class)
+ ;
+
+ $RenderStack = $State->get(RenderStack::class)
+ ->push(self::renderFootnotes())
+ ->push(self::expandAbbreviations())
+ ;
+
+ $this->State = $State
+ ->setting($BlockTypes)
+ ->setting($InlineTypes)
+ ->setting($RenderStack)
+ ;
}
- #
- # ~
- #
-
- protected function unmarkedText($text)
+ public function state(): State
{
- $text = parent::unmarkedText($text);
-
- if (isset($this->DefinitionData['Abbreviation']))
- {
- foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
- {
- $pattern = '/\b'.preg_quote($abbreviation, '/').'\b/';
-
- $text = preg_replace($pattern, ''.$abbreviation.'', $text);
- }
- }
-
- return $text;
+ return $this->State;
}
- protected function buildFootnoteElement()
+ /** @return \Closure(Renderable[],State):Renderable[] */
+ public static function expandAbbreviations()
{
- $Element = array(
- 'name' => 'div',
- 'attributes' => array('class' => 'footnotes'),
- 'handler' => 'elements',
- 'text' => array(
- array(
- 'name' => 'hr',
- ),
- array(
- 'name' => 'ol',
- 'handler' => 'elements',
- 'text' => array(),
- ),
- ),
- );
-
- uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
-
- foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
- {
- if ( ! isset($DefinitionData['number']))
- {
- continue;
+ /**
+ * @param Renderable[] $Rs
+ * @param State $S
+ * @return Renderable[] $Rs
+ */
+ return function (array $Rs, State $S): array {
+ $abbrvs = $S->get(AbbreviationBook::class)->all();
+
+ if (empty($abbrvs)) {
+ return $Rs;
}
- $text = $DefinitionData['text'];
-
- $text = parent::text($text);
-
- $numbers = range(1, $DefinitionData['count']);
-
- $backLinksMarkup = '';
-
- foreach ($numbers as $number)
- {
- $backLinksMarkup .= ' ';
- }
-
- $backLinksMarkup = substr($backLinksMarkup, 1);
-
- if (substr($text, - 4) === '
')
- {
- $backLinksMarkup = ' '.$backLinksMarkup;
-
- $text = substr_replace($text, $backLinksMarkup.'', - 4);
- }
- else
- {
- $text .= "\n".''.$backLinksMarkup.'
';
- }
-
- $Element['text'][1]['text'] []= array(
- 'name' => 'li',
- 'attributes' => array('id' => 'fn:'.$definitionId),
- 'rawHtml' => "\n".$text."\n",
+ return \array_map(
+ function (Renderable $R) use ($abbrvs): Renderable {
+ if ($R instanceof TransformableRenderable) {
+ foreach ($abbrvs as $abbrv => $meaning) {
+ $R = $R->replacingAll(
+ $abbrv,
+ new Element('abbr', ['title' => $meaning], [new Text($abbrv)])
+ );
+ }
+ }
+
+ return $R;
+ },
+ $Rs
);
- }
-
- return $Element;
+ };
}
- # ~
-
- protected function parseAttributeData($attributeString)
+ /** @return \Closure(Renderable[],State):Renderable[] */
+ public static function renderFootnotes()
{
- $Data = array();
-
- $attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
-
- foreach ($attributes as $attribute)
- {
- if ($attribute[0] === '#')
- {
- $Data['id'] = substr($attribute, 1);
- }
- else # "."
- {
- $classes []= substr($attribute, 1);
- }
- }
-
- if (isset($classes))
- {
- $Data['class'] = implode(' ', $classes);
- }
-
- return $Data;
- }
-
- # ~
-
- protected function processTag($elementMarkup) # recursive
- {
- # http://stackoverflow.com/q/1148928/200145
- libxml_use_internal_errors(true);
-
- $DOMDocument = new DOMDocument;
-
- # http://stackoverflow.com/q/11309194/200145
- $elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
-
- # http://stackoverflow.com/q/4879946/200145
- $DOMDocument->loadHTML($elementMarkup);
- $DOMDocument->removeChild($DOMDocument->doctype);
- $DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
-
- $elementText = '';
-
- if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
- {
- foreach ($DOMDocument->documentElement->childNodes as $Node)
- {
- $elementText .= $DOMDocument->saveHTML($Node);
- }
-
- $DOMDocument->documentElement->removeAttribute('markdown');
-
- $elementText = "\n".$this->text($elementText)."\n";
- }
- else
- {
- foreach ($DOMDocument->documentElement->childNodes as $Node)
- {
- $nodeMarkup = $DOMDocument->saveHTML($Node);
-
- if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
- {
- $elementText .= $this->processTag($nodeMarkup);
- }
- else
- {
- $elementText .= $nodeMarkup;
- }
+ /**
+ * @param Renderable[] $Rs
+ * @param State $S
+ * @return Renderable[] $Rs
+ */
+ return function (array $Rs, State $S): array {
+ $FB = $S->get(FootnoteBook::class);
+
+ if (empty($FB->all())) {
+ return $Rs;
}
- }
-
- # because we don't want for markup to get encoded
- $DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
- $markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
- $markup = str_replace('placeholder\x1A', $elementText, $markup);
-
- return $markup;
+ return \array_merge($Rs, [new Element(
+ 'div',
+ ['class' => 'footnotes'],
+ [
+ Element::selfClosing('hr', []),
+ new Element('ol', [], \array_map(
+ function (Footnote $F) use ($FB, $S): Element {
+ $BackLink = [
+ new RawHtml(' '),
+ ...\array_map(
+ function (int $n) use ($F, $FB): Container {
+ return new Container([
+ new Element(
+ 'a',
+ [
+ 'href' => '#fnref'.\strval($n).':'.$F->title(),
+ 'rev' => 'footnote',
+ 'class' => 'footnote-backref'
+ ],
+ [new RawHtml('↩')]
+ ),
+ ...($n < $FB->inlineRecord($F->title()) ? [new Text(' ')] : [])
+ ]);
+ },
+ ($count = $FB->inlineRecord($F->title())) > 1 ? \range(1, $count) : [1]
+ )
+ ];
+
+ [$StateRenderables, $S] = Parsedown::lines($F->lines(), $S);
+
+ $InnerRender = $S->applyTo($StateRenderables);
+
+ $lastItem = $InnerRender[\count($InnerRender) -1];
+
+ if ($lastItem instanceof Element && $lastItem->name() === 'p' && $contents = $lastItem->contents()) {
+ $InnerRender[\count($InnerRender) -1] = new Element('p', [], \array_merge($contents, $BackLink));
+ } else {
+ $InnerRender[] = new Element('p', [], [...$BackLink]);
+ }
+
+ return new Element('li', ['id' => 'fn:'.$F->title()], $InnerRender);
+ },
+ $FB->all()
+ ))
+ ]
+ )]);
+ };
}
-
- # ~
-
- protected function sortFootnotes($A, $B) # callback
- {
- return $A['number'] - $B['number'];
- }
-
- #
- # Fields
- #
-
- protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
}
From a745cb6b0ac6f9ac796c01a3684c43e5e0e4ab1b Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 22:45:31 +0100
Subject: [PATCH 17/28] GitHub actions
---
.github/workflows/ci.yml | 98 ++++++++++++++++++++++++++++++++++++++++
.travis.yml | 48 --------------------
2 files changed, 98 insertions(+), 48 deletions(-)
create mode 100644 .github/workflows/ci.yml
delete mode 100644 .travis.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..82a81aa
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,98 @@
+name: ParsedownExtra
+on: [push, pull_request]
+jobs:
+ units:
+ name: Unit Tests
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ php: [8.0, 7.4, 7.3, 7.2, 7.1]
+
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: composer:v2
+
+ - uses: actions/checkout@v2
+ - name: Run Tests
+ run: |
+ composer remove infection/infection --no-update --dev
+ composer remove roave/infection-static-analysis-plugin --no-update --dev
+ composer update --prefer-dist --no-interaction --no-progress
+ composer test-units
+
+ mutations:
+ name: Mutation Tests
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ php: [8.0]
+
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: composer:v2
+
+ - uses: actions/checkout@v2
+ - name: Run Tests
+ run: |
+ composer update --prefer-dist --no-interaction --no-progress
+ vendor/bin/roave-infection-static-analysis-plugin --show-mutations --threads=4 --min-msi=80 --min-covered-msi=80
+
+ static-analysis:
+ name: Code Format and Static Analysis
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ php: [8.0]
+
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: composer:v2
+
+ - uses: actions/checkout@v2
+ - name: Run Tests
+ run: |
+ composer install --prefer-dist --no-interaction --no-progress
+ composer test-static -- --shepherd
+ composer test-formatting
+ composer test-dead-code
+
+ commonmark:
+ name: CommonMark
+ continue-on-error: true
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ php: [8.0]
+
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: composer:v2
+
+ - uses: actions/checkout@v2
+ - name: Install
+ run: composer install --prefer-dist --no-interaction --no-progress
+
+ - name: CommonMark Strict
+ continue-on-error: true
+ run: composer test-commonmark
+
+ - name: CommonMark Weak
+ continue-on-error: true
+ run: composer test-commonmark-weak
+
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 3ae8263..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-language: php
-
-dist: trusty
-sudo: false
-
-stages:
- - Code Format and Static Analysis
- - Units
-
-cache:
- directories:
- - $HOME/.composer/cache
-
-jobs:
- fast_finish: true
- allow_failures:
- - env: ALLOW_FAILURE
-
- include:
- - stage: Code Format and Static Analysis
- php: 7.4
- install: composer install --prefer-dist --no-interaction --no-progress
- script:
- - '[ -z "$TRAVIS_TAG" ] || [ "$TRAVIS_TAG" == "$(php -r "require(\"vendor/autoload.php\"); echo Erusev\Parsedown\Parsedown::version;")" ]'
- - composer test-static -- --shepherd
- - composer test-formatting
- - composer test-dead-code
-
- - &MUTATION_AND_UNIT_TEST
- stage: Units
- php: 7.1
- install:
- - composer update --prefer-dist --no-interaction --no-progress
- script:
- - composer test-units
- - vendor/bin/infection --show-mutations --threads=4 --min-msi=90 --min-covered-msi=90
-
- - <<: *MUTATION_AND_UNIT_TEST
- php: 7.2
- - <<: *MUTATION_AND_UNIT_TEST
- php: 7.3
- - <<: *MUTATION_AND_UNIT_TEST
- php: 7.4
-
- - <<: *MUTATION_AND_UNIT_TEST
- php: nightly
- env: ALLOW_FAILURE
- script: composer test-units
From 9d8fb6fad3532f3c9f1c65df5f7291c7dd2ea242 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 22:51:45 +0100
Subject: [PATCH 18/28] Remove use of spread operator
---
src/ParsedownExtra.php | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/src/ParsedownExtra.php b/src/ParsedownExtra.php
index 91585c8..d984422 100644
--- a/src/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -124,11 +124,12 @@ public static function renderFootnotes()
Element::selfClosing('hr', []),
new Element('ol', [], \array_map(
function (Footnote $F) use ($FB, $S): Element {
- $BackLink = [
- new RawHtml(' '),
- ...\array_map(
+ $BackLink = \array_merge(
+ [new RawHtml(' ')],
+ \array_map(
function (int $n) use ($F, $FB): Container {
- return new Container([
+ return new Container(\array_merge(
+ [
new Element(
'a',
[
@@ -137,13 +138,13 @@ function (int $n) use ($F, $FB): Container {
'class' => 'footnote-backref'
],
[new RawHtml('↩')]
- ),
- ...($n < $FB->inlineRecord($F->title()) ? [new Text(' ')] : [])
- ]);
+ )],
+ ($n < $FB->inlineRecord($F->title()) ? [new Text(' ')] : [])
+ ));
},
($count = $FB->inlineRecord($F->title())) > 1 ? \range(1, $count) : [1]
)
- ];
+ );
[$StateRenderables, $S] = Parsedown::lines($F->lines(), $S);
@@ -154,7 +155,7 @@ function (int $n) use ($F, $FB): Container {
if ($lastItem instanceof Element && $lastItem->name() === 'p' && $contents = $lastItem->contents()) {
$InnerRender[\count($InnerRender) -1] = new Element('p', [], \array_merge($contents, $BackLink));
} else {
- $InnerRender[] = new Element('p', [], [...$BackLink]);
+ $InnerRender[] = new Element('p', [], $BackLink);
}
return new Element('li', ['id' => 'fn:'.$F->title()], $InnerRender);
From a6790f958578323dbffcad8962ce1b079116dd61 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 22:54:51 +0100
Subject: [PATCH 19/28] Add infection config for mutation testing
---
infection.json.dist | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 infection.json.dist
diff --git a/infection.json.dist b/infection.json.dist
new file mode 100644
index 0000000..43325e8
--- /dev/null
+++ b/infection.json.dist
@@ -0,0 +1,18 @@
+{
+ "timeout": 10,
+ "source": {
+ "directories": [
+ "src"
+ ]
+ },
+ "logs": {
+ "text": "infection.log"
+ },
+ "mutators": {
+ "@default": true,
+ "@cast": false,
+ "This": false,
+ "FunctionCall": false,
+ "NewObject": false
+ }
+}
From a51ebc0e463c5a874106e9855bb6b457b01efa5d Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Fri, 15 Oct 2021 23:24:06 +0100
Subject: [PATCH 20/28] Implement new StateBearer requirements
---
src/ParsedownExtra.php | 6 ++++++
tests/ParsedownExtraTest.php | 2 +-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/ParsedownExtra.php b/src/ParsedownExtra.php
index d984422..334a9d1 100644
--- a/src/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -69,6 +69,12 @@ public function state(): State
return $this->State;
}
+ /** @return self */
+ public static function from(StateBearer $StateBearer)
+ {
+ return new self($StateBearer);
+ }
+
/** @return \Closure(Renderable[],State):Renderable[] */
public static function expandAbbreviations()
{
diff --git a/tests/ParsedownExtraTest.php b/tests/ParsedownExtraTest.php
index 9df57d2..9baedf7 100644
--- a/tests/ParsedownExtraTest.php
+++ b/tests/ParsedownExtraTest.php
@@ -17,6 +17,6 @@ protected function initDirs()
protected function initState(string $testName): StateBearer
{
- return new ParsedownExtra(parent::initState($testName));
+ return ParsedownExtra::from(parent::initState($testName));
}
}
From d9c88132afb3a63f452c3b1a9135089f956a8700 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sat, 16 Oct 2021 00:06:46 +0100
Subject: [PATCH 21/28] Features are all StateBearers
---
src/Features/Abbreviations.php | 82 +++++++++++++++
src/Features/CustomAttributes.php | 161 ++++++++++++++++++++++++++++++
src/Features/Definitions.php | 39 ++++++++
src/Features/Footnotes.php | 123 +++++++++++++++++++++++
src/ParsedownExtra.php | 53 +++-------
5 files changed, 418 insertions(+), 40 deletions(-)
create mode 100644 src/Features/Abbreviations.php
create mode 100644 src/Features/CustomAttributes.php
create mode 100644 src/Features/Definitions.php
create mode 100644 src/Features/Footnotes.php
diff --git a/src/Features/Abbreviations.php b/src/Features/Abbreviations.php
new file mode 100644
index 0000000..b92a417
--- /dev/null
+++ b/src/Features/Abbreviations.php
@@ -0,0 +1,82 @@
+state();
+
+ $BlockTypes = $State->get(BlockTypes::class)
+ ->addingMarkedLowPrecedence('*', [Abbreviation::class])
+ ;
+
+ $RenderStack = $State->get(RenderStack::class)
+ ->push(self::expandAbbreviations())
+ ;
+
+ $this->State = $State
+ ->setting($BlockTypes)
+ ->setting($RenderStack)
+ ;
+ }
+
+ public function state(): State
+ {
+ return $this->State;
+ }
+
+ /** @return self */
+ public static function from(StateBearer $StateBearer)
+ {
+ return new self($StateBearer);
+ }
+
+ /** @return \Closure(Renderable[],State):Renderable[] */
+ public static function expandAbbreviations()
+ {
+ /**
+ * @param Renderable[] $Rs
+ * @param State $S
+ * @return Renderable[] $Rs
+ */
+ return function (array $Rs, State $S): array {
+ $abbrvs = $S->get(AbbreviationBook::class)->all();
+
+ if (empty($abbrvs)) {
+ return $Rs;
+ }
+
+ return \array_map(
+ function (Renderable $R) use ($abbrvs): Renderable {
+ if ($R instanceof TransformableRenderable) {
+ foreach ($abbrvs as $abbrv => $meaning) {
+ $R = $R->replacingAll(
+ $abbrv,
+ new Element('abbr', ['title' => $meaning], [new Text($abbrv)])
+ );
+ }
+ }
+
+ return $R;
+ },
+ $Rs
+ );
+ };
+ }
+}
diff --git a/src/Features/CustomAttributes.php b/src/Features/CustomAttributes.php
new file mode 100644
index 0000000..32f6cce
--- /dev/null
+++ b/src/Features/CustomAttributes.php
@@ -0,0 +1,161 @@
+state();
+
+ $BlockTypes = $State->get(BlockTypes::class)
+ ->replacing(CoreHeader::class, Header::class)
+ ->replacing(CoreSetextHeader::class, SetextHeader::class)
+ ;
+
+ $InlineTypes = $State->get(InlineTypes::class)
+ ->replacing(CoreLink::class, Link::class)
+ ->replacing(CoreImage::class, Image::class)
+ ;
+
+ $this->State = $State
+ ->setting($BlockTypes)
+ ->setting($InlineTypes)
+ ;
+ }
+
+ public function state(): State
+ {
+ return $this->State;
+ }
+
+ /** @return self */
+ public static function from(StateBearer $StateBearer)
+ {
+ return new self($StateBearer);
+ }
+
+ /** @return \Closure(Renderable[],State):Renderable[] */
+ public static function expandAbbreviations()
+ {
+ /**
+ * @param Renderable[] $Rs
+ * @param State $S
+ * @return Renderable[] $Rs
+ */
+ return function (array $Rs, State $S): array {
+ $abbrvs = $S->get(AbbreviationBook::class)->all();
+
+ if (empty($abbrvs)) {
+ return $Rs;
+ }
+
+ return \array_map(
+ function (Renderable $R) use ($abbrvs): Renderable {
+ if ($R instanceof TransformableRenderable) {
+ foreach ($abbrvs as $abbrv => $meaning) {
+ $R = $R->replacingAll(
+ $abbrv,
+ new Element('abbr', ['title' => $meaning], [new Text($abbrv)])
+ );
+ }
+ }
+
+ return $R;
+ },
+ $Rs
+ );
+ };
+ }
+
+ /** @return \Closure(Renderable[],State):Renderable[] */
+ public static function renderFootnotes()
+ {
+ /**
+ * @param Renderable[] $Rs
+ * @param State $S
+ * @return Renderable[] $Rs
+ */
+ return function (array $Rs, State $S): array {
+ $FB = $S->get(FootnoteBook::class);
+
+ if (empty($FB->all())) {
+ return $Rs;
+ }
+
+ return \array_merge($Rs, [new Element(
+ 'div',
+ ['class' => 'footnotes'],
+ [
+ Element::selfClosing('hr', []),
+ new Element('ol', [], \array_map(
+ function (Footnote $F) use ($FB, $S): Element {
+ $BackLink = \array_merge(
+ [new RawHtml(' ')],
+ \array_map(
+ function (int $n) use ($F, $FB): Container {
+ return new Container(\array_merge(
+ [
+ new Element(
+ 'a',
+ [
+ 'href' => '#fnref'.\strval($n).':'.$F->title(),
+ 'rev' => 'footnote',
+ 'class' => 'footnote-backref'
+ ],
+ [new RawHtml('↩')]
+ )],
+ ($n < $FB->inlineRecord($F->title()) ? [new Text(' ')] : [])
+ ));
+ },
+ ($count = $FB->inlineRecord($F->title())) > 1 ? \range(1, $count) : [1]
+ )
+ );
+
+ [$StateRenderables, $S] = Parsedown::lines($F->lines(), $S);
+
+ $InnerRender = $S->applyTo($StateRenderables);
+
+ $lastItem = $InnerRender[\count($InnerRender) -1];
+
+ if ($lastItem instanceof Element && $lastItem->name() === 'p' && $contents = $lastItem->contents()) {
+ $InnerRender[\count($InnerRender) -1] = new Element('p', [], \array_merge($contents, $BackLink));
+ } else {
+ $InnerRender[] = new Element('p', [], $BackLink);
+ }
+
+ return new Element('li', ['id' => 'fn:'.$F->title()], $InnerRender);
+ },
+ $FB->all()
+ ))
+ ]
+ )]);
+ };
+ }
+}
diff --git a/src/Features/Definitions.php b/src/Features/Definitions.php
new file mode 100644
index 0000000..ef249fa
--- /dev/null
+++ b/src/Features/Definitions.php
@@ -0,0 +1,39 @@
+state();
+
+ $BlockTypes = $State->get(BlockTypes::class)
+ ->addingMarkedLowPrecedence(':', [DefinitionList::class])
+
+ ;
+
+ $this->State = $State
+ ->setting($BlockTypes)
+ ;
+ }
+
+ public function state(): State
+ {
+ return $this->State;
+ }
+
+ /** @return self */
+ public static function from(StateBearer $StateBearer)
+ {
+ return new self($StateBearer);
+ }
+}
diff --git a/src/Features/Footnotes.php b/src/Features/Footnotes.php
new file mode 100644
index 0000000..8d3b1d0
--- /dev/null
+++ b/src/Features/Footnotes.php
@@ -0,0 +1,123 @@
+state();
+
+ $BlockTypes = $State->get(BlockTypes::class)
+ ->addingMarkedHighPrecedence('[', [Footnote::class])
+ ;
+
+ $InlineTypes = $State->get(InlineTypes::class)
+ ->addingHighPrecedence('[', [InlineFootnote::class])
+ ;
+
+ $RenderStack = $State->get(RenderStack::class)
+ ->push(self::renderFootnotes())
+ ;
+
+ $this->State = $State
+ ->setting($BlockTypes)
+ ->setting($InlineTypes)
+ ->setting($RenderStack)
+ ;
+ }
+
+ public function state(): State
+ {
+ return $this->State;
+ }
+
+ /** @return self */
+ public static function from(StateBearer $StateBearer)
+ {
+ return new self($StateBearer);
+ }
+
+ /** @return \Closure(Renderable[],State):Renderable[] */
+ public static function renderFootnotes()
+ {
+ /**
+ * @param Renderable[] $Rs
+ * @param State $S
+ * @return Renderable[] $Rs
+ */
+ return function (array $Rs, State $S): array {
+ $FB = $S->get(FootnoteBook::class);
+
+ if (empty($FB->all())) {
+ return $Rs;
+ }
+
+ return \array_merge($Rs, [new Element(
+ 'div',
+ ['class' => 'footnotes'],
+ [
+ Element::selfClosing('hr', []),
+ new Element('ol', [], \array_map(
+ function (Footnote $F) use ($FB, $S): Element {
+ $BackLink = \array_merge(
+ [new RawHtml(' ')],
+ \array_map(
+ function (int $n) use ($F, $FB): Container {
+ return new Container(\array_merge(
+ [
+ new Element(
+ 'a',
+ [
+ 'href' => '#fnref'.\strval($n).':'.$F->title(),
+ 'rev' => 'footnote',
+ 'class' => 'footnote-backref'
+ ],
+ [new RawHtml('↩')]
+ )],
+ ($n < $FB->inlineRecord($F->title()) ? [new Text(' ')] : [])
+ ));
+ },
+ ($count = $FB->inlineRecord($F->title())) > 1 ? \range(1, $count) : [1]
+ )
+ );
+
+ [$StateRenderables, $S] = Parsedown::lines($F->lines(), $S);
+
+ $InnerRender = $S->applyTo($StateRenderables);
+
+ $lastItem = $InnerRender[\count($InnerRender) -1];
+
+ if ($lastItem instanceof Element && $lastItem->name() === 'p' && $contents = $lastItem->contents()) {
+ $InnerRender[\count($InnerRender) -1] = new Element('p', [], \array_merge($contents, $BackLink));
+ } else {
+ $InnerRender[] = new Element('p', [], $BackLink);
+ }
+
+ return new Element('li', ['id' => 'fn:'.$F->title()], $InnerRender);
+ },
+ $FB->all()
+ ))
+ ]
+ )]);
+ };
+ }
+}
diff --git a/src/ParsedownExtra.php b/src/ParsedownExtra.php
index 334a9d1..aaf2d62 100644
--- a/src/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -2,13 +2,6 @@
namespace Erusev\ParsedownExtra;
-use Erusev\Parsedown\Components\Blocks\Header as CoreHeader;
-use Erusev\Parsedown\Components\Blocks\SetextHeader as CoreSetextHeader;
-use Erusev\Parsedown\Components\Inlines\Image as CoreImage;
-use Erusev\Parsedown\Components\Inlines\Link as CoreLink;
-use Erusev\Parsedown\Configurables\BlockTypes;
-use Erusev\Parsedown\Configurables\InlineTypes;
-use Erusev\Parsedown\Configurables\RenderStack;
use Erusev\Parsedown\Html\Renderable;
use Erusev\Parsedown\Html\Renderables\Container;
use Erusev\Parsedown\Html\Renderables\Element;
@@ -18,16 +11,13 @@
use Erusev\Parsedown\Parsedown;
use Erusev\Parsedown\State;
use Erusev\Parsedown\StateBearer;
-use Erusev\ParsedownExtra\Components\Blocks\Abbreviation;
-use Erusev\ParsedownExtra\Components\Blocks\DefinitionList;
use Erusev\ParsedownExtra\Components\Blocks\Footnote;
-use Erusev\ParsedownExtra\Components\Blocks\Header;
-use Erusev\ParsedownExtra\Components\Blocks\SetextHeader;
-use Erusev\ParsedownExtra\Components\Inlines\Footnote as InlineFootnote;
-use Erusev\ParsedownExtra\Components\Inlines\Image;
-use Erusev\ParsedownExtra\Components\Inlines\Link;
use Erusev\ParsedownExtra\Configurables\AbbreviationBook;
use Erusev\ParsedownExtra\Configurables\FootnoteBook;
+use Erusev\ParsedownExtra\Features\Abbreviations;
+use Erusev\ParsedownExtra\Features\CustomAttributes;
+use Erusev\ParsedownExtra\Features\Definitions;
+use Erusev\ParsedownExtra\Features\Footnotes;
final class ParsedownExtra implements StateBearer
{
@@ -36,32 +26,15 @@ final class ParsedownExtra implements StateBearer
public function __construct(StateBearer $StateBearer = null)
{
- $State = ($StateBearer ?? new State)->state();
-
- $BlockTypes = $State->get(BlockTypes::class)
- ->addingMarkedLowPrecedence(':', [DefinitionList::class])
- ->addingMarkedLowPrecedence('*', [Abbreviation::class])
- ->addingMarkedHighPrecedence('[', [Footnote::class])
- ->replacing(CoreHeader::class, Header::class)
- ->replacing(CoreSetextHeader::class, SetextHeader::class)
- ;
-
- $InlineTypes = $State->get(InlineTypes::class)
- ->addingHighPrecedence('[', [InlineFootnote::class])
- ->replacing(CoreLink::class, Link::class)
- ->replacing(CoreImage::class, Image::class)
- ;
-
- $RenderStack = $State->get(RenderStack::class)
- ->push(self::renderFootnotes())
- ->push(self::expandAbbreviations())
- ;
-
- $this->State = $State
- ->setting($BlockTypes)
- ->setting($InlineTypes)
- ->setting($RenderStack)
- ;
+ $StateBearer = Footnotes::from(
+ CustomAttributes::from(
+ Definitions::from(
+ Abbreviations::from($StateBearer ?? new State)
+ )
+ )
+ );
+
+ $this->State = $StateBearer->state();
}
public function state(): State
From 368636d6f9466f5ca2395af606e4e4b251c22fe2 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sat, 16 Oct 2021 00:15:03 +0100
Subject: [PATCH 22/28] This is nicer formatting
---
src/ParsedownExtra.php | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/ParsedownExtra.php b/src/ParsedownExtra.php
index aaf2d62..2c3df56 100644
--- a/src/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -26,13 +26,10 @@ final class ParsedownExtra implements StateBearer
public function __construct(StateBearer $StateBearer = null)
{
- $StateBearer = Footnotes::from(
- CustomAttributes::from(
- Definitions::from(
- Abbreviations::from($StateBearer ?? new State)
- )
- )
- );
+ $StateBearer = Abbreviations::from($StateBearer ?? new State);
+ $StateBearer = Definitions::from($StateBearer);
+ $StateBearer = CustomAttributes::from($StateBearer);
+ $StateBearer = Footnotes::from($StateBearer);
$this->State = $StateBearer->state();
}
From d22fd1854e4654516095862804bc03292fb642b7 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sat, 16 Oct 2021 00:17:12 +0100
Subject: [PATCH 23/28] Relax mutation testing
We'll bump this back up later
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 82a81aa..c6db2f0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -43,7 +43,7 @@ jobs:
- name: Run Tests
run: |
composer update --prefer-dist --no-interaction --no-progress
- vendor/bin/roave-infection-static-analysis-plugin --show-mutations --threads=4 --min-msi=80 --min-covered-msi=80
+ vendor/bin/roave-infection-static-analysis-plugin --show-mutations --threads=4 --min-msi=60 --min-covered-msi=70
static-analysis:
name: Code Format and Static Analysis
From 16097ba21e2c76964c854aa3282343c4708e0b2b Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sat, 16 Oct 2021 00:19:57 +0100
Subject: [PATCH 24/28] No longer need these in parsedown extra class
---
.github/workflows/ci.yml | 2 +-
src/ParsedownExtra.php | 108 ---------------------------------------
2 files changed, 1 insertion(+), 109 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c6db2f0..c729f37 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -43,7 +43,7 @@ jobs:
- name: Run Tests
run: |
composer update --prefer-dist --no-interaction --no-progress
- vendor/bin/roave-infection-static-analysis-plugin --show-mutations --threads=4 --min-msi=60 --min-covered-msi=70
+ vendor/bin/roave-infection-static-analysis-plugin --show-mutations --threads=4 --min-msi=70 --min-covered-msi=85
static-analysis:
name: Code Format and Static Analysis
diff --git a/src/ParsedownExtra.php b/src/ParsedownExtra.php
index 2c3df56..84addfc 100644
--- a/src/ParsedownExtra.php
+++ b/src/ParsedownExtra.php
@@ -2,18 +2,8 @@
namespace Erusev\ParsedownExtra;
-use Erusev\Parsedown\Html\Renderable;
-use Erusev\Parsedown\Html\Renderables\Container;
-use Erusev\Parsedown\Html\Renderables\Element;
-use Erusev\Parsedown\Html\Renderables\RawHtml;
-use Erusev\Parsedown\Html\Renderables\Text;
-use Erusev\Parsedown\Html\TransformableRenderable;
-use Erusev\Parsedown\Parsedown;
use Erusev\Parsedown\State;
use Erusev\Parsedown\StateBearer;
-use Erusev\ParsedownExtra\Components\Blocks\Footnote;
-use Erusev\ParsedownExtra\Configurables\AbbreviationBook;
-use Erusev\ParsedownExtra\Configurables\FootnoteBook;
use Erusev\ParsedownExtra\Features\Abbreviations;
use Erusev\ParsedownExtra\Features\CustomAttributes;
use Erusev\ParsedownExtra\Features\Definitions;
@@ -44,102 +34,4 @@ public static function from(StateBearer $StateBearer)
{
return new self($StateBearer);
}
-
- /** @return \Closure(Renderable[],State):Renderable[] */
- public static function expandAbbreviations()
- {
- /**
- * @param Renderable[] $Rs
- * @param State $S
- * @return Renderable[] $Rs
- */
- return function (array $Rs, State $S): array {
- $abbrvs = $S->get(AbbreviationBook::class)->all();
-
- if (empty($abbrvs)) {
- return $Rs;
- }
-
- return \array_map(
- function (Renderable $R) use ($abbrvs): Renderable {
- if ($R instanceof TransformableRenderable) {
- foreach ($abbrvs as $abbrv => $meaning) {
- $R = $R->replacingAll(
- $abbrv,
- new Element('abbr', ['title' => $meaning], [new Text($abbrv)])
- );
- }
- }
-
- return $R;
- },
- $Rs
- );
- };
- }
-
- /** @return \Closure(Renderable[],State):Renderable[] */
- public static function renderFootnotes()
- {
- /**
- * @param Renderable[] $Rs
- * @param State $S
- * @return Renderable[] $Rs
- */
- return function (array $Rs, State $S): array {
- $FB = $S->get(FootnoteBook::class);
-
- if (empty($FB->all())) {
- return $Rs;
- }
-
- return \array_merge($Rs, [new Element(
- 'div',
- ['class' => 'footnotes'],
- [
- Element::selfClosing('hr', []),
- new Element('ol', [], \array_map(
- function (Footnote $F) use ($FB, $S): Element {
- $BackLink = \array_merge(
- [new RawHtml(' ')],
- \array_map(
- function (int $n) use ($F, $FB): Container {
- return new Container(\array_merge(
- [
- new Element(
- 'a',
- [
- 'href' => '#fnref'.\strval($n).':'.$F->title(),
- 'rev' => 'footnote',
- 'class' => 'footnote-backref'
- ],
- [new RawHtml('↩')]
- )],
- ($n < $FB->inlineRecord($F->title()) ? [new Text(' ')] : [])
- ));
- },
- ($count = $FB->inlineRecord($F->title())) > 1 ? \range(1, $count) : [1]
- )
- );
-
- [$StateRenderables, $S] = Parsedown::lines($F->lines(), $S);
-
- $InnerRender = $S->applyTo($StateRenderables);
-
- $lastItem = $InnerRender[\count($InnerRender) -1];
-
- if ($lastItem instanceof Element && $lastItem->name() === 'p' && $contents = $lastItem->contents()) {
- $InnerRender[\count($InnerRender) -1] = new Element('p', [], \array_merge($contents, $BackLink));
- } else {
- $InnerRender[] = new Element('p', [], $BackLink);
- }
-
- return new Element('li', ['id' => 'fn:'.$F->title()], $InnerRender);
- },
- $FB->all()
- ))
- ]
- )]);
- };
- }
}
From dda3ef2cb11aaf6681c471860c019e8c8d0c3917 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sat, 16 Oct 2021 00:42:49 +0100
Subject: [PATCH 25/28] Update php-cs-fixer
---
.php_cs.dist => .php-cs-fixer.dist.php | 4 ++--
composer.json | 2 +-
phpunit.xml.dist | 2 +-
src/Components/Blocks/Abbreviation.php | 1 -
src/Components/Blocks/DefinitionList.php | 1 -
src/Components/Blocks/Footnote.php | 1 -
src/Components/Blocks/Header.php | 1 -
src/Components/Blocks/SetextHeader.php | 1 -
src/Components/Inlines/Footnote.php | 1 -
src/Components/Inlines/Image.php | 1 -
src/Components/Inlines/Link.php | 1 -
11 files changed, 4 insertions(+), 12 deletions(-)
rename .php_cs.dist => .php-cs-fixer.dist.php (97%)
diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php
similarity index 97%
rename from .php_cs.dist
rename to .php-cs-fixer.dist.php
index f2b25f9..cbd0b85 100644
--- a/.php_cs.dist
+++ b/.php-cs-fixer.dist.php
@@ -29,9 +29,9 @@
'strict_param' => true,
'whitespace_after_comma_in_array' => true,
];
-return Config::create()
+return (new Config)
->setRules($rules)
->setFinder($finder)
->setUsingCache(false)
->setRiskyAllowed(true)
-;
\ No newline at end of file
+;
diff --git a/composer.json b/composer.json
index fc615f7..7eb1204 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,7 @@
"require-dev": {
"phpunit/phpunit": "^9.3.11||^8.5.21||^7.5.20",
"vimeo/psalm": "^4.10.0",
- "friendsofphp/php-cs-fixer": "^2.13",
+ "friendsofphp/php-cs-fixer": "^3.0.0",
"infection/infection": "^0.25.0",
"roave/infection-static-analysis-plugin": "^1.10.0"
},
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 49736c9..df09a45 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,7 +1,7 @@
-
+
tests/ParsedownExtraTest.php
diff --git a/src/Components/Blocks/Abbreviation.php b/src/Components/Blocks/Abbreviation.php
index 4c6f01c..3bf1965 100644
--- a/src/Components/Blocks/Abbreviation.php
+++ b/src/Components/Blocks/Abbreviation.php
@@ -2,7 +2,6 @@
namespace Erusev\ParsedownExtra\Components\Blocks;
-use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\StateUpdatingBlock;
use Erusev\Parsedown\Html\Renderables\Invisible;
diff --git a/src/Components/Blocks/DefinitionList.php b/src/Components/Blocks/DefinitionList.php
index 92f6927..b6e6e7c 100644
--- a/src/Components/Blocks/DefinitionList.php
+++ b/src/Components/Blocks/DefinitionList.php
@@ -3,7 +3,6 @@
namespace Erusev\ParsedownExtra\Components\Blocks;
use Erusev\Parsedown\AST\Handler;
-use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\AcquisitioningBlock;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\Blocks\Paragraph;
diff --git a/src/Components/Blocks/Footnote.php b/src/Components/Blocks/Footnote.php
index 148cfdd..3bc21d3 100644
--- a/src/Components/Blocks/Footnote.php
+++ b/src/Components/Blocks/Footnote.php
@@ -2,7 +2,6 @@
namespace Erusev\ParsedownExtra\Components\Blocks;
-use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\ContinuableBlock;
use Erusev\Parsedown\Components\StateUpdatingBlock;
diff --git a/src/Components/Blocks/Header.php b/src/Components/Blocks/Header.php
index a88fa1a..cacc629 100644
--- a/src/Components/Blocks/Header.php
+++ b/src/Components/Blocks/Header.php
@@ -3,7 +3,6 @@
namespace Erusev\ParsedownExtra\Components\Blocks;
use Erusev\Parsedown\AST\Handler;
-use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\Blocks\Header as BaseHeader;
use Erusev\Parsedown\Configurables\HeaderSlug;
diff --git a/src/Components/Blocks/SetextHeader.php b/src/Components/Blocks/SetextHeader.php
index c2b3459..1ef77ed 100644
--- a/src/Components/Blocks/SetextHeader.php
+++ b/src/Components/Blocks/SetextHeader.php
@@ -3,7 +3,6 @@
namespace Erusev\ParsedownExtra\Components\Blocks;
use Erusev\Parsedown\AST\Handler;
-use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\AcquisitioningBlock;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\Blocks\SetextHeader as BaseSetextHeader;
diff --git a/src/Components/Inlines/Footnote.php b/src/Components/Inlines/Footnote.php
index a4d112c..1878334 100644
--- a/src/Components/Inlines/Footnote.php
+++ b/src/Components/Inlines/Footnote.php
@@ -2,7 +2,6 @@
namespace Erusev\ParsedownExtra\Components\Inlines;
-use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Inline;
use Erusev\Parsedown\Components\Inlines\WidthTrait;
use Erusev\Parsedown\Html\Renderables\Element;
diff --git a/src/Components/Inlines/Image.php b/src/Components/Inlines/Image.php
index 1466444..f2294e3 100644
--- a/src/Components/Inlines/Image.php
+++ b/src/Components/Inlines/Image.php
@@ -3,7 +3,6 @@
namespace Erusev\ParsedownExtra\Components\Inlines;
use Erusev\Parsedown\AST\Handler;
-use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Inline;
use Erusev\Parsedown\Components\Inlines\WidthTrait;
use Erusev\Parsedown\Configurables\SafeMode;
diff --git a/src/Components/Inlines/Link.php b/src/Components/Inlines/Link.php
index 3d87e5e..851f41c 100644
--- a/src/Components/Inlines/Link.php
+++ b/src/Components/Inlines/Link.php
@@ -3,7 +3,6 @@
namespace Erusev\ParsedownExtra\Components\Inlines;
use Erusev\Parsedown\AST\Handler;
-use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Inline;
use Erusev\Parsedown\Components\Inlines\Link as BaseLink;
use Erusev\Parsedown\Components\Inlines\Url;
From f89df7093a59e73dd799222fde9d91bb6431d68a Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sat, 21 May 2022 22:44:51 +0100
Subject: [PATCH 26/28] Use beta release of parsedown core
---
composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/composer.json b/composer.json
index 7eb1204..06a55af 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,7 @@
}
],
"require": {
- "erusev/parsedown": "^2.0.0|2.0.x@dev",
+ "erusev/parsedown": "^2.0.0|^2.0.0-beta-1",
"php": "^7.1||^8.0",
"ext-mbstring": "*"
},
From d5a20c65a896df92f2a2fcf760c6c9c24f401f85 Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sat, 21 May 2022 22:45:30 +0100
Subject: [PATCH 27/28] Use new error level instead of totally typed
---
psalm.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/psalm.xml b/psalm.xml
index ae8cdcd..de7a58d 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -1,6 +1,6 @@
From aaed6a888230a9ffb5ef35e9f403ba2b5e4e21ef Mon Sep 17 00:00:00 2001
From: Aidan Woods
Date: Sat, 21 May 2022 22:46:56 +0100
Subject: [PATCH 28/28] Test on PHP 8.1
---
.github/workflows/ci.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c729f37..2bd0c8d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,7 +6,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
- php: [8.0, 7.4, 7.3, 7.2, 7.1]
+ php: [8.1, 8.0, 7.4, 7.3, 7.2, 7.1]
runs-on: ${{ matrix.os }}
steps:
@@ -29,7 +29,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
- php: [8.0]
+ php: [8.1]
runs-on: ${{ matrix.os }}
steps:
@@ -50,7 +50,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
- php: [8.0]
+ php: [8.1]
runs-on: ${{ matrix.os }}
steps:
@@ -74,7 +74,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
- php: [8.0]
+ php: [8.1]
runs-on: ${{ matrix.os }}
steps: