Skip to content

Commit

Permalink
Update the features:load command
Browse files Browse the repository at this point in the history
  • Loading branch information
EmanueleMinotto committed Mar 22, 2017
1 parent 54a0871 commit 205e4ac
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
### Changed

* Updated symfony dependencies to require at least v2.8
* Command `features:load` now allows both paths and bundles as an array of arguments

### Fixed

Expand Down
106 changes: 70 additions & 36 deletions Command/LoadFeatureCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
namespace Ae\FeatureBundle\Command;

use Ae\FeatureBundle\Twig\Node\FeatureNode;
use Exception;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Twig_Node;
use Twig_Source;

/**
* @author Carlo Forghieri <carlo@adespresso.com>
Expand All @@ -27,9 +27,9 @@ protected function configure()
->setName('features:load')
->setDescription('Persist new features found in templates')
->addArgument(
'bundle',
InputArgument::REQUIRED,
'The bundle where to load the features'
'path',
InputArgument::REQUIRED | InputArgument::IS_ARRAY,
'The path or bundle where to load the features'
)
->addOption(
'dry-run',
Expand All @@ -44,52 +44,53 @@ protected function configure()
*/
public function execute(InputInterface $input, OutputInterface $output)
{
$twig = $this->getContainer()->get('twig');
$bundle = $this
->getApplication()
->getKernel()
->getBundle($input->getArgument('bundle'));

if (!$bundle) {
throw new InvalidArgumentException("Bundle `$bundle` does not exists");
}
$container = $this->getContainer();
$twig = $container->get('twig');
$files = $this->getFinderInstance($input->getArgument('path'));

$found = [];
$dir = $bundle->getPath().'/Resources/views/';
if (!is_dir($dir)) {
throw new Exception("'Directory `$dir` does not exists.");
}
$finder = new Finder();
$files = $finder->files()->name('*.html.twig')->in($dir);
foreach ($files as $file) {
$tree = $twig->parse(
$twig->tokenize(file_get_contents($file->getPathname()))
);
$tree = $twig->parse($twig->tokenize(new Twig_Source(
file_get_contents($file->getPathname()),
$file->getFilename(),
$file->getPathname()
)));
$tags = $this->findFeatureNodes($tree);
if ($tags) {
$found = array_merge($found, $tags);

foreach ($tags as $tag) {
$output->writeln(sprintf(
'Found <info>%s</info>.<info>%s</info> in <info>%s</info>',
$tag['parent'],
$tag['name'],
$file->getFilename()
));
}

if (empty($tags)) {
continue;
}

$found = array_merge($found, $tags);

foreach ($tags as $tag) {
$output->writeln(sprintf(
'Found <info>%s</info>.<info>%s</info> in <info>%s</info>',
$tag['parent'],
$tag['name'],
$file->getFilename()
));
}
}

if ($input->getOption('dry-run')) {
return;
}

$manager = $this->getContainer()->get('cw_feature.manager');
$manager = $container->get('ae_feature.manager');
foreach ($found as $tag) {
$manager->findOrCreate($tag['name'], $tag['parent']);
}
}

protected function findFeatureNodes(Twig_Node $node)
/**
* Find feature nodes.
*
* @param Twig_Node $node
*
* @return array
*/
private function findFeatureNodes(Twig_Node $node)
{
$found = [];
$stack = [$node];
Expand Down Expand Up @@ -122,4 +123,37 @@ protected function findFeatureNodes(Twig_Node $node)

return array_values($found);
}

/**
* Gets a Finder instance with required paths.
*
* @param array $dirsOrBundles Required directories or bundles
*
* @return Finder
*/
private function getFinderInstance(array $dirsOrBundles)
{
$finder = new Finder();
$application = $this->getApplication();

$kernel = null;
$bundles = [];
if ($application instanceof Application) {
$kernel = $application->getKernel();
$bundles = $kernel->getBundles();
}

foreach ($dirsOrBundles as $dirOrBundle) {
if (null !== $kernel && isset($bundles[$dirOrBundle])) {
$bundle = $kernel->getBundle($dirOrBundle);
$dirOrBundle = $bundle->getPath().'/Resources/views/';
}

$finder->in($dirOrBundle);
}

return $finder
->files()
->name('*.twig');
}
}
7 changes: 4 additions & 3 deletions Resources/doc/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ The provided commands are:
$ console features:create [--enabled] [--role ROLE] <parent> <name>
$ console features:disable <parent> <name>
$ console features:enable [--role ROLE] <parent> <name>
$ console features:load [--dry-run] <bundle>
$ console features:load [--dry-run] <path> (<path>)...
By executing the ``features:load`` command, used features are loaded and stored
directly from the bundle *.twig files inside the ``Resources/views`` directory.
The ``features:load`` command accepts a path like ``app/Resources/views/`` or a
bundle like ``AppBundle`` and the command will look inside the
``Resources/views`` directory for *.twig files.
3 changes: 3 additions & 0 deletions Tests/Command/Fixtures/load_feature.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% feature "featureA" from "group" %}
foo
{% endfeature %}
146 changes: 146 additions & 0 deletions Tests/Command/LoadFeatureCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

