Add plugin:list command

This commit is contained in:
Romain Neutron
2014-01-29 18:17:34 +01:00
parent 62b68066e6
commit 6b81b2b39e
13 changed files with 320 additions and 3 deletions

View File

@@ -16,6 +16,7 @@ namespace KonsoleKommander;
* @license http://opensource.org/licenses/gpl-3.0 GPLv3
* @link www.phraseanet.com
*/
use Alchemy\Phrasea\Command\Plugin\ListPlugin;
use Alchemy\Phrasea\Core\Version;
use Alchemy\Phrasea\Command\BuildMissingSubdefs;
use Alchemy\Phrasea\Command\CreateCollection;
@@ -94,6 +95,7 @@ $cli->command(new RescanTechnicalDatas('records:rescan-technical-datas'));
$cli->command(new BuildMissingSubdefs('records:build-missing-subdefs'));
$cli->command(new AddPlugin());
$cli->command(new ListPlugin());
$cli->command(new RemovePlugin());
$cli->command(new Configuration());
$cli->command(new XSendFileConfigurationDumper());

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Command\Plugin;
use Alchemy\Phrasea\Plugin\Plugin;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ListPlugin extends AbstractPluginCommand
{
public function __construct()
{
parent::__construct('plugins:list');
$this
->setDescription('Lists installed plugins');
}
protected function doExecute(InputInterface $input, OutputInterface $output)
{
$plugins = array_map(function (Plugin $plugin) {
if ($plugin->isErroneous()) {
return array('<error>'.$plugin->getName().'</error>', '<error>Error : '.$plugin->getError()->getMessage().'</error>', '');
}
return array($plugin->getName(), $plugin->getManifest()->getVersion(), $plugin->getManifest()->getDescription());
}, $this->container['plugins.manager']->listPlugins());
$table = $this->getHelperSet()->get('table');
$table
->setHeaders(array('Name', 'Version', 'Description'))
->setRows($plugins)
;
$table->render($output);
return 0;
}
}

View File

@@ -11,6 +11,7 @@
namespace Alchemy\Phrasea\Core\CLIProvider;
use Alchemy\Phrasea\Plugin\PluginManager;
use Alchemy\Phrasea\Plugin\Schema\ManifestValidator;
use Alchemy\Phrasea\Plugin\Management\PluginsExplorer;
use Alchemy\Phrasea\Plugin\Management\ComposerInstaller;
@@ -31,6 +32,10 @@ class PluginServiceProvider implements ServiceProviderInterface
{
$app['plugins.schema'] = realpath(__DIR__ . '/../../../../conf.d/plugin-schema.json');
$app['plugins.manager'] = $app->share(function (Application $app) {
return new PluginManager($app['plugins.directory'], $app['plugins.plugins-validator']);
});
$app['plugins.json-validator'] = $app->share(function (Application $app) {
return new JsonValidator();
});

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Plugin;
use Alchemy\Phrasea\Plugin\Exception\PluginValidationException;
use Alchemy\Phrasea\Plugin\Schema\Manifest;
class Plugin
{
private $error;
private $manifest;
private $name;
public function __construct($name, Manifest $manifest = null, PluginValidationException $error = null)
{
if ($manifest === $error || (null !== $manifest && null !== $error)) {
throw new \LogicException('A plugin is either installed (with a stable manifest) or on error (given its error).');
}
$this->name = $name;
$this->manifest = $manifest;
$this->error = $error;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return Boolean
*/
public function isErroneous()
{
return null !== $this->error;
}
/**
* @return Manifest
*/
public function getManifest()
{
return $this->manifest;
}
/**
* @return PluginValidationException
*/
public function getError()
{
return $this->error;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2014 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Plugin;
use Alchemy\Phrasea\Plugin\Schema\PluginValidator;
use Alchemy\Phrasea\Plugin\Exception\PluginValidationException;
use Symfony\Component\Finder\Finder;
class PluginManager
{
private $pluginDir;
private $validator;
public function __construct($pluginDir, PluginValidator $validator)
{
$this->pluginDir = $pluginDir;
$this->validator = $validator;
}
/**
* @return Plugin[] An array containing plugins
*/
public function listPlugins()
{
$finder = new Finder();
$finder
->depth(0)
->in($this->pluginDir)
->directories();
$plugins = array();
foreach ($finder as $pluginDir) {
$manifest = $error = null;
$name = $pluginDir->getBasename();
try {
$manifest = $this->validator->validatePlugin((string) $pluginDir);
} catch (PluginValidationException $e) {
$error = $e;
}
$plugins[$name] = new Plugin($name, $manifest, $error);
}
return $plugins;
}
public function hasPlugin($name)
{
$plugins = $this->listPlugins();
return isset($plugins[$name]);
}
}

View File

@@ -11,8 +11,6 @@
namespace Alchemy\Phrasea\Plugin\Schema;
use Alchemy\Phrasea\Plugin\Schema\ManifestValidator;
use Alchemy\Phrasea\Plugin\Schema\Manifest;
use Alchemy\Phrasea\Plugin\Exception\PluginValidationException;
use Alchemy\Phrasea\Plugin\Exception\JsonValidationException;

View File

@@ -0,0 +1,36 @@
<?php
namespace Alchemy\Tests\Phrasea\Command\Plugin;
use Alchemy\Phrasea\Command\Plugin\ListPlugin;
class ListPluginTest extends PluginCommandTestCase
{
public function testExecute()
{
$input = $this->getMock('Symfony\Component\Console\Input\InputInterface');
$output = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
$table = $this->getMockBuilder('Symfony\Component\Console\Helper\TableHelper')
->disableOriginalConstructor()
->getMock();
$table->expects($this->once())
->method('setHeaders')
->will($this->returnSelf());
$helperSet = $this->getMockBuilder('Symfony\Component\Console\Helper\HelperSet')
->disableOriginalConstructor()
->getMock();
$helperSet->expects($this->once())
->method('get')
->will($this->returnValue($table));
$command = new ListPlugin();
$command->setContainer(self::$DI['cli']);
$command->setHelperSet($helperSet);
$result = $command->execute($input, $output);
$this->assertSame(0, $result);
}
}

View File

@@ -18,6 +18,11 @@ class PluginServiceProvidertest extends ServiceProviderTestCase
'plugins.json-validator',
'JsonSchema\Validator'
),
array(
'Alchemy\Phrasea\Core\CLIProvider\PluginServiceProvider',
'plugins.manager',
'Alchemy\Phrasea\Plugin\PluginManager'
),
array(
'Alchemy\Phrasea\Core\CLIProvider\PluginServiceProvider',
'plugins.plugins-validator',

View File

@@ -2,7 +2,6 @@
namespace Alchemy\Phrasea\Plugin\Management;
use Alchemy\Phrasea\Plugin\Management\ComposerInstaller;
use Alchemy\Phrasea\Utilities\ComposerSetup;
use Guzzle\Http\Client as Guzzle;
use Symfony\Component\Process\ExecutableFinder;

View File

@@ -0,0 +1,43 @@
<?php
namespace Alchemy\Tests\Phrasea\Plugin;
use Alchemy\Phrasea\Plugin\PluginManager;
use Alchemy\Phrasea\Plugin\Schema\PluginValidator;
class PluginManagerTest extends PluginTestCase
{
public function testListGoodPlugins()
{
$manager = new PluginManager(__DIR__ . '/Fixtures/PluginDirInstalled', self::$DI['cli']['plugins.plugins-validator']);
$plugins = $manager->listPlugins();
$this->assertCount(1, $plugins);
$plugin = array_pop($plugins);
$this->assertFalse($plugin->isErroneous());
}
public function testListWrongPlugins()
{
$manager = new PluginManager(__DIR__ . '/Fixtures/WrongPlugins', self::$DI['cli']['plugins.plugins-validator']);
$plugins = $manager->listPlugins();
$this->assertCount(8, $plugins);
$plugin = array_pop($plugins);
$this->assertTrue($plugin->isErroneous());
}
public function testHasPlugin()
{
$manager = new PluginManager(__DIR__ . '/Fixtures/PluginDirInstalled', self::$DI['cli']['plugins.plugins-validator']);
$this->assertTrue($manager->hasPlugin('test-plugin'));
$this->assertFalse($manager->hasPlugin('test-plugin2'));
}
private function createValidatorMock()
{
return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Schema\PluginValidator')
->disableOriginalConstructor()
->getMock();
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Alchemy\Tests\Phrasea\Plugin;
use Alchemy\Phrasea\Plugin\Plugin;
class PluginTest extends PluginTestCase
{
public function testGetters()
{
$manifest = $this->createManifestMock();
$error = $this->getMock('Alchemy\Phrasea\Plugin\Exception\PluginValidationException');
$plugin = new Plugin('toto', $manifest, null);
$this->assertSame('toto', $plugin->getName());
$this->assertSame($manifest, $plugin->getManifest());
$this->assertNull($plugin->getError());
$this->assertFalse($plugin->isErroneous());
$plugin = new Plugin('toto', null, $error);
$this->assertSame('toto', $plugin->getName());
$this->assertNull($plugin->getManifest());
$this->assertSame($error, $plugin->getError());
$this->assertTrue($plugin->isErroneous());
}
/**
* @expectedException \LogicException
*/
public function testBothNull()
{
new Plugin('toto', null, null);
}
/**
* @expectedException \LogicException
*/
public function testBothNotNull()
{
$manifest = $this->createManifestMock();
$error = $this->getMock('Alchemy\Phrasea\Plugin\Exception\PluginValidationException');
new Plugin('toto', $manifest, $error);
}
}

View File

@@ -30,4 +30,11 @@ class PluginTestCase extends \PhraseanetPHPUnitAbstract
{
return __DIR__ . '/../../../../../lib/conf.d/plugin-schema.json';
}
protected function createManifestMock()
{
return $this->getMockBuilder('Alchemy\Phrasea\Plugin\Schema\Manifest')
->disableOriginalConstructor()
->getMock();
}
}