Skip to content

Commit

Permalink
Merge pull request #409 from thephpleague/gfm-extensions
Browse files Browse the repository at this point in the history
Github-Flavored Markdown
  • Loading branch information
colinodell committed Feb 8, 2020
2 parents 83739a9 + c93840d commit a13ee46
Show file tree
Hide file tree
Showing 108 changed files with 4,682 additions and 23 deletions.
3 changes: 3 additions & 0 deletions .styleci.yml
Expand Up @@ -17,5 +17,8 @@ finder:
- ".phpstorm.meta.php"
- "CommonMarkCoreExtension.php"
- "FakeEmptyHtmlRenderer.php"
- "InlinesOnlyExtension.php"
- "SmartPunctExtension.php"
- "TableExtension.php"
not-path:
- ".ripstech"
41 changes: 28 additions & 13 deletions README.md
Expand Up @@ -49,6 +49,33 @@ Full documentation on advanced usage, configuration, and customization can be fo

Information on how to upgrade to newer versions of this library can be found at <https://commonmark.thephpleague.com/releases>.

## 💻 Github-Flavored Markdown

Github-Flavored Markdown can be parsed by using the `GithubFlavoredMarkdownConverter`:

```php
use League\CommonMark\GithubFlavoredMarkdownConverter;

$converter = new GithubFlavoredMarkdownConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);

echo $converter->convertToHtml('# Hello World!');

// <h1>Hello World!</h1>
```

This is a drop-in replacement for the `CommonMarkConverter` which adds additional features found in the GFM spec:

- Autolinks
- Disallowed raw HTML
- Strikethrough
- Tables
- Task Lists