namespace Ae\FeatureBundle\Tests\Command;

use Ae\FeatureBundle\Command\LoadFeatureCommand;
use Ae\FeatureBundle\Service\Feature;
use Ae\FeatureBundle\Twig\Extension\FeatureExtension;
use LogicException;
use PHPUnit_Framework_TestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Twig_Environment;
use Twig_Loader_Array;

/**
* @covers \Ae\FeatureBundle\Command\LoadFeatureCommand
*/
class LoadFeatureCommandTest extends PHPUnit_Framework_TestCase
{
/**
* @var KernelInterface
*/
private $kernel;

/**
* @var ContainerInterface
*/
private $container;

/**
* {@inheritdoc}
*/
protected function setUp()
{
$this->kernel = $this
->getMockBuilder(KernelInterface::class)
->disableOriginalConstructor()
->getMock();
$this->container = $this
->getMockBuilder(ContainerInterface::class)
->disableOriginalConstructor()
->getMock();

$this->kernel
->method('getContainer')
->willReturn($this->container);
}

public function testExecuteEmpty()
{
$this->container
->expects($this->exactly(2))
->method('get')
->will($this->returnValueMap([
['twig', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, null],
['ae_feature.manager', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, null],
]));

$this->kernel
->method('getBundles')
->willReturn([]);

$this->kernel
->method('getContainer')
->willReturn($this->container);

$application = new Application($this->kernel);
$application->add(new LoadFeatureCommand());

$command = $application->find('features:load');
$commandTester = new CommandTester($command);
$commandTester->execute([
'command' => $command->getName(),
'path' => [sys_get_temp_dir()],
]);

$this->assertEmpty($commandTester->getDisplay());
}

public function testExecuteWithoutArguments()
{
$this->container
->expects($this->exactly(1))
->method('get')
->with('twig');

$this->kernel
->method('getBundles')
->willReturn([]);

$application = new Application($this->kernel);
$application->add(new LoadFeatureCommand());

$command = $application->find('features:load');
$commandTester = new CommandTester($command);

$this->setExpectedException(LogicException::class);
$commandTester->execute([
'command' => $command->getName(),
'path' => [],
]);
}

public function testExecuteWithTemplates()
{
$service = $this
->getMockBuilder(Feature::class)
->disableOriginalConstructor()
->getMock();

$twig = new Twig_Environment(new Twig_Loader_Array());
$twig->addExtension(new FeatureExtension($service));

$this->container
->expects($this->exactly(1))
->method('get')
->with('twig')
->willReturn($twig);

$this->kernel
->method('getBundles')
->willReturn([]);

$application = new Application($this->kernel);
$application->add(new LoadFeatureCommand());

$command = $application->find('features:load');
$commandTester = new CommandTester($command);

$commandTester->execute([
'command' => $command->getName(),
'path' => [
__DIR__.'/Fixtures',
],
'--dry-run' => true,
]);

$this->assertSame(
'Found group.featureA in load_feature.twig',
trim($commandTester->getDisplay())
);
}
}

0 comments on commit 205e4ac

Please sign in to comment.