Browse Source

Start prototyping out Command dispatching.

Add rough code for how commands will be invoked. There is still a ton
not done but it looks like it is going to work out. I'm not in love with
all the typechecking that is going on, but I'll come back to that.
Mark Story 8 years ago
parent
commit
2c9c75261e

+ 24 - 0
src/Console/Command.php

@@ -119,4 +119,28 @@ class Command
     public function initialize()
     {
     }
+
+    /**
+     * Run the command.
+     *
+     * @param array $argv
+     */
+    public function run(array $argv, ConsoleIo $io)
+    {
+        // Initialize command
+        // Parse argv.
+        // If invalid, or help was requested show help
+        // execute the command
+        $args = new Arguments([], [], []);
+        return $this->execute($args, $io);
+    }
+
+    /**
+     * Implement this method with your command's logic.
+     *
+     * @return null|int The exit code or null for success
+     */
+    public function execute(Arguments $args, ConsoleIo $io)
+    {
+    }
 }

+ 62 - 17
src/Console/CommandRunner.php

@@ -134,13 +134,14 @@ class CommandRunner
         array_shift($argv);
 
         $io = $io ?: new ConsoleIo();
-        $shell = $this->getShell($io, $commands, array_shift($argv));
+        $name = $this->resolveName($commands, $io, array_shift($argv));
 
-        try {
-            $shell->initialize();
-            $result = $shell->runCommand($argv, true);
-        } catch (StopException $e) {
-            return $e->getCode();
+        $shell = $this->getShell($io, $commands, $name);
+        if ($shell instanceof Shell) {
+            $result = $this->runShell($shell, $argv);
+        }
+        if ($shell instanceof Command) {
+            $result = $shell->run($argv, $io);
         }
 
         if ($result === null || $result === true) {
@@ -159,10 +160,39 @@ class CommandRunner
      * @param \Cake\Console\ConsoleIo $io The IO wrapper for the created shell class.
      * @param \Cake\Console\CommandCollection $commands The command collection to find the shell in.
      * @param string $name The command name to find
-     * @return \Cake\Console\Shell
+     * @return \Cake\Console\Shell|\Cake\Console\Command
      */
     protected function getShell(ConsoleIo $io, CommandCollection $commands, $name)
     {
+        $instance = $commands->get($name);
+        if (is_string($instance)) {
+            $instance = $this->createShell($instance, $io);
+        }
+        if (method_exists($instance, 'setRootName')) {
+            $instance->setRootName($this->root);
+        }
+        if (method_exists($instance, 'setName')) {
+            $instance->setName("{$this->root} {$name}");
+        }
+        if ($instance instanceof CommandCollectionAwareInterface) {
+            $instance->setCommandCollection($commands);
+        }
+
+        return $instance;
+    }
+
+    /**
+     * Resolve the command name into a name that exists in the collection.
+     *
+     * Apply backwards compatibile inflections and aliases.
+     *
+     * @param \Cake\Console\CommandCollection $commands The command collection to check.
+     * @param \Cake\Console\ConsoleIo $io ConsoleIo object for errors.
+     * @param string $name The name from the CLI args.
+     * @return string The resolved name.
+     */
+    protected function resolveName($commands, $io, $name)
+    {
         if (!$name) {
             $io->err('<error>No command provided. Choose one of the available commands.</error>', 2);
             $name = 'help';
@@ -179,27 +209,42 @@ class CommandRunner
                 " Run `{$this->root} --help` to get the list of valid commands."
             );
         }
-        $instance = $commands->get($name);
-        if (is_string($instance)) {
-            $instance = $this->createShell($instance, $io);
-        }
-        $instance->setRootName($this->root);
-        if ($instance instanceof CommandCollectionAwareInterface) {
-            $instance->setCommandCollection($commands);
-        }
 
-        return $instance;
+        return $name;
+    }
+
+    /**
+     * Execute a Shell class.
+     *
+     * @param \Cake\Console\Shell $shell The shell to run.
+     * @param array $argv The CLI arguments to invoke.
+     * @return int Exit code
+     */
+    protected function runShell(Shell $shell, array $argv)
+    {
+        try {
+            $shell->initialize();
+            return $shell->runCommand($argv, true);
+        } catch (StopException $e) {
+            return $e->getCode();
+        }
     }
 
     /**
      * The wrapper for creating shell instances.
      *
      * @param string $className Shell class name.
+     * @param string $name The name of the command.
      * @param \Cake\Console\ConsoleIo $io The IO wrapper for the created shell class.
      * @return \Cake\Console\Shell
      */
     protected function createShell($className, ConsoleIo $io)
     {
-        return new $className($io);
+        if (is_subclass_of($className, Shell::class)) {
+            return new $className($io);
+        }
+
+        // Command class
+        return new $className();
     }
 }

