Permalink
Browse files

Continued refactoring `Create` command. Implementing `Adapter` genera…

…tor to generate class adapter stubs.
  • Loading branch information...
nateabele committed May 29, 2012
1 parent 9ede286 commit d88b21151a7ea39938043fef69cddd5007bb5d5e
Showing with 219 additions and 28 deletions.
  1. +66 −20 console/command/Create.php
  2. +152 −8 console/command/create/Adapter.php
  3. +1 −0 tests/cases/core/LibrariesTest.php
View
@@ -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'],
@@ -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);
}
}
@@ -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',

0 comments on commit d88b211

Please sign in to comment.