diff --git a/.travis.yml b/.travis.yml index 3f0ab1bde..b7f8605be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ install: script: - ./vendor/bin/simple-phpunit - (cd extra/html-extra && ./vendor/bin/simple-phpunit) + - (cd extra/markdown-extra && travis_retry composer install) jobs: fast_finish: true diff --git a/CHANGELOG b/CHANGELOG index 1ebc3a100..c999e2563 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ * 2.12.0 (2019-XX-XX) - * added the HtmlExtension: "html_classes" function and "data_uri" filter + * added the Markdown extension in the "extra" repositories + * added the HtmlExtension extension in the "extra" repositories * fixed cache when opcache is installed but disabled * fixed using macros in arrow functions diff --git a/doc/filters/html_to_markdown.rst b/doc/filters/html_to_markdown.rst new file mode 100644 index 000000000..32482f76c --- /dev/null +++ b/doc/filters/html_to_markdown.rst @@ -0,0 +1,65 @@ +``html_to_markdown`` +==================== + +.. versionadded:: 2.12 + The ``html_to_markdown`` filter was added in Twig 2.12. + +The ``html_to_markdown`` filter converts a block of HTML to Markdown: + +.. code-block:: twig + + {% apply html_to_markdown %} + +

Hello!

+ + {% endapply %} + +You can also add some options by passing them as an argument to the filter: + +.. code-block:: twig + + {% apply html_to_markdown({hard_break: false}) %} + +

Hello!

