Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 175 additions & 151 deletions composer.lock

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions src/CommandArgument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace PhpSchool\PhpWorkshop;

/**
* @author Aydin Hassan <aydin@hotmail.co.uk>
*/
class CommandArgument
{
/**
* @var string
*/
private $name;

/**
* @var bool
*/
private $optional;

/**
* @param string $name The name of the argument
* @param bool $optional Whether it is required or not
*/
public function __construct($name, $optional = false)
{
$this->name = $name;
$this->optional = $optional;
}

/**
* @param string $name
* @return static
*/
public static function optional($name)
{
return new static($name, true);
}

/**
* @param string $name
* @return static
*/
public static function required($name)
{
return new static($name);
}

/**
* @return string
*/
public function getName()
{
return $this->name;
}

/**
* @return bool
*/
public function isRequired()
{
return !$this->isOptional();
}

/**
* @return bool
*/
public function isOptional()
{
return $this->optional;
}
}
49 changes: 44 additions & 5 deletions src/CommandDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace PhpSchool\PhpWorkshop;

use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException;

/**
* Represents a command in the workshop framework. Simply consists of a
* command name, required arguments and either a service name or callable to
Expand All @@ -18,9 +20,9 @@ class CommandDefinition
private $name;

/**
* @var string[]
* @var CommandArgument[]
*/
private $args;
private $args = [];

/**
* @var string|callable
Expand All @@ -29,14 +31,51 @@ class CommandDefinition

/**
* @param string $name The name of the command (this is how the student would invoke the command from the cli)
* @param string[] $args A list of required arguments. This must be an array of strings.
* @param string[]|CommandArgument[] $args A list of arguments. Must be an array of strings or `CommandArgument`'s.
* @param string|callable $commandCallable The name of a callable container entry or an actual PHP callable.
*/
public function __construct($name, array $args, $commandCallable)
{
$this->name = $name;
$this->args = $args;
$this->commandCallable = $commandCallable;

array_walk($args, function ($arg) {
$this->addArgument($arg);
});
}

/**
* @param string|CommandArgument $argument
* @return $this
*/
public function addArgument($argument)
{
if (!is_string($argument) && !$argument instanceof CommandArgument) {
throw InvalidArgumentException::notValidParameter(
'argument',
['string', CommandArgument::class],
$argument
);
}

if (is_string($argument)) {
$argument = new CommandArgument($argument);
}

if (count($this->args) === 0) {
$this->args[] = $argument;
return $this;
}

$previousArgument = end($this->args);
if ($previousArgument->isOptional() && $argument->isRequired()) {
throw new InvalidArgumentException(sprintf(
'A required argument cannot follow an optional argument'
));
}

$this->args[] = $argument;
return $this;
}

/**
Expand All @@ -52,7 +91,7 @@ public function getName()
/**
* Get the list of required arguments.
*
* @return array
* @return CommandArgument[]
*/
public function getRequiredArgs()
{
Expand Down
29 changes: 22 additions & 7 deletions src/CommandRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private function addCommand(CommandDefinition $c)
*
* @param array $args
* @return int
* @throws CliRouteNotExists
* @throws CliRouteNotExistsException
*/
public function route(array $args = null)
{
Expand All @@ -109,15 +109,30 @@ public function route(array $args = null)
$commandName = $command;
}
$command = $this->commands[$commandName];
if (count($args) !== count($command->getRequiredArgs())) {
$receivedArgs = count($args);
$missingArgs = array_slice($command->getRequiredArgs(), $receivedArgs);
throw new MissingArgumentException($commandName, $missingArgs);
}

$this->checkRequiredArgs($commandName, $command->getRequiredArgs(), $args);

return $this->resolveCallable($command, array_merge([$appName], $args));
}

/**
* @param string $commandName
* @param array $definitionArgs
* @param array $givenArgs
*/
private function checkRequiredArgs($commandName, array $definitionArgs, array $givenArgs)
{
while (null !== ($definitionArg = array_shift($definitionArgs))) {
$arg = array_shift($givenArgs);

if (null == $arg && !$definitionArg->isOptional()) {
throw new MissingArgumentException($commandName, array_map(function (CommandArgument $argument) {
return $argument->getName();
}, array_merge([$definitionArg], $definitionArgs)));
}
}
}

/**
* Get the closest command to the one typed, but only if there is 3 or less
* characters different
Expand Down Expand Up @@ -187,6 +202,6 @@ private function resolveCallable(CommandDefinition $command, array $args)
*/
private function callCommand(callable $command, array $arguments)
{
return call_user_func_array($command, $arguments);
return $command(...$arguments);
}
}
1 change: 0 additions & 1 deletion test/Asset/PatchableExercise.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ public function getProblem()
*/
public function tearDown()
{

}

