duplicated the code helper code to the Twig bundle

The code has been duplicated and not moved for BC reasons.

This code has been duplicated in the Twig bundle to be able to decouple
the web profiler and the exception templates.
This commit is contained in:
Fabien Potencier 2012-11-12 15:06:38 +01:00
parent f3c644061a
commit 56fe8d1ffd
3 changed files with 167 additions and 30 deletions

View File

@ -11,26 +11,33 @@
namespace Symfony\Bundle\TwigBundle\Extension;
use Symfony\Component\DependencyInjection\ContainerInterface;
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 8);
}
/**
* Twig extension for Symfony code helper
*
* Twig extension relate to PHP code and used by the profiler and the default exception templates.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CodeExtension extends \Twig_Extension
{
private $container;
private $fileLinkFormat;
private $rootDir;
private $charset;
/**
* Constructor of Twig Extension to provide functions for code formatting
* Constructor.
*
* @param ContainerInterface $container A ContainerInterface instance
* @param string $fileLinkFormat The format for links to source files
* @param string $rootDir The project root directory
* @param string $charset The charset
*/
public function __construct(ContainerInterface $container)
public function __construct($fileLinkFormat, $rootDir, $charset)
{
$this->container = $container;
$this->fileLinkFormat = empty($fileLinkFormat) ? ini_get('xdebug.file_link_format') : $fileLinkFormat;
$this->rootDir = str_replace('\\', '/', $rootDir).'/';
$this->charset = $charset;
}
/**
@ -52,46 +59,174 @@ class CodeExtension extends \Twig_Extension
public function abbrClass($class)
{
return $this->container->get('templating.helper.code')->abbrClass($class);
$parts = explode('\\', $class);
$short = array_pop($parts);
return sprintf("<abbr title=\"%s\">%s</abbr>", $class, $short);
}
public function abbrMethod($method)
{
return $this->container->get('templating.helper.code')->abbrMethod($method);
if (false !== strpos($method, '::')) {
list($class, $method) = explode('::', $method, 2);
$result = sprintf("%s::%s()", $this->abbrClass($class), $method);
} elseif ('Closure' === $method) {
$result = sprintf("<abbr title=\"%s\">%s</abbr>", $method, $method);
} else {
$result = sprintf("<abbr title=\"%s\">%s</abbr>()", $method, $method);
}
return $result;
}
/**
* Formats an array as a string.
*
* @param array $args The argument array
*
* @return string
*/
public function formatArgs($args)
{
return $this->container->get('templating.helper.code')->formatArgs($args);
$result = array();
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
$parts = explode('\\', $item[1]);
$short = array_pop($parts);
$formattedValue = sprintf("<em>object</em>(<abbr title=\"%s\">%s</abbr>)", $item[1], $short);
} elseif ('array' === $item[0]) {
$formattedValue = sprintf("<em>array</em>(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('string' === $item[0]) {
$formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES, $this->getCharset()));
} elseif ('null' === $item[0]) {
$formattedValue = '<em>null</em>';
} elseif ('boolean' === $item[0]) {
$formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
} elseif ('resource' === $item[0]) {
$formattedValue = '<em>resource</em>';
} else {
$formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES, $this->getCharset()), true));
}
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
}
return implode(', ', $result);
}
/**
* Formats an array as a string.
*
* @param array $args The argument array
*
* @return string
*/
public function formatArgsAsText($args)
{
return $this->container->get('templating.helper.code')->formatArgsAsText($args);
return strip_tags($this->formatArgs($args));
}
/**
* Returns an excerpt of a code file around the given line number.
*
* @param string $file A file path
* @param int $line The selected line number
*
* @return string An HTML string
*/
public function fileExcerpt($file, $line)
{
return $this->container->get('templating.helper.code')->fileExcerpt($file, $line);
if (is_readable($file)) {
$code = highlight_file($file, true);
// remove main code/span tags
$code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
$content = preg_split('#<br />#', $code);
$lines = array();
for ($i = max($line - 3, 1), $max = min($line + 3, count($content)); $i <= $max; $i++) {
$lines[] = '<li'.($i == $line ? ' class="selected"' : '').'><code>'.self::fixCodeMarkup($content[$i - 1]).'</code></li>';
}
return '<ol start="'.max($line - 3, 1).'">'.implode("\n", $lines).'</ol>';
}
}
/**
* Formats a file path.
*
* @param string $file An absolute file path
* @param integer $line The line number
* @param string $text Use this text for the link rather than the file path
*
* @return string
*/
public function formatFile($file, $line, $text = null)
{
return $this->container->get('templating.helper.code')->formatFile($file, $line, $text);
if (null === $text) {
$file = trim($file);
$fileStr = $file;
if (0 === strpos($fileStr, $this->rootDir)) {
$fileStr = str_replace($this->rootDir, '', str_replace('\\', '/', $fileStr));
$fileStr = sprintf('<abbr title="%s">kernel.root_dir</abbr>/%s', $this->rootDir, $fileStr);
}
$text = "$fileStr at line $line";
}
if (false !== $link = $this->getFileLink($file, $line)) {
return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', htmlspecialchars($link, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset), $text);
}
return $text;
}
/**
* Returns the link for a given file/line pair.
*
* @param string $file An absolute file path
* @param integer $line The line number
*
* @return string A link of false
*/
public function getFileLink($file, $line)
{
return $this->container->get('templating.helper.code')->getFileLink($file, $line);
if ($this->fileLinkFormat && is_file($file)) {
return strtr($this->fileLinkFormat, array('%f' => $file, '%l' => $line));
}
return false;
}
public function formatFileFromText($text)
{
return $this->container->get('templating.helper.code')->formatFileFromText($text);
$that = $this;
return preg_replace_callback('/in ("|&quot;)?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) use ($that) {
return 'in '.$that->formatFile($match[2], $match[3]);
}, $text);
}
public function getName()
{
return 'code';
}
protected static function fixCodeMarkup($line)
{
// </span> ending tag from previous line
$opening = strpos($line, '<span');
$closing = strpos($line, '</span>');
if (false !== $closing && (false === $opening || $closing < $opening)) {
$line = substr_replace($line, '', $closing, 7);
}
// missing </span> tag at the end of line
$opening = strpos($line, '<span');
$closing = strpos($line, '</span>');
if (false !== $opening && (false === $closing || $closing > $opening)) {
$line .= '</span>';
}
return $line;
}
}