+ + {% endapply %} + +.. note:: + + The options are the ones provided by the ``league/html-to-markdown`` package. + +You can also use the filter on an included file: + +.. code-block:: twig + + {{ include('some_template.html.twig')|html_to_markdown }} + +.. note:: + + The ``markdown_to_html`` filter is part of the ``MarkdownExtension`` which + is not installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/markdown-extension + + Then, add it on the Twig environment:: + + use Twig\Extra\Markdown\MarkdownMarkdownExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new MarkdownExtension()); + + If you are not using Symfony, you must also register the extension runtime:: + + use Twig\Extra\Markdown\DefaultMarkdown; + use Twig\Extra\Markdown\MarkdownRuntime; + use Twig\RuntimeLoader\RuntimeLoaderInterface; + + $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface { + public function load($class) { + if (MarkdownRuntime::class === $class) { + return new MarkdownRuntime(new DefaultMarkdown()); + } + } + }); diff --git a/doc/filters/index.rst b/doc/filters/index.rst index 9eeff175e..ae8e8ee84 100644 --- a/doc/filters/index.rst +++ b/doc/filters/index.rst @@ -17,6 +17,7 @@ Filters filter first format + html_to_markdown join json_encode keys @@ -24,6 +25,7 @@ Filters length lower map + markdown_to_html merge nl2br number_format diff --git a/doc/filters/markdown_to_html.rst b/doc/filters/markdown_to_html.rst new file mode 100644 index 000000000..dc4f46c73 --- /dev/null +++ b/doc/filters/markdown_to_html.rst @@ -0,0 +1,64 @@ +``markdown_to_html`` +==================== + +.. versionadded:: 2.12 + The ``markdown_to_html`` filter was added in Twig 2.12. + +The ``markdown_to_html`` filter converts a block of Markdown to HTML: + +.. code-block:: twig + + {% apply markdown_to_html %} + Title + ====== + + Hello! + {% endapply %} + +Note that you can indent the Markdown content as leading whitespaces will be +removed consistently before conversion: + +.. code-block:: twig + + {% apply markdown_to_html %} + Title + ====== + + Hello! + {% endapply %} + +You can also use the filter on an included file: + +.. code-block:: twig + + {{ include('some_template.markdown.twig')|markdown_to_html }} + +.. note:: + + The ``markdown_to_html`` filter is part of the ``MarkdownExtension`` which + is not installed by default. Install it first: + + .. code-block:: bash + + $ composer req twig/markdown-extension + + Then, add it on the Twig environment:: + + use Twig\Extra\Markdown\MarkdownMarkdownExtension; + + $twig = new \Twig\Environment(...); + $twig->addExtension(new MarkdownExtension()); + + If you are not using Symfony, you must also register the extension runtime:: + + use Twig\Extra\Markdown\DefaultMarkdown; + use Twig\Extra\Markdown\MarkdownRuntime; + use Twig\RuntimeLoader\RuntimeLoaderInterface; + + $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface { + public function load($class) { + if (MarkdownRuntime::class === $class) { + return new MarkdownRuntime(new DefaultMarkdown()); + } + } + }); diff --git a/extra/markdown-extra/.gitignore b/extra/markdown-extra/.gitignore new file mode 100644 index 000000000..76367ee5b --- /dev/null +++ b/extra/markdown-extra/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +phpunit.xml +.phpunit.result.cache diff --git a/extra/markdown-extra/LICENSE b/extra/markdown-extra/LICENSE new file mode 100644 index 000000000..1a1869751 --- /dev/null +++ b/extra/markdown-extra/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extra/markdown-extra/README.md b/extra/markdown-extra/README.md new file mode 100644 index 000000000..6cdd06bdd --- /dev/null +++ b/extra/markdown-extra/README.md @@ -0,0 +1,11 @@ +Twig Markdown Extension +======================= + +This package is a Twig extension that provides the following: + + * [`markdown_to_html`][1] filter: generates HTML from a Markdown block; + + * [`html_to_markdown`][2] filter: generates Markdown from an HTML block. + +[1]: https://twig.symfony.com/markdown_to_html +[2]: https://twig.symfony.com/html_to_markdown diff --git a/extra/markdown-extra/composer.json b/extra/markdown-extra/composer.json new file mode 100644 index 000000000..28e7e9426 --- /dev/null +++ b/extra/markdown-extra/composer.json @@ -0,0 +1,42 @@ +{ + "name": "twig/markdown-extra", + "type": "library", + "description": "A Twig extension for Markdown", + "keywords": ["twig", "html", "markdown"], + "homepage": "https://twig.symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "require": { + "php": "^7.1.3", + "twig/twig": "^2.4|^3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4@dev", + "erusev/parsedown": "^1.7", + "league/commonmark": "^1.0", + "league/html-to-markdown": "^4.8", + "michelf/php-markdown": "^1.8" + }, + "autoload": { + "psr-4" : { + "Twig\\Extra\\Markdown\\" : "src/" + } + }, + "autoload-dev": { + "psr-4" : { + "Twig\\Extra\\Markdown\\Tests\\" : "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.12-dev" + } + } +} diff --git a/extra/markdown-extra/phpunit.xml.dist b/extra/markdown-extra/phpunit.xml.dist new file mode 100644 index 000000000..4b84ca9c2 --- /dev/null +++ b/extra/markdown-extra/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./tests/ + + + + + + ./ + + ./tests + ./vendor + + + + diff --git a/extra/markdown-extra/src/DefaultMarkdown.php b/extra/markdown-extra/src/DefaultMarkdown.php new file mode 100644 index 000000000..24aad2e34 --- /dev/null +++ b/extra/markdown-extra/src/DefaultMarkdown.php @@ -0,0 +1,39 @@ +converter = new ErusevMarkdown(); + } elseif (class_exists(CommonMarkConverter::class)) { + $this->converter = new LeagueMarkdown(); + } elseif (class_exists(MarkdownExtra::class)) { + $this->converter = new MichelfMarkdown(); + } else { + throw new \LogicException('You cannot use the "markdown_to_html" filter as no Markdown library is available; try running "composer require erusev/parsedown".'); + } + } + + public function convert(string $body): string + { + return $this->converter->convert($body); + } +} diff --git a/extra/markdown-extra/src/ErusevMarkdown.php b/extra/markdown-extra/src/ErusevMarkdown.php new file mode 100644 index 000000000..f4f7e1c48 --- /dev/null +++ b/extra/markdown-extra/src/ErusevMarkdown.php @@ -0,0 +1,29 @@ +converter = $converter ?: new Parsedown(); + } + + public function convert(string $body): string + { + return $this->converter->text($body); + } +} diff --git a/extra/markdown-extra/src/LeagueMarkdown.php b/extra/markdown-extra/src/LeagueMarkdown.php new file mode 100644 index 000000000..ed7e7dd32 --- /dev/null +++ b/extra/markdown-extra/src/LeagueMarkdown.php @@ -0,0 +1,29 @@ +converter = $converter ?: new CommonMarkConverter(); + } + + public function convert(string $body): string + { + return $this->converter->convertToHtml($body); + } +} diff --git a/extra/markdown-extra/src/MarkdownExtension.php b/extra/markdown-extra/src/MarkdownExtension.php new file mode 100644 index 000000000..ff6a6d3d6 --- /dev/null +++ b/extra/markdown-extra/src/MarkdownExtension.php @@ -0,0 +1,48 @@ + ['all']]), + new TwigFilter('html_to_markdown', 'Twig\\Extra\\Markdown\\twig_html_to_markdown', ['is_safe' => ['all']]), + ]; + } +} + +function twig_html_to_markdown(string $body, array $options = []): string +{ + static $converters; + + if (!class_exists(HtmlConverter::class)) { + throw new \LogicException('You cannot use the "html_to_markdown" filter as league/html-to-markdown is not installed; try running "composer require league/html-to-markdown".'); + } + + $options = $options + [ + 'hard_break' => true, + 'strip_tags' => true, + 'remove_nodes' => 'head style', + ]; + + if (!isset($converters[$key = serialize($options)])) { + $converters[$key] = new HtmlConverter($options); + } + + return $converters[$key]->convert($body); +} diff --git a/extra/markdown-extra/src/MarkdownInterface.php b/extra/markdown-extra/src/MarkdownInterface.php new file mode 100644 index 000000000..10406ea90 --- /dev/null +++ b/extra/markdown-extra/src/MarkdownInterface.php @@ -0,0 +1,17 @@ +converter = $converter; + } + + public function convert(string $body): string + { + // remove indentation + if ($white = substr($body, 0, strspn($body, " \t\r\n\0\x0B"))) { + $body = preg_replace("{^$white}m", '', $body); + } + + return $this->converter->convert($body); + } +} diff --git a/extra/markdown-extra/src/MichelfMarkdown.php b/extra/markdown-extra/src/MichelfMarkdown.php new file mode 100644 index 000000000..c87b4aa07 --- /dev/null +++ b/extra/markdown-extra/src/MichelfMarkdown.php @@ -0,0 +1,34 @@ +hard_wrap = true; + } + + $this->converter = $converter; + } + + public function convert(string $body): string + { + return $this->converter->defaultTransform($body); + } +} diff --git a/extra/markdown-extra/tests/Fixtures/html_to_markdown.test b/extra/markdown-extra/tests/Fixtures/html_to_markdown.test new file mode 100644 index 000000000..52ef0b6cb --- /dev/null +++ b/extra/markdown-extra/tests/Fixtures/html_to_markdown.test @@ -0,0 +1,37 @@ +--TEST-- +"html_to_markdown" filter +--TEMPLATE-- +{% apply html_to_markdown %} + +

Hello

+

Great!

+ +{% endapply %} + + +{% apply html_to_markdown({hard_break: false}) %} + Great
Break +{% endapply %} + + +{{ include('html')|html_to_markdown }} +--TEMPLATE(html)-- + +

Hello

+

Great!

+ +--DATA-- +return [] +--EXPECT-- +Hello +===== + +**Great!** + +Great +Break + +Hello +===== + +**Great!** diff --git a/extra/markdown-extra/tests/FunctionalTest.php b/extra/markdown-extra/tests/FunctionalTest.php new file mode 100644 index 000000000..26d057b2d --- /dev/null +++ b/extra/markdown-extra/tests/FunctionalTest.php @@ -0,0 +1,86 @@ + $template, + 'html' => <<addExtension(new MarkdownExtension()); + $twig->addRuntimeLoader(new class($class) implements RuntimeLoaderInterface { + private $class; + + public function __construct(string $class) + { + $this->class = $class; + } + + public function load($c) + { + if (MarkdownRuntime::class === $c) { + return new $c(new $this->class()); + } + } + }); + $this->assertRegExp('{'.$expected.'}m', trim($twig->render('index'))); + } + } + + public function getMarkdownTests() + { + return [ + [<<Hello\n+

Great!

"], + [<<Hello\n+

Great!

"], + ["{{ include('html')|markdown_to_html }}", "

Hello

\n+

Great!

"], + ]; + } +} diff --git a/extra/markdown-extra/tests/IntegrationTest.php b/extra/markdown-extra/tests/IntegrationTest.php new file mode 100644 index 000000000..7474ec769 --- /dev/null +++ b/extra/markdown-extra/tests/IntegrationTest.php @@ -0,0 +1,30 @@ +getRootNode(); + + $rootNode + ->children() + ->arrayNode('html') + ->{class_exists(HtmlExtension::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->end() + ->end() + ; + + $rootNode + ->children() + ->arrayNode('markdown') + ->{class_exists(MarkdownExtension::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->end() + ->end() + ; + + return $treeBuilder; + } +} diff --git a/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php b/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php new file mode 100644 index 000000000..ba4bc54e1 --- /dev/null +++ b/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php @@ -0,0 +1,38 @@ + + */ +class TwigExtraExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + if ($this->isConfigEnabled($container, $config['html'])) { + $loader->load('html.xml'); + } + + if ($this->isConfigEnabled($container, $config['markdown'])) { + $loader->load('markdown.xml'); + } + } +} diff --git a/extra/twig-extra-bundle/LICENSE b/extra/twig-extra-bundle/LICENSE new file mode 100644 index 000000000..1a1869751 --- /dev/null +++ b/extra/twig-extra-bundle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extra/twig-extra-bundle/README.md b/extra/twig-extra-bundle/README.md new file mode 100644 index 000000000..f7602e4fe --- /dev/null +++ b/extra/twig-extra-bundle/README.md @@ -0,0 +1,5 @@ +Twig Extra Bundle +================= + +This package is a Symfony bundle that allows to use all "extra" extensions +without any configuration. diff --git a/extra/twig-extra-bundle/Resources/config/html.xml b/extra/twig-extra-bundle/Resources/config/html.xml new file mode 100644 index 000000000..203a05af0 --- /dev/null +++ b/extra/twig-extra-bundle/Resources/config/html.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/extra/twig-extra-bundle/Resources/config/markdown.xml b/extra/twig-extra-bundle/Resources/config/markdown.xml new file mode 100644 index 000000000..258245217 --- /dev/null +++ b/extra/twig-extra-bundle/Resources/config/markdown.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/extra/twig-extra-bundle/TwigExtraBundle.php b/extra/twig-extra-bundle/TwigExtraBundle.php new file mode 100644 index 000000000..c1c2e24cc --- /dev/null +++ b/extra/twig-extra-bundle/TwigExtraBundle.php @@ -0,0 +1,18 @@ + + + + + + + + + + ./tests/ + + + + + + ./ + + ./tests + ./vendor + + + +