From 9190e0f4dd8ac74965bf2d935e797e1a98dbeff6 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 18 Feb 2014 03:48:31 +0100 Subject: [PATCH 1/2] Changed markdown library to cebe/markdown - supports GFM now - allows defining different flavors fixes #47 --- composer.json | 3 +- extensions/apidoc/composer.json | 3 +- extensions/apidoc/helpers/ApiMarkdown.php | 198 ++++++++++++---------- extensions/apidoc/helpers/Markdown.php | 60 ------- framework/composer.json | 2 +- framework/helpers/BaseMarkdown.php | 86 ++++++++-- framework/helpers/Markdown.php | 16 +- 7 files changed, 186 insertions(+), 182 deletions(-) delete mode 100644 extensions/apidoc/helpers/Markdown.php diff --git a/composer.json b/composer.json index d159bb004a..86246670c7 100644 --- a/composer.json +++ b/composer.json @@ -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.*", diff --git a/extensions/apidoc/composer.json b/extensions/apidoc/composer.json index f2a3771af5..36ef971700 100644 --- a/extensions/apidoc/composer.json +++ b/extensions/apidoc/composer.json @@ -21,8 +21,7 @@ "require": { "yiisoft/yii2": "*", "yiisoft/yii2-bootstrap": "*", - "phpdocumentor/reflection": ">=1.0.3", - "erusev/parsedown": "0.9.*" + "phpdocumentor/reflection": ">=1.0.3" }, "autoload": { "psr-4": { "yii\\apidoc\\": "" } diff --git a/extensions/apidoc/helpers/ApiMarkdown.php b/extensions/apidoc/helpers/ApiMarkdown.php index 9d0c523209..3bbe411e70 100644 --- a/extensions/apidoc/helpers/ApiMarkdown.php +++ b/extensions/apidoc/helpers/ApiMarkdown.php @@ -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,39 @@ use yii\apidoc\templates\BaseRenderer; * @author Carsten Brandt * @since 2.0 */ -class ApiMarkdown extends Markdown +class ApiMarkdown extends GithubMarkdown { /** * @var BaseRenderer */ public static $renderer; + + protected $context; + /** - * @var ApiMarkdown + * Renders a code block */ - private static $_instance; - - private $context; - - public function highlight(&$block, &$markup) + protected function renderCode($block) { - // TODO improve code highlighting - if (strncmp($block['text'], '" . $this->highlight(implode("\n", $block['content']) . "\n", $block['language']) . ''; } else { - $text = highlight_string("\n and tags added by php $text = substr(trim($text), 36, -16); - $code = '
'."\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) { - static::$renderer->context->errors[] = [ - 'file' => ($context !== null) ? $context->sourceFile : null, - 'message' => 'broken link to ' . $typeName . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''), - ]; - $markup .= '' . $typeName . '::' . $subjectName . ''; - } 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 .= '' . $type->name . '::' . $subjectName . ''; - } - } - return $offset; - } elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) { - $markup .= static::$renderer->subjectLink($subject, $title); - return $offset; - } + if (($pos = strpos($object, '::')) !== false) { + $typeName = substr($object, 0, $pos); + $subjectName = substr($object, $pos + 2); if ($context !== null) { // Collection resolves relative types - $object = (new Collection([$object], $context->phpDocContext))->__toString(); + $typeName = (new Collection([$typeName], $context->phpDocContext))->__toString(); } - if (($type = static::$renderer->context->getType($object)) !== null) { - $markup .= static::$renderer->typeLink($type, $title); - return $offset; + $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 [ + '' . $typeName . '::' . $subjectName . '', + $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 ' . $type->name . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''), + ]; + return [ + '' . $type->name . '::' . $subjectName . '', + $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 .= '' . $object . ''; - 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 [ + '' . $object . '', + $offset + ]; + } + return ['[[', 2]; } /** @@ -140,23 +155,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'); } } } diff --git a/extensions/apidoc/helpers/Markdown.php b/extensions/apidoc/helpers/Markdown.php deleted file mode 100644 index 7bd6b3c839..0000000000 --- a/extensions/apidoc/helpers/Markdown.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @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); - } -} diff --git a/framework/composer.json b/framework/composer.json index 0dd71a215b..bd11fc7648 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -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\\": "" } diff --git a/framework/helpers/BaseMarkdown.php b/framework/helpers/BaseMarkdown.php index 52585340a8..f0a5fccb65 100644 --- a/framework/helpers/BaseMarkdown.php +++ b/framework/helpers/BaseMarkdown.php @@ -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 + * @author Carsten Brandt * @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(); + $parser = static::getParser($flavor); + return $parser->parse($parser); + } + + /** + * 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($parser); + } + + /** + * @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; } - foreach ($config as $name => $value) { - static::$markdown->{$name} = $value; - } - return static::$markdown->transform($content); + return static::$flavors[$flavor]; } } diff --git a/framework/helpers/Markdown.php b/framework/helpers/Markdown.php index 326cd34370..88debe8522 100644 --- a/framework/helpers/Markdown.php +++ b/framework/helpers/Markdown.php @@ -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_', - * ]); - * ``` + * For more details please refer to the [Markdown library documentation](https://github.com/cebe/markdown#readme). * - * 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 Alexander Makarov + * @author Carsten Brandt * @since 2.0 */ class Markdown extends BaseMarkdown From c9c7db9ae5c73f4480c3e85e6ff4bec8c12e4743 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 18 Feb 2014 05:01:49 +0100 Subject: [PATCH 2/2] fixed several issues with apidoc generator --- docs/guide/caching.md | 2 +- .../apidoc/commands/RenderController.php | 5 +++ extensions/apidoc/composer.json | 3 +- extensions/apidoc/helpers/ApiMarkdown.php | 42 +++++++++++++++++++ .../templates/bootstrap/layouts/main.php | 2 +- framework/helpers/BaseMarkdown.php | 4 +- 6 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docs/guide/caching.md b/docs/guide/caching.md index 1e4119d437..8e7fdea61e 100644 --- a/docs/guide/caching.md +++ b/docs/guide/caching.md @@ -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) diff --git a/extensions/apidoc/commands/RenderController.php b/extensions/apidoc/commands/RenderController.php index 02030fe7bd..43b66e7256 100644 --- a/extensions/apidoc/commands/RenderController.php +++ b/extensions/apidoc/commands/RenderController.php @@ -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'; diff --git a/extensions/apidoc/composer.json b/extensions/apidoc/composer.json index 36ef971700..69f9c4b637 100644 --- a/extensions/apidoc/composer.json +++ b/extensions/apidoc/composer.json @@ -21,7 +21,8 @@ "require": { "yiisoft/yii2": "*", "yiisoft/yii2-bootstrap": "*", - "phpdocumentor/reflection": ">=1.0.3" + "phpdocumentor/reflection": ">=1.0.3", + "nikic/php-parser": "0.9.*" }, "autoload": { "psr-4": { "yii\\apidoc\\": "" } diff --git a/extensions/apidoc/helpers/ApiMarkdown.php b/extensions/apidoc/helpers/ApiMarkdown.php index 3bbe411e70..cad9df033a 100644 --- a/extensions/apidoc/helpers/ApiMarkdown.php +++ b/extensions/apidoc/helpers/ApiMarkdown.php @@ -29,6 +29,48 @@ class ApiMarkdown extends GithubMarkdown protected $context; + /** + * @inheritDoc + */ + 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]; + } + /** * Renders a code block */ diff --git a/extensions/apidoc/templates/bootstrap/layouts/main.php b/extensions/apidoc/templates/bootstrap/layouts/main.php index 1e953a62cc..801ee654b1 100644 --- a/extensions/apidoc/templates/bootstrap/layouts/main.php +++ b/extensions/apidoc/templates/bootstrap/layouts/main.php @@ -31,7 +31,7 @@ $this->beginPage(); 'options' => [ 'class' => 'navbar-inverse navbar-fixed-top', ], - 'padded' => false, + 'renderInnerContainer' => false, 'view' => $this, ]); $extItems = []; diff --git a/framework/helpers/BaseMarkdown.php b/framework/helpers/BaseMarkdown.php index f0a5fccb65..a22e45ea8b 100644 --- a/framework/helpers/BaseMarkdown.php +++ b/framework/helpers/BaseMarkdown.php @@ -57,7 +57,7 @@ class BaseMarkdown public static function process($markdown, $flavor = 'original') { $parser = static::getParser($flavor); - return $parser->parse($parser); + return $parser->parse($markdown); } /** @@ -73,7 +73,7 @@ class BaseMarkdown public static function processParagraph($markdown, $flavor = 'original') { $parser = static::getParser($flavor); - return $parser->parseParagraph($parser); + return $parser->parseParagraph($markdown); } /**