See the [Extensions documentation](https://commonmark.thephpleague.com/1.0/customization/extensions/) for more details on how to include only certain GFM features if you don't want them all.

## 🗃️ Related Packages

### Integrations
Expand All @@ -62,19 +89,7 @@ Information on how to upgrade to newer versions of this library can be found at
- [Twig Markdown extension](https://github.com/twigphp/markdown-extension)
- [Twig filter and tag](https://github.com/aptoma/twig-markdown)

### GFM Extensions

You can easily add support for Github-Flavored Markdown by installing the [`league/commonmark-extras`](https://github.com/thephpleague/commonmark-extras) package, which includes bundles all of the extensions listed below:

| Feature | Package Name | Description |
| ------- | ------------ | ----------- |
| Autolinks | [`league/commonmark-ext-autolink`](https://github.com/thephpleague/commonmark-ext-autolink) | Automatically links URLs, emails, and (optionally) @-mentions without needing to use `<...>` |
| Smart Punctuation | [`league/commonmark-ext-smartpunct`](https://github.com/thephpleague/commonmark-ext-smartpunct) | Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents |
| Strikethrough | [`league/commonmark-ext-strikethrough`](https://github.com/thephpleague/commonmark-ext-strikethrough) | Adds support for `~~strikethrough~~` syntax |
| Task Lists | [`league/commonmark-ext-task-list`](https://github.com/thephpleague/commonmark-ext-task-list) | Support for Github-style task lists |
| Tables | [`league/commonmark-ext-table`](https://github.com/thephpleague/commonmark-ext-table) | GFM-style tables |

### Other PHP League Extensions
### Extensions

- [`league/commonmark-ext-inlines-only`](https://github.com/thephpleague/commonmark-ext-inlines-only) - Renders inline text without paragraph tags or other block-level elements
- [`league/commonmark-ext-external-link`](https://github.com/thephpleague/commonmark-ext-external-link) - Mark external links, make them open in new windows, etc.
Expand Down
16 changes: 14 additions & 2 deletions composer.json
Expand Up @@ -28,15 +28,16 @@
"cebe/markdown": "~1.0",
"commonmark/commonmark.js": "0.29.1",
"erusev/parsedown": "~1.0",
"github/gfm": "0.29.0",
"michelf/php-markdown": "~1.4",
"mikehaertl/php-shellcommand": "^1.4",
"phpstan/phpstan-shim": "^0.11.5",
"phpunit/phpunit": "^7.5",
"scrutinizer/ocular": "^1.5",
"symfony/finder": "^4.2"
},
"suggest": {
"league/commonmark-extras": "Library of useful extensions including smart punctuation"
"conflict": {
"scrutinizer/ocular": "1.7.*"
},
"repositories": [
{
Expand All @@ -49,6 +50,17 @@
"type": "zip"
}
}
},
{
"type": "package",
"package": {
"name": "github/gfm",
"version": "0.29.0",
"dist": {
"url": "https://github.com/github/cmark-gfm/archive/0.29.0.gfm.0.zip",
"type": "zip"
}
}
}
],
"autoload": {
Expand Down
6 changes: 4 additions & 2 deletions docs/1.0/configuration.md
Expand Up @@ -7,7 +7,7 @@ redirect_from: /0.20/configuration/
Configuration
=============

You can provide an array of configuration options to the `CommonMarkConverter` when creating it::
You can provide an array of configuration options to the `CommonMarkConverter` when creating it:

~~~php
<?php
Expand All @@ -34,7 +34,7 @@ Here's a list of currently-supported options:

* `renderer` - Array of options for rendering HTML
* `block_separator` - String to use for separating renderer block elements
* `inner_separator` - String to use for separating inner block contents
* `inner_separator` - String to use for separating inner block contents
* `soft_break` - String to use for rendering soft breaks
* `enable_em` - Disable `<em>` parsing by setting to `false`; enable with `true` (default: `true`)
* `enable_strong` - Disable `<strong>` parsing by setting to `false`; enable with `true` (default: `true`)
Expand All @@ -47,6 +47,8 @@ Here's a list of currently-supported options:
* `allow_unsafe_links` - Remove risky link and image URLs by setting this to `false` (default: `true`)
* `max_nesting_level` - The maximum nesting level for blocks (default: infinite). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested. Added in 0.17.

Additional configuration options are available for some of the [available extensions](/1.0/customization/extensions/) - refer to their individual documentation for more details.

The following options have been deprecated. They will no longer work once 1.0.0 is released:

* `safe` - Prevents rendering of raw HTML if set to `true` (default: `false`)
Expand Down
48 changes: 48 additions & 0 deletions docs/1.0/customization/extensions.md
Expand Up @@ -18,6 +18,7 @@ final class EmojiExtension implements ExtensionInterface
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment
// TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
->addInlineParser(new EmojiParser(), 20)
->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
;
Expand All @@ -34,3 +35,50 @@ $environment->addExtension(new EmojiExtension();
$converter = new CommonMarkConverter([], $environment);
echo $converter->convertToHtml('Hello! :wave:');
```

## Included Extensions

Starting in v1.3, this library includes several extensions to support Github-Flavored Markdown. You can manually add the GFM extension to your environment like this:

```php
$environment = Environment::createCommonMarkExtension();
$environment->addExtension(new GithubFlavoredMarkdownExtension();

$converter = new CommonMarkConverter([], $environment);
echo $converter->convertToHtml('Hello GFM!');

```

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

```php
$environment = Environment::createCommonMarkExtension();
// Remove any of the lines below if you don't want a particular feature
$environment->addExtension(new AutolinkExtension());
$environment->addExtension(new DisallowedRawHtmlExtension());
$environment->addExtension(new StrikethroughExtension());
$environment->addExtension(new TableExtension());
$environment->addExtension(new TaskListExtension());

$converter = new CommonMarkConverter([], $environment);
echo $converter->convertToHtml('Hello GFM!');
```

### GFM Extensions

| Extension | Purpose | Documentation |
| --------- | ------- | ------------- |
| **`GithubFlavoredMarkdownExtension`** | Enables full support for GFM. Includes the following sub-extensions by default: | |
| `AutolinkExtension` | Enables automatic linking of URLs within text without needing to wrap them with Markdown syntax | [Documentation](https://github.com/thephpleague/commonmark/blob/master/src/Extension/Autolink/README.md) |
| `DisallowedRawHtmlExtension` | Disables certain kinds of HTML tags that could affect page rendering | |
| `StrikethroughExtension` | Allows using tilde characters (`~~`) for ~strikethrough~ formatting | [Documentation](https://github.com/thephpleague/commonmark/blob/master/src/Extension/Strikethrough/README.md) |
| `TableExtension` | Enables you to create HTML tables | [Documentation](https://github.com/thephpleague/commonmark/blob/master/src/Extension/Table/README.md) |
| `TaskListExtension` | Allows the creation of task lists | [Documentation](https://github.com/thephpleague/commonmark/blob/master/src/Extension/TaskList/README.md) |

### Other Useful Extensions

| Extension | Purpose | Documentation |
| --------- | ------- | ------------- |
| `ExternalLinkExtension` | Tags external links with additional markup | [Documentation](https://github.com/thephpleague/commonmark/blob/master/src/Extension/ExternalLink/README.md) |
| `InlinesOnlyExtension` | Only includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc. | [Documentation](https://github.com/thephpleague/commonmark/blob/master/src/Extension/InlinesOnly/README.md) |
| `SmartPunctExtension` | Intelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents | [Documentation](https://github.com/thephpleague/commonmark/blob/master/src/Extension/SmartPunct/README.md) |
11 changes: 10 additions & 1 deletion src/Block/Renderer/ListItemRenderer.php
Expand Up @@ -16,7 +16,9 @@

use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\ListItem;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\TaskList\TaskListItemMarker;
use League\CommonMark\HtmlElement;

final class ListItemRenderer implements BlockRendererInterface
Expand All @@ -35,7 +37,7 @@ public function render(AbstractBlock $block, ElementRendererInterface $htmlRende
}

$contents = $htmlRenderer->renderBlocks($block->children(), $inTightList);
if (\substr($contents, 0, 1) === '<') {
if (\substr($contents, 0, 1) === '<' && !$this->startsTaskListItem($block)) {
$contents = "\n" . $contents;
}
if (\substr($contents, -1, 1) === '>') {
Expand All @@ -48,4 +50,11 @@ public function render(AbstractBlock $block, ElementRendererInterface $htmlRende

return $li;
}

private function startsTaskListItem(ListItem $block): bool
{
$firstChild = $block->firstChild();

return $firstChild instanceof Paragraph && $firstChild->firstChild() instanceof TaskListItemMarker;
}
}
9 changes: 9 additions & 0 deletions src/Environment.php
Expand Up @@ -21,6 +21,7 @@
use League\CommonMark\Event\AbstractEvent;
use League\CommonMark\Extension\CommonMarkCoreExtension;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
use League\CommonMark\Util\Configuration;
Expand Down Expand Up @@ -354,6 +355,14 @@ public static function createCommonMarkEnvironment(): Environment
return $environment;
}

public static function createGFMEnvironment(): EnvironmentInterface
{
$environment = self::createCommonMarkEnvironment();
$environment->addExtension(new GithubFlavoredMarkdownExtension());

return $environment;
}

/**
* {@inheritdoc}
*/
Expand Down
25 changes: 25 additions & 0 deletions src/Extension/Autolink/AutolinkExtension.php
@@ -0,0 +1,25 @@
<?php

/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace League\CommonMark\Extension\Autolink;

use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ExtensionInterface;

final class AutolinkExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment->addEventListener(DocumentParsedEvent::class, new EmailAutolinkProcessor());
$environment->addEventListener(DocumentParsedEvent::class, new UrlAutolinkProcessor());
}
}
78 changes: 78 additions & 0 deletions src/Extension/Autolink/EmailAutolinkProcessor.php
@@ -0,0 +1,78 @@
<?php

/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace League\CommonMark\Extension\Autolink;

use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Inline\Element\Link;
use League\CommonMark\Inline\Element\Text;

final class EmailAutolinkProcessor
{
const REGEX = '/([A-Za-z0-9.\-_+]+@[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.]+)/';

/**
* @param DocumentParsedEvent $e
*
* @return void
*/
public function __invoke(DocumentParsedEvent $e)
{
$walker = $e->getDocument()->walker();

while ($event = $walker->next()) {
$node = $event->getNode();
if ($node instanceof Text && !($node->parent() instanceof Link)) {
self::processAutolinks($node);
}
}
}

private static function processAutolinks(Text $node)
{
$contents = \preg_split(self::REGEX, $node->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE);

if ($contents === false || \count($contents) === 1) {
return;
}

$leftovers = '';
foreach ($contents as $i => $content) {
if ($i % 2 === 0) {
$text = $leftovers . $content;
if ($text !== '') {
$node->insertBefore(new Text($leftovers . $content));
}

$leftovers = '';
continue;
}

// Does the URL end with punctuation that should be stripped?
if (\substr($content, -1) === '.') {
// Add the punctuation later
$content = \substr($content, 0, -1);
$leftovers = '.';
}

// The last character cannot be - or _
if (\in_array(\substr($content, -1), ['-', '_'])) {
$node->insertBefore(new Text($content . $leftovers));
$leftovers = '';
continue;
}

$node->insertBefore(new Link('mailto:' . $content, $content));
}

$node->detach();
}
}

0 comments on commit a13ee46

Please sign in to comment.