From 7d61154e7a143751087002804595e13fc834426e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 19 Dec 2013 01:01:08 +0100 Subject: [PATCH] Add command to list twig functions, filters, globals and tests --- .../TwigBundle/Command/DebugCommand.php | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php diff --git a/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php b/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php new file mode 100644 index 0000000000..a2bf79ee72 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Command/DebugCommand.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Lists twig functions, filters, globals and tests present in the current project + * + * @author Jordi Boggiano + */ +class DebugCommand extends ContainerAwareCommand +{ + protected function configure() + { + $this + ->setName('twig:debug') + ->setDefinition(array( + new InputArgument('filter', InputArgument::OPTIONAL, 'Show details for all entries matching this filter'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'Output format: text or json', 'text'), + )) + ->setDescription('Shows a list of twig functions, filters, globals and tests') + ->setHelp(<<%command.name% command outputs a list of twig functions, +filters, globals and tests. Output can be filtered with an optional argument. + +php %command.full_name% + +The command lists all functions, filters, etc. + +php %command.full_name% date + +The command lists everything that contains the word date. + +php %command.full_name% --format=json + +The command lists everything in a machine readable json format. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $twig = $this->getContainer()->get('twig'); + $types = array('functions', 'filters', 'tests', 'globals'); + + if ($input->getOption('format') === 'json') { + $data = array(); + foreach ($types as $type) { + foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) { + $data[$type][$name] = $this->getMetadata($type, $entity); + } + } + $data['tests'] = array_keys($data['tests']); + $output->writeln(json_encode($data)); + + return 0; + } + + $filter = $input->getArgument('filter'); + + foreach ($types as $index => $type) { + $items = array(); + foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) { + if (!$filter || false !== strpos($name, $filter)) { + $items[$name] = $name . $this->getPrettyMetadata($type, $entity); + } + } + + if (!$items) { + continue; + } + if ($index > 0) { + $output->writeln(''); + } + $output->writeln('' . ucfirst($type) . ''); + ksort($items); + foreach ($items as $item) { + $output->writeln(' '.$item); + } + } + + return 0; + } + + private function getMetadata($type, $entity) + { + if ($type === 'globals') { + return $entity; + } + if ($type === 'tests') { + return; + } + if ($type === 'functions' || $type === 'filters') { + $args = array(); + $cb = $entity->getCallable(); + if (is_null($cb)) { + return; + } + if (is_array($cb)) { + if (!method_exists($cb[0], $cb[1])) { + return; + } + $refl = new \ReflectionMethod($cb[0], $cb[1]); + } elseif (is_object($cb) && is_callable($cb)) { + $refl = new \ReflectionMethod($cb, '__invoke'); + } elseif (function_exists($cb)) { + $refl = new \ReflectionFunction($cb); + } elseif (is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) { + $refl = new \ReflectionMethod($m[1], $m[2]); + } else { + throw new \UnexpectedValueException('Unsupported callback type'); + } + + // filter out context/environment args + $args = array_filter($refl->getParameters(), function ($param) use ($entity) { + if ($entity->needsContext() && $param->getName() === 'context') { + return false; + } + + return !$param->getClass() || $param->getClass()->getName() !== 'Twig_Environment'; + }); + + // format args + $args = array_map(function ($param) { + if ($param->isDefaultValueAvailable()) { + return $param->getName() . ' = ' . json_encode($param->getDefaultValue()); + } + + return $param->getName(); + }, $args); + + if ($type === 'filters') { + // remove the value the filter is applied on + array_shift($args); + } + + return $args; + } + } + + private function getPrettyMetadata($type, $entity) + { + if ($type === 'tests') { + return ''; + } + + try { + $meta = $this->getMetadata($type, $entity); + if ($meta === null) { + return '(unknown?)'; + } + } catch (\UnexpectedValueException $e) { + return ' ' . $e->getMessage() . ''; + } + + if ($type === 'globals') { + if (is_object($meta)) { + return ' = object('.get_class($meta).')'; + } + + return ' = '.substr(@json_encode($meta), 0, 50); + } + + if ($type === 'functions') { + return '(' . implode(', ', $meta) . ')'; + } + + if ($type === 'filters') { + return $meta ? '(' . implode(', ', $meta) . ')' : ''; + } + } +}