/**
Expand Down
1 change: 0 additions & 1 deletion test/Check/ComposerCheckTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public function setUp()

$this->assertTrue($this->check->canRun(ExerciseType::CGI()));
$this->assertTrue($this->check->canRun(ExerciseType::CLI()));

}

public function testExceptionIsThrownIfNotValidExercise()
Expand Down
26 changes: 26 additions & 0 deletions test/CommandArgumentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace PhpSchool\PhpWorkshopTest;

use PhpSchool\PhpWorkshop\CommandArgument;
use PHPUnit_Framework_TestCase;

/**
* @author Aydin Hassan <aydin@hotmail.co.uk>
*/
class CommandArgumentTest extends PHPUnit_Framework_TestCase
{
public function testRequiredArgument()
{
$arg = new CommandArgument('arg1');
$this->assertSame('arg1', $arg->getName());
$this->assertFalse($arg->isOptional());
}

public function testOptionalArgument()
{
$arg = new CommandArgument('arg1', true);
$this->assertSame('arg1', $arg->getName());
$this->assertTrue($arg->isOptional());
}
}
51 changes: 47 additions & 4 deletions test/CommandDefinitionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,68 @@

namespace PhpSchool\PhpWorkshopTest;

use PhpSchool\PhpWorkshop\CommandArgument;
use PhpSchool\PhpWorkshop\Exception\InvalidArgumentException;
use PHPUnit_Framework_TestCase;
use PhpSchool\PhpWorkshop\CommandDefinition;

/**
* Class CommandDefinitionTest
* @package PhpSchool\PhpWorkshopTest
* @author Aydin Hassan <aydin@hotmail.co.uk>
*/
class CommandDefinitionTest extends PHPUnit_Framework_TestCase
{

public function testGettersSetters()
public function testGettersSettersWithStringArgs()
{
$callable = function () {
};
$definition = new CommandDefinition('animal', ['name'], $callable);

$this->assertSame($definition->getName(), 'animal');
$this->assertSame(['name'], $definition->getRequiredArgs());

$requiredArgs = $definition->getRequiredArgs();

$this->assertCount(1, $requiredArgs);
$this->assertInstanceOf(CommandArgument::class, $requiredArgs[0]);
$this->assertSame('name', $requiredArgs[0]->getName());
$this->assertSame($callable, $definition->getCommandCallable());
}

public function testGettersSettersWithObjArgs()
{
$callable = function () {
};
$definition = new CommandDefinition('animal', [new CommandArgument('name')], $callable);

$this->assertSame($definition->getName(), 'animal');

$requiredArgs = $definition->getRequiredArgs();

$this->assertCount(1, $requiredArgs);
$this->assertInstanceOf(CommandArgument::class, $requiredArgs[0]);
$this->assertSame('name', $requiredArgs[0]->getName());
$this->assertSame($callable, $definition->getCommandCallable());
}

public function testExceptionIsThrowWhenTryingToAddRequiredArgAfterOptionalArg()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('A required argument cannot follow an optional argument');

$definition = new CommandDefinition('animal', [], 'strlen');
$definition
->addArgument(CommandArgument::optional('optional-arg'))
->addArgument(CommandArgument::required('required-arg'));
}

public function testExceptionIsThrownWithWrongParameterToAddArgument()
{
$this->expectException(InvalidArgumentException::class);
$msg = 'Parameter: "argument" can only be one of: "string", "PhpSchool\PhpWorkshop\CommandArgument" ';
$msg .= 'Received: "stdClass"';

$this->expectExceptionMessage($msg);
$definition = new CommandDefinition('animal', [], 'strlen');
$definition->addArgument(new \stdClass);
}
}
38 changes: 38 additions & 0 deletions test/CommandRouterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Interop\Container\ContainerInterface;
use InvalidArgumentException;
use PhpSchool\PhpWorkshop\CommandArgument;
use PHPUnit_Framework_TestCase;
use PhpSchool\PhpWorkshop\CommandDefinition;
use PhpSchool\PhpWorkshop\CommandRouter;
Expand Down Expand Up @@ -291,4 +292,41 @@ public function testRouteCommandSpeltIncorrectlyStillRoutes()
);
$router->route(['app', 'verifu', 'some-exercise', 'program.php']);
}

public function testRouteCommandWithOptionalArgument()
{
$mock = $this->getMockBuilder('stdClass')
->setMethods(['__invoke'])
->getMock();

$mock->expects($this->at(0))
->method('__invoke')
->with('app', 'some-exercise')
->will($this->returnValue(true));

$mock->expects($this->at(1))
->method('__invoke')
->with('app', 'some-exercise', 'program.php')
->will($this->returnValue(true));

$c = $this->createMock(ContainerInterface::class);
$router = new CommandRouter(
[
new CommandDefinition(
'verify',
[
'exercise',
new CommandArgument('program', true),
new CommandArgument('some-other-arg', true)
],
$mock
)
],
'verify',
$c
);
$router->route(['app', 'verify', 'some-exercise']);
$router->route(['app', 'verify', 'some-exercise', 'program.php']);
$router->route(['app', 'verify', 'some-exercise', 'program.php', 'some-other-arg-value']);
}
}