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 @@
"yiisoft/yii2-composer": "*",
"yiisoft/jquery": "~2.0 | ~1.10",
"ezyang/htmlpurifier": "4.6.*",
"michelf/php-markdown": "1.3.*",
"phpspec/php-diff": ">=1.0.2"
"cebe/markdown": "0.9.*"
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
......
......@@ -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
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).
* [[\yii\caching\WinCache]]: uses PHP [WinCache](http://iis.net/downloads/microsoft/wincache-extension)
......
......@@ -65,6 +65,11 @@ class RenderController extends Controller
$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();
$cacheFile = $targetDir . '/cache/' . md5(serialize($files)) . '.tmp';
......
......@@ -22,7 +22,7 @@
"yiisoft/yii2": "*",
"yiisoft/yii2-bootstrap": "*",
"phpdocumentor/reflection": ">=1.0.3",
"erusev/parsedown": "0.9.*"
"nikic/php-parser": "0.9.*"
},
"autoload": {
"psr-4": { "yii\\apidoc\\": "" }
......
......@@ -7,10 +7,12 @@
namespace yii\apidoc\helpers;
use cebe\markdown\GithubMarkdown;
use phpDocumentor\Reflection\DocBlock\Type\Collection;
use yii\apidoc\models\MethodDoc;
use yii\apidoc\models\TypeDoc;
use yii\apidoc\templates\BaseRenderer;
use yii\helpers\Markdown;
/**
* A Markdown helper with support for class reference links.
......@@ -18,26 +20,81 @@ use yii\apidoc\templates\BaseRenderer;
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ApiMarkdown extends Markdown
class ApiMarkdown extends GithubMarkdown
{
/**
* @var BaseRenderer
*/
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);
}
private $context;
/**
* 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];
}
/**
* 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
if (strncmp($block['text'], '<?php', 5) === 0) {
$text = highlight_string(trim($block['text']), true);
if (strncmp($code, '<?php', 5) === 0) {
$text = highlight_string(trim($code), true);
} else {
$text = highlight_string("<?php ".trim($block['text']), true);
$text = highlight_string("<?php ".trim($code), true);
$text = str_replace('&lt;?php', '', $text);
if (($pos = strpos($text, '&nbsp;')) !== false) {
$text = substr($text, 0, $pos) . substr($text, $pos + 6);
......@@ -46,93 +103,93 @@ class ApiMarkdown extends Markdown
// remove <code><span style="color: #000000">\n and </span>tags added by php
$text = substr(trim($text), 36, -16);
$code = '<pre><code';
if (isset($block['language']))
{
if ($block['language'] !== 'php') {
return false;
}
$code .= ' class="language-'.$block['language'].'"';
}
$code .= '>'.$text.'</code></pre>'."\n";
$markup .= $code;
return true;
return $text;
}
public function init()
protected function inlineMarkers()
{
$this->registerBlockHander('code', [$this, 'highlight']);
$this->registerBlockHander('fenced', [$this, 'highlight']);
return array_merge(parent::inlineMarkers(), [
'[[' => 'parseApiLinks',
]);
}
$context = &$this->context;
// register marker for code links
$this->unregisterInlineMarkerHandler('[');
$this->registerInlineMarkerHandler('[[', function($text, &$markup) use (&$context) {
protected function parseApiLinks($text)
{
$context = $this->context;
if (preg_match('/^\[\[([\w\d\\\\\(\):$]+)(\|[^\]]*)?\]\]/', $text, $matches)) {
if (preg_match('/^\[\[([\w\d\\\\\(\):$]+)(\|[^\]]*)?\]\]/', $text, $matches)) {
$offset = strlen($matches[0]);
$offset = strlen($matches[0]);
$object = $matches[1];
$title = (empty($matches[2]) || $matches[2] == '|') ? null : substr($matches[2], 1);
$object = $matches[1];
$title = (empty($matches[2]) || $matches[2] == '|') ? null : substr($matches[2], 1);
if (($pos = strpos($object, '::')) !== false) {
$typeName = substr($object, 0, $pos);
$subjectName = substr($object, $pos + 2);
if ($context !== null) {
// Collection resolves relative types
$typeName = (new Collection([$typeName], $context->phpDocContext))->__toString();
}
$type = static::$renderer->context->getType($typeName);
if ($type === null) {
if (($pos = strpos($object, '::')) !== false) {
$typeName = substr($object, 0, $pos);
$subjectName = substr($object, $pos + 2);
if ($context !== null) {
// Collection resolves relative types
$typeName = (new Collection([$typeName], $context->phpDocContext))->__toString();
}
$type = static::$renderer->context->getType($typeName);
if ($type === null) {
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $typeName . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''),
];
return [
'<span style="background: #f00;">' . $typeName . '::' . $subjectName . '</span>',
$offset
];
} else {
if (($subject = $type->findSubject($subjectName)) !== null) {
if ($title === null) {
$title = $type->name . '::' . $subject->name;
if ($subject instanceof MethodDoc) {
$title .= '()';
}
}
return [
static::$renderer->subjectLink($subject, $title),
$offset
];
} else {
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $typeName . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''),
'message' => 'broken link to ' . $type->name . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''),
];
return [
'<span style="background: #ff0;">' . $type->name . '</span><span style="background: #f00;">::' . $subjectName . '</span>',
$offset
];
$markup .= '<span style="background: #f00;">' . $typeName . '::' . $subjectName . '</span>';
} else {
if (($subject = $type->findSubject($subjectName)) !== null) {
if ($title === null) {
$title = $type->name . '::' . $subject->name;
if ($subject instanceof MethodDoc) {
$title .= '()';
}
}
$markup .= static::$renderer->subjectLink($subject, $title);
} else {
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'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 $offset;
} elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) {
$markup .= static::$renderer->subjectLink($subject, $title);
return $offset;
}
if ($context !== null) {
// Collection resolves relative types
$object = (new Collection([$object], $context->phpDocContext))->__toString();
}
if (($type = static::$renderer->context->getType($object)) !== null) {
$markup .= static::$renderer->typeLink($type, $title);
return $offset;
}
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $object . (($context !== null) ? ' in ' . $context->name : ''),
} elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) {
return [
static::$renderer->subjectLink($subject, $title),
$offset
];
$markup .= '<span style="background: #f00;">' . $object . '</span>';
return $offset;
} else {
$markup .= '[[';
return 2;
}
});
$this->registerInlineMarkerHandler('[', null);
if ($context !== null) {
// Collection resolves relative types
$object = (new Collection([$object], $context->phpDocContext))->__toString();
}
if (($type = static::$renderer->context->getType($object)) !== null) {
return [
static::$renderer->typeLink($type, $title),
$offset
];
}
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $object . (($context !== null) ? ' in ' . $context->name : ''),
];
return [
'<span style="background: #f00;">' . $object . '</span>',
$offset
];
}
return ['[[', 2];
}
/**
......@@ -140,23 +197,24 @@ class ApiMarkdown extends Markdown
*
* @param string $content
* @param TypeDoc $context
* @param bool $paragraph
* @return string
*/
public static function process($content, $context = null, $line = false)
public static function process($content, $context = null, $paragraph = false)
{
if (static::$_instance === null) {
static::$_instance = new static;
if (!isset(Markdown::$flavors['api'])) {
Markdown::$flavors['api'] = new static;
}
if (is_string($context)) {
$context = static::$renderer->context->getType($context);
}
static::$_instance->context = $context;
Markdown::$flavors['api']->context = $context;
if ($line) {
return static::$_instance->parseLine($content);
if ($paragraph) {
return Markdown::processParagraph($content, 'api');
} 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();
'options' => [
'class' => 'navbar-inverse navbar-fixed-top',
],
'padded' => false,
'renderInnerContainer' => false,
'view' => $this,
]);
$extItems = [];
......
......@@ -55,7 +55,7 @@
"yiisoft/yii2-composer": "*",
"yiisoft/jquery": "~2.0 | ~1.10",
"ezyang/htmlpurifier": "4.6.*",
"michelf/php-markdown": "1.3.*"
"cebe/markdown": "0.9.*"
},
"autoload": {
"psr-4": { "yii\\": "" }
......
......@@ -7,38 +7,94 @@
namespace yii\helpers;
use Michelf\MarkdownExtra;
use Yii;
use yii\base\InvalidParamException;
/**
* BaseMarkdown provides concrete implementation for [[Markdown]].
*
* Do not use BaseMarkdown. Use [[Markdown]] instead.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
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
* Converts markdown into HTML.
*
* @param string $content
* @param array $config
* @return string
* @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($content, $config = [])
public static function process($markdown, $flavor = 'original')
{
if (static::$markdown === null) {
static::$markdown = new MarkdownExtra();
}
foreach ($config as $name => $value) {
static::$markdown->{$name} = $value;
$parser = static::getParser($flavor);
return $parser->parse($markdown);
}
/**
* Converts markdown into HTML but only parses inline elements.
*
* This can be useful for parsing small comments or description lines.
*
* @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 processParagraph($markdown, $flavor = 'original')
{
$parser = static::getParser($flavor);
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) {
$parser->{$name} = $value;
}
}
static::$flavors[$flavor] = $parser;
}
return static::$markdown->transform($content);
return static::$flavors[$flavor];
}
}
......@@ -13,21 +13,15 @@ namespace yii\helpers;
* Basic usage is the following:
*
* ```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
* $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 the [Markdown library documentation](https://github.com/cebe/markdown#readme).
*
* For more details please refer to [PHP Markdown library documentation](http://michelf.ca/projects/php-markdown/).
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
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