Commit 557e3430 by Alexander Makarov

Merge pull request #2471 from yiisoft/markdown

Changed markdown library to cebe/markdown
parents c370b2e7 c9c7db9a
...@@ -75,8 +75,7 @@ ...@@ -75,8 +75,7 @@
"yiisoft/yii2-composer": "*", "yiisoft/yii2-composer": "*",
"yiisoft/jquery": "~2.0 | ~1.10", "yiisoft/jquery": "~2.0 | ~1.10",
"ezyang/htmlpurifier": "4.6.*", "ezyang/htmlpurifier": "4.6.*",
"michelf/php-markdown": "1.3.*", "cebe/markdown": "0.9.*"
"phpspec/php-diff": ">=1.0.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "3.7.*", "phpunit/phpunit": "3.7.*",
......
...@@ -61,7 +61,7 @@ is a summary of the available cache components: ...@@ -61,7 +61,7 @@ is a summary of the available cache components:
the fastest one when dealing with cache in a distributed applications (e.g. with several servers, load the fastest one when dealing with cache in a distributed applications (e.g. with several servers, load
balancers, etc.) balancers, etc.)
* [[\yii\caching\RedisCache]]: implements a cache component based on [Redis](http://redis.io/) key-value store * [[\yii\redis\Cache]]: implements a cache component based on [Redis](http://redis.io/) key-value store
(redis version 2.6.12 or higher is required). (redis version 2.6.12 or higher is required).
* [[\yii\caching\WinCache]]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension) * [[\yii\caching\WinCache]]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension)
......
...@@ -65,6 +65,11 @@ class RenderController extends Controller ...@@ -65,6 +65,11 @@ class RenderController extends Controller
$this->stdout('done.' . PHP_EOL, Console::FG_GREEN); $this->stdout('done.' . PHP_EOL, Console::FG_GREEN);
if (empty($files)) {
$this->stderr('Error: No php files found to process.' . PHP_EOL);
return 1;
}
$context = new Context(); $context = new Context();
$cacheFile = $targetDir . '/cache/' . md5(serialize($files)) . '.tmp'; $cacheFile = $targetDir . '/cache/' . md5(serialize($files)) . '.tmp';
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"yiisoft/yii2-bootstrap": "*", "yiisoft/yii2-bootstrap": "*",
"phpdocumentor/reflection": ">=1.0.3", "phpdocumentor/reflection": ">=1.0.3",
"erusev/parsedown": "0.9.*" "nikic/php-parser": "0.9.*"
}, },
"autoload": { "autoload": {
"psr-4": { "yii\\apidoc\\": "" } "psr-4": { "yii\\apidoc\\": "" }
......
...@@ -7,10 +7,12 @@ ...@@ -7,10 +7,12 @@
namespace yii\apidoc\helpers; namespace yii\apidoc\helpers;
use cebe\markdown\GithubMarkdown;
use phpDocumentor\Reflection\DocBlock\Type\Collection; use phpDocumentor\Reflection\DocBlock\Type\Collection;
use yii\apidoc\models\MethodDoc; use yii\apidoc\models\MethodDoc;
use yii\apidoc\models\TypeDoc; use yii\apidoc\models\TypeDoc;
use yii\apidoc\templates\BaseRenderer; use yii\apidoc\templates\BaseRenderer;
use yii\helpers\Markdown;
/** /**
* A Markdown helper with support for class reference links. * A Markdown helper with support for class reference links.
...@@ -18,26 +20,81 @@ use yii\apidoc\templates\BaseRenderer; ...@@ -18,26 +20,81 @@ use yii\apidoc\templates\BaseRenderer;
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class ApiMarkdown extends Markdown class ApiMarkdown extends GithubMarkdown
{ {
/** /**
* @var BaseRenderer * @var BaseRenderer
*/ */
public static $renderer; public static $renderer;
protected $context;
/** /**
* @var ApiMarkdown * @inheritDoc
*/ */
private static $_instance; protected function identifyLine($lines, $current)
{
if (strncmp($lines[$current], '~~~', 3) === 0) {
return 'fencedCode';
}
return parent::identifyLine($lines, $current);
}
/**
* Consume lines for a fenced code block
*/
protected function consumeFencedCode($lines, $current)
{
// consume until ```
$block = [
'type' => 'code',
'content' => [],
];
$line = rtrim($lines[$current]);
if (strncmp($lines[$current], '~~~', 3) === 0) {
$fence = '~~~';
$language = 'php';
} else {
$fence = substr($line, 0, $pos = strrpos($line, '`') + 1);
$language = substr($line, $pos);
}
if (!empty($language)) {
$block['language'] = $language;
}
for($i = $current + 1, $count = count($lines); $i < $count; $i++) {
if (rtrim($line = $lines[$i]) !== $fence) {
$block['content'][] = $line;
} else {
break;
}
}
return [$block, $i];
}
private $context; /**
* Renders a code block
*/
protected function renderCode($block)
{
if (isset($block['language'])) {
$class = isset($block['language']) ? ' class="language-' . $block['language'] . '"' : '';
return "<pre><code$class>" . $this->highlight(implode("\n", $block['content']) . "\n", $block['language']) . '</code></pre>';
} else {
return parent::renderCode($block);
}
}
public function highlight(&$block, &$markup) protected function highlight($code, $language)
{ {
if ($language !== 'php') {
return htmlspecialchars($code, ENT_NOQUOTES, 'UTF-8');
}
// TODO improve code highlighting // TODO improve code highlighting
if (strncmp($block['text'], '<?php', 5) === 0) { if (strncmp($code, '<?php', 5) === 0) {
$text = highlight_string(trim($block['text']), true); $text = highlight_string(trim($code), true);
} else { } else {
$text = highlight_string("<?php ".trim($block['text']), true); $text = highlight_string("<?php ".trim($code), true);
$text = str_replace('&lt;?php', '', $text); $text = str_replace('&lt;?php', '', $text);
if (($pos = strpos($text, '&nbsp;')) !== false) { if (($pos = strpos($text, '&nbsp;')) !== false) {
$text = substr($text, 0, $pos) . substr($text, $pos + 6); $text = substr($text, 0, $pos) . substr($text, $pos + 6);
...@@ -46,29 +103,19 @@ class ApiMarkdown extends Markdown ...@@ -46,29 +103,19 @@ class ApiMarkdown extends Markdown
// remove <code><span style="color: #000000">\n and </span>tags added by php // remove <code><span style="color: #000000">\n and </span>tags added by php
$text = substr(trim($text), 36, -16); $text = substr(trim($text), 36, -16);
$code = '<pre><code'; return $text;
if (isset($block['language']))
{
if ($block['language'] !== 'php') {
return false;
}
$code .= ' class="language-'.$block['language'].'"';
} }
$code .= '>'.$text.'</code></pre>'."\n";
$markup .= $code; protected function inlineMarkers()
return true; {
return array_merge(parent::inlineMarkers(), [
'[[' => 'parseApiLinks',
]);
} }
public function init() protected function parseApiLinks($text)
{ {
$this->registerBlockHander('code', [$this, 'highlight']); $context = $this->context;
$this->registerBlockHander('fenced', [$this, 'highlight']);
$context = &$this->context;
// register marker for code links
$this->unregisterInlineMarkerHandler('[');
$this->registerInlineMarkerHandler('[[', function($text, &$markup) use (&$context) {
if (preg_match('/^\[\[([\w\d\\\\\(\):$]+)(\|[^\]]*)?\]\]/', $text, $matches)) { if (preg_match('/^\[\[([\w\d\\\\\(\):$]+)(\|[^\]]*)?\]\]/', $text, $matches)) {
...@@ -90,7 +137,10 @@ class ApiMarkdown extends Markdown ...@@ -90,7 +137,10 @@ class ApiMarkdown extends Markdown
'file' => ($context !== null) ? $context->sourceFile : null, 'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $typeName . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''), 'message' => 'broken link to ' . $typeName . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''),
]; ];
$markup .= '<span style="background: #f00;">' . $typeName . '::' . $subjectName . '</span>'; return [
'<span style="background: #f00;">' . $typeName . '::' . $subjectName . '</span>',
$offset
];
} else { } else {
if (($subject = $type->findSubject($subjectName)) !== null) { if (($subject = $type->findSubject($subjectName)) !== null) {
if ($title === null) { if ($title === null) {
...@@ -99,40 +149,47 @@ class ApiMarkdown extends Markdown ...@@ -99,40 +149,47 @@ class ApiMarkdown extends Markdown
$title .= '()'; $title .= '()';
} }
} }
$markup .= static::$renderer->subjectLink($subject, $title); return [
static::$renderer->subjectLink($subject, $title),
$offset
];
} else { } else {
static::$renderer->context->errors[] = [ static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null, 'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $type->name . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''), 'message' => 'broken link to ' . $type->name . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''),
]; ];
$markup .= '<span style="background: #ff0;">' . $type->name . '</span><span style="background: #f00;">::' . $subjectName . '</span>'; return [
'<span style="background: #ff0;">' . $type->name . '</span><span style="background: #f00;">::' . $subjectName . '</span>',
$offset
];
} }
} }
return $offset;
} elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) { } elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) {
$markup .= static::$renderer->subjectLink($subject, $title); return [
return $offset; static::$renderer->subjectLink($subject, $title),
$offset
];
} }
if ($context !== null) { if ($context !== null) {
// Collection resolves relative types // Collection resolves relative types
$object = (new Collection([$object], $context->phpDocContext))->__toString(); $object = (new Collection([$object], $context->phpDocContext))->__toString();
} }
if (($type = static::$renderer->context->getType($object)) !== null) { if (($type = static::$renderer->context->getType($object)) !== null) {
$markup .= static::$renderer->typeLink($type, $title); return [
return $offset; static::$renderer->typeLink($type, $title),
$offset
];
} }
static::$renderer->context->errors[] = [ static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null, 'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $object . (($context !== null) ? ' in ' . $context->name : ''), 'message' => 'broken link to ' . $object . (($context !== null) ? ' in ' . $context->name : ''),
]; ];
$markup .= '<span style="background: #f00;">' . $object . '</span>'; return [
return $offset; '<span style="background: #f00;">' . $object . '</span>',
} else { $offset
$markup .= '[['; ];
return 2;
} }
}); return ['[[', 2];
$this->registerInlineMarkerHandler('[', null);
} }
/** /**
...@@ -140,23 +197,24 @@ class ApiMarkdown extends Markdown ...@@ -140,23 +197,24 @@ class ApiMarkdown extends Markdown
* *
* @param string $content * @param string $content
* @param TypeDoc $context * @param TypeDoc $context
* @param bool $paragraph
* @return string * @return string
*/ */
public static function process($content, $context = null, $line = false) public static function process($content, $context = null, $paragraph = false)
{ {
if (static::$_instance === null) { if (!isset(Markdown::$flavors['api'])) {
static::$_instance = new static; Markdown::$flavors['api'] = new static;
} }
if (is_string($context)) { if (is_string($context)) {
$context = static::$renderer->context->getType($context); $context = static::$renderer->context->getType($context);
} }
static::$_instance->context = $context; Markdown::$flavors['api']->context = $context;
if ($line) { if ($paragraph) {
return static::$_instance->parseLine($content); return Markdown::processParagraph($content, 'api');
} else { } else {
return static::$_instance->parse($content); return Markdown::process($content, 'api');
} }
} }
} }
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\apidoc\helpers;
use Parsedown;
use yii\base\Component;
/**
* A Markdown helper with support for class reference links.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Markdown extends Component
{
private $_parseDown;
protected function getParseDown()
{
if ($this->_parseDown === null) {
$this->_parseDown = new ParseDown();
}
return $this->_parseDown;
}
public function parse($markdown)
{
return $this->getParseDown()->parse($markdown);
}
public function parseLine($markdown)
{
return $this->getParseDown()->parseLine($markdown);
}
public function registerBlockHander($blockName, $callback)
{
$this->getParseDown()->register_block_handler($blockName, $callback);
}
public function unregisterBlockHander($blockName)
{
$this->getParseDown()->remove_block_handler($blockName);
}
public function registerInlineMarkerHandler($marker, $callback)
{
$this->getParseDown()->add_span_marker($marker, $callback);
}
public function unregisterInlineMarkerHandler($marker)
{
$this->getParseDown()->remove_span_marker($marker);
}
}
...@@ -31,7 +31,7 @@ $this->beginPage(); ...@@ -31,7 +31,7 @@ $this->beginPage();
'options' => [ 'options' => [
'class' => 'navbar-inverse navbar-fixed-top', 'class' => 'navbar-inverse navbar-fixed-top',
], ],
'padded' => false, 'renderInnerContainer' => false,
'view' => $this, 'view' => $this,
]); ]);
$extItems = []; $extItems = [];
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
"yiisoft/yii2-composer": "*", "yiisoft/yii2-composer": "*",
"yiisoft/jquery": "~2.0 | ~1.10", "yiisoft/jquery": "~2.0 | ~1.10",
"ezyang/htmlpurifier": "4.6.*", "ezyang/htmlpurifier": "4.6.*",
"michelf/php-markdown": "1.3.*" "cebe/markdown": "0.9.*"
}, },
"autoload": { "autoload": {
"psr-4": { "yii\\": "" } "psr-4": { "yii\\": "" }
......
...@@ -7,38 +7,94 @@ ...@@ -7,38 +7,94 @@
namespace yii\helpers; namespace yii\helpers;
use Michelf\MarkdownExtra; use Yii;
use yii\base\InvalidParamException;
/** /**
* BaseMarkdown provides concrete implementation for [[Markdown]]. * BaseMarkdown provides concrete implementation for [[Markdown]].
* *
* Do not use BaseMarkdown. Use [[Markdown]] instead. * Do not use BaseMarkdown. Use [[Markdown]] instead.
* *
* @author Alexander Makarov <sam@rmcreative.ru> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class BaseMarkdown class BaseMarkdown
{ {
/** /**
* @var MarkdownExtra * @var array a map of markdown flavor names to corresponding parser class configurations.
*/ */
protected static $markdown; public static $flavors = [
'original' => [
'class' => 'cebe\markdown\Markdown',
'html5' => true,
],
'gfm' => [
'class' => 'cebe\markdown\GithubMarkdown',
'html5' => true,
],
'gfm-comment' => [
'class' => 'cebe\markdown\Markdown',
'html5' => true,
'enableNewlines' => true,
],
];
/**
* @var string the markdown flavor to use when none is specified explicitly.
* Defaults to `original`.
* @see $flavors
*/
public static $defaultFlavor = 'original';
/**
* Converts markdown into HTML.
*
* @param string $markdown the markdown text to parse
* @param string $flavor the markdown flavor to use. See [[$flavors]] for available values.
* @return string the parsed HTML output
* @throws \yii\base\InvalidParamException when an undefined flavor is given.
*/
public static function process($markdown, $flavor = 'original')
{
$parser = static::getParser($flavor);
return $parser->parse($markdown);
}
/** /**
* Converts markdown into HTML * Converts markdown into HTML but only parses inline elements.
*
* This can be useful for parsing small comments or description lines.
* *
* @param string $content * @param string $markdown the markdown text to parse
* @param array $config * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values.
* @return string * @return string the parsed HTML output
* @throws \yii\base\InvalidParamException when an undefined flavor is given.
*/ */
public static function process($content, $config = []) public static function processParagraph($markdown, $flavor = 'original')
{ {
if (static::$markdown === null) { $parser = static::getParser($flavor);
static::$markdown = new MarkdownExtra(); return $parser->parseParagraph($markdown);
} }
/**
* @param string $flavor
* @return \cebe\markdown\Parser
* @throws \yii\base\InvalidParamException when an undefined flavor is given.
*/
private static function getParser($flavor)
{
/** @var \cebe\markdown\Markdown $parser */
if (!isset(static::$flavors[$flavor])) {
throw new InvalidParamException("Markdown flavor '$flavor' is not defined.'");
} elseif(!is_object($config = static::$flavors[$flavor])) {
$parser = Yii::createObject($config);
if (is_array($config)) {
foreach ($config as $name => $value) { foreach ($config as $name => $value) {
static::$markdown->{$name} = $value; $parser->{$name} = $value;
}
}
static::$flavors[$flavor] = $parser;
} }
return static::$markdown->transform($content); return static::$flavors[$flavor];
} }
} }
...@@ -13,21 +13,15 @@ namespace yii\helpers; ...@@ -13,21 +13,15 @@ namespace yii\helpers;
* Basic usage is the following: * Basic usage is the following:
* *
* ```php * ```php
* $myHtml = Markdown::process($myText); * $myHtml = Markdown::process($myText); // use original markdown flavor
* $myHtml = Markdown::process($myText, 'gfm'); // use github flavored markdown
* ``` * ```
* *
* If you want to configure the parser: * You can configure multiple flavors using the [[$flavors]] property.
* *
* ```php * For more details please refer to the [Markdown library documentation](https://github.com/cebe/markdown#readme).
* $myHtml = Markdown::process($myText, [
* 'fn_id_prefix' => 'footnote_',
* ]);
* ```
*
* Note that in order to use this helper you need to install "michelf/php-markdown" Composer package.
* *
* For more details please refer to [PHP Markdown library documentation](http://michelf.ca/projects/php-markdown/). * @author Carsten Brandt <mail@cebe.cc>
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0 * @since 2.0
*/ */
class Markdown extends BaseMarkdown class Markdown extends BaseMarkdown
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment