1
0
Fork 0

Merge remote-tracking branch 'giosh94mhz/script_groups'

pull/4616/head
Jordi Boggiano 2015-11-14 13:47:09 +00:00
commit db5ef12540
3 changed files with 158 additions and 0 deletions

View File

@ -200,3 +200,18 @@ simply running `composer test`:
> **Note:** Composer's bin-dir is pushed on top of the PATH so that binaries
> of dependencies are easily accessible as CLI commands when writing scripts.
Composer script can also called from other scripts, by prefixing the command name
by `@`. For example the following syntax is valid:
```json
{
"scripts": {
"test": [
"@clearCache",
"phpunit"
],
"clearCache": "rm -rf cache/*"
}
}
```

View File

@ -45,6 +45,7 @@ class EventDispatcher
protected $loader;
protected $process;
protected $listeners;
private $eventStack;
/**
* Constructor.
@ -58,6 +59,7 @@ class EventDispatcher
$this->composer = $composer;
$this->io = $io;
$this->process = $process ?: new ProcessExecutor($io);
$this->eventStack = array();
}
/**
@ -145,11 +147,21 @@ class EventDispatcher
{
$listeners = $this->getListeners($event);
$this->pushEvent($event);
$return = 0;
foreach ($listeners as $callable) {
if (!is_string($callable) && is_callable($callable)) {
$event = $this->checkListenerExpectedEvent($callable, $event);
$return = false === call_user_func($callable, $event) ? 1 : 0;
} elseif ($this->isComposerScript($callable)) {
if ($this->io->isVerbose()) {
$this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable));
} else {
$this->io->writeError(sprintf('> %s', $callable));
}
$scriptName = substr($callable, 1);
$return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode()));
} elseif ($this->isPhpScript($callable)) {
$className = substr($callable, 0, strpos($callable, '::'));
$methodName = substr($callable, strpos($callable, '::') + 2);
@ -190,6 +202,8 @@ class EventDispatcher
}
}
$this->popEvent();
return $return;
}
@ -362,4 +376,42 @@ class EventDispatcher
{
return false === strpos($callable, ' ') && false !== strpos($callable, '::');
}
/**
* Checks if string given references a composer run-script
*
* @param string $callable
* @return bool
*/
protected function isComposerScript($callable)
{
return '@' === substr($callable, 0, 1);
}
/**
* Push an event to the stack of active event
*
* @param Event $event
* @throws \RuntimeException
* @return number
*/
protected function pushEvent(Event $event)
{
$eventName = $event->getName();
if (in_array($eventName, $this->eventStack)) {
throw new \RuntimeException(sprintf("Recursive call to '%s' detected", $eventName));
}
return array_push($this->eventStack, $eventName);
}
/**
* Pops the active event from the stack
*
* @return mixed
*/
protected function popEvent()
{
return array_pop($this->eventStack);
}
}

View File

@ -142,6 +142,97 @@ class EventDispatcherTest extends TestCase
$dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
}
public function testDispatcherCanExecuteComposerScriptGroups()
{
$process = $this->getMock('Composer\Util\ProcessExecutor');
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs(array(
$composer = $this->getMock('Composer\Composer'),
$io = $this->getMock('Composer\IO\IOInterface'),
$process,
))
->setMethods(array(
'getListeners',
))
->getMock();
$process->expects($this->exactly(3))
->method('execute')
->will($this->returnValue(0));
$dispatcher->expects($this->atLeastOnce())
->method('getListeners')
->will($this->returnCallback(function (Event $event) {
if ($event->getName() === 'root') {
return array('@group');
} elseif ($event->getName() === 'group') {
return array('echo -n foo', '@subgroup', 'echo -n bar');
} elseif ($event->getName() === 'subgroup') {
return array('echo -n baz');
}
return array();
}));
$io->expects($this->any())
->method('isVerbose')
->willReturn(1);
$io->expects($this->at(1))
->method('writeError')
->with($this->equalTo('> root: @group'));
$io->expects($this->at(3))
->method('writeError')
->with($this->equalTo('> group: echo -n foo'));
$io->expects($this->at(5))
->method('writeError')
->with($this->equalTo('> group: @subgroup'));
$io->expects($this->at(7))
->method('writeError')
->with($this->equalTo('> subgroup: echo -n baz'));
$io->expects($this->at(9))
->method('writeError')
->with($this->equalTo('> group: echo -n bar'));
$dispatcher->dispatch('root', new CommandEvent('root', $composer, $io));
}
/**
* @expectedException RuntimeException
*/
public function testDispatcherDetectInfiniteRecursion()
{
$process = $this->getMock('Composer\Util\ProcessExecutor');
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
->setConstructorArgs(array(
$composer = $this->getMock('Composer\Composer'),
$io = $this->getMock('Composer\IO\IOInterface'),
$process,
))
->setMethods(array(
'getListeners',
))
->getMock();
$dispatcher->expects($this->atLeastOnce())
->method('getListeners')
->will($this->returnCallback(function (Event $event) {
if ($event->getName() === 'root') {
return array('@recurse');
} elseif ($event->getName() === 'recurse') {
return array('@root');
}
return array();
}));
$dispatcher->dispatch('root', new CommandEvent('root', $composer, $io));
}
private function getDispatcherStubForListenersTest($listeners, $io)
{
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')