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_ -
-
-
-
-

markdown

-
-

markdown

-
-
-
-
-_no markdown_ -
-

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: