added the Markdown extension

This commit is contained in:
Fabien Potencier
2019-08-10 08:55:31 +02:00
parent f8c0c76f46
commit 741ea46e02
30 changed files with 849 additions and 1 deletions
+1
View File
@@ -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
+2 -1
View File
@@ -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
+65
View File
@@ -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 %}
<html>
<h1>Hello!</h1>
</html>
{% 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}) %}
<html>
<h1>Hello!</h1>
</html>
{% 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());
}
}
});
+2
View File
@@ -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
+64
View File
@@ -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());
}
}
});
+4
View File
@@ -0,0 +1,4 @@
vendor/
composer.lock
phpunit.xml
.phpunit.result.cache
+19
View File
@@ -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.
+11
View File
@@ -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
+42
View File
@@ -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"
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Twig Markdown Extension Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
@@ -0,0 +1,39 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown;
use League\CommonMark\CommonMarkConverter;
use Michelf\MarkdownExtra;
use Parsedown;
class DefaultMarkdown implements MarkdownInterface
{
private $converter;
public function __construct()
{
if (class_exists(Parsedown::class)) {
$this->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);
}
}
@@ -0,0 +1,29 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown;
use Parsedown;
class ErusevMarkdown implements MarkdownInterface
{
private $converter;
public function __construct(Parsedown $converter = null)
{
$this->converter = $converter ?: new Parsedown();
}
public function convert(string $body): string
{
return $this->converter->text($body);
}
}
@@ -0,0 +1,29 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown;
use League\CommonMark\CommonMarkConverter;
class LeagueMarkdown implements MarkdownInterface
{
private $converter;
public function __construct(CommonMarkConverter $converter = null)
{
$this->converter = $converter ?: new CommonMarkConverter();
}
public function convert(string $body): string
{
return $this->converter->convertToHtml($body);
}
}
@@ -0,0 +1,48 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown;
use League\HTMLToMarkdown\HtmlConverter;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class MarkdownExtension extends AbstractExtension
{
public function getFilters()
{
return [
new TwigFilter('markdown_to_html', ['Twig\\Extra\\Markdown\\MarkdownRuntime', 'convert'], ['is_safe' => ['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);
}
@@ -0,0 +1,17 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown;
interface MarkdownInterface
{
public function convert(string $body): string;
}
@@ -0,0 +1,32 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown;
class MarkdownRuntime
{
private $converter;
public function __construct(MarkdownInterface $converter)
{
$this->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);
}
}
@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown;
use Michelf\MarkdownExtra;
class MichelfMarkdown implements MarkdownInterface
{
private $converter;
public function __construct(CommonMarkConverter $converter = null)
{
if (null === $converter) {
$converter = new MarkdownExtra();
$converter->hard_wrap = true;
}
$this->converter = $converter;
}
public function convert(string $body): string
{
return $this->converter->defaultTransform($body);
}
}
@@ -0,0 +1,37 @@
--TEST--
"html_to_markdown" filter
--TEMPLATE--
{% apply html_to_markdown %}
<html>
<h1>Hello</h1>
<p><b><span>Great!</span></b></p>
</html>
{% endapply %}
{% apply html_to_markdown({hard_break: false}) %}
<html>Great<br>Break</html>
{% endapply %}
{{ include('html')|html_to_markdown }}
--TEMPLATE(html)--
<html>
<h1>Hello</h1>
<p><b>Great!</b></p>
</html>
--DATA--
return []
--EXPECT--
Hello
=====
**Great!**
Great
Break
Hello
=====
**Great!**
@@ -0,0 +1,86 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown\Tests;
use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Extra\Markdown\DefaultMarkdown;
use Twig\Extra\Markdown\ErusevMarkdown;
use Twig\Extra\Markdown\LeagueMarkdown;
use Twig\Extra\Markdown\MarkdownExtension;
use Twig\Extra\Markdown\MarkdownRuntime;
use Twig\Extra\Markdown\MichelfMarkdown;
use Twig\Loader\ArrayLoader;
use Twig\RuntimeLoader\RuntimeLoaderInterface;
class FunctionalTest extends TestCase
{
/**
* @dataProvider getMarkdownTests
*/
public function testMarkdown(string $template, string $expected): void
{
foreach ([LeagueMarkdown::class, ErusevMarkdown::class, MichelfMarkdown::class, DefaultMarkdown::class] as $class) {
$twig = new Environment(new ArrayLoader([
'index' => $template,
'html' => <<<EOF
Hello
=====
Great!
EOF
]));
$twig->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 [
[<<<EOF
{% apply markdown_to_html %}
Hello
=====
Great!
{% endapply %}
EOF
, "<h1>Hello</h1>\n+<p>Great!</p>"],
[<<<EOF
{% apply markdown_to_html %}
Hello
=====
Great!
{% endapply %}
EOF
, "<h1>Hello</h1>\n+<p>Great!</p>"],
["{{ include('html')|markdown_to_html }}", "<h1>Hello</h1>\n+<p>Great!</p>"],
];
}
}
@@ -0,0 +1,30 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\Markdown\Tests;
use Twig\Extra\Markdown\MarkdownExtension;
use Twig\Test\IntegrationTestCase;
class IntegrationTest extends IntegrationTestCase
{
public function getExtensions()
{
return [
new MarkdownExtension(),
];
}
public function getFixturesDir()
{
return __DIR__.'/Fixtures/';
}
}
+4
View File
@@ -0,0 +1,4 @@
vendor/
composer.lock
phpunit.xml
.phpunit.result.cache
@@ -0,0 +1,44 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\TwigExtraBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Twig\Extra\Html\HtmlExtension;
use Twig\Extra\Markdown\MarkdownExtension;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('twig_extra');
$rootNode = $treeBuilder->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;
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\TwigExtraBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
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');
}
}
}
+19
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
Twig Extra Bundle
=================
This package is a Symfony bundle that allows to use all "extra" extensions
without any configuration.
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults public="false" />
<service id="twig.extension.html" class="Twig\Extra\Html\HtmlExtension">
<tag name="twig.extension" />
</service>
</services>
</container>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults public="false" />
<service id="twig.extension.markdown" class="Twig\Extra\Markdown\MarkdownExtension">
<tag name="twig.extension" />
</service>
<service id="twig.runtime.markdown" class="Twig\Extra\Markdown\MarkdownRuntime">
<argument type="service" id="twig.markdown.default" />
<tag name="twig.runtime" />
</service>
<service id="twig.markdown.default" class="Twig\Extra\Markdown\DefaultMarkdown" />
</services>
</container>
@@ -0,0 +1,18 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extra\TwigExtraBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class TwigExtraBundle extends Bundle
{
}
+35
View File
@@ -0,0 +1,35 @@
{
"name": "twig/extra-bundle",
"type": "symfony-bundle",
"description": "A Symfony bundle for extra Twig extension",
"keywords": ["twig", "extra", "bundle"],
"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"
},
"autoload": {
"psr-4" : {
"Twig\\Extra\\TwigExtraBundle\\" : "src/"
}
},
"autoload-dev": {
"psr-4" : {
"Twig\\Extra\\TwigExtraBundle\\Tests\\" : "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.12-dev"
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Twig Extra bundle Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>