Skip to content
This repository was archived by the owner on Jul 1, 2023. It is now read-only.

Introduce markdown environment #22

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 44 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# FBMarkdown

FBMarkdown is an extensible parser and renderer for [GitHub Flavored Markdown](https://github.github.com/gfm/),
written in [Hack](http://hacklang.org).

Expand All @@ -22,26 +23,24 @@ FBMarkdown exists to address all of these goals.

## Requirements

- HHVM 3.24 or above.
- HHVM 3.56 or above.
- [hhvm-autoload](https://github.com/hhvm/hhvm-autoload)

## Installing FBMarkdown

hhvm composer.phar require facebook/fbmarkdown
```sh
composer require facebook/fbmarkdown
```

## Using FBMarkdown

```Hack
```hack
use namespace Facebook\Markdown;

function render(string $markdown): string {
$ast = Markdown\parse(new Markdown\ParserContext(), $markdown);
$environment = Markdown\html_environment();

$html = (new Markdown\HTMLRenderer(
new Markdown\RenderContext()
))->render($ast);

return $html;
return $environment->convert($markdown);
}
```

Expand All @@ -54,8 +53,9 @@ FBMarkdown currently supports three types of Markdown sources, with plans to exp
- __User-generated content__: All HTML is disabled, as are links and images regardless of schemes. If links are re-enabled, `rel="nofollow ugc"` will be added to all links.

To make changes to these default settings:

- You may alter the keyset of allowed URI schemes by calling the Parser function `setAllowedURISchemes()`.
- You may enable embedded HTML by calling the Parser function `enableHTML_UNSAFE()`. __N.B.: For complete compatibility with GitHub Flavored Markdown, support for embedded HTML must be enabled.__
- You may enable embedded HTML by calling the Parser function `enableHTML_UNSAFE()`. __N.B.: For complete compatibility with GitHub Flavored Markdown, support for embedded HTML must be enabled.__
- You may disable image filtering by calling the Renderer function `disableImageFiltering()`.
- You may add `rel="nofollow ugc"` to all links by calling the Renderer function `addNoFollowUGCAllLinks()`.

Expand Down Expand Up @@ -89,27 +89,52 @@ Extend `Facebook\Markdown\Inlines\Inline` or a subclass, and pass your classname
`$render_ctx->getInlineContext()->prependInlineTypes(...)`.

There are then several approaches to rendering:
- instantiate your subclass, and add support for it to a custom renderer
- instantiate your subclass, and make it implement the `Facebook\Markdown\RenderableAsHTML` interface
- if it could be replaced with several existing inlines, return a
`Facebook\Markdown\Inlines\InlineSequence`, then you won't need to extend the renderer.

- instantiate your subclass, and add support for it to a custom renderer
- instantiate your subclass, and make it implement the `Facebook\Markdown\RenderableAsHTML` interface
- if it could be replaced with several existing inlines, return a
`Facebook\Markdown\Inlines\InlineSequence`, then you won't need to extend the renderer.

#### Blocks

You will need to implement the `Facebook\Markdown\UnparsedBlocks\BlockProducer` interface, and pass your classname
to `$render_ctx->getBlockContext()->prependBlockTypes(...)`.

There are then several approaches to rendering:
- create a subclass of `Block`, and add support for it to a custom renderer
- create a subclass of `Block`, and make it implement the `Facebook\Markdown\RenderableAsHTML` interface
- if it could be replaced with several existing blocks, return a
`Facebook\Markdown\Blocks\BlockSequence`
- if it could be replaced with a paragraph of inlines, return a `Facebook\Markdown\Blocks\InlineSequenceBlock`

- create a subclass of `Block`, and add support for it to a custom renderer
- create a subclass of `Block`, and make it implement the `Facebook\Markdown\RenderableAsHTML` interface
- if it could be replaced with several existing blocks, return a
`Facebook\Markdown\Blocks\BlockSequence`
- if it could be replaced with a paragraph of inlines, return a `Facebook\Markdown\Blocks\InlineSequenceBlock`

### Transforming The AST

Extend `Facebook\Markdown\RenderFilter`, and pass it to `$render_ctx->appendFilters(...)`.

## FBMarkdown Environment

FBMarkdown provides a `Environment` class which acts as a glow between the parser and renderer.

By default, FBMarkdown ships with 2 functions which allow you to create an HTML environment, and unsafe HTML environment.

- `Facebook\Markdown\html_environment`
- `Facebook\Markdown\unsafe_html_environment`

The `Environment` makes it easy to extend the parser, and renderer via the `Plugin` system.

```hack
$environment = Markdown\html_environment();

$environment->use(new UserMentionPlugin());
$environment->user(new ExternalLinkPlugin());

$html = $environment->convert($markdown);
```

each plugin must extend the `Facebook\Markdown\Plugin` class, and can provide
multiple [block producers](#blocks), [inline types](#inlines), and [render filters](#transforming-the-ast).

### Examples

The Hack and HHVM documentation uses most of these approaches; see:
Expand Down
63 changes: 63 additions & 0 deletions src/Environment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?hh // strict
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

namespace Facebook\Markdown;

final class Environment<T> {
public function __construct(
private ParserContext $parser,
private RenderContext $context,
private Renderer<T> $renderer,
) {}

public function getParser(): ParserContext {
return $this->parser;
}

public function getContext(): RenderContext {
return $this->context;
}

public function getRenderer(): Renderer<T> {
return $this->renderer;
}

public function getInlineContext(): Inlines\Context {
return $this->parser->getInlineContext();
}

public function getBlockContext(): UnparsedBlocks\Context {
return $this->parser->getBlockContext();
}

public function getFilters(): Container<RenderFilter> {
return $this->context->getFilters();
}

public function use(Plugin $plugin): void {
$this->getBlockContext()
->prependBlockTypes(...$plugin->getBlockProducers());
$this->getInlineContext()
->prependInlineTypes(...$plugin->getInlineTypes());
$this->getContext()->appendFilters(...$plugin->getRenderFilters());
}

public function parse(string $markdown): ASTNode {
return parse($this->parser, $markdown);
}

public function render(ASTNode $markdown): T {
return $this->renderer->render($markdown);
}

public function convert(string $markdown): T {
return $this->render($this->parse($markdown));
}
}
35 changes: 35 additions & 0 deletions src/Plugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?hh // strict
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

namespace Facebook\Markdown;

abstract class Plugin {
/**
* @see Facebook\Markdown\RenderContext::appendFilters()
*/
public function getRenderFilters(): Container<RenderFilter> {
return vec[];
}

/**
* @see Facebook\Markdown\Inlines\Context::prependInlineTypes()
*/
public function getInlineTypes(): Container<classname<Inlines\Inline>> {
return vec[];
}

/**
* @see Facebook\Markdown\UnparsedBlocks\Context::prependBlockTypes()
*/
public function getBlockProducers(
): Container<classname<UnparsedBlocks\BlockProducer>> {
return vec[];
}
}
29 changes: 29 additions & 0 deletions src/html_environment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?hh // strict
/*
* Copyright (c) 2004-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

namespace Facebook\Markdown;

function html_environment(): Environment<string> {
$parser = new ParserContext();
$context = new RenderContext();
$renderer = new HTMLRenderer($context);

return new Environment($parser, $context, $renderer);
}

function unsafe_html_environment(): Environment<string> {
$parser = new ParserContext();
$context = new RenderContext();
$renderer = new HTMLRenderer($context);

$parser->enableHTML_UNSAFE();

return new Environment($parser, $context, $renderer);
}