View File

@ -66,7 +66,9 @@
<service id="twig.extension.code" class="%twig.extension.code.class%" public="false">
<tag name="twig.extension" />
<argument type="service" id="service_container" />
<argument>%templating.helper.code.file_link_format%</argument>
<argument>%kernel.root_dir%</argument>
<argument>%kernel.charset%</argument>
</service>
<service id="twig.extension.routing" class="%twig.extension.routing.class%" public="false">

View File

@ -9,23 +9,18 @@
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper;
namespace Symfony\Bundle\TwigBundle\Tests\Extension;
use Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper;
use Symfony\Bundle\TwigBundle\Extension\CodeExtension;
class CodeHelperTest extends \PHPUnit_Framework_TestCase
class CodeExtensionTest extends \PHPUnit_Framework_TestCase
{
protected static $helper;
public static function setUpBeforeClass()
{
self::$helper = new CodeHelper('txmt://open?url=file://%f&line=%l', '/root', 'UTF-8');
}
protected $helper;
public function testFormatFile()
{
$expected = sprintf('<a href="txmt://open?url=file://%s&amp;line=25" title="Click to open this file" class="file_link">%s at line 25</a>', __FILE__, __FILE__);
$this->assertEquals($expected, self::$helper->formatFile(__FILE__, 25));
$this->assertEquals($expected, $this->getExtension()->formatFile(__FILE__, 25));
}
/**
@ -33,7 +28,7 @@ class CodeHelperTest extends \PHPUnit_Framework_TestCase
*/
public function testGettingClassAbbreviation($class, $abbr)
{
$this->assertEquals(self::$helper->abbrClass($class), $abbr);
$this->assertEquals($this->getExtension()->abbrClass($class), $abbr);
}
/**
@ -41,7 +36,7 @@ class CodeHelperTest extends \PHPUnit_Framework_TestCase
*/
public function testGettingMethodAbbreviation($method, $abbr)
{
$this->assertEquals(self::$helper->abbrMethod($method), $abbr);
$this->assertEquals($this->getExtension()->abbrMethod($method), $abbr);
}
public function getClassNameProvider()
@ -64,6 +59,11 @@ class CodeHelperTest extends \PHPUnit_Framework_TestCase
public function testGetName()
{
$this->assertEquals('code', self::$helper->getName());
$this->assertEquals('code', $this->getExtension()->getName());
}
protected function getExtension()
{
return new CodeExtension('txmt://open?url=file://%f&line=%l', '/root', 'UTF-8');
}
}