+ 34 - 24
tests/TestCase/Console/CommandRunnerTest.php

@@ -22,6 +22,7 @@ use Cake\Core\Configure;
 use Cake\Http\BaseApplication;
 use Cake\TestSuite\Stub\ConsoleOutput;
 use Cake\TestSuite\TestCase;
+use TestApp\Command\ExampleCommand;
 use TestApp\Shell\SampleShell;
 
 /**
@@ -238,14 +239,7 @@ class CommandRunnerTest extends TestCase
      */
     public function testRunValidCommandWithAbort()
     {
-        $app = $this->getMockBuilder(BaseApplication::class)
-            ->setMethods(['middleware', 'bootstrap', 'console'])
-            ->setConstructorArgs([$this->config])
-            ->getMock();
-
-        $commands = new CommandCollection(['failure' => SampleShell::class]);
-        $app->method('console')->will($this->returnValue($commands));
-
+        $app = $this->makeAppWithCommands(['failure' => SampleShell::class]);
         $output = new ConsoleOutput();
 
         $runner = new CommandRunner($app, 'cake');
@@ -260,14 +254,7 @@ class CommandRunnerTest extends TestCase
      */
     public function testRunValidCommandReturnInteger()
     {
-        $app = $this->getMockBuilder(BaseApplication::class)
-            ->setMethods(['middleware', 'bootstrap', 'console'])
-            ->setConstructorArgs([$this->config])
-            ->getMock();
-
-        $commands = new CommandCollection(['failure' => SampleShell::class]);
-        $app->method('console')->will($this->returnValue($commands));
-
+        $app = $this->makeAppWithCommands(['failure' => SampleShell::class]);
         $output = new ConsoleOutput();
 
         $runner = new CommandRunner($app, 'cake');
@@ -282,14 +269,7 @@ class CommandRunnerTest extends TestCase
      */
     public function testRunRootNamePropagates()
     {
-        $app = $this->getMockBuilder(BaseApplication::class)
-            ->setMethods(['middleware', 'bootstrap', 'console'])
-            ->setConstructorArgs([$this->config])
-            ->getMock();
-
-        $commands = new CommandCollection(['sample' => SampleShell::class]);
-        $app->method('console')->will($this->returnValue($commands));
-
+        $app = $this->makeAppWithCommands(['sample' => SampleShell::class]);
         $output = new ConsoleOutput();
 
         $runner = new CommandRunner($app, 'widget');
@@ -300,6 +280,24 @@ class CommandRunnerTest extends TestCase
     }
 
     /**
+     * Test running a valid command
+     *
+     * @return void
+     */
+    public function testRunValidCommandClass()
+    {
+        $app = $this->makeAppWithCommands(['ex' => ExampleCommand::class]);
+        $output = new ConsoleOutput();
+
+        $runner = new CommandRunner($app, 'cake');
+        $result = $runner->run(['cake', 'ex'], $this->getMockIo($output));
+        $this->assertSame(Shell::CODE_SUCCESS, $result);
+
+        $messages = implode("\n", $output->messages());
+        $this->assertContains('Example Command!', $messages);
+    }
+
+    /**
      * Test that run() fires off the buildCommands event.
      *
      * @return void
@@ -321,6 +319,18 @@ class CommandRunnerTest extends TestCase
         $this->assertTrue($this->eventTriggered, 'Should have triggered event.');
     }
 
+    protected function makeAppWithCommands($commands)
+    {
+        $app = $this->getMockBuilder(BaseApplication::class)
+            ->setMethods(['middleware', 'bootstrap', 'console'])
+            ->setConstructorArgs([$this->config])
+            ->getMock();
+        $collection = new CommandCollection($commands);
+        $app->method('console')->will($this->returnValue($collection));
+
+        return $app;
+    }
+
     protected function getMockIo($output)
     {
         $io = $this->getMockBuilder(ConsoleIo::class)

+ 14 - 0
tests/test_app/TestApp/Command/ExampleCommand.php

@@ -0,0 +1,14 @@
+<?php
+namespace TestApp\Command;
+
+use Cake\Console\Arguments;
+use Cake\Console\Command;
+use Cake\Console\ConsoleIo;
+
+class ExampleCommand extends Command
+{
+    public function execute(Arguments $args, ConsoleIo $io)
+    {
+        $io->out('Example Command!');
+    }
+}