From d88b21151a7ea39938043fef69cddd5007bb5d5e Mon Sep 17 00:00:00 2001 From: Nate Abele Date: Tue, 29 May 2012 08:56:33 -0400 Subject: [PATCH] Continued refactoring `Create` command. Implementing `Adapter` generator to generate class adapter stubs. --- console/command/Create.php | 86 ++++++++++++---- console/command/create/Adapter.php | 160 +++++++++++++++++++++++++++-- tests/cases/core/LibrariesTest.php | 1 + 3 files changed, 219 insertions(+), 28 deletions(-) diff --git a/console/command/Create.php b/console/command/Create.php index 1c57a89871..47f5188148 100644 --- a/console/command/Create.php +++ b/console/command/Create.php @@ -11,6 +11,7 @@ use lithium\util\String; use lithium\core\Libraries; use lithium\util\Inflector; +use lithium\analysis\Inspector; use lithium\core\ClassNotFoundException; /** @@ -39,6 +40,14 @@ class Create extends \lithium\console\Command { */ public $template = null; + /** + * Specifies the service location path of the sub-commands used to generate classes or other + * files. Defaults to `'command.create'`. + * + * @var string + */ + public $commandPath = 'command.create'; + /** * Holds library data from `lithium\core\Libraries::get()`. * @@ -53,6 +62,18 @@ class Create extends \lithium\console\Command { */ protected $_commands = array(); + /** + * List of commands to run when executing against a name rather than a command. + * + * @var array + */ + protected $_defaultCommands = array( + array('model'), + array('controller'), + array('test', 'model'), + array('test', 'controller') + ); + /** * Class initializer. Parses template and sets up params that need to be filled. * @@ -63,26 +84,26 @@ protected function _init() { $this->library = $this->library ?: true; $defaults = array('prefix' => null, 'path' => null); $this->_library = (array) Libraries::get($this->library) + $defaults; - $commands = Libraries::locate('command.create'); - - $this->_commands = array_combine($commands, array_map( - function($cmd) { - return str_replace('_', '-', Inflector::underscore( - substr($cmd, strrpos($cmd, '\\') + 1) - )); - }, - $commands - )); + $commands = Libraries::locate($this->commandPath); + + $map = function($cmd) { + return str_replace('_', '-', Inflector::underscore( + substr($cmd, strrpos($cmd, '\\') + 1) + )); + }; + $this->_commands = array_combine($commands, array_map($map, $commands)); } /** * Run the create command. Takes `$command` and delegates to `$command::$method` * - * @param string $command + * @param string $command The name of the command to run, which specifies what type of file to + * generate. Available options can be determined by running the `generators` + * command. * @return boolean */ public function run($command = null) { - if ($command && !in_array($command, $this->_commands)) { + if ($command && !in_array($command, $this->_commands) && !$this->request->args()) { return $this->_default($command); } $this->request->shift(); @@ -98,6 +119,35 @@ public function run($command = null) { return false; } + /** + * Lists the available generator commands that can be used to produce stub classes and files. + * + * @return boolean + */ + public function generators() { + $this->out(''); + $this->out('Available console generators:'); + $this->out(''); + + foreach ($this->_commands as $class => $title) { + $this->out("- {$title}"); + $info = Inspector::info($class); + + if ($info['description']) { + $this->out($info['description']); + } + if ($info['description'] && $info['text']) { + $this->out(''); + } + if ($info['text']) { + $this->out($info['text']); + } + $this->out(''); + $this->out(''); + } + return true; + } + /** * Execute the given sub-command for the current request. * @@ -133,13 +183,9 @@ protected function _execute($command) { * @return boolean */ protected function _default($name) { - $commands = array( - array('model', Inflector::pluralize($name)), - array('controller', Inflector::pluralize($name)), - array('test', 'model', Inflector::pluralize($name)), - array('test', 'controller', Inflector::pluralize($name)) - ); - foreach ($commands as $args) { + foreach ($this->_defaultCommands as $args) { + array_push($args, Inflector::pluralize($name)); + $command = $this->template = $this->request->params['command'] = array_shift($args); $this->request->params['action'] = array_shift($args); $this->request->params['args'] = $args; @@ -158,7 +204,7 @@ protected function _default($name) { * @param array $options * @return string */ - protected function _namespace($request, $options = array()) { + protected function _namespace($request, $options = array()) { $name = $request->command; $defaults = array( 'prefix' => $this->_library['prefix'], diff --git a/console/command/create/Adapter.php b/console/command/create/Adapter.php index 8a6519dbd6..7985f42c30 100644 --- a/console/command/create/Adapter.php +++ b/console/command/create/Adapter.php @@ -8,27 +8,171 @@ namespace lithium\console\command\create; +use RuntimeException; +use lithium\util\String; use lithium\util\Inflector; +use lithium\core\Libraries; +use lithium\analysis\Inspector; /** - * Generate a Model class in the `--library` namespace - * - * `li3 create model Posts` - * `li3 create --library=li3_plugin model Posts` - * + * Generate an adapter class in the `--library` namespace, according to the `--type` specified. */ class Adapter extends \lithium\console\command\Create { - public $class; + /** + * The class to create an adapter for. Can be a short class name, i.e. `Cache`, or a + * fully-qualified class name, i.e. `lithium\storage\Session`. The class must have an + * `$_adapters` property defined, and may also have a custom class type registered with + * `Libraries::paths()`. + * + * @see lithium\core\Adaptable::$_adapters + * @see lithium\core\Libraries::paths() + * @var string + */ + public $type; + + /** + * The base class that the generated adapter should extend. + * + * @var string + */ + public $parent = '\lithium\core\Object'; + + protected $_methodDef = "\tpublic function {:name}({:params}) {\n\t\t\n\t}"; + + protected $_methodDefEnabled = "\tpublic static function enabled() {\n\t\t\n\t}"; + + protected $_typeClass; + + protected function _init() { + parent::_init(); + + $findOptions = array( + 'recursive' => true, + 'filter' => '/' . preg_quote($this->type, '/') . '/', + 'exclude' => '/Mock|Test|\\\\adapter\\\\/i' + ); + + switch (true) { + case (class_exists($this->type)): + $this->_typeClass = $this->type; + break; + case (($located = Libraries::find(true, $findOptions)) && count($located) == 1): + $this->_typeClass = reset($located); + break; + case ($located && count($located) > 1): + $this->out("Multiple matches found for '{$this->type}'"); + + foreach ($located as $i => $class) { + $i++; + $this->out("$i) {$class}"); + } + $located = $located[intval($this->in('Please select one:')) - 1]; + break; + default: + $msg = "Unabled to find class for which to generate adapter. Please use the "; + $msg .= "--type flag to specify a fully-qualified class name."; + throw new RuntimeException($msg); + } + } /** - * Get the class name for the model. + * Get the class name for the adapter. * * @param string $request * @return string */ protected function _class($request) { - return Inflector::camelize(Inflector::pluralize($request->action)); + return Inflector::camelize($request->action); + } + + protected function _namespace($request, $options = array()) { + $path = explode('.', $this->_adapterPath($this->_typeClass)); + $type = array_shift($path); + $paths = Libraries::paths($type); + + $result = str_replace('\\\\', '\\', String::insert(reset($paths), array( + 'library' => $this->_library['prefix'], + 'namespace' => join('\\', $path), + 'class' => null, + 'name' => null + ))); + return rtrim($result, '\\'); + } + + /** + * Gets the dot-separated adapter lookup path for the given adaptable class. + * + * @param string $class The fully-namespaced class name of the class to get the path for. + * @return string Returns the dot-separated service lookup path used to find adapters for the + * given class. + */ + protected function _adapterPath($class) { + if (!$result = Inspector::info($class . '::$_adapters')) { + $msg = "Class `{$class}` is not a valid adapter-supporting class, "; + $msg .= "no `\$_adapters` class property found."; + throw new RuntimeException($msg); + } + return $result['value']; + } + + protected function _parent($request) { + if (class_exists($this->parent)) { + return '\\' . ltrim($this->parent, '\\'); + } + $result = Libraries::find(true, array( + 'recursive' => true, + 'filter' => '/' . preg_quote($this->parent, '/') . '/', + 'exclude' => '/Mock|Test|\\\\adapter\\\\/i' + )); + return reset($result); + } + + protected function _methods($request) { + $methods = array(); + + Inspector::methods($this->_typeClass)->map(function($method) use (&$methods) { + $methods[$method->getName()] = array_map( + function($param) { + $name = '$' . $param->getName(); + + if (!$param->isOptional()) { + return $name; + } + $name .= ' = '; + + switch (true) { + case ($param->getDefaultValue() === null): + $name .= 'null'; + break; + case ($param->getDefaultValue() === array()): + $name .= 'array()'; + break; + default: + $name .= var_export($param->getDefaultValue(), true); + break; + } + + if ($name === '$options = array()' || $name === '$config = array()') { + $name = "array {$name}"; + } + return $name; + }, + $method->getParameters() + ); + }); + $result = array(); + + foreach ($methods as $name => $params) { + if (!$params || array_shift($params) !== '$name') { + continue; + } + $params = join(', ', $params); + $result[] = String::insert($this->_methodDef, compact('name', 'params')); + } + $result[] = $this->_methodDefEnabled; + + return join("\n\n", $result); } } diff --git a/tests/cases/core/LibrariesTest.php b/tests/cases/core/LibrariesTest.php index ca700dd375..a1362f71e6 100644 --- a/tests/cases/core/LibrariesTest.php +++ b/tests/cases/core/LibrariesTest.php @@ -534,6 +534,7 @@ public function testLocateCommandInLithiumRecursiveTrue() { 'lithium\console\command\Route', 'lithium\console\command\Test', 'lithium\console\command\g11n\Extract', + 'lithium\console\command\create\Adapter', 'lithium\console\command\create\Controller', 'lithium\console\command\create\Mock', 'lithium\console\command\create\Model',