diff --git a/app/console/cake b/app/console/cake new file mode 100755 index 000000000..d4d067d59 --- /dev/null +++ b/app/console/cake @@ -0,0 +1,26 @@ +#!/bin/bash +################################################################################ +# +# Bake is a shell script for running CakePHP bake script +# PHP 5 +# +# CakePHP(tm) : Rapid Development Framework (http://cakephp.org) +# Copyright 2005-2010, Cake Software Foundation, Inc. +# +# Licensed under The MIT License +# Redistributions of files must retain the above copyright notice. +# +# @copyright Copyright 2005-2010, Cake Software Foundation, Inc. +# @link http://cakephp.org CakePHP(tm) Project +# @package cake +# @subpackage cake.app.console +# @since CakePHP(tm) v 2.0 +# @license MIT License (http://www.opensource.org/licenses/mit-license.php) +# +################################################################################ +LIB=${0/%cake/} +APP=`pwd` + +exec php -q ${LIB}cake.php -working "${APP}" "$@" + +exit; \ No newline at end of file diff --git a/app/console/cake.bat b/app/console/cake.bat new file mode 100644 index 000000000..6499c2f22 --- /dev/null +++ b/app/console/cake.bat @@ -0,0 +1,33 @@ +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Bake is a shell script for running CakePHP bake script +:: PHP 5 +:: +:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org) +:: Copyright 2005-2010, Cake Software Foundation, Inc. +:: +:: Licensed under The MIT License +:: Redistributions of files must retain the above copyright notice. +:: +:: @copyright Copyright 2005-2010, Cake Software Foundation, Inc. +:: @link http://cakephp.org CakePHP(tm) Project +:: @package cake +:: @subpackage cake.app.console +:: @since CakePHP(tm) v 2.0 +:: @license MIT License (http://www.opensource.org/licenses/mit-license.php) +:: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: In order for this script to work as intended, the cake\console\ folder must be in your PATH + +@echo. +@echo off + +SET app=%0 +SET lib=%~dp0 + +php -q "%lib%cake.php" -working "%CD%" %* + +echo. + +exit /B %ERRORLEVEL% \ No newline at end of file diff --git a/app/console/cake.php b/app/console/cake.php new file mode 100755 index 000000000..af02ae4ff --- /dev/null +++ b/app/console/cake.php @@ -0,0 +1,25 @@ +#!/usr/bin/php -q +<?php +/** + * Command-line code generation utility to automate programmer chores. + * + * Shell dispatcher class + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 1.2.0.5012 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +require_once(dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'cake' . DIRECTORY_SEPARATOR . 'console' . DIRECTORY_SEPARATOR . 'shell_dispatcher.php'); + +return ShellDispatcher::run($argv); diff --git a/cake/console/templates/skel/tests/groups/empty b/app/console/shells/empty similarity index 100% rename from cake/console/templates/skel/tests/groups/empty rename to app/console/shells/empty diff --git a/cake/console/templates/skel/vendors/shells/tasks/empty b/app/console/shells/tasks/empty similarity index 100% rename from cake/console/templates/skel/vendors/shells/tasks/empty rename to app/console/shells/tasks/empty diff --git a/cake/tests/test_app/plugins/test_plugin/vendors/shells/tasks/empty b/app/console/shells/templates/empty similarity index 100% rename from cake/tests/test_app/plugins/test_plugin/vendors/shells/tasks/empty rename to app/console/shells/templates/empty diff --git a/cake/basics.php b/cake/basics.php index 88ea14bdb..602bec763 100644 --- a/cake/basics.php +++ b/cake/basics.php @@ -92,20 +92,39 @@ */ function debug($var = false, $showHtml = false, $showFrom = true) { if (Configure::read('debug') > 0) { + $file = ''; + $line = ''; if ($showFrom) { $calledFrom = debug_backtrace(); - echo '<strong>' . substr(str_replace(ROOT, '', $calledFrom[0]['file']), 1) . '</strong>'; - echo ' (line <strong>' . $calledFrom[0]['line'] . '</strong>)'; + $file = substr(str_replace(ROOT, '', $calledFrom[0]['file']), 1); + $line = $calledFrom[0]['line']; } - echo "\n<pre class=\"cake-debug\">\n"; + $html = <<<HTML +<strong>%s</strong> (line <strong>%s</strong>) +<pre class="cake-debug"> +%s +</pre> +HTML; + $text = <<<TEXT +%s (line %s) +########## DEBUG ########## +%s +########################### + +TEXT; + $template = $html; + if (php_sapi_name() == 'cli') { + $template = $text; + } $var = print_r($var, true); if ($showHtml) { $var = str_replace('<', '<', str_replace('>', '>', $var)); } - echo $var . "\n</pre>\n"; + printf($template, $file, $line, $var); } } + if (!function_exists('sortByKey')) { /** diff --git a/cake/console/cake.php b/cake/console/cake.php index a445bb64a..2db99284e 100644 --- a/cake/console/cake.php +++ b/cake/console/cake.php @@ -20,638 +20,6 @@ * @since CakePHP(tm) v 1.2.0.5012 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ +require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR. 'shell_dispatcher.php'); -/** - * Shell dispatcher - * - * @package cake - * @subpackage cake.cake.console - */ -class ShellDispatcher { - -/** - * Standard input stream. - * - * @var filehandle - * @access public - */ - public $stdin; - -/** - * Standard output stream. - * - * @var filehandle - * @access public - */ - public $stdout; - -/** - * Standard error stream. - * - * @var filehandle - * @access public - */ - public $stderr; - -/** - * Contains command switches parsed from the command line. - * - * @var array - * @access public - */ - public $params = array(); - -/** - * Contains arguments parsed from the command line. - * - * @var array - * @access public - */ - public $args = array(); - -/** - * The file name of the shell that was invoked. - * - * @var string - * @access public - */ - public $shell = null; - -/** - * The class name of the shell that was invoked. - * - * @var string - * @access public - */ - public $shellClass = null; - -/** - * The command called if public methods are available. - * - * @var string - * @access public - */ - public $shellCommand = null; - -/** - * The path locations of shells. - * - * @var array - * @access public - */ - public $shellPaths = array(); - -/** - * The path to the current shell location. - * - * @var string - * @access public - */ - public $shellPath = null; - -/** - * The name of the shell in camelized. - * - * @var string - * @access public - */ - public $shellName = null; - -/** - * TaskCollection object for the command - * - * @var TaskCollection - */ - protected $_Tasks; - -/** - * Constructor - * - * The execution of the script is stopped after dispatching the request with - * a status code of either 0 or 1 according to the result of the dispatch. - * - * @param array $args the argv - * @return void - */ - public function __construct($args = array()) { - set_time_limit(0); - - $this->__initConstants(); - $this->parseParams($args); - $this->_initEnvironment(); - $this->__buildPaths(); - $this->_stop($this->dispatch() === false ? 1 : 0); - } - -/** - * Defines core configuration. - * - * @access private - */ - function __initConstants() { - if (function_exists('ini_set')) { - ini_set('display_errors', '1'); - ini_set('error_reporting', E_ALL & ~E_DEPRECATED); - ini_set('html_errors', false); - ini_set('implicit_flush', true); - ini_set('max_execution_time', 0); - } - - if (!defined('CAKE_CORE_INCLUDE_PATH')) { - define('DS', DIRECTORY_SEPARATOR); - define('CAKE_CORE_INCLUDE_PATH', dirname(dirname(dirname(__FILE__)))); - define('DISABLE_DEFAULT_ERROR_HANDLING', false); - define('CAKEPHP_SHELL', true); - if (!defined('CORE_PATH')) { - if (function_exists('ini_set') && ini_set('include_path', CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ini_get('include_path'))) { - define('CORE_PATH', null); - } else { - define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS); - } - } - } - } - -/** - * Defines current working environment. - * - */ - protected function _initEnvironment() { - $this->stdin = fopen('php://stdin', 'r'); - $this->stdout = fopen('php://stdout', 'w'); - $this->stderr = fopen('php://stderr', 'w'); - - if (!$this->__bootstrap()) { - $this->stderr("\nCakePHP Console: "); - $this->stderr("\nUnable to load Cake core:"); - $this->stderr("\tMake sure " . DS . 'cake' . DS . 'libs exists in ' . CAKE_CORE_INCLUDE_PATH); - $this->_stop(); - } - - if (!isset($this->args[0]) || !isset($this->params['working'])) { - $this->stderr("\nCakePHP Console: "); - $this->stderr('This file has been loaded incorrectly and cannot continue.'); - $this->stderr('Please make sure that ' . DIRECTORY_SEPARATOR . 'cake' . DIRECTORY_SEPARATOR . 'console is in your system path,'); - $this->stderr('and check the manual for the correct usage of this command.'); - $this->stderr('(http://manual.cakephp.org/)'); - $this->_stop(); - } - - if (basename(__FILE__) != basename($this->args[0])) { - $this->stderr("\nCakePHP Console: "); - $this->stderr('Warning: the dispatcher may have been loaded incorrectly, which could lead to unexpected results...'); - if ($this->getInput('Continue anyway?', array('y', 'n'), 'y') == 'n') { - $this->_stop(); - } - } - - $this->shiftArgs(); - } - -/** - * Builds the shell paths. - * - * @access private - * @return void - */ - function __buildPaths() { - $paths = array(); - if (!class_exists('Folder')) { - require LIBS . 'folder.php'; - } - $plugins = App::objects('plugin', null, false); - foreach ((array)$plugins as $plugin) { - $pluginPath = App::pluginPath($plugin); - $path = $pluginPath . 'vendors' . DS . 'shells' . DS; - if (file_exists($path)) { - $paths[] = $path; - } - } - - $vendorPaths = array_values(App::path('vendors')); - foreach ($vendorPaths as $vendorPath) { - $path = rtrim($vendorPath, DS) . DS . 'shells' . DS; - if (file_exists($path)) { - $paths[] = $path; - } - } - - $this->shellPaths = array_values(array_unique(array_merge($paths, App::path('shells')))); - } - -/** - * Initializes the environment and loads the Cake core. - * - * @return boolean Success. - * @access private - */ - function __bootstrap() { - - define('ROOT', $this->params['root']); - define('APP_DIR', $this->params['app']); - define('APP_PATH', $this->params['working'] . DS); - define('WWW_ROOT', APP_PATH . $this->params['webroot'] . DS); - if (!is_dir(ROOT . DS . APP_DIR . DS . 'tmp')) { - define('TMP', CORE_PATH . 'cake' . DS . 'console' . DS . 'templates' . DS . 'skel' . DS . 'tmp' . DS); - } - - $boot = file_exists(ROOT . DS . APP_DIR . DS . 'config' . DS . 'bootstrap.php'); - require CORE_PATH . 'cake' . DS . 'bootstrap.php'; - require_once CORE_PATH . 'cake' . DS . 'console' . DS . 'console_error_handler.php'; - set_exception_handler(array('ConsoleErrorHandler', 'handleException')); - - if (!file_exists(APP_PATH . 'config' . DS . 'core.php')) { - include_once CORE_PATH . 'cake' . DS . 'console' . DS . 'templates' . DS . 'skel' . DS . 'config' . DS . 'core.php'; - App::build(); - } - if (!defined('FULL_BASE_URL')) { - define('FULL_BASE_URL', '/'); - } - - return true; - } - -/** - * Clear the console - * - * @return void - */ - public function clear() { - if (empty($this->params['noclear'])) { - if ( DS === '/') { - passthru('clear'); - } else { - passthru('cls'); - } - } - } - -/** - * Dispatches a CLI request - * - * @return boolean - */ - public function dispatch() { - $arg = $this->shiftArgs(); - - if (!$arg) { - $this->help(); - return false; - } - if ($arg == 'help') { - $this->help(); - return true; - } - - list($plugin, $shell) = pluginSplit($arg); - $this->shell = $shell; - $this->shellName = Inflector::camelize($shell); - $this->shellClass = $this->shellName . 'Shell'; - - $arg = null; - - if (isset($this->args[0])) { - $arg = $this->args[0]; - $this->shellCommand = Inflector::variable($arg); - } - - $Shell = $this->_getShell($plugin); - - if (!$Shell) { - $title = sprintf(__('Error: Class %s could not be loaded.'), $this->shellClass); - $this->stderr($title . "\n"); - return false; - } - - $methods = array(); - - if (is_a($Shell, 'Shell')) { - $Shell->initialize(); - $Shell->loadTasks(); - - foreach ($Shell->taskNames as $task) { - if (is_a($Shell->{$task}, 'Shell')) { - $Shell->{$task}->initialize(); - $Shell->{$task}->loadTasks(); - } - } - - $task = Inflector::camelize($arg); - - if (in_array($task, $Shell->taskNames)) { - $this->shiftArgs(); - $Shell->{$task}->startup(); - - if (isset($this->args[0]) && $this->args[0] == 'help') { - if (method_exists($Shell->{$task}, 'help')) { - $Shell->{$task}->help(); - } else { - $this->help(); - } - return true; - } - return $Shell->{$task}->execute(); - } - $methods = array_diff(get_class_methods('Shell'), array('help')); - } - $methods = array_diff(get_class_methods($Shell), $methods); - $added = in_array(strtolower($arg), array_map('strtolower', $methods)); - $private = $arg[0] == '_' && method_exists($Shell, $arg); - - if (!$private) { - if ($added) { - $this->shiftArgs(); - $Shell->startup(); - return $Shell->{$arg}(); - } - if (method_exists($Shell, 'main')) { - $Shell->startup(); - return $Shell->main(); - } - } - - $title = sprintf(__('Error: Unknown %1$s command %2$s.'), $this->shellName, $arg); - $message = sprintf(__('For usage try `cake %s help`'), $this->shell); - $this->stderr($title . "\n" . $message . "\n"); - return false; - } - -/** - * Get shell to use, either plugin shell or application shell - * - * All paths in the shellPaths property are searched. - * shell, shellPath and shellClass properties are taken into account. - * - * @param string $plugin Optionally the name of a plugin - * @return mixed False if no shell could be found or an object on success - */ - protected function _getShell($plugin = null) { - foreach ($this->shellPaths as $path) { - $this->shellPath = $path . $this->shell . '.php'; - $pluginShellPath = DS . $plugin . DS . 'vendors' . DS . 'shells' . DS; - - if ((strpos($path, $pluginShellPath) !== false || !$plugin) && file_exists($this->shellPath)) { - $loaded = true; - break; - } - } - if (!isset($loaded)) { - return false; - } - - if (!class_exists('Shell')) { - require CONSOLE_LIBS . 'shell.php'; - } - - if (!class_exists($this->shellClass)) { - require $this->shellPath; - } - if (!class_exists($this->shellClass)) { - return false; - } - $Shell = new $this->shellClass($this); - return $Shell; - } - -/** - * Returns a TaskCollection object for Shells to use when loading their tasks. - * - * @return TaskCollection object. - */ - public function getTaskCollection() { - if (empty($this->_Tasks)) { - $this->_Tasks = new TaskCollection($this); - } - return $this->_Tasks; - } - -/** - * Prompts the user for input, and returns it. - * - * @param string $prompt Prompt text. - * @param mixed $options Array or string of options. - * @param string $default Default input value. - * @return Either the default value, or the user-provided input. - */ - public function getInput($prompt, $options = null, $default = null) { - if (!is_array($options)) { - $printOptions = ''; - } else { - $printOptions = '(' . implode('/', $options) . ')'; - } - - if ($default === null) { - $this->stdout($prompt . " $printOptions \n" . '> ', false); - } else { - $this->stdout($prompt . " $printOptions \n" . "[$default] > ", false); - } - $result = fgets($this->stdin); - - if ($result === false) { - exit; - } - $result = trim($result); - - if ($default != null && empty($result)) { - return $default; - } - return $result; - } - -/** - * Outputs to the stdout filehandle. - * - * @param string $string String to output. - * @param boolean $newline If true, the outputs gets an added newline. - * @return integer Returns the number of bytes output to stdout. - */ - public function stdout($string, $newline = true) { - if ($newline) { - return fwrite($this->stdout, $string . "\n"); - } else { - return fwrite($this->stdout, $string); - } - } - -/** - * Outputs to the stderr filehandle. - * - * @param string $string Error text to output. - */ - public function stderr($string) { - fwrite($this->stderr, $string); - } - -/** - * Parses command line options - * - * @param array $params Parameters to parse - */ - public function parseParams($params) { - $this->__parseParams($params); - $defaults = array('app' => 'app', 'root' => dirname(dirname(dirname(__FILE__))), 'working' => null, 'webroot' => 'webroot'); - $params = array_merge($defaults, array_intersect_key($this->params, $defaults)); - $isWin = false; - foreach ($defaults as $default => $value) { - if (strpos($params[$default], '\\') !== false) { - $isWin = true; - break; - } - } - $params = str_replace('\\', '/', $params); - - if (!empty($params['working']) && (!isset($this->args[0]) || isset($this->args[0]) && $this->args[0]{0} !== '.')) { - if (empty($this->params['app']) && $params['working'] != $params['root']) { - $params['root'] = dirname($params['working']); - $params['app'] = basename($params['working']); - } else { - $params['root'] = $params['working']; - } - } - - if ($params['app'][0] == '/' || preg_match('/([a-z])(:)/i', $params['app'], $matches)) { - $params['root'] = dirname($params['app']); - } elseif (strpos($params['app'], '/')) { - $params['root'] .= '/' . dirname($params['app']); - } - - $params['app'] = basename($params['app']); - $params['working'] = rtrim($params['root'], '/') . '/' . $params['app']; - - if (!empty($matches[0]) || !empty($isWin)) { - $params = str_replace('/', '\\', $params); - } - - $this->params = array_merge($this->params, $params); - } - -/** - * Helper for recursively parsing params - * - * @return array params - * @access private - */ - function __parseParams($params) { - $count = count($params); - for ($i = 0; $i < $count; $i++) { - if (isset($params[$i])) { - if ($params[$i]{0} === '-') { - $key = substr($params[$i], 1); - $this->params[$key] = true; - unset($params[$i]); - if (isset($params[++$i])) { - if ($params[$i]{0} !== '-') { - $this->params[$key] = str_replace('"', '', $params[$i]); - unset($params[$i]); - } else { - $i--; - $this->__parseParams($params); - } - } - } else { - $this->args[] = $params[$i]; - unset($params[$i]); - } - - } - } - } - -/** - * Removes first argument and shifts other arguments up - * - * @return mixed Null if there are no arguments otherwise the shifted argument - */ - public function shiftArgs() { - return array_shift($this->args); - } - -/** - * Shows console help - * - */ - public function help() { - $this->clear(); - $this->stdout("\nWelcome to CakePHP v" . Configure::version() . " Console"); - $this->stdout("---------------------------------------------------------------"); - $this->stdout("Current Paths:"); - $this->stdout(" -app: ". $this->params['app']); - $this->stdout(" -working: " . rtrim($this->params['working'], DS)); - $this->stdout(" -root: " . rtrim($this->params['root'], DS)); - $this->stdout(" -core: " . rtrim(CORE_PATH, DS)); - $this->stdout(""); - $this->stdout("Changing Paths:"); - $this->stdout("your working path should be the same as your application path"); - $this->stdout("to change your path use the '-app' param."); - $this->stdout("Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp"); - - $this->stdout("\nAvailable Shells:"); - $shellList = array(); - foreach ($this->shellPaths as $path) { - if (!is_dir($path)) { - continue; - } - $shells = App::objects('file', $path); - if (empty($shells)) { - continue; - } - if (preg_match('@plugins[\\\/]([^\\\/]*)@', $path, $matches)) { - $type = Inflector::camelize($matches[1]); - } elseif (preg_match('@([^\\\/]*)[\\\/]vendors[\\\/]@', $path, $matches)) { - $type = $matches[1]; - } elseif (strpos($path, CAKE_CORE_INCLUDE_PATH . DS . 'cake') === 0) { - $type = 'CORE'; - } else { - $type = 'app'; - } - foreach ($shells as $shell) { - if ($shell !== 'shell.php') { - $shell = str_replace('.php', '', $shell); - $shellList[$shell][$type] = $type; - } - } - } - if ($shellList) { - ksort($shellList); - if (DS === '/') { - $width = exec('tput cols') - 2; - } - if (empty($width)) { - $width = 80; - } - $columns = max(1, floor($width / 30)); - $rows = ceil(count($shellList) / $columns); - - foreach ($shellList as $shell => $types) { - sort($types); - $shellList[$shell] = str_pad($shell . ' [' . implode ($types, ', ') . ']', $width / $columns); - } - $out = array_chunk($shellList, $rows); - for ($i = 0; $i < $rows; $i++) { - $row = ''; - for ($j = 0; $j < $columns; $j++) { - if (!isset($out[$j][$i])) { - continue; - } - $row .= $out[$j][$i]; - } - $this->stdout(" " . $row); - } - } - $this->stdout("\nTo run a command, type 'cake shell_name [args]'"); - $this->stdout("To get help on a specific command, type 'cake shell_name help'"); - } - -/** - * Stop execution of the current script - * - * @param $status see http://php.net/exit for values - * @return void - */ - protected function _stop($status = 0) { - exit($status); - } -} -if (!defined('DISABLE_AUTO_DISPATCH')) { - $dispatcher = new ShellDispatcher($argv); -} +return ShellDispatcher::run($argv); diff --git a/cake/console/console_error_handler.php b/cake/console/libs/console_error_handler.php similarity index 86% rename from cake/console/console_error_handler.php rename to cake/console/libs/console_error_handler.php index 10a8028fd..5dda75a3c 100644 --- a/cake/console/console_error_handler.php +++ b/cake/console/libs/console_error_handler.php @@ -14,11 +14,11 @@ * @link http://cakephp.org CakePHP(tm) Project * @package cake * @subpackage cake.cake.console - * @since CakePHP(tm) v 1.2.0.5074 + * @since CakePHP(tm) v 2.0 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ - App::import('Core', 'ErrorHandler'); +require_once 'console_output.php'; /** * Error Handler for Cake console. Does simple printing of the @@ -44,7 +44,7 @@ class ConsoleErrorHandler extends ErrorHandler { * @param array $messages Error messages */ function __construct($error) { - $this->stderr = fopen('php://stderr', 'w'); + $this->stderr = new ConsoleOutput('php://stderr'); parent::__construct($error); } @@ -105,15 +105,11 @@ class ConsoleErrorHandler extends ErrorHandler { * @return void */ public function _outputMessage($template = null) { - $this->stderr($this->error->getMessage() . "\n" . $this->error->getTraceAsString()); + $this->stderr->write(sprintf( + __("<error>Error:</error> %s\n%s"), + $this->error->getMessage(), + $this->error->getTraceAsString() + )); } -/** - * Outputs to the stderr filehandle. - * - * @param string $string Error text to output. - */ - public function stderr($string) { - fwrite($this->stderr, "Error: ". $string . "\n"); - } } diff --git a/cake/console/libs/console_input.php b/cake/console/libs/console_input.php new file mode 100644 index 000000000..33127ae9b --- /dev/null +++ b/cake/console/libs/console_input.php @@ -0,0 +1,50 @@ +<?php +/** + * ConsoleInput file. + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +/** + * Object wrapper for interacting with stdin + * + * @package cake.console + */ +class ConsoleInput { +/** + * Input value. + * + * @var resource + */ + protected $_input; + +/** + * Constructor + * + * @return void + */ + public function __construct($handle = 'php://stdin') { + $this->_input = fopen($handle, 'r'); + } + +/** + * Read a value from the stream + * + * @return mixed The value of the stream + */ + public function read() { + return fgets($this->_input); + } +} \ No newline at end of file diff --git a/cake/console/libs/console_input_argument.php b/cake/console/libs/console_input_argument.php new file mode 100644 index 000000000..3e8d1eb7d --- /dev/null +++ b/cake/console/libs/console_input_argument.php @@ -0,0 +1,144 @@ +<?php +/** + * ConsoleArgumentOption file + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +/** + * An object to represent a single argument used in the command line. + * ConsoleOptionParser creates these when you use addArgument() + * + * @see ConsoleOptionParser::addArgument() + * @package cake.console + */ +class ConsoleInputArgument { + + protected $_name, $_help, $_required, $_choices; + +/** + * Make a new Input Argument + * + * @param mixed $name The long name of the option, or an array with all the properites. + * @param string $help The help text for this option + * @param boolean $required Whether this argument is required. Missing required args will trigger exceptions + * @param arraty $choices Valid choices for this option. + * @return void + */ + public function __construct($name, $help = '', $required = false, $choices = array()) { + if (is_array($name) && isset($name['name'])) { + foreach ($name as $key => $value) { + $this->{'_' . $key} = $value; + } + } else { + $this->_name = $name; + $this->_help = $help; + $this->_required = $required; + $this->_choices = $choices; + } + } + +/** + * Get the name of the argument + * + * @return string + */ + public function name() { + return $this->_name; + } + +/** + * Generate the help for this this argument. + * + * @param int $width The width to make the name of the option. + * @return string + */ + public function help($width = 0) { + $name = $this->_name; + if (strlen($name) < $width) { + $name = str_pad($name, $width, ' '); + } + $optional = ''; + if (!$this->isRequired()) { + $optional = __(' <comment>(optional)</comment>'); + } + if (!empty($this->_choices)) { + $optional .= sprintf(__(' <comment>(choices: %s)</comment>'), implode('|', $this->_choices)); + } + return sprintf('%s%s%s', $name, $this->_help, $optional); + } + +/** + * Get the usage value for this argument + * + * @return string + */ + public function usage() { + $name = $this->_name; + if (!empty($this->_choices)) { + $name = implode('|', $this->_choices); + } + $name = '<' . $name . '>'; + if (!$this->isRequired()) { + $name = '[' . $name . ']'; + } + return $name; + } + +/** + * Check if this argument is a required argument + * + * @return boolean + */ + public function isRequired() { + return (bool) $this->_required; + } + +/** + * Check that $value is a valid choice for this argument. + * + * @return boolean + */ + public function validChoice($value) { + if (empty($this->_choices)) { + return true; + } + if (!in_array($value, $this->_choices)) { + throw new InvalidArgumentException(sprintf( + __('"%s" is not a valid value for %s. Please use one of "%s"'), + $value, $this->_name, implode(', ', $this->_choices) + )); + } + return true; + } + +/** + * Append this argument to the passed in SimpleXml object. + * + * @param SimpleXmlElement The parent element. + * @return SimpleXmlElement The parent with this argument appended. + */ + public function xml(SimpleXmlElement $parent) { + $option = $parent->addChild('argument'); + $option->addAttribute('name', $this->_name); + $option->addAttribute('help', $this->_help); + $option->addAttribute('required', $this->isRequired()); + $choices = $option->addChild('choices'); + foreach ($this->_choices as $valid) { + $choices->addChild('choice', $valid); + } + return $parent; + } +} \ No newline at end of file diff --git a/cake/console/libs/console_input_option.php b/cake/console/libs/console_input_option.php new file mode 100644 index 000000000..173e19cdc --- /dev/null +++ b/cake/console/libs/console_input_option.php @@ -0,0 +1,166 @@ +<?php +/** + * ConsoleInputOption file + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +/** + * An object to represent a single option used in the command line. + * ConsoleOptionParser creates these when you use addOption() + * + * @see ConsoleOptionParser::addOption() + * @package cake.console + */ +class ConsoleInputOption { + + protected $_name, $_short, $_help, $_boolean, $_default, $_choices; + +/** + * Make a new Input Option + * + * @param mixed $name The long name of the option, or an array with all the properites. + * @param string $short The short alias for this option + * @param string $help The help text for this option + * @param boolean $boolean Whether this option is a boolean option. Boolean options don't consume extra tokens + * @param string $default The default value for this option. + * @param arraty $choices Valid choices for this option. + * @return void + */ + public function __construct($name, $short = null, $help = '', $boolean = false, $default = '', $choices = array()) { + if (is_array($name) && isset($name['name'])) { + foreach ($name as $key => $value) { + $this->{'_' . $key} = $value; + } + } else { + $this->_name = $name; + $this->_short = $short; + $this->_help = $help; + $this->_boolean = $boolean; + $this->_default = $default; + $this->_choices = $choices; + } + } + +/** + * Get the name of the argument + * + * @return string + */ + public function name() { + return $this->_name; + } + +/** + * Generate the help for this this option. + * + * @param int $width The width to make the name of the option. + * @return string + */ + public function help($width = 0) { + $default = $short = ''; + if (!empty($this->_default) && $this->_default !== true) { + $default = sprintf(__(' <comment>(default: %s)</comment>'), $this->_default); + } + if (!empty($this->_choices)) { + $default .= sprintf(__(' <comment>(choices: %s)</comment>'), implode('|', $this->_choices)); + } + if (!empty($this->_short)) { + $short = ', -' . $this->_short; + } + $name = sprintf('--%s%s', $this->_name, $short); + if (strlen($name) < $width) { + $name = str_pad($name, $width, ' '); + } + return sprintf('%s%s%s', $name, $this->_help, $default); + } + +/** + * Get the usage value for this option + * + * @return string + */ + public function usage() { + $name = empty($this->_short) ? '--' . $this->_name : '-' . $this->_short; + $default = ''; + if (!empty($this->_default) && $this->_default !== true) { + $default = ' ' . $this->_default; + } + if (!empty($this->_choices)) { + $default = ' ' . implode('|', $this->_choices); + } + return sprintf('[%s%s]', $name, $default); + } + +/** + * Get the default value for this option + * + * @return void + */ + public function defaultValue() { + return $this->_default; + } + +/** + * Check if this option is a boolean option + * + * @return boolean + */ + public function isBoolean() { + return (bool) $this->_boolean; + } + +/** + * Check that a value is a valid choice for this option. + * + * @return boolean + */ + public function validChoice($value) { + if (empty($this->_choices)) { + return true; + } + if (!in_array($value, $this->_choices)) { + throw new InvalidArgumentException(sprintf( + __('"%s" is not a valid value for --%s. Please use one of "%s"'), + $value, $this->_name, implode(', ', $this->_choices) + )); + } + return true; + } + +/** + * Append the option's xml into the parent. + * + * @param SimpleXmlElement The parent element. + * @return SimpleXmlElement The parent with this option appended. + */ + public function xml(SimpleXmlElement $parent) { + $option = $parent->addChild('option'); + $option->addAttribute('name', '--' . $this->_name); + $short = ''; + if (strlen($this->_short)) { + $short = $this->_short; + } + $option->addAttribute('short', '-' . $short); + $option->addAttribute('boolean', $this->_boolean); + $option->addChild('default', $this->_default); + $choices = $option->addChild('choices'); + foreach ($this->_choices as $valid) { + $choices->addChild('choice', $valid); + } + return $parent; + } +} diff --git a/cake/console/libs/console_input_subcommand.php b/cake/console/libs/console_input_subcommand.php new file mode 100644 index 000000000..514c5a1b9 --- /dev/null +++ b/cake/console/libs/console_input_subcommand.php @@ -0,0 +1,105 @@ +<?php +/** + * ConsoleInputSubcommand file + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +/** + * An object to represent a single subcommand used in the command line. + * ConsoleOptionParser creates these when you use addSubcommand() + * + * @see ConsoleOptionParser::addSubcommand() + * @package cake.console + */ +class ConsoleInputSubcommand { + + protected $_name; + protected $_help; + protected $_parser; + +/** + * Make a new Subcommand + * + * @param mixed $name The long name of the subcommand, or an array with all the properites. + * @param string $help The help text for this option + * @param ConsoleOptionParser $parser A parser for this subcommand. + * @return void + */ + public function __construct($name, $help = '', $parser = null) { + if (is_array($name) && isset($name['name'])) { + foreach ($name as $key => $value) { + $this->{'_' . $key} = $value; + } + } else { + $this->_name = $name; + $this->_help = $help; + $this->_parser = $parser; + } + if (is_array($this->_parser)) { + $this->_parser['command'] = $this->_name; + $this->_parser = ConsoleOptionParser::buildFromArray($this->_parser); + } + } + +/** + * Get the name of the subcommand + * + * @return string + */ + public function name() { + return $this->_name; + } + +/** + * Generate the help for this this subcommand. + * + * @param int $width The width to make the name of the subcommand. + * @return string + */ + public function help($width = 0) { + $name = $this->_name; + if (strlen($name) < $width) { + $name = str_pad($name, $width, ' '); + } + return $name . $this->_help; + } + +/** + * Get the usage value for this option + * + * @return mixed Either false or a ConsoleOptionParser + */ + public function parser() { + if ($this->_parser instanceof ConsoleOptionParser) { + return $this->_parser; + } + return false; + } + +/** + * Append this subcommand to the Parent element + * + * @param SimpleXmlElement The parent element. + * @return SimpleXmlElement The parent with this subcommand appended. + */ + public function xml(SimpleXmlElement $parent) { + $command = $parent->addChild('command'); + $command->addAttribute('name', $this->_name); + $command->addAttribute('help', $this->_help); + return $parent; + } +} diff --git a/cake/console/libs/console_option_parser.php b/cake/console/libs/console_option_parser.php new file mode 100644 index 000000000..c1779d828 --- /dev/null +++ b/cake/console/libs/console_option_parser.php @@ -0,0 +1,591 @@ +<?php +/** + * ConsoleOptionParser file + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +require_once CONSOLE_LIBS . 'console_input_option.php'; +require_once CONSOLE_LIBS . 'console_input_argument.php'; +require_once CONSOLE_LIBS . 'console_input_subcommand.php'; +require_once CONSOLE_LIBS . 'help_formatter.php'; + +/** + * Handles parsing the ARGV in the command line and provides support + * for GetOpt compatible option definition. Provides a builder pattern implementation + * for creating shell option parsers. + * + * @package cake + * @subpackage cake.cake.console + */ +class ConsoleOptionParser { + +/** + * Description text - displays before options when help is generated + * + * @see ConsoleOptionParser::description() + * @var string + */ + protected $_description = null; + +/** + * Epilog text - displays after options when help is generated + * + * @see ConsoleOptionParser::epilog() + * @var string + */ + protected $_epilog = null; + +/** + * Option definitions. + * + * @see ConsoleOptionParser::addOption() + * @var array + */ + protected $_options = array(); + +/** + * Map of short -> long options, generated when using addOption() + * + * @var string + */ + protected $_shortOptions = array(); + +/** + * Positional argument definitions. + * + * @see ConsoleOptionParser::addArgument() + * @var array + */ + protected $_args = array(); + +/** + * Subcommands for this Shell. + * + * @var array + */ + protected $_subcommands = array(); + +/** + * Construct an OptionParser so you can define its behavior + * + * ### Options + * + * Named arguments come in two forms, long and short. Long arguments are preceeded + * by two - and give a more verbose option name. i.e. `--version`. Short arguments are + * preceeded by one - and are only one character long. They usually match with a long option, + * and provide a more terse alternative. + * + * #### Using Options + * + * Options can be defined with both long and short forms. By using `$parser->addOption()` + * you can define new options. The name of the option is used as its long form, and you + * can supply an additional short form, with the `short` option. + * + * Calling options can be done using syntax similar to most *nix command line tools. Long options + * cane either include an `=` or leave it out. + * + * `cake myshell command --connection default --name=something` + * + * Short options can be defined singally or in groups. + * + * `cake myshell command -cn` + * + * ### Positional arguments + * + * If no positional arguments are defined, all of them will be parsed. If you define positional + * arguments any arguments greater than those defined will cause exceptions. Additionally you can + * declare arguments as optional, by setting the required param to false. + * + * `$parser->addArgument('model', array('required' => false));` + * + * ### Providing Help text + * + * By providing help text for your positional arguments and named arguments, the ConsoleOptionParser + * can generate a help display for you. You can view the help for shells by using the `--help` or `-h` switch. + * + * @param string $command The command name this parser is for. The command name is used for generating help. + * @param boolean $defaultOptions Whether you want the verbose and quiet options set. + */ + public function __construct($command = null, $defaultOptions = true) { + $this->_command = $command; + + $this->addOption('help', array( + 'short' => 'h', + 'help' => 'Display this help.', + 'boolean' => true + )); + + if ($defaultOptions) { + $this->addOption('verbose', array( + 'short' => 'v', + 'help' => __('Enable verbose output.'), + 'boolean' => true + ))->addOption('quiet', array( + 'short' => 'q', + 'help' => __('Enable quiet output.'), + 'boolean' => true + )); + } + } + +/** + * Static factory method for creating new OptionParsers so you can chain methods off of them. + * + * @param string $command The command name this parser is for. The command name is used for generating help. + * @param boolean $defaultOptions Whether you want the verbose and quiet options set. + * @return ConsoleOptionParser + */ + public static function create($command, $defaultOptions = true) { + return new ConsoleOptionParser($command, $defaultOptions); + } + +/** + * Build a parser from an array. Uses an array like + * + * {{{ + * $spec = array( + * 'description' => 'text', + * 'epilog' => 'text', + * 'arguments' => array( + * // list of arguments compatible with addArguments. + * ), + * 'options' => array( + * // list of options compatible with addOptions + * ), + * 'subcommands' => array( + * // list of subcommands to add. + * ) + * ); + * }}} + * + * @param array $spec The spec to build the OptionParser with. + * @return ConsoleOptionParser + */ + public static function buildFromArray($spec) { + $parser = new ConsoleOptionParser($spec['command']); + if (!empty($spec['arguments'])) { + $parser->addArguments($spec['arguments']); + } + if (!empty($spec['options'])) { + $parser->addOptions($spec['options']); + } + if (!empty($spec['subcommands'])) { + $parser->addSubcommands($spec['subcommands']); + } + if (!empty($spec['description'])) { + $parser->description($spec['description']); + } + if (!empty($spec['epilog'])) { + $parser->epilog($spec['epilog']); + } + return $parser; + } + +/** + * Get or set the command name for shell/task + * + * @param string $text The text to set, or null if you want to read + * @return mixed If reading, the value of the command. If setting $this will be returned + */ + public function command($text = null) { + if ($text !== null) { + $this->_command = $text; + return $this; + } + return $this->_command; + } + +/** + * Get or set the description text for shell/task + * + * @param mixed $text The text to set, or null if you want to read. . If an array the text will be imploded with "\n" + * @return mixed If reading, the value of the description. If setting $this will be returned + */ + public function description($text = null) { + if ($text !== null) { + if (is_array($text)) { + $text = implode("\n", $text); + } + $this->_description = $text; + return $this; + } + return $this->_description; + } + +/** + * Get or set an epilog to the parser. The epilog is added to the end of + * the options and arguments listing when help is generated. + * + * @param mixed $text Text when setting or null when reading. If an array the text will be imploded with "\n" + * @return mixed If reading, the value of the epilog. If setting $this will be returned. + */ + public function epilog($text = null) { + if ($text !== null) { + if (is_array($text)) { + $text = implode("\n", $text); + } + $this->_epilog = $text; + return $this; + } + return $this->_epilog; + } + +/** + * Add an option to the option parser. Options allow you to define optional or required + * parameters for your console application. Options are defined by the parameters they use. + * + * ### Params + * + * - `short` - The single letter variant for this option, leave undefined for none. + * - `help` - Help text for this option. Used when generating help for the option. + * - `default` - The default value for this option. Defaults are added into the parsed params when the + * attached option is not provided or has no value. Using default and boolean together will not work. + * are added into the parsed parameters when the option is undefined. Defaults to null. + * - `boolean` - The option uses no value, its just a boolean switch. Defaults to false. + * If an option is defined as boolean, it will always be added to the parsed params. If no present + * it will be false, if present it will be true. + * - `choices` A list of valid choices for this option. If left empty all values are valid.. + * An exception will be raised when parse() encounters an invalid value. + * + * @param string $name The long name you want to the value to be parsed out as when options are parsed. + * @param array $params An array of parameters that define the behavior of the option + * @return returns $this. + */ + public function addOption($name, $params = array()) { + $defaults = array( + 'name' => $name, + 'short' => null, + 'help' => '', + 'default' => null, + 'boolean' => false, + 'choices' => array() + ); + $options = array_merge($defaults, $params); + $this->_options[$name] = new ConsoleInputOption($options); + if (!empty($options['short'])) { + $this->_shortOptions[$options['short']] = $name; + } + return $this; + } + +/** + * Add a positional argument to the option parser. + * + * ### Params + * + * - `help` The help text to display for this argument. + * - `required` Whether this parameter is required. + * - `index` The index for the arg, if left undefined the argument will be put + * onto the end of the arguments. If you define the same index twice the first + * option will be overwritten. + * - `choices` A list of valid choices for this argument. If left empty all values are valid.. + * An exception will be raised when parse() encounters an invalid value. + * + * @param string $name The name of the argument. + * @param array $params Parameters for the argument, see above. + * @return $this. + */ + public function addArgument($name, $params = array()) { + $defaults = array( + 'name' => $name, + 'help' => '', + 'index' => count($this->_args), + 'required' => false, + 'choices' => array() + ); + $options = array_merge($defaults, $params); + $index = $options['index']; + unset($options['index']); + + $this->_args[$index] = new ConsoleInputArgument($options); + return $this; + } + +/** + * Add multiple arugments at once. Take an array of arugment defintions. + * The keys are used as the argument names, and the values as params for the argument. + * + * @param array $args Array of arguments to add. + * @see ConsoleOptionParser::addArgument() + * @return $this + */ + public function addArguments(array $args) { + foreach ($args as $name => $params) { + $this->addArgument($name, $params); + } + return $this; + } + +/** + * Add multiple options at once. Takes an array of option definitions. + * The keys are used as option names, and the values as params for the option. + * + * @param array $options Array of options to add. + * @see ConsoleOptionParser::addOption() + * @return $this + */ + public function addOptions(array $options) { + foreach ($options as $name => $params) { + $this->addOption($name, $params); + } + return $this; + } + +/** + * Append a subcommand to the subcommand list. + * Subcommands are usually methods on your Shell, but can also be used to document + * Tasks + * + * ### Params + * + * - `help` - Help text for the subcommand. + * - `parser` - A ConsoleOptionParser for the subcommand. This allows you to create method + * specific option parsers. When help is generated for a subcommand, if a parser is present + * it will be used. + * + * @param string $name Name of the subcommand + * @param array $params Array of params, see above. + * @return $this. + */ + public function addSubcommand($name, $params = array()) { + $defaults = array( + 'name' => $name, + 'help' => '', + 'parser' => null + ); + $options = array_merge($defaults, $params); + $this->_subcommands[$name] = new ConsoleInputSubcommand($options); + return $this; + } + +/** + * Add multiple subcommands at once. + * + * @param array $commands Array of subcommands. + * @return $this + */ + public function addSubcommands(array $commands) { + foreach ($commands as $name => $params) { + $this->addSubcommand($name, $params); + } + return $this; + } + +/** + * Gets the arguments defined in the parser. + * + * @return array Array of argument descriptions + */ + public function arguments() { + return $this->_args; + } + +/** + * Get the defined options in the parser. + * + * @return array + */ + public function options() { + return $this->_options; + } + +/** + * Get the array of defined subcommands + * + * @return array + */ + public function subcommands() { + return $this->_subcommands; + } + +/** + * Parse the argv array into a set of params and args. If $command is not null + * and $command is equal to a subcommand that has a parser, that parser will be used + * to parse the $argv + * + * @param array $argv Array of args (argv) to parse. + * @param string $command The subcommand to use. If this parameter is a subcommand, that has a parser, + * That parser will be used to parse $argv instead. + * @return Array array($params, $args) + * @throws InvalidArgumentException When an invalid parameter is encountered. + * RuntimeException when required arguments are not supplied. + */ + public function parse($argv, $command = null) { + if (isset($this->_subcommands[$command]) && $this->_subcommands[$command]->parser()) { + return $this->_subcommands[$command]->parser()->parse($argv); + } + $params = $args = array(); + $this->_tokens = $argv; + while ($token = array_shift($this->_tokens)) { + if (substr($token, 0, 2) == '--') { + $params = $this->_parseLongOption($token, $params); + } elseif (substr($token, 0, 1) == '-') { + $params = $this->_parseShortOption($token, $params); + } else { + $args = $this->_parseArg($token, $args); + } + } + foreach ($this->_args as $i => $arg) { + if ($arg->isRequired() && !isset($args[$i]) && empty($params['help'])) { + throw new RuntimeException( + sprintf(__('Missing required arguments. %s is required.'), $arg->name()) + ); + } + } + foreach ($this->_options as $option) { + $name = $option->name(); + $isBoolean = $option->isBoolean(); + $default = $option->defaultValue(); + + if ($default !== null && !isset($params[$name]) && !$isBoolean) { + $params[$name] = $default; + } + if ($isBoolean && !isset($params[$name])) { + $params[$name] = false; + } + } + return array($params, $args); + } + +/** + * Gets formatted help for this parser object. + * Generates help text based on the description, options, arguments, subcommands and epilog + * in the parser. + * + * @param string $subcommand If present and a valid subcommand that has a linked parser. + * That subcommands help will be shown instead. + * @param int $width The width to format user content to. Defaults to 72 + * @return string Generated help. + */ + public function help($subcommand = null, $format = 'text', $width = 72) { + if ( + isset($this->_subcommands[$subcommand]) && + $this->_subcommands[$subcommand]->parser() instanceof self + ) { + $subparser = $this->_subcommands[$subcommand]->parser(); + $subparser->command($this->command() . ' ' . $subparser->command()); + return $subparser->help(null, $format, $width); + } + $formatter = new HelpFormatter($this); + if ($format == 'text' || $format === true) { + return $formatter->text($width); + } elseif ($format == 'xml') { + return $formatter->xml(); + } + } + +/** + * Parse the value for a long option out of $this->_tokens. Will handle + * options with an `=` in them. + * + * @param string $option The option to parse. + * @param array $params The params to append the parsed value into + * @return array Params with $option added in. + */ + protected function _parseLongOption($option, $params) { + $name = substr($option, 2); + if (strpos($name, '=') !== false) { + list($name, $value) = explode('=', $name, 2); + array_unshift($this->_tokens, $value); + } + return $this->_parseOption($name, $params); + } + +/** + * Parse the value for a short option out of $this->_tokens + * If the $option is a combination of multiple shortcuts like -otf + * they will be shifted onto the token stack and parsed individually. + * + * @param string $option The option to parse. + * @param array $params The params to append the parsed value into + * @return array Params with $option added in. + */ + protected function _parseShortOption($option, $params) { + $key = substr($option, 1); + if (strlen($key) > 1) { + $flags = str_split($key); + $key = $flags[0]; + for ($i = 1, $len = count($flags); $i < $len; $i++) { + array_unshift($this->_tokens, '-' . $flags[$i]); + } + } + $name = $this->_shortOptions[$key]; + return $this->_parseOption($name, $params); + } + +/** + * Parse an option by its name index. + * + * @param string $option The option to parse. + * @param array $params The params to append the parsed value into + * @return array Params with $option added in. + */ + protected function _parseOption($name, $params) { + if (!isset($this->_options[$name])) { + throw new InvalidArgumentException(sprintf(__('Unknown option `%s`'), $name)); + } + $option = $this->_options[$name]; + $isBoolean = $option->isBoolean(); + $nextValue = $this->_nextToken(); + if (!$isBoolean && !empty($nextValue) && $nextValue{0} != '-') { + array_shift($this->_tokens); + $value = $nextValue; + } elseif ($isBoolean) { + $value = true; + } else { + $value = $option->defaultValue(); + } + if ($option->validChoice($value)) { + $params[$name] = $value; + return $params; + } + } + +/** + * Parse an argument, and ensure that the argument doesn't exceed the number of arguments + * and that the argument is a valid choice. + * + * @param string $argument The argument to append + * @param array $args The array of parsed args to append to. + * @return array Args + */ + protected function _parseArg($argument, $args) { + if (empty($this->_args)) { + array_push($args, $argument); + return $args; + } + $next = count($args); + if (!isset($this->_args[$next])) { + throw new InvalidArgumentException(__('Too many arguments.')); + } + + if ($this->_args[$next]->validChoice($argument)) { + array_push($args, $argument); + return $args; + } + } + +/** + * Find the next token in the argv set. + * + * @param string + * @return next token or '' + */ + protected function _nextToken() { + return isset($this->_tokens[0]) ? $this->_tokens[0] : ''; + } + +} diff --git a/cake/console/libs/console_output.php b/cake/console/libs/console_output.php new file mode 100644 index 000000000..ef47266f0 --- /dev/null +++ b/cake/console/libs/console_output.php @@ -0,0 +1,289 @@ +<?php +/** + * ConsoleOutput file. + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +/** + * Object wrapper for outputing information from a shell application. + * Can be connected to any stream resource that can be used with fopen() + * + * Can generate colourzied output on consoles that support it. There are a few + * built in styles + * + * - `error` Error messages. + * - `warning` Warning messages. + * - `info` Informational messages. + * - `comment` Additional text. + * - `question` Magenta text used for user prompts + * + * By defining styles with addStyle() you can create custom console styles. + * + * ### Using styles in output + * + * You can format console output using tags with the name of the style to apply. From inside a shell object + * + * `$this->out('<warning>Overwrite:</warning> foo.php was overwritten.');` + * + * This would create orange 'Overwrite:' text, while the rest of the text would remain the normal colour. + * See ConsoleOutput::styles() to learn more about defining your own styles. Nested styles are not supported + * at this time. + * + * @package cake.console + */ +class ConsoleOutput { +/** + * Raw output constant - no modification of output text. + */ + const RAW = 0; + +/** + * Plain output - tags will be stripped. + */ + const PLAIN = 1; + +/** + * Colour output - Convert known tags in to ANSI color escape codes. + */ + const COLOR = 2; + +/** + * Constant for a newline. + */ + const LF = PHP_EOL; + +/** + * File handle for output. + * + * @var resource + */ + protected $_output; + +/** + * The current output type. Manipulated with ConsoleOutput::outputAs(); + * + * @var integer. + */ + protected $_outputAs = self::COLOR; + +/** + * text colors used in coloured output. + * + * @var array + */ + protected static $_foregroundColors = array( + 'black' => 30, + 'red' => 31, + 'green' => 32, + 'yellow' => 33, + 'blue' => 34, + 'magenta' => 35, + 'cyan' => 36, + 'white' => 37 + ); + +/** + * background colours used in coloured output. + * + * @var array + */ + protected static $_backgroundColors = array( + 'black' => 40, + 'red' => 41, + 'green' => 42, + 'yellow' => 43, + 'blue' => 44, + 'magenta' => 45, + 'cyan' => 46, + 'white' => 47 + ); + +/** + * formatting options for coloured output + * + * @var string + */ + protected static $_options = array( + 'bold' => 1, + 'underline' => 4, + 'blink' => 5, + 'reverse' => 7, + ); + +/** + * Styles that are available as tags in console output. + * You can modify these styles with ConsoleOutput::styles() + * + * @var array + */ + protected static $_styles = array( + 'error' => array('text' => 'red', 'underline' => true), + 'warning' => array('text' => 'yellow'), + 'info' => array('text' => 'cyan'), + 'success' => array('text' => 'green'), + 'comment' => array('text' => 'blue'), + 'question' => array('text' => "magenta"), + ); + +/** + * Construct the output object. + * + * Checks for a pretty console enviornment. Ansicon allows pretty consoles + * on windows, and is supported. + * + * @return void + */ + public function __construct($stream = 'php://stdout') { + $this->_output = fopen($stream, 'w'); + + if (DS == '\\' && !(bool)env('ANSICON')) { + $this->_outputAs = self::PLAIN; + } + } + +/** + * Outputs a single or multiple messages to stdout. If no parameters + * are passed outputs just a newline. + * + * @param mixed $message A string or a an array of strings to output + * @param integer $newlines Number of newlines to append + * @return integer Returns the number of bytes returned from writing to stdout. + */ + public function write($message, $newlines = 1) { + if (is_array($message)) { + $message = implode(self::LF, $message); + } + return $this->_write($this->styleText($message . str_repeat(self::LF, $newlines))); + } + +/** + * Apply styling to text. + * + * @param string $text Text with styling tags. + * @return string String with color codes added. + */ + public function styleText($text) { + if ($this->_outputAs == self::RAW) { + return $text; + } + if ($this->_outputAs == self::PLAIN) { + return strip_tags($text); + } + return preg_replace_callback( + '/<(?<tag>[a-z0-9-_]+)>(?<text>.*?)<\/(\1)>/ims', array($this, '_replaceTags'), $text + ); + } + +/** + * Replace tags with color codes. + * + * @param array $matches. + * @return string + */ + protected function _replaceTags($matches) { + $style = $this->styles($matches['tag']); + if (empty($style)) { + return '<' . $matches['tag'] . '>' . $matches['text'] . '</' . $matches['tag'] . '>'; + } + + $styleInfo = array(); + if (!empty($style['text']) && isset(self::$_foregroundColors[$style['text']])) { + $styleInfo[] = self::$_foregroundColors[$style['text']]; + } + if (!empty($style['background']) && isset(self::$_backgroundColors[$style['background']])) { + $styleInfo[] = self::$_backgroundColors[$style['background']]; + } + unset($style['text'], $style['background']); + foreach ($style as $option => $value) { + if ($value) { + $styleInfo[] = self::$_options[$option]; + } + } + return "\033[" . implode($styleInfo, ';') . 'm' . $matches['text'] . "\033[0m"; + } + +/** + * Writes a message to the output stream + * + * @param string $message Message to write. + * @return boolean success + */ + protected function _write($message) { + return fwrite($this->_output, $message); + } + +/** + * Get the current styles offered, or append new ones in. + * + * ### Get a style definition + * + * `$this->output->styles('error');` + * + * ### Get all the style definitions + * + * `$this->output->styles();` + * + * ### Create or modify an existing style + * + * `$this->output->styles('annoy', array('text' => 'purple', 'background' => 'yellow', 'blink' => true));` + * + * ### Remove a style + * + * `$this->output->styles('annoy', false);` + * + * @param string $style The style to get or create. + * @param mixed $definition The array definition of the style to change or create a style + * or false to remove a style. + * @return mixed If you are getting styles, the style or null will be returned. If you are creating/modifying + * styles true will be returned. + */ + function styles($style = null, $definition = null) { + if ($style === null && $definition === null) { + return self::$_styles; + } + if (is_string($style) && $definition === null) { + return isset(self::$_styles[$style]) ? self::$_styles[$style] : null; + } + if ($definition === false) { + unset(self::$_styles[$style]); + return true; + } + self::$_styles[$style] = $definition; + return true; + } + +/** + * Get/Set the output type to use. The output type how formatting tags are treated. + * + * @param int $type The output type to use. Should be one of the class contstants. + * @return mixed Either null or the value if getting. + */ + public function outputAs($type = null) { + if ($type === null) { + return $this->_outputAs; + } + $this->_outputAs = $type; + } + +/** + * clean up and close handles + * + * @return void + */ + public function __destruct() { + fclose($this->_output); + } +} \ No newline at end of file diff --git a/cake/console/libs/help_formatter.php b/cake/console/libs/help_formatter.php new file mode 100644 index 000000000..7af4cadf1 --- /dev/null +++ b/cake/console/libs/help_formatter.php @@ -0,0 +1,176 @@ +<?php +/** + * HelpFormatter + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +/** + * HelpFormatter formats help for console shells. Can format to either + * text or XML formats. Uses ConsoleOptionParser methods to generate help. + * + * Generally not directly used. Using $parser->help($command, 'xml'); is usually + * how you would access help. Or via the `--help=xml` option on the command line. + * + * Xml output is useful for intergration with other tools like IDE's or other build tools. + * + */ +class HelpFormatter { +/** + * Build the help formatter for a an OptionParser + * + * @return void + */ + public function __construct(ConsoleOptionParser $parser) { + $this->_parser = $parser; + } + +/** + * Get the help as formatted text suitable for output on the command line. + * + * @param integer $width The width of the help output. + * @return string + */ + public function text($width = 72) { + $parser = $this->_parser; + $out = array(); + $description = $parser->description(); + if (!empty($description)) { + $out[] = String::wrap($description, $width); + $out[] = ''; + } + $out[] = '<info>Usage:</info>'; + $out[] = $this->_generateUsage(); + $out[] = ''; + $subcommands = $parser->subcommands(); + if (!empty($subcommands)) { + $out[] = '<info>Subcommands:</info>'; + $out[] = ''; + $max = $this->_getMaxLength($subcommands) + 2; + foreach ($subcommands as $command) { + $out[] = String::wrap($command->help($max), array( + 'width' => $width, + 'indent' => str_repeat(' ', $max), + 'indentAt' => 1 + )); + } + $out[] = ''; + $out[] = sprintf( + __('To see help on a subcommand use <info>`cake %s [subcommand] --help`</info>'), + $parser->command() + ); + $out[] = ''; + } + + $options = $parser->options(); + if (!empty($options)) { + $max = $this->_getMaxLength($options) + 8; + $out[] = '<info>Options:</info>'; + $out[] = ''; + foreach ($options as $option) { + $out[] = String::wrap($option->help($max), array( + 'width' => $width, + 'indent' => str_repeat(' ', $max), + 'indentAt' => 1 + )); + } + $out[] = ''; + } + + $arguments = $parser->arguments(); + if (!empty($arguments)) { + $max = $this->_getMaxLength($arguments) + 2; + $out[] = '<info>Arguments:</info>'; + $out[] = ''; + foreach ($arguments as $argument) { + $out[] = String::wrap($argument->help($max), array( + 'width' => $width, + 'indent' => str_repeat(' ', $max), + 'indentAt' => 1 + )); + } + $out[] = ''; + } + $epilog = $parser->epilog(); + if (!empty($epilog)) { + $out[] = String::wrap($epilog, $width); + $out[] = ''; + } + return implode("\n", $out); + } + +/** + * Generate the usage for a shell based on its arguments and options. + * Usage strings favour short options over the long ones. and optional args will + * be indicated with [] + * + * @return string + */ + protected function _generateUsage() { + $usage = array('cake ' . $this->_parser->command()); + $subcommands = $this->_parser->subcommands(); + if (!empty($subcommands)) { + $usage[] = '[subcommand]'; + } + foreach ($this->_parser->options() as $option) { + $usage[] = $option->usage(); + } + foreach ($this->_parser->arguments() as $argument) { + $usage[] = $argument->usage(); + } + return implode(' ', $usage); + } + +/** + * Iterate over a collection and find the longest named thing. + * + * @return integer + */ + protected function _getMaxLength($collection) { + $max = 0; + foreach ($collection as $item) { + $max = (strlen($item->name()) > $max) ? strlen($item->name()) : $max; + } + return $max; + } + +/** + * Get the help as an xml string. + * + * @param boolean $string Return the SimpleXml object or a string. Defaults to true. + * @return mixed. See $string + */ + public function xml($string = true) { + $parser = $this->_parser; + $xml = new SimpleXmlElement('<shell></shell>'); + $xml->addChild('commmand', $parser->command()); + $xml->addChild('description', $parser->description()); + + $xml->addChild('epilog', $parser->epilog()); + $subcommands = $xml->addChild('subcommands'); + foreach ($parser->subcommands() as $command) { + $command->xml($subcommands); + } + $options = $xml->addChild('options'); + foreach ($parser->options() as $option) { + $option->xml($options); + } + $arguments = $xml->addChild('arguments'); + foreach ($parser->arguments() as $argument) { + $argument->xml($arguments); + } + return $string ? $xml->asXml() : $xml; + } +} \ No newline at end of file diff --git a/cake/console/libs/task_collection.php b/cake/console/libs/task_collection.php index d1ec21733..d28b13b6d 100644 --- a/cake/console/libs/task_collection.php +++ b/cake/console/libs/task_collection.php @@ -20,11 +20,18 @@ App::import('Core', 'ObjectCollection'); class TaskCollection extends ObjectCollection { /** - * Shell Dispatcher to give to tasks. and use to find tasks. + * Shell to use to set params to tasks. * * @var array */ - protected $_Dispatch; + protected $_Shell; + +/** + * The directory inside each shell path that contains tasks. + * + * @var string + */ + public $taskPathPrefix = 'tasks/'; /** * Constructor @@ -32,11 +39,13 @@ class TaskCollection extends ObjectCollection { * @param array $paths Array of paths to search for tasks on . * @return void */ - public function __construct(ShellDispatcher $Dispatcher) { - $this->_Dispatch = $Dispatcher; + public function __construct(Shell $Shell) { + $this->_Shell = $Shell; } + /** - * Loads/constructs a task. Will return the instance in the registry if it already exists. + * Loads/constructs a task. Will return the instance in the collection + * if it already exists. * * @param string $task Task name to load * @param array $settings Settings for the task. @@ -53,35 +62,21 @@ class TaskCollection extends ObjectCollection { $taskFile = Inflector::underscore($name); $taskClass = $name . 'Task'; if (!class_exists($taskClass)) { - $taskFile = $this->_getPath($taskFile); - require_once $taskFile; + if (!App::import('Shell', $plugin . $this->taskPathPrefix . $name)) { + throw new MissingTaskFileException($taskFile . '.php'); + } if (!class_exists($taskClass)) { throw new MissingTaskClassException($taskClass); } } - $this->_loaded[$name] = new $taskClass($this->_Dispatch); + $this->_loaded[$name] = new $taskClass( + $this->_Shell->stdout, $this->_Shell->stderr, $this->_Shell->stdin + ); if ($enable === true) { $this->_enabled[] = $name; } return $this->_loaded[$name]; } -/** - * Find a task file on one of the paths. - * - * @param string $file Underscored name of the file to find missing .php - * @return string Filename to the task - * @throws MissingTaskFileException - */ - protected function _getPath($file) { - foreach ($this->_Dispatch->shellPaths as $path) { - $taskPath = $path . 'tasks' . DS . $file . '.php'; - if (file_exists($taskPath)) { - return $taskPath; - } - } - throw new MissingTaskFileException($file . '.php'); - } - } diff --git a/cake/console/shell_dispatcher.php b/cake/console/shell_dispatcher.php new file mode 100644 index 000000000..5b1b6a8fe --- /dev/null +++ b/cake/console/shell_dispatcher.php @@ -0,0 +1,327 @@ +<?php +/** + * ShellDispatcher file + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +/** + * Shell dispatcher handles dispatching cli commands. + * + * @package cake + * @subpackage cake.cake.console + */ +class ShellDispatcher { + +/** + * Contains command switches parsed from the command line. + * + * @var array + * @access public + */ + public $params = array(); + +/** + * Contains arguments parsed from the command line. + * + * @var array + * @access public + */ + public $args = array(); + +/** + * Constructor + * + * The execution of the script is stopped after dispatching the request with + * a status code of either 0 or 1 according to the result of the dispatch. + * + * @param array $args the argv + * @return void + */ + public function __construct($args = array(), $bootstrap = true) { + set_time_limit(0); + + if ($bootstrap) { + $this->__initConstants(); + } + $this->parseParams($args); + if ($bootstrap) { + $this->_initEnvironment(); + } + } + +/** + * Run the dispatcher + * + * @return void + */ + public static function run($argv) { + $dispatcher = new ShellDispatcher($argv); + $dispatcher->_stop($dispatcher->dispatch() === false ? 1 : 0); + } + +/** + * Defines core configuration. + * + * @access private + */ + function __initConstants() { + if (function_exists('ini_set')) { + ini_set('display_errors', '1'); + ini_set('error_reporting', E_ALL & ~E_DEPRECATED); + ini_set('html_errors', false); + ini_set('implicit_flush', true); + ini_set('max_execution_time', 0); + } + + if (!defined('CAKE_CORE_INCLUDE_PATH')) { + define('DS', DIRECTORY_SEPARATOR); + define('CAKE_CORE_INCLUDE_PATH', dirname(dirname(dirname(__FILE__)))); + define('DISABLE_DEFAULT_ERROR_HANDLING', false); + define('CAKEPHP_SHELL', true); + if (!defined('CORE_PATH')) { + if (function_exists('ini_set') && ini_set('include_path', CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ini_get('include_path'))) { + define('CORE_PATH', null); + } else { + define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS); + } + } + } + } + +/** + * Defines current working environment. + * + */ + protected function _initEnvironment() { + if (!$this->__bootstrap()) { + $message = "Unable to load CakePHP core.\nMake sure " . DS . 'cake' . DS . 'libs exists in ' . CAKE_CORE_INCLUDE_PATH; + throw new RuntimeException($message); + } + + if (!isset($this->args[0]) || !isset($this->params['working'])) { + $message = "This file has been loaded incorrectly and cannot continue.\n" . + "Please make sure that " . DIRECTORY_SEPARATOR . "cake" . DIRECTORY_SEPARATOR . "console is in your system path,\n" . + "and check the cookbook for the correct usage of this command.\n" . + "(http://book.cakephp.org/)"; + throw new RuntimeException($message); + } + + $this->shiftArgs(); + } + +/** + * Initializes the environment and loads the Cake core. + * + * @return boolean Success. + * @access private + */ + function __bootstrap() { + + define('ROOT', $this->params['root']); + define('APP_DIR', $this->params['app']); + define('APP_PATH', $this->params['working'] . DS); + define('WWW_ROOT', APP_PATH . $this->params['webroot'] . DS); + if (!is_dir(ROOT . DS . APP_DIR . DS . 'tmp')) { + define('TMP', CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS . 'console' . DS . 'templates' . DS . 'skel' . DS . 'tmp' . DS); + } + + $boot = file_exists(ROOT . DS . APP_DIR . DS . 'config' . DS . 'bootstrap.php'); + require CORE_PATH . 'cake' . DS . 'bootstrap.php'; + require_once CONSOLE_LIBS . 'console_error_handler.php'; + set_exception_handler(array('ConsoleErrorHandler', 'handleException')); + + if (!file_exists(APP_PATH . 'config' . DS . 'core.php')) { + include_once CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS . 'console' . DS . 'templates' . DS . 'skel' . DS . 'config' . DS . 'core.php'; + App::build(); + } + if (!defined('FULL_BASE_URL')) { + define('FULL_BASE_URL', '/'); + } + + return true; + } + +/** + * Dispatches a CLI request + * + * @return boolean + */ + public function dispatch() { + $shell = $this->shiftArgs(); + + if (!$shell) { + $this->help(); + return false; + } + if (in_array($shell, array('help', '--help', '-h'))) { + $this->help(); + return true; + } + + $Shell = $this->_getShell($shell); + + $command = null; + if (isset($this->args[0])) { + $command = $this->args[0]; + } + + if ($Shell instanceof Shell) { + $Shell->initialize(); + $Shell->loadTasks(); + return $Shell->runCommand($command, $this->args); + } + $methods = array_diff(get_class_methods($Shell), get_class_methods('Shell')); + $added = in_array($command, $methods); + $private = $command[0] == '_' && method_exists($Shell, $command); + + if (!$private) { + if ($added) { + $this->shiftArgs(); + $Shell->startup(); + return $Shell->{$command}(); + } + if (method_exists($Shell, 'main')) { + $Shell->startup(); + return $Shell->main(); + } + } + throw new MissingShellMethodException(array('shell' => $shell, 'method' => $arg)); + } + +/** + * Get shell to use, either plugin shell or application shell + * + * All paths in the loaded shell paths are searched. + * + * @param string $shell Optionally the name of a plugin + * @return mixed False if no shell could be found or an object on success + * @throws MissingShellFileException, MissingShellClassException when errors are encountered. + */ + protected function _getShell($shell) { + list($plugin, $shell) = pluginSplit($shell, true); + + $loaded = App::import('Shell', $plugin . $shell); + $class = Inflector::camelize($shell) . 'Shell'; + + if (!$loaded) { + throw new MissingShellFileException(array('shell' => $shell)); + } + if (!class_exists($class)) { + throw new MissingShellClassException(array('shell' => $class)); + } + $Shell = new $class(); + return $Shell; + } + +/** + * Parses command line options and extracts the directory paths from $params + * + * @param array $params Parameters to parse + */ + public function parseParams($args) { + $this->_parsePaths($args); + + $defaults = array( + 'app' => 'app', + 'root' => dirname(dirname(dirname(__FILE__))), + 'working' => null, + 'webroot' => 'webroot' + ); + $params = array_merge($defaults, array_intersect_key($this->params, $defaults)); + $isWin = false; + foreach ($defaults as $default => $value) { + if (strpos($params[$default], '\\') !== false) { + $isWin = true; + break; + } + } + $params = str_replace('\\', '/', $params); + + if (!empty($params['working']) && (!isset($this->args[0]) || isset($this->args[0]) && $this->args[0]{0} !== '.')) { + if (empty($this->params['app']) && $params['working'] != $params['root']) { + $params['root'] = dirname($params['working']); + $params['app'] = basename($params['working']); + } else { + $params['root'] = $params['working']; + } + } + + if ($params['app'][0] == '/' || preg_match('/([a-z])(:)/i', $params['app'], $matches)) { + $params['root'] = dirname($params['app']); + } elseif (strpos($params['app'], '/')) { + $params['root'] .= '/' . dirname($params['app']); + } + + $params['app'] = basename($params['app']); + $params['working'] = rtrim($params['root'], '/') . '/' . $params['app']; + + if (!empty($matches[0]) || !empty($isWin)) { + $params = str_replace('/', '\\', $params); + } + + $this->params = array_merge($this->params, $params); + } + +/** + * Parses out the paths from from the argv + * + * @return void + */ + protected function _parsePaths($args) { + $parsed = array(); + $keys = array('-working', '--working', '-app', '--app', '-root', '--root'); + foreach ($keys as $key) { + $index = array_search($key, $args); + if ($index !== false) { + $keyname = str_replace('-', '', $key); + $valueIndex = $index + 1; + $parsed[$keyname] = $args[$valueIndex]; + array_splice($args, $index, 2); + } + } + $this->args = $args; + $this->params = $parsed; + } + +/** + * Removes first argument and shifts other arguments up + * + * @return mixed Null if there are no arguments otherwise the shifted argument + */ + public function shiftArgs() { + return array_shift($this->args); + } + +/** + * Shows console help. Performs an internal dispatch to the CommandList Shell + * + */ + public function help() { + $this->args = array_merge(array('command_list'), $this->args); + $this->dispatch(); + } + +/** + * Stop execution of the current script + * + * @param $status see http://php.net/exit for values + * @return void + */ + protected function _stop($status = 0) { + exit($status); + } +} \ No newline at end of file diff --git a/cake/console/libs/acl.php b/cake/console/shells/acl.php similarity index 61% rename from cake/console/libs/acl.php rename to cake/console/shells/acl.php index 3d24c4014..169bd14d2 100644 --- a/cake/console/libs/acl.php +++ b/cake/console/shells/acl.php @@ -66,6 +66,7 @@ class AclShell extends Shell { * */ public function startup() { + parent::startup(); if (isset($this->params['connection'])) { $this->connection = $this->params['connection']; } @@ -83,7 +84,7 @@ class AclShell extends Shell { $this->_stop(); } - if ($this->command && !in_array($this->command, array('help'))) { + if ($this->command) { if (!config('database')) { $this->out(__('Your database configuration was not found. Take a moment to create one.'), true); $this->args = null; @@ -105,20 +106,7 @@ class AclShell extends Shell { * */ public function main() { - $out = __('Available ACL commands:') . "\n"; - $out .= "\t - create\n"; - $out .= "\t - delete\n"; - $out .= "\t - setParent\n"; - $out .= "\t - getPath\n"; - $out .= "\t - check\n"; - $out .= "\t - grant\n"; - $out .= "\t - deny\n"; - $out .= "\t - inherit\n"; - $out .= "\t - view\n"; - $out .= "\t - initdb\n"; - $out .= "\t - help\n\n"; - $out .= __("For help, run the 'help' command. For help on a specific command, run 'help <command>'"); - $this->out($out); + $this->out($this->OptionParser->help()); } /** @@ -126,8 +114,6 @@ class AclShell extends Shell { * */ public function create() { - $this->_checkArgs(3, 'create'); - $this->checkNodeType(); extract($this->__dataVars()); $class = ucfirst($this->args[0]); @@ -143,13 +129,13 @@ class AclShell extends Shell { if (is_string($data) && $data != '/') { $data = array('alias' => $data); } elseif (is_string($data)) { - $this->error(__('/ can not be used as an alias!'), __("\t/ is the root, please supply a sub alias")); + $this->error(__('/ can not be used as an alias!') . __(" / is the root, please supply a sub alias")); } $data['parent_id'] = $parent; $this->Acl->{$class}->create(); if ($this->Acl->{$class}->save($data)) { - $this->out(sprintf(__("New %s '%s' created.\n"), $class, $this->args[2]), true); + $this->out(sprintf(__("<success>New %s</success> '%s' created."), $class, $this->args[2]), 2); } else { $this->err(sprintf(__("There was a problem creating a new %s '%s'."), $class, $this->args[2])); } @@ -160,17 +146,15 @@ class AclShell extends Shell { * */ public function delete() { - $this->_checkArgs(2, 'delete'); - $this->checkNodeType(); extract($this->__dataVars()); $identifier = $this->parseIdentifier($this->args[1]); $nodeId = $this->_getNodeId($class, $identifier); if (!$this->Acl->{$class}->delete($nodeId)) { - $this->error(__('Node Not Deleted'), sprintf(__('There was an error deleting the %s. Check that the node exists'), $class) . ".\n"); + $this->error(__('Node Not Deleted') . sprintf(__('There was an error deleting the %s. Check that the node exists'), $class) . ".\n"); } - $this->out(sprintf(__('%s deleted'), $class) . ".\n", true); + $this->out(sprintf(__('<success>%s deleted.</success>'), $class), 2); } /** @@ -178,8 +162,6 @@ class AclShell extends Shell { * */ public function setParent() { - $this->_checkArgs(3, 'setParent'); - $this->checkNodeType(); extract($this->__dataVars()); $target = $this->parseIdentifier($this->args[1]); $parent = $this->parseIdentifier($this->args[2]); @@ -203,8 +185,6 @@ class AclShell extends Shell { * */ public function getPath() { - $this->_checkArgs(2, 'getPath'); - $this->checkNodeType(); extract($this->__dataVars()); $identifier = $this->parseIdentifier($this->args[1]); @@ -247,13 +227,12 @@ class AclShell extends Shell { * */ public function check() { - $this->_checkArgs(3, 'check'); extract($this->__getParams()); if ($this->Acl->check($aro, $aco, $action)) { - $this->out(sprintf(__('%s is allowed.'), $aroName), true); + $this->out(sprintf(__('%s is <success>allowed</success>.'), $aroName), true); } else { - $this->out(sprintf(__('%s is not allowed.'), $aroName), true); + $this->out(sprintf(__('%s is <error>not allowed</error>.'), $aroName), true); } } @@ -262,13 +241,12 @@ class AclShell extends Shell { * */ public function grant() { - $this->_checkArgs(3, 'grant'); extract($this->__getParams()); if ($this->Acl->allow($aro, $aco, $action)) { - $this->out(__('Permission granted.'), true); + $this->out(__('Permission <success>granted</success>.'), true); } else { - $this->out(__('Permission was not granted.'), true); + $this->out(__('Permission was <error>not granted</error>.'), true); } } @@ -277,7 +255,6 @@ class AclShell extends Shell { * */ public function deny() { - $this->_checkArgs(3, 'deny'); extract($this->__getParams()); if ($this->Acl->deny($aro, $aco, $action)) { @@ -292,7 +269,6 @@ class AclShell extends Shell { * */ public function inherit() { - $this->_checkArgs(3, 'inherit'); extract($this->__getParams()); if ($this->Acl->inherit($aro, $aco, $action)) { @@ -307,8 +283,6 @@ class AclShell extends Shell { * */ public function view() { - $this->_checkArgs(1, 'view'); - $this->checkNodeType(); extract($this->__dataVars()); if (isset($this->args[1])) { @@ -368,116 +342,168 @@ class AclShell extends Shell { * */ public function initdb() { - $this->Dispatch->args = array('schema', 'create', 'DbAcl'); - $this->Dispatch->dispatch(); + return $this->dispatchShell('schema create DbAcl'); } /** - * Show help screen. + * Get the option parser. * + * @return void */ - public function help() { - $head = "-----------------------------------------------\n"; - $head .= __('Usage: cake acl <command> <arg1> <arg2>...') . "\n"; - $head .= "-----------------------------------------------\n"; - $head .= __('Commands:') . "\n"; - - $commands = array( - 'create' => "create aro|aco <parent> <node>\n" . - "\t" . __("Creates a new ACL object <node> under the parent") . "\n" . - "\t" . __("specified by <parent>, an id/alias.") . "\n" . - "\t" . __("The <parent> and <node> references can be") . "\n" . - "\t" . __("in one of the following formats:") . "\n\n" . - "\t\t- " . __("<model>.<id> - The node will be bound to a") . "\n" . - "\t\t" . __("specific record of the given model.") . "\n\n" . - "\t\t- " . __("<alias> - The node will be given a string alias,") . "\n" . - "\t\t" . __(" (or path, in the case of <parent>)") . "\n" . - "\t\t " . __("i.e. 'John'. When used with <parent>,") . "\n" . - "\t\t" . __("this takes the form of an alias path,") . "\n" . - "\t\t " . __("i.e. <group>/<subgroup>/<parent>.") . "\n\n" . - "\t" . __("To add a node at the root level,") . "\n" . - "\t" . __("enter 'root' or '/' as the <parent> parameter.") . "\n", - - 'delete' => "delete aro|aco <node>\n" . - "\t" . __("Deletes the ACL object with the given <node> reference") . "\n" . - "\t" . __("For more detailed parameter usage info,") . "\n" . - "\t" . __("see help for the 'create' command."), - - 'setparent' => "setParent aro|aco <node> <parent node>\n" . - "\t" . __("Moves the ACL object specified by <node> beneath") . "\n" . - "\t" . __("the parent ACL object specified by <parent>.") . "\n" . - "\t" . __("For more detailed parameter usage info,") . "\n" . - "\t" . __("see help for the 'create' command."), - - 'getpath' => "getPath aro|aco <node>\n" . - "\t" . __("Returns the path to the ACL object specified by <node>. This command", true) . "\n" . - "\t" . __("is useful in determining the inhertiance of permissions for a certain", true) . "\n" . - "\t" . __("object in the tree.", true) . "\n" . - "\t" . __("For more detailed parameter usage info,", true) . "\n" . - "\t" . __("see help for the 'create' command.", true), - - 'check' => "check <node> <node> [<aco_action>] " . __("or", true) . " all\n" . - "\t" . __("Use this command to check ACL permissions.", true) . "\n" . - "\t" . __("For more detailed parameter usage info,", true) . "\n" . - "\t" . __("see help for the 'create' command.", true), - - 'grant' => "grant <aronode> <aconode> [<aco_action>] " . __("or", true) . " all\n" . - "\t" . __("Use this command to grant ACL permissions. Once executed, the ARO", true) . "\n" . - "\t" . __("specified (and its children, if any) will have ALLOW access to the", true) . "\n" . - "\t" . __("specified ACO action (and the ACO's children, if any).", true) . "\n" . - "\t" . __("For more detailed parameter usage info,", true) . "\n" . - "\t" . __("see help for the 'create' command.", true), - - 'deny' => "deny <aronode> <aconode> [<aco_action>]" . __("or", true) . " all\n" . - "\t" . __("Use this command to deny ACL permissions. Once executed, the ARO", true) . "\n" . - "\t" . __("specified (and its children, if any) will have DENY access to the", true) . "\n" . - "\t" . __("specified ACO action (and the ACO's children, if any).", true) . "\n" . - "\t" . __("For more detailed parameter usage info,", true) . "\n" . - "\t" . __("see help for the 'create' command.", true), - - 'inherit' => "inherit <aronode> <aconode> [<aco_action>]" . __("or", true) . " all\n" . - "\t" . __("Use this command to force a child ARO object to inherit its", true) . "\n" . - "\t" . __("permissions settings from its parent.", true) . "\n" . - "\t" . __("For more detailed parameter usage info,", true) . "\n" . - "\t" . __("see help for the 'create' command.", true), - - 'view' => "view aro|aco [<node>]\n" . - "\t" . __("The view command will return the ARO or ACO tree.") . "\n" . - "\t" . __("The optional node parameter allows you to return") . "\n" . - "\t" . __("only a portion of the requested tree.") . "\n" . - "\t" . __("For more detailed parameter usage info,") . "\n" . - "\t" . __("see help for the 'create' command."), - - 'initdb' => "initdb\n". - "\t" . __("Uses this command : cake schema run create DbAcl"), - - 'help' => "help [<command>]\n" . - "\t" . __("Displays this help message, or a message on a specific command.") + public function getOptionParser() { + $parser = parent::getOptionParser(); + + $type = array( + 'choices' => array('aro', 'aco'), + 'required' => true, + 'help' => __('Type of node to create.') ); - - $this->out($head); - if (!isset($this->args[0])) { - foreach ($commands as $cmd) { - $this->out("{$cmd}\n\n"); - } - } elseif (isset($commands[strtolower($this->args[0])])) { - $this->out($commands[strtolower($this->args[0])] . "\n\n"); - } else { - $this->out(sprintf(__("Command '%s' not found"), $this->args[0])); - } - } - -/** - * Check that first argument specifies a valid Node type (ARO/ACO) - * - */ - public function checkNodeType() { - if (!isset($this->args[0])) { - return false; - } - if ($this->args[0] != 'aco' && $this->args[0] != 'aro') { - $this->error(sprintf(__("Missing/Unknown node type: '%s'"), $this->args[0]), __('Please specify which ACL object type you wish to create. Either "aro" or "aco"')); - } + + $parser->description('A console tool for managing the DbAcl') + ->addSubcommand('create', array( + 'help' => __('Create a new ACL node'), + 'parser' => array( + 'description' => __('Creates a new ACL object <node> under the parent'), + 'arguments' => array( + 'type' => $type, + 'parent' => array( + 'help' => __('The node selector for the parent.'), + 'required' => true + ), + 'alias' => array( + 'help' => __('The alias to use for the newly created node.'), + 'required' => true + ) + ) + ) + ))->addSubcommand('delete', array( + 'help' => __('Deletes the ACL object with the given <node> reference'), + 'parser' => array( + 'description' => __('Delete an ACL node.'), + 'arguments' => array( + 'type' => $type, + 'node' => array( + 'help' => __('The node identifier to delete.'), + 'required' => true, + ) + ) + ) + ))->addSubcommand('setparent', array( + 'help' => __('Moves the ACL node under a new parent.'), + 'parser' => array( + 'description' => __('Moves the ACL object specified by <node> beneath <parent>'), + 'arguments' => array( + 'type' => $type, + 'node' => array( + 'help' => __('The node to move'), + 'required' => true, + ), + 'parent' => array( + 'help' => __('The new parent for <node>.'), + 'required' => true + ) + ) + ) + ))->addSubcommand('getpath', array( + 'help' => __('Print out the path to an ACL node.'), + 'parser' => array( + 'description' => array( + __("Returns the path to the ACL object specified by <node>."), + __("This command is useful in determining the inhertiance of permissions"), + __("for a certain object in the tree.") + ), + 'arguments' => array( + 'type' => $type, + 'node' => array( + 'help' => __('The node to get the path of'), + 'required' => true, + ) + ) + ) + ))->addSubcommand('check', array( + 'help' => __('Check the permissions between an ACO and ARO.'), + 'parser' => array( + 'description' => array( + __("Use this command to grant ACL permissions. Once executed, the ARO "), + __("specified (and its children, if any) will have ALLOW access to the"), + __("specified ACO action (and the ACO's children, if any).") + ), + 'arguments' => array( + 'aro' => array('help' => __('ARO to check.'), 'required' => true), + 'aco' => array('help' => __('ACO to check.'), 'required' => true), + 'action' => array('help' => __('Action to check'), 'default' => 'all') + ) + ) + ))->addSubcommand('grant', array( + 'help' => __('Grant an ARO permissions to an ACO.'), + 'parser' => array( + 'description' => array( + __("Use this command to grant ACL permissions. Once executed, the ARO"), + __("specified (and its children, if any) will have ALLOW access to the"), + __("specified ACO action (and the ACO's children, if any).") + ), + 'arguments' => array( + 'aro' => array('help' => __('ARO to grant permission to.'), 'required' => true), + 'aco' => array('help' => __('ACO to grant access to.'), 'required' => true), + 'action' => array('help' => __('Action to grant'), 'default' => 'all') + ) + ) + ))->addSubcommand('deny', array( + 'help' => __('Deny an ARO permissions to an ACO.'), + 'parser' => array( + 'description' => array( + __("Use this command to deny ACL permissions. Once executed, the ARO"), + __("specified (and its children, if any) will have DENY access to the"), + __("specified ACO action (and the ACO's children, if any).") + ), + 'arguments' => array( + 'aro' => array('help' => __('ARO to deny.'), 'required' => true), + 'aco' => array('help' => __('ACO to deny.'), 'required' => true), + 'action' => array('help' => __('Action to deny'), 'default' => 'all') + ) + ) + ))->addSubcommand('inherit', array( + 'help' => __('Inherit an ARO\'s parent permissions.'), + 'parser' => array( + 'description' => array( + __("Use this command to force a child ARO object to inherit its"), + __("permissions settings from its parent.") + ), + 'arguments' => array( + 'aro' => array('help' => __('ARO to have permisssions inherit.'), 'required' => true), + 'aco' => array('help' => __('ACO to inherit permissions on.'), 'required' => true), + 'action' => array('help' => __('Action to inherit'), 'default' => 'all') + ) + ) + ))->addSubcommand('view', array( + 'help' => __('View a tree or a single node\'s subtree.'), + 'parser' => array( + 'description' => array( + __("The view command will return the ARO or ACO tree."), + __("The optional node parameter allows you to return"), + __("only a portion of the requested tree.") + ), + 'arguments' => array( + 'type' => $type, + 'node' => array('help' => __('The optional node to view the subtree of.')) + ) + ) + ))->addSubcommand('initdb', array( + 'help' => __('Initialize the DbAcl tables. Uses this command : cake schema run create DbAcl') + ))->epilog( + array( + 'Node and parent arguments can be in one of the following formats:', + '', + ' - <model>.<id> - The node will be bound to a specific record of the given model.', + '', + ' - <alias> - The node will be given a string alias (or path, in the case of <parent>)', + " i.e. 'John'. When used with <parent>, this takes the form of an alias path,", + " i.e. <group>/<subgroup>/<parent>.", + '', + "To add a node at the root level, enter 'root' or '/' as the <parent> parameter." + ) + ); + return $parser; } /** @@ -488,7 +514,7 @@ class AclShell extends Shell { * @return boolean Success */ public function nodeExists() { - if (!$this->checkNodeType() && !isset($this->args[1])) { + if (!isset($this->args[0]) || !isset($this->args[1])) { return false; } extract($this->__dataVars($this->args[0])); diff --git a/cake/console/libs/api.php b/cake/console/shells/api.php similarity index 87% rename from cake/console/libs/api.php rename to cake/console/shells/api.php index 26acdccb0..d0c19da7e 100644 --- a/cake/console/libs/api.php +++ b/cake/console/shells/api.php @@ -60,7 +60,7 @@ class ApiShell extends Shell { */ public function main() { if (empty($this->args)) { - return $this->help(); + return $this->out($this->OptionParser->help()); } $type = strtolower($this->args[0]); @@ -78,7 +78,6 @@ class ApiShell extends Shell { $file = Inflector::underscore($this->args[1]); $class = Inflector::camelize($file); } - $objects = App::objects('class', $path); if (in_array($class, $objects)) { if (in_array($type, array('behavior', 'component', 'helper')) && $type !== $file) { @@ -88,19 +87,18 @@ class ApiShell extends Shell { } } else { - $this->err(sprintf(__('%s not found'), $class)); - $this->_stop(); + $this->error(sprintf(__('%s not found'), $class)); } $parsed = $this->__parseClass($path . $file .'.php', $class); if (!empty($parsed)) { - if (isset($this->params['m'])) { - if (!isset($parsed[$this->params['m']])) { - $this->err(sprintf(__('%s::%s() could not be found'), $class, $this->params['m'])); + if (isset($this->params['method'])) { + if (!isset($parsed[$this->params['method']])) { + $this->err(sprintf(__('%s::%s() could not be found'), $class, $this->params['method'])); $this->_stop(); } - $method = $parsed[$this->params['m']]; + $method = $parsed[$this->params['method']]; $this->out($class .'::'.$method['method'] . $method['parameters']); $this->hr(); $this->out($method['comment'], true); @@ -136,6 +134,23 @@ class ApiShell extends Shell { } } +/** + * Get and configure the optionparser. + * + * @return ConsoleOptionParser + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + $parser->addArgument('type', array( + 'help' => 'Either a full path or type of class (model, behavior, controller, component, view, helper)' + ))->addArgument('className', array( + 'help' => 'A CakePHP core class name (e.g: Component, HtmlHelper).' + ))->addOption('method', array( + 'short' => 'm', + 'help' => __('The specific method you want help on.') + ))->description(__('Lookup doc block comments for classes in CakePHP.')); + return $parser; + } /** * Show help for this shell. * diff --git a/cake/console/libs/bake.php b/cake/console/shells/bake.php similarity index 68% rename from cake/console/libs/bake.php rename to cake/console/shells/bake.php index 047dda1c0..dfd048194 100644 --- a/cake/console/libs/bake.php +++ b/cake/console/shells/bake.php @@ -43,22 +43,13 @@ class BakeShell extends Shell { * Override loadTasks() to handle paths * */ - public function loadTasks() { - parent::loadTasks(); + public function startup() { + parent::startup(); $task = Inflector::classify($this->command); if (isset($this->{$task}) && !in_array($task, array('Project', 'DbConfig'))) { if (isset($this->params['connection'])) { $this->{$task}->connection = $this->params['connection']; } - foreach($this->args as $i => $arg) { - if (strpos($arg, '.')) { - list($this->params['plugin'], $this->args[$i]) = pluginSplit($arg); - break; - } - } - if (isset($this->params['plugin'])) { - $this->{$task}->plugin = $this->params['plugin']; - } } } @@ -68,8 +59,9 @@ class BakeShell extends Shell { */ public function main() { if (!is_dir($this->DbConfig->path)) { - if ($this->Project->execute()) { - $this->DbConfig->path = $this->params['working'] . DS . 'config' . DS; + $path = $this->Project->execute(); + if (!empty($path)) { + $this->DbConfig->path = $path . 'config' . DS; } else { return false; } @@ -188,38 +180,54 @@ class BakeShell extends Shell { $this->out(__('Bake All complete')); array_shift($this->args); } else { - $this->err(__('Bake All could not continue without a valid model')); + $this->error(__('Bake All could not continue without a valid model')); } $this->_stop(); } /** - * Displays help contents + * get the option parser. * + * @return void */ - public function help() { - $this->out('CakePHP Bake:'); - $this->hr(); - $this->out('The Bake script generates controllers, views and models for your application.'); - $this->out('If run with no command line arguments, Bake guides the user through the class'); - $this->out('creation process. You can customize the generation process by telling Bake'); - $this->out('where different parts of your application are using command line arguments.'); - $this->hr(); - $this->out("Usage: cake bake <command> <arg1> <arg2>..."); - $this->hr(); - $this->out('Params:'); - $this->out("\t-app <path> Absolute/Relative path to your app folder.\n"); - $this->out('Commands:'); - $this->out("\n\tbake help\n\t\tshows this help message."); - $this->out("\n\tbake all <name>\n\t\tbakes complete MVC. optional <name> of a Model"); - $this->out("\n\tbake project <path>\n\t\tbakes a new app folder in the path supplied\n\t\tor in current directory if no path is specified"); - $this->out("\n\tbake plugin <name>\n\t\tbakes a new plugin folder in the path supplied\n\t\tor in current directory if no path is specified."); - $this->out("\n\tbake db_config\n\t\tbakes a database.php file in config directory."); - $this->out("\n\tbake model\n\t\tbakes a model. run 'bake model help' for more info"); - $this->out("\n\tbake view\n\t\tbakes views. run 'bake view help' for more info"); - $this->out("\n\tbake controller\n\t\tbakes a controller. run 'bake controller help' for more info"); - $this->out("\n\tbake fixture\n\t\tbakes fixtures. run 'bake fixture help' for more info."); - $this->out("\n\tbake test\n\t\tbakes unit tests. run 'bake test help' for more info."); - $this->out(); + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + 'The Bake script generates controllers, views and models for your application.' . + 'If run with no command line arguments, Bake guides the user through the class' . + 'creation process. You can customize the generation process by telling Bake' . + 'where different parts of your application are using command line arguments.' + )->addSubcommand('all', array( + 'help' => __('Bake a complete MVC. optional <name> of a Model'), + ))->addSubcommand('project', array( + 'help' => __('Bake a new app folder in the path supplied or in current directory if no path is specified'), + 'parser' => $this->Project->getOptionParser() + ))->addSubcommand('plugin', array( + 'help' => __('Bake a new plugin folder in the path supplied or in current directory if no path is specified.'), + 'parser' => $this->Plugin->getOptionParser() + ))->addSubcommand('db_config', array( + 'help' => __('Bake a database.php file in config directory.'), + 'parser' => $this->DbConfig->getOptionParser() + ))->addSubcommand('model', array( + 'help' => __('Bake a model.'), + 'parser' => $this->Model->getOptionParser() + ))->addSubcommand('view', array( + 'help' => __('Bake views for controllers.'), + 'parser' => $this->View->getOptionParser() + ))->addSubcommand('controller', array( + 'help' => __('Bake a controller.'), + 'parser' => $this->Controller->getOptionParser() + ))->addSubcommand('fixture', array( + 'help' => __('Bake a fixture.'), + 'parser' => $this->Fixture->getOptionParser() + ))->addSubcommand('test', array( + 'help' => __('Bake a unit test.'), + 'parser' => $this->Test->getOptionParser() + ))->addOption('connection', array( + 'help' => __('Database connection to use in conjunction with `bake all`.'), + 'short' => 'c', + 'default' => 'default' + )); } + } diff --git a/cake/console/shells/command_list.php b/cake/console/shells/command_list.php new file mode 100644 index 000000000..613f65dd7 --- /dev/null +++ b/cake/console/shells/command_list.php @@ -0,0 +1,235 @@ +<?php +/** + * CommandListTest file + * + * PHP 5 + * + * CakePHP : Rapid Development Framework (http://cakephp.org) + * Copyright 2006-2010, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2006-2010, Cake Software Foundation, Inc. + * @link http://cakephp.org CakePHP Project + * @package cake + * @subpackage cake.tests.cases.console.libs + * @since CakePHP v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +/** + * Shows a list of commands available from the console. + * + * @package cake.console.libs + */ +class CommandListShell extends Shell { + +/** + * startup + * + * @return void + */ + public function startup() { + if (empty($this->params['xml'])) { + parent::startup(); + } + } + +/** + * Main function Prints out the list of shells. + * + * @return void + */ + public function main() { + if (empty($this->params['xml'])) { + $this->out("<info>Current Paths:</info>", 2); + $this->out(" -app: ". APP_DIR); + $this->out(" -working: " . rtrim(APP_PATH, DS)); + $this->out(" -root: " . rtrim(ROOT, DS)); + $this->out(" -core: " . rtrim(CORE_PATH, DS)); + $this->out(""); + $this->out("<info>Changing Paths:</info>", 2); + $this->out("Your working path should be the same as your application path"); + $this->out("to change your path use the '-app' param."); + $this->out("Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp", 2); + + $this->out("<info>Available Shells:</info>", 2); + } + + $shellList = $this->_getShellList(); + + if ($shellList) { + ksort($shellList); + if (empty($this->params['xml'])) { + if (!empty($this->params['sort'])) { + $this->_asSorted($shellList); + } else { + $this->_asText($shellList); + } + } else { + $this->_asXml($shellList); + } + } + } + +/** + * Gets the shell command listing. + * + * @return array + */ + protected function _getShellList() { + $shellList = array(); + + $corePaths = App::core('shells'); + $shellList = $this->_appendShells('CORE', $corePaths, $shellList); + + $appPaths = array_diff(App::path('shells'), $corePaths); + $shellList = $this->_appendShells('app', $appPaths, $shellList); + + $plugins = App::objects('plugin'); + foreach ($plugins as $plugin) { + $pluginPath = App::pluginPath($plugin) . 'console' . DS . 'shells' . DS; + $shellList = $this->_appendShells($plugin, array($pluginPath), $shellList); + } + return $shellList; + } + +/** + * Scan the provided paths for shells, and append them into $shellList + * + * @return array + */ + protected function _appendShells($type, $paths, $shellList) { + foreach ($paths as $path) { + if (!is_dir($path)) { + continue; + } + $shells = App::objects('file', $path); + + if (empty($shells)) { + continue; + } + foreach ($shells as $shell) { + if ($shell !== 'shell.php') { + $shell = str_replace('.php', '', $shell); + $shellList[$shell][$type] = $type; + } + } + } + return $shellList; + } + +/** + * Output text. + * + * @return void + */ + protected function _asText($shellList) { + if (DS === '/') { + $width = exec('tput cols') - 2; + } + if (empty($width)) { + $width = 80; + } + $columns = max(1, floor($width / 30)); + $rows = ceil(count($shellList) / $columns); + + foreach ($shellList as $shell => $types) { + sort($types); + $shellList[$shell] = str_pad($shell . ' [' . implode ($types, ', ') . ']', $width / $columns); + } + $out = array_chunk($shellList, $rows); + for ($i = 0; $i < $rows; $i++) { + $row = ''; + for ($j = 0; $j < $columns; $j++) { + if (!isset($out[$j][$i])) { + continue; + } + $row .= $out[$j][$i]; + } + $this->out(" " . $row); + } + $this->out(); + $this->out("To run a command, type <info>cake shell_name [args]</info>"); + $this->out("To get help on a specific command, type <info>cake shell_name --help</info>", 2); + } + +/** + * Generates the shell list sorted by where the shells are found. + * + * @return void + */ + protected function _asSorted($shellList) { + $grouped = array(); + foreach ($shellList as $shell => $types) { + foreach ($types as $type) { + $type = Inflector::camelize($type); + if (empty($grouped[$type])) { + $grouped[$type] = array(); + } + $grouped[$type][] = $shell; + } + } + if (!empty($grouped['App'])) { + sort($grouped['App'], SORT_STRING); + $this->out('[ App ]'); + $this->out(' ' . implode(', ', $grouped['App']), 2); + unset($grouped['App']); + } + foreach ($grouped as $section => $shells) { + if ($section == 'CORE') { + continue; + } + sort($shells, SORT_STRING); + $this->out('[ ' . $section . ' ]'); + $this->out(' ' . implode(', ', $shells), 2); + } + if (!empty($grouped['CORE'])) { + sort($grouped['CORE'], SORT_STRING); + $this->out('[ Core ]'); + $this->out(' ' . implode(', ', $grouped['CORE']), 2); + } + $this->out(); + } + +/** + * Output as XML + * + * @return void + */ + protected function _asXml($shellList) { + $plugins = App::objects('plugin'); + $shells = new SimpleXmlElement('<shells></shells>'); + foreach ($shellList as $name => $location) { + $source = current($location); + $callable = $name; + if (in_array($source, $plugins)) { + $callable = Inflector::underscore($source) . '.' . $name; + } + $shell = $shells->addChild('shell'); + $shell->addAttribute('name', $name); + $shell->addAttribute('call_as', $callable); + $shell->addAttribute('provider', $source); + $shell->addAttribute('help', $callable . ' -h'); + } + $this->out($shells->saveXml()); + } + +/** + * get the option parser + * + * @return void + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description('Get the list of available shells for this CakePHP application.') + ->addOption('xml', array( + 'help' => __('Get the listing as XML.'), + 'boolean' => true + ))->addOption('sort', array( + 'help' => __('Sorts the commands by where they are located.'), + 'boolean' => true + )); + } +} diff --git a/cake/console/libs/console.php b/cake/console/shells/console.php similarity index 100% rename from cake/console/libs/console.php rename to cake/console/shells/console.php diff --git a/cake/console/libs/i18n.php b/cake/console/shells/i18n.php similarity index 77% rename from cake/console/libs/i18n.php rename to cake/console/shells/i18n.php index a9d2888a4..c40160e54 100644 --- a/cake/console/libs/i18n.php +++ b/cake/console/shells/i18n.php @@ -65,7 +65,7 @@ class I18nShell extends Shell { * */ public function main() { - $this->out(__('I18n Shell')); + $this->out(__('<info>I18n Shell</info>')); $this->hr(); $this->out(__('[E]xtract POT file from sources')); $this->out(__('[I]nitialize i18n database table')); @@ -81,7 +81,7 @@ class I18nShell extends Shell { $this->initdb(); break; case 'h': - $this->help(); + $this->out($this->OptionParser->help()); break; case 'q': exit(0); @@ -98,27 +98,23 @@ class I18nShell extends Shell { * */ public function initdb() { - $this->Dispatch->args = array('schema', 'create', 'i18n'); - $this->Dispatch->dispatch(); + $this->dispatchShell('schema create i18n'); } /** - * Show help screen. + * Get and configure the Option parser * + * @return ConsoleOptionParser */ - public function help() { - $this->hr(); - $this->out(__('I18n Shell:')); - $this->hr(); - $this->out(__('I18n Shell initializes i18n database table for your application')); - $this->out(__('and generates .pot file(s) with translations.')); - $this->hr(); - $this->out(__('usage:')); - $this->out(' cake i18n help'); - $this->out(' cake i18n initdb [-datasource custom]'); - $this->out(); - $this->hr(); - - $this->Extract->help(); + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + __('I18n Shell initializes i18n database table for your application and generates .pot files(s) with translations.') + )->addSubcommand('initdb', array( + 'help' => __('Initialize the i18n table.') + ))->addSubcommand('extract', array( + 'help' => __('Extract the po translations from your application'), + 'parser' => $this->Extract->getOptionParser() + )); } } diff --git a/cake/console/libs/schema.php b/cake/console/shells/schema.php similarity index 75% rename from cake/console/libs/schema.php rename to cake/console/shells/schema.php index 45e7795d9..612e57e45 100644 --- a/cake/console/libs/schema.php +++ b/cake/console/shells/schema.php @@ -40,6 +40,13 @@ class SchemaShell extends Shell { */ private $__dry = null; +/** + * Schema class being used. + * + * @var CakeSchema + */ + public $Schema; + /** * Override initialize * @@ -95,14 +102,6 @@ class SchemaShell extends Shell { $this->Schema =& new CakeSchema(compact('name', 'path', 'file', 'connection', 'plugin')); } -/** - * Override main - * - */ - public function main() { - $this->help(); - } - /** * Read and output contents of schema object * path to read as second arg @@ -128,7 +127,7 @@ class SchemaShell extends Shell { public function generate() { $this->out(__('Generating Schema...')); $options = array(); - if (isset($this->params['f'])) { + if (isset($this->params['force'])) { $options = array('models' => false); } @@ -156,8 +155,8 @@ class SchemaShell extends Shell { $result = $Folder->read(); $numToUse = false; - if (isset($this->params['s'])) { - $numToUse = $this->params['s']; + if (isset($this->params['snapshot'])) { + $numToUse = $this->params['snapshot']; } $count = 0; @@ -203,7 +202,7 @@ class SchemaShell extends Shell { $this->err(__('Schema could not be loaded')); $this->_stop(); } - if (isset($this->params['write'])) { + if (!empty($this->params['write'])) { if ($this->params['write'] == 1) { $write = Inflector::underscore($this->Schema->name); } else { @@ -263,22 +262,22 @@ class SchemaShell extends Shell { */ function _loadSchema() { $name = $plugin = null; - if (isset($this->params['name'])) { + if (!empty($this->params['name'])) { $name = $this->params['name']; } - if (isset($this->params['plugin'])) { + if (!empty($this->params['plugin'])) { $plugin = $this->params['plugin']; } - if (isset($this->params['dry'])) { + if (!empty($this->params['dry'])) { $this->__dry = true; $this->out(__('Performing a dry run.')); } $options = array('name' => $name, 'plugin' => $plugin); - if (isset($this->params['s'])) { + if (!empty($this->params['snapshot'])) { $fileName = rtrim($this->Schema->file, '.php'); - $options['file'] = $fileName . '_' . $this->params['s'] . '.php'; + $options['file'] = $fileName . '_' . $this->params['snapshot'] . '.php'; } $Schema =& $this->Schema->load($options); @@ -348,7 +347,7 @@ class SchemaShell extends Shell { $this->out(__('Comparing Database to Schema...')); $options = array(); - if (isset($this->params['f'])) { + if (isset($this->params['force'])) { $options['models'] = false; } $Old = $this->Schema->read($options); @@ -422,78 +421,97 @@ class SchemaShell extends Shell { } /** - * Displays help contents + * get the option parser * + * @return void */ - public function help() { - $help = <<<TEXT -The Schema Shell generates a schema object from -the database and updates the database from the schema. ---------------------------------------------------------------- -Usage: cake schema <command> <arg1> <arg2>... ---------------------------------------------------------------- -Params: - -connection <config> - set db config <config>. uses 'default' if none is specified - - -path <dir> - path <dir> to read and write schema.php. - default path: {$this->Schema->path} - - -name <name> - Classname to use. If <name> is Plugin.className, it will - set the plugin and name params. - - -file <name> - file <name> to read and write. - default file: {$this->Schema->file} - - -s <number> - snapshot <number> to use for run. - - -dry - Perform a dry run on create + update commands. - Queries will be output to window instead of executed. - - -f - force 'generate' to create a new schema. - - -plugin - Indicate the plugin to use. - -Commands: - - schema help - shows this help message. - - schema view <name> - read and output contents of schema file. - - schema generate - reads from 'connection' writes to 'path' - To force generation of all tables into the schema, use the -f param. - Use 'schema generate snapshot <number>' to generate snapshots - which you can use with the -s parameter in the other operations. - - schema dump <name> - Dump database sql based on schema file to stdout. - If you use the `-write` param is used a .sql will be generated. - If `-write` is a filename, then that file name will be generate. - If `-write` is a full path, the schema will be written there. - - schema create <name> <table> - Drop and create tables based on schema file - optional <table> argument can be used to create only a single - table in the schema. Pass the -s param with a number to use a snapshot. - Use the `-dry` param to preview the changes. - - schema update <name> <table> - Alter the tables based on schema file. Optional <table> - parameter will only update one table. - To use a snapshot pass the `-s` param with the snapshot number. - To preview the changes that will be done use `-dry`. -TEXT; - $this->out($help); - $this->_stop(); + public function getOptionParser() { + $plugin = array( + 'help' => __('The plugin to use.'), + ); + $connection = array( + 'help' => __('Set the db config to use.'), + 'default' => 'default' + ); + $path = array( + 'help' => __('Path to read and write schema.php'), + 'default' => CONFIGS . 'schema' + ); + $file = array( + 'help' => __('File name to read and write.'), + 'default' => 'schema.php' + ); + $name = array( + 'help' => __('Classname to use. If its Plugin.class, both name and plugin options will be set.') + ); + $snapshot = array( + 'short' => 's', + 'help' => __('Snapshot number to use/make.') + ); + $dry = array( + 'help' => 'Perform a dry run on create and update commands. Queries will be output instead of run.', + 'boolean' => true + ); + $force = array( + 'short' => 'f', + 'help' => __('Force "generate" to create a new schema'), + 'boolean' => true + ); + $write = array( + 'help' => __('Write the dumped SQL to a file.') + ); + + $parser = parent::getOptionParser(); + $parser->description( + 'The Schema Shell generates a schema object from' . + 'the database and updates the database from the schema.' + )->addSubcommand('view', array( + 'help' => 'read and output the contents of a schema file', + 'parser' => array( + 'options' => compact('plugin', 'path', 'file', 'name', 'connection'), + 'arguments' => compact('name') + ) + ))->addSubcommand('generate', array( + 'help' => __('Reads from --connection and writes to --path. Generate snapshots with -s'), + 'parser' => array( + 'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'snapshot', 'force'), + 'arguments' => array( + 'snapshot' => array('help' => __('Generate a snapshot.')) + ) + ) + ))->addSubcommand('dump', array( + 'help' => __('Dump database SQL based on a schema file to stdout.'), + 'parser' => array( + 'options' => compact('plugin', 'path', 'file', 'name', 'connection'), + 'arguments' => compact('name') + ) + ))->addSubcommand('create', array( + 'help' => __('Drop and create tables based on the schema file.'), + 'parser' => array( + 'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'dry', 'snapshot'), + 'args' => array( + 'name' => array( + 'help' => __('Name of schema to use.') + ), + 'table' => array( + 'help' => __('Only create the specified table.') + ) + ) + ) + ))->addSubcommand('update', array( + 'help' => __('Alter the tables based on the schema file.'), + 'parser' => array( + 'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'dry', 'snapshot'), + 'args' => array( + 'name' => array( + 'help' => __('Name of schema to use.') + ), + 'table' => array( + 'help' => __('Only create the specified table.') + ) + ) + ) + )); + return $parser; } } diff --git a/cake/console/libs/shell.php b/cake/console/shells/shell.php similarity index 53% rename from cake/console/libs/shell.php rename to cake/console/shells/shell.php index 85b15946c..8ce538105 100644 --- a/cake/console/libs/shell.php +++ b/cake/console/shells/shell.php @@ -17,7 +17,10 @@ * @since CakePHP(tm) v 1.2.0.5012 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -App::import('Shell', 'TaskCollection'); +require_once CONSOLE_LIBS . 'task_collection.php'; +require_once CONSOLE_LIBS . 'console_output.php'; +require_once CONSOLE_LIBS . 'console_input.php'; +require_once CONSOLE_LIBS . 'console_option_parser.php'; /** * Base class for command-line utilities for automating programmer chores. @@ -28,12 +31,18 @@ App::import('Shell', 'TaskCollection'); class Shell extends Object { /** - * An instance of the ShellDispatcher object that loaded this script - * - * @var ShellDispatcher - * @access public + * Output constants for making verbose and quiet shells. */ - public $Dispatch = null; + const VERBOSE = 2; + const NORMAL = 1; + const QUIET = 0; + +/** + * An instance of ConsoleOptionParser that has been configured for this class. + * + * @var ConsoleOptionParser + */ + public $OptionParser; /** * If true, the script will ask for permission to perform actions. @@ -43,15 +52,6 @@ class Shell extends Object { */ public $interactive = true; -/** - * Holds the DATABASE_CONFIG object for the app. Null if database.php could not be found, - * or the app does not exist. - * - * @var DATABASE_CONFIG - * @access public - */ - public $DbConfig = null; - /** * Contains command switches parsed from the command line. * @@ -60,6 +60,13 @@ class Shell extends Object { */ public $params = array(); +/** + * The command (method/task) that is being run. + * + * @var string + */ + public $command; + /** * Contains arguments parsed from the command line. * @@ -68,30 +75,6 @@ class Shell extends Object { */ public $args = array(); -/** - * The file name of the shell that was invoked. - * - * @var string - * @access public - */ - public $shell = null; - -/** - * The class name of the shell that was invoked. - * - * @var string - * @access public - */ - public $className = null; - -/** - * The command called if public methods are available. - * - * @var string - * @access public - */ - public $command = null; - /** * The name of the shell in camelized. * @@ -100,14 +83,6 @@ class Shell extends Object { */ public $name = null; -/** - * An alias for the shell - * - * @var string - * @access public - */ - public $alias = null; - /** * Contains tasks to load and instantiate * @@ -139,30 +114,56 @@ class Shell extends Object { */ public $Tasks; +/** + * Normalized map of tasks. + * + * @var string + */ + protected $_taskMap = array(); + +/** + * stdout object. + * + * @var ConsoleOutput + */ + public $stdout; + +/** + * stderr object. + * + * @var ConsoleOutput + */ + public $stderr; + +/** + * stdin object + * + * @var ConsoleInput + */ + public $stdin; + /** * Constructs this Shell instance. * */ - function __construct(&$dispatch) { - $vars = array('params', 'args', 'shell', 'shellCommand' => 'command'); - - foreach ($vars as $key => $var) { - if (is_string($key)) { - $this->{$var} =& $dispatch->{$key}; - } else { - $this->{$var} =& $dispatch->{$var}; - } - } - + function __construct($stdout = null, $stderr = null, $stdin = null) { if ($this->name == null) { - $this->name = get_class($this); + $this->name = Inflector::underscore(str_replace(array('Shell', 'Task'), '', get_class($this))); } + $this->Tasks = new TaskCollection($this); - if ($this->alias == null) { - $this->alias = $this->name; + $this->stdout = $stdout; + $this->stderr = $stderr; + $this->stdin = $stdin; + if ($this->stdout == null) { + $this->stdout = new ConsoleOutput('php://stdout'); + } + if ($this->stderr == null) { + $this->stderr = new ConsoleOutput('php://stderr'); + } + if ($this->stdin == null) { + $this->stdin = new ConsoleInput('php://stdin'); } - $this->Dispatch =& $dispatch; - $this->Tasks = $dispatch->getTaskCollection(); } /** @@ -190,31 +191,15 @@ class Shell extends Object { * */ protected function _welcome() { - $this->Dispatch->clear(); + $this->clear(); $this->out(); - $this->out('Welcome to CakePHP v' . Configure::version() . ' Console'); + $this->out('<info>Welcome to CakePHP v' . Configure::version() . ' Console</info>'); $this->hr(); - $this->out('App : '. $this->params['app']); - $this->out('Path: '. $this->params['working']); + $this->out('App : '. APP_DIR); + $this->out('Path: '. APP_PATH); $this->hr(); } -/** - * Loads database file and constructs DATABASE_CONFIG class - * makes $this->DbConfig available to subclasses - * - * @return bool - */ - protected function _loadDbConfig() { - if (config('database') && class_exists('DATABASE_CONFIG')) { - $this->DbConfig =& new DATABASE_CONFIG(); - return true; - } - $this->err('Database config could not be loaded.'); - $this->out('Run `bake` to create the database configuration.'); - return false; - } - /** * if public $uses = true * Loads AppModel file and constructs AppModel class @@ -228,11 +213,6 @@ class Shell extends Object { return; } - if ($this->uses === true && App::import('Model', 'AppModel')) { - $this->AppModel =& new AppModel(false, false, false); - return true; - } - if ($this->uses !== true && !empty($this->uses)) { $uses = is_array($this->uses) ? $this->uses : array($this->uses); @@ -260,14 +240,159 @@ class Shell extends Object { if ($this->tasks === true || empty($this->tasks) || empty($this->Tasks)) { return true; } - $tasks = TaskCollection::normalizeObjectArray((array)$this->tasks); - foreach ($tasks as $task => $properties) { - $this->{$task} = $this->Tasks->load($properties['class'], $properties['settings']); + $this->_taskMap = TaskCollection::normalizeObjectArray((array)$this->tasks); + foreach ($this->_taskMap as $task => $properties) { $this->taskNames[] = $task; } return true; } +/** + * Check to see if this shell has a task with the provided name. + * + * @param string $task The task name to check. + * @return boolean Success + */ + public function hasTask($task) { + return isset($this->_taskMap[Inflector::camelize($task)]); + } + +/** + * Check to see if this shell has a callable method by the given name. + * + * @param string $name The method name to check. + * @return boolean + */ + public function hasMethod($name) { + if (empty($this->_reflection)) { + $this->_reflection = new ReflectionClass($this); + } + try { + $method = $this->_reflection->getMethod($name); + if (!$method->isPublic() || substr($name, 0, 1) === '_') { + return false; + } + if ($method->getDeclaringClass() != $this->_reflection) { + return false; + } + return true; + } catch (ReflectionException $e) { + return false; + } + } + +/** + * Dispatch a command to another Shell. Similar to Object::requestAction() + * but intended for running shells from other shells. + * + * ### Usage: + * + * With a string commmand: + * + * `return $this->dispatchShell('schema create DbAcl');` + * + * With an array command: + * + * `return $this->dispatchShell('schema', 'create', 'i18n', '--dry');` + * + * @param mixed $command Either an array of args similar to $argv. Or a string command, that can be + * exploded on space to simulate argv. + * @return mixed. The return of the other shell. + */ + public function dispatchShell() { + $args = func_get_args(); + if (is_string($args[0]) && count($args) == 1) { + $args = explode(' ', $args[0]); + } + + $Dispatcher = new ShellDispatcher($args, false); + return $Dispatcher->dispatch(); + } + +/** + * Runs the Shell with the provided argv + * + * @param array $argv Array of arguments to run the shell with. This array should be missing the shell name. + * @return void + */ + public function runCommand($command, $argv) { + $isTask = $this->hasTask($command); + $isMethod = $this->hasMethod($command); + $isMain = $this->hasMethod('main'); + + if ($isTask || $isMethod && $command !== 'execute') { + array_shift($argv); + } + + $this->OptionParser = $this->getOptionParser(); + list($this->params, $this->args) = $this->OptionParser->parse($argv, $command); + $this->command = $command; + + if (!empty($this->params['help'])) { + return $this->_displayHelp($command); + } + + if (($isTask || $isMethod || $isMain) && $command !== 'execute' ) { + $this->startup(); + } + + if ($isTask) { + $command = Inflector::camelize($command); + return $this->{$command}->runCommand('execute', $argv); + } + if ($isMethod) { + return $this->{$command}(); + } + if ($isMain) { + return $this->main(); + } + return $this->out($this->OptionParser->help($command)); + } + +/** + * Display the help in the correct format + * + * @return void + */ + protected function _displayHelp($command) { + $format = 'text'; + if (!empty($this->args[0]) && $this->args[0] == 'xml') { + $format = 'xml'; + $this->output->outputAs(ConsoleOutput::RAW); + } else { + $this->_welcome(); + } + return $this->out($this->OptionParser->help($command, $format)); + } + +/** + * Gets the option parser instance and configures it. + * By overriding this method you can configure the ConsoleOptionParser before returning it. + * + * @return ConsoleOptionParser + */ + public function getOptionParser() { + $parser = new ConsoleOptionParser($this->name); + return $parser; + } + +/** + * Overload get for lazy building of tasks + * + * @return void + */ + public function __get($name) { + if (empty($this->{$name}) && in_array($name, $this->taskNames)) { + $properties = $this->_taskMap[$name]; + $this->{$name} = $this->Tasks->load($properties['class'], $properties['settings']); + $this->{$name}->args =& $this->args; + $this->{$name}->params =& $this->params; + $this->{$name}->initialize(); + $this->{$name}->loadTasks(); + } + return $this->{$name}; + } + /** * Prompts the user for input, and returns it. * @@ -280,7 +405,7 @@ class Shell extends Object { if (!$this->interactive) { return $default; } - $in = $this->Dispatch->getInput($prompt, $options, $default); + $in = $this->_getInput($prompt, $options, $default); if ($options && is_string($options)) { if (strpos($options, ',')) { @@ -293,7 +418,7 @@ class Shell extends Object { } if (is_array($options)) { while ($in == '' || ($in && (!in_array(strtolower($in), $options) && !in_array(strtoupper($in), $options)) && !in_array($in, $options))) { - $in = $this->Dispatch->getInput($prompt, $options, $default); + $in = $this->_getInput($prompt, $options, $default); } } if ($in) { @@ -301,19 +426,86 @@ class Shell extends Object { } } +/** + * Prompts the user for input, and returns it. + * + * @param string $prompt Prompt text. + * @param mixed $options Array or string of options. + * @param string $default Default input value. + * @return Either the default value, or the user-provided input. + */ + protected function _getInput($prompt, $options, $default) { + if (!is_array($options)) { + $printOptions = ''; + } else { + $printOptions = '(' . implode('/', $options) . ')'; + } + + if ($default === null) { + $this->stdout->write('<question>' . $prompt . '</question>' . " $printOptions \n" . '> ', 0); + } else { + $this->stdout->write('<question>' . $prompt . '</question>' . " $printOptions \n" . "[$default] > ", 0); + } + $result = $this->stdin->read(); + + if ($result === false) { + $this->_stop(1); + } + $result = trim($result); + + if ($default != null && empty($result)) { + return $default; + } + return $result; + } + +/** + * Wrap a block of text. + * Allows you to set the width, and indenting on a block of text. + * + * ### Options + * + * - `width` The width to wrap to. Defaults to 72 + * - `wordWrap` Only wrap on words breaks (spaces) Defaults to true. + * - `indent` Indent the text with the string provided. Defaults to null. + * + * @param string $text Text the text to format. + * @param mixed $options Array of options to use, or an integer to wrap the text to. + * @return string Wrapped / indented text + * @see String::wrap() + */ + public function wrapText($text, $options = array()) { + return String::wrap($text, $options); + } + /** * Outputs a single or multiple messages to stdout. If no parameters * are passed outputs just a newline. * + * ### Output levels + * + * There are 3 built-in output level. Shell::QUIET, Shell::NORMAL, Shell::VERBOSE. + * The verbose and quiet output levels, map to the `verbose` and `quiet` output switches + * present in most shells. Using Shell::QUIET for a message means it will always display. + * While using Shell::VERBOSE means it will only display when verbose output is toggled. + * * @param mixed $message A string or a an array of strings to output * @param integer $newlines Number of newlines to append + * @param integer $level The message's output level, see above. * @return integer Returns the number of bytes returned from writing to stdout. */ - public function out($message = null, $newlines = 1) { - if (is_array($message)) { - $message = implode($this->nl(), $message); + public function out($message = null, $newlines = 1, $level = Shell::NORMAL) { + $currentLevel = Shell::NORMAL; + if (!empty($this->params['verbose'])) { + $currentLevel = Shell::VERBOSE; } - return $this->Dispatch->stdout($message . $this->nl($newlines), false); + if (!empty($this->params['quiet'])) { + $currentLevel = Shell::QUIET; + } + if ($level <= $currentLevel) { + return $this->stdout->write($message, $newlines); + } + return true; } /** @@ -324,10 +516,7 @@ class Shell extends Object { * @param integer $newlines Number of newlines to append */ public function err($message = null, $newlines = 1) { - if (is_array($message)) { - $message = implode($this->nl(), $message); - } - $this->Dispatch->stderr($message . $this->nl($newlines)); + $this->stderr->write($message, $newlines); } /** @@ -338,17 +527,18 @@ class Shell extends Object { * @return string */ function nl($multiplier = 1) { - return str_repeat("\n", $multiplier); + return str_repeat(ConsoleOutput::LF, $multiplier); } /** * Outputs a series of minus characters to the standard output, acts as a visual separator. * * @param integer $newlines Number of newlines to pre- and append + * @param integer $width Width of the line, defaults to 63 */ - public function hr($newlines = 0) { + public function hr($newlines = 0, $width = 63) { $this->out(null, $newlines); - $this->out('---------------------------------------------------------------'); + $this->out(str_repeat('-', $width)); $this->out(null, $newlines); } @@ -360,7 +550,7 @@ class Shell extends Object { * @param string $message An optional error message */ public function error($title, $message = null) { - $this->err(sprintf(__('Error: %s'), $title)); + $this->err(sprintf(__('<error>Error:</error> %s'), $title)); if (!empty($message)) { $this->err($message); @@ -369,21 +559,17 @@ class Shell extends Object { } /** - * Will check the number args matches otherwise throw an error + * Clear the console * - * @param integer $expectedNum Expected number of paramters - * @param string $command Command + * @return void */ - protected function _checkArgs($expectedNum, $command = null) { - if (!$command) { - $command = $this->command; - } - if (count($this->args) < $expectedNum) { - $message[] = "Got: " . count($this->args); - $message[] = "Expected: {$expectedNum}"; - $message[] = "Please type `cake {$this->shell} help` for help"; - $message[] = "on usage of the {$this->name} {$command}."; - $this->error('Wrong number of parameters', $message); + public function clear() { + if (empty($this->params['noclear'])) { + if ( DS === '/') { + passthru('clear'); + } else { + passthru('cls'); + } } } @@ -398,20 +584,22 @@ class Shell extends Object { $path = str_replace(DS . DS, DS, $path); $this->out(); - $this->out(sprintf(__('Creating file %s'), $path)); if (is_file($path) && $this->interactive === true) { - $prompt = sprintf(__('File `%s` exists, overwrite?'), $path); - $key = $this->in($prompt, array('y', 'n', 'q'), 'n'); + $this->out(sprintf(__('<warning>File `%s` exists</warning>'), $path)); + $key = $this->in(__('Do you want to overwrite?'), array('y', 'n', 'q'), 'n'); if (strtolower($key) == 'q') { - $this->out(__('Quitting.'), 2); + $this->out(__('<error>Quitting</error>.'), 2); $this->_stop(); } elseif (strtolower($key) != 'y') { $this->out(sprintf(__('Skip `%s`'), $path), 2); return false; } + } else { + $this->out(sprintf(__('Creating file %s'), $path)); } + if (!class_exists('File')) { require LIBS . 'file.php'; } @@ -419,27 +607,14 @@ class Shell extends Object { if ($File = new File($path, true)) { $data = $File->prepare($contents); $File->write($data); - $this->out(sprintf(__('Wrote `%s`'), $path)); + $this->out(sprintf(__('<success>Wrote</success> `%s`'), $path)); return true; } else { - $this->err(sprintf(__('Could not write to `%s`.'), $path), 2); + $this->err(sprintf(__('<error>Could not write to `%s`</error>.'), $path), 2); return false; } } -/** - * Outputs usage text on the standard output. Implement it in subclasses. - * - */ - public function help() { - if ($this->command != null) { - $this->err("Unknown {$this->name} command `{$this->command}`."); - $this->err("For usage, try `cake {$this->shell} help`.", 2); - } else { - $this->Dispatch->help(); - } - } - /** * Action to create a Unit Test * @@ -449,13 +624,13 @@ class Shell extends Object { if (App::import('vendor', 'simpletest' . DS . 'simpletest')) { return true; } - $prompt = 'SimpleTest is not installed. Do you want to bake unit test files anyway?'; + $prompt = 'PHPUnit is not installed. Do you want to bake unit test files anyway?'; $unitTest = $this->in($prompt, array('y','n'), 'y'); $result = strtolower($unitTest) == 'y' || strtolower($unitTest) == 'yes'; if ($result) { $this->out(); - $this->out('You can download SimpleTest from http://simpletest.org'); + $this->out('You can download PHPUnit from http://phpunit.de'); } return $result; } diff --git a/cake/console/libs/tasks/bake.php b/cake/console/shells/tasks/bake.php similarity index 72% rename from cake/console/libs/tasks/bake.php rename to cake/console/shells/tasks/bake.php index 7da7b820b..6a89e187c 100644 --- a/cake/console/libs/tasks/bake.php +++ b/cake/console/shells/tasks/bake.php @@ -51,9 +51,27 @@ class BakeTask extends Shell { public function getPath() { $path = $this->path; if (isset($this->plugin)) { - $name = substr($this->name, 0, strlen($this->name) - 4); - $path = $this->_pluginPath($this->plugin) . Inflector::pluralize(Inflector::underscore($name)) . DS; + $path = $this->_pluginPath($this->plugin) . Inflector::pluralize(Inflector::underscore($this->name)) . DS; } return $path; } + +/** + * Base execute method parses some parameters and sets some properties on the bake tasks. + * call when overriding execute() + * + * @return void + */ + public function execute() { + foreach($this->args as $i => $arg) { + if (strpos($arg, '.')) { + list($this->params['plugin'], $this->args[$i]) = pluginSplit($arg); + break; + } + } + if (isset($this->params['plugin'])) { + $this->plugin = $this->params['plugin']; + } + } + } diff --git a/cake/console/libs/tasks/controller.php b/cake/console/shells/tasks/controller.php similarity index 91% rename from cake/console/libs/tasks/controller.php rename to cake/console/shells/tasks/controller.php index 21f53b0df..2d7433936 100644 --- a/cake/console/libs/tasks/controller.php +++ b/cake/console/shells/tasks/controller.php @@ -56,6 +56,7 @@ class ControllerTask extends BakeTask { * */ public function execute() { + parent::execute(); if (empty($this->args)) { return $this->_interactive(); } @@ -69,26 +70,22 @@ class ControllerTask extends BakeTask { } $controller = $this->_controllerName($this->args[0]); - $actions = 'scaffold'; + $actions = ''; - if (!empty($this->args[1]) && ($this->args[1] == 'public' || $this->args[1] == 'scaffold')) { + if (!empty($this->params['public'])) { $this->out(__('Baking basic crud methods for ') . $controller); - $actions = $this->bakeActions($controller); - } elseif (!empty($this->args[1]) && $this->args[1] == 'admin') { - $admin = $this->Project->getPrefix(); - if ($admin) { - $this->out(sprintf(__('Adding %s methods'), $admin)); - $actions = $this->bakeActions($controller, $admin); - } + $actions .= $this->bakeActions($controller); } - - if (!empty($this->args[2]) && $this->args[2] == 'admin') { + if (!empty($this->params['admin'])) { $admin = $this->Project->getPrefix(); if ($admin) { $this->out(sprintf(__('Adding %s methods'), $admin)); $actions .= "\n" . $this->bakeActions($controller, $admin); } } + if (empty($actions)) { + $actions = 'scaffold'; + } if ($this->bake($controller, $actions)) { if ($this->_checkUnitTest()) { @@ -431,6 +428,34 @@ class ControllerTask extends BakeTask { return $controllerName; } +/** + * get the option parser. + * + * @return void + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + __('Bake a controller for a model. Using options you can bake public, admin or both.') + )->addArgument('name', array( + 'help' => __('Name of the controller to bake. Can use Plugin.name to bake controllers into plugins.') + ))->addOption('public', array( + 'help' => __('Bake a controller with basic crud actions (index, view, add, edit, delete).'), + 'boolean' => true + ))->addOption('admin', array( + 'help' => __('Bake a controller with crud actions for one of the Routing.prefixes.'), + 'boolean' => true + ))->addOption('plugin', array( + 'short' => 'p', + 'help' => __('Plugin to bake the controller into.') + ))->addOption('connection', array( + 'short' => 'c', + 'help' => __('The connection the controller\'s model is on.') + ))->addSubcommand('all', array( + 'help' => __('Bake all controllers with CRUD methods.') + ))->epilog(__('Omitting all arguments and options will enter into an interactive mode.')); + } + /** * Displays help contents * diff --git a/cake/console/libs/tasks/db_config.php b/cake/console/shells/tasks/db_config.php similarity index 92% rename from cake/console/libs/tasks/db_config.php rename to cake/console/shells/tasks/db_config.php index c62cbe1f3..d7e5a20c0 100644 --- a/cake/console/libs/tasks/db_config.php +++ b/cake/console/shells/tasks/db_config.php @@ -30,7 +30,6 @@ class DbConfigTask extends Shell { * path to CONFIG directory * * @var string - * @access public */ public $path = null; @@ -38,12 +37,19 @@ class DbConfigTask extends Shell { * Default configuration settings to use * * @var array - * @access private */ protected $_defaultConfig = array( - 'name' => 'default', 'driver'=> 'mysql', 'persistent'=> 'false', 'host'=> 'localhost', - 'login'=> 'root', 'password'=> 'password', 'database'=> 'project_name', - 'schema'=> null, 'prefix'=> null, 'encoding' => null, 'port' => null + 'name' => 'default', + 'driver'=> 'mysql', + 'persistent'=> 'false', + 'host'=> 'localhost', + 'login'=> 'root', + 'password'=> 'password', + 'database'=> 'project_name', + 'schema'=> null, + 'prefix'=> null, + 'encoding' => null, + 'port' => null ); /** @@ -60,7 +66,7 @@ class DbConfigTask extends Shell { * @var string */ public function initialize() { - $this->path = $this->params['working'] . DS . 'config' . DS; + $this->path = APP . 'config' . DS; } /** @@ -305,10 +311,10 @@ class DbConfigTask extends Shell { $out .= "class DATABASE_CONFIG {\n\n"; foreach ($configs as $config) { - $config = array_merge($this->__defaultConfig, $config); + $config = array_merge($this->_defaultConfig, $config); extract($config); - $out .= "\tvar \${$name} = array(\n"; + $out .= "\tpublic \${$name} = array(\n"; $out .= "\t\t'driver' => '{$driver}',\n"; $out .= "\t\t'persistent' => {$persistent},\n"; $out .= "\t\t'host' => '{$host}',\n"; @@ -337,7 +343,6 @@ class DbConfigTask extends Shell { } $out .= "}\n"; - $out .= "?>"; $filename = $this->path . 'database.php'; return $this->createFile($filename, $out); } @@ -362,4 +367,16 @@ class DbConfigTask extends Shell { } return $useDbConfig; } + +/** + * get the option parser + * + * @return ConsoleOptionParser + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + __('Bake new database configuration settings.') + ); + } } diff --git a/cake/console/libs/tasks/extract.php b/cake/console/shells/tasks/extract.php similarity index 89% rename from cake/console/libs/tasks/extract.php rename to cake/console/shells/tasks/extract.php index a03790cad..78a95fb3d 100644 --- a/cake/console/libs/tasks/extract.php +++ b/cake/console/shells/tasks/extract.php @@ -90,6 +90,13 @@ class ExtractTask extends Shell { */ private $__output = null; +/** + * An array of directories to exclude. + * + * @var array + */ + protected $_exclude = array(); + /** * Execution method always used for tasks * @@ -97,14 +104,17 @@ class ExtractTask extends Shell { * @access private */ function execute() { + if (!empty($this->params['exclude'])) { + $this->_exclude = explode(',', $this->params['exclude']); + } if (isset($this->params['files']) && !is_array($this->params['files'])) { $this->__files = explode(',', $this->params['files']); } if (isset($this->params['paths'])) { $this->__paths = explode(',', $this->params['paths']); } else { - $defaultPath = $this->params['working']; - $message = sprintf(__("What is the full path you would like to extract?\nExample: %s\n[Q]uit [D]one"), $this->params['root'] . DS . 'myapp'); + $defaultPath = APP_PATH; + $message = sprintf(__("What is the full path you would like to extract?\nExample: %s\n[Q]uit [D]one"), $this->Dispatch->params['root'] . DS . 'myapp'); while (true) { $response = $this->in($message, null, $defaultPath); if (strtoupper($response) === 'Q') { @@ -182,6 +192,27 @@ class ExtractTask extends Shell { $this->out(__('Done.')); } +/** + * Get & configure the option parser + * + * @return void + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description(__('CakePHP Language String Extraction:')) + ->addOption('app', array('help' => __('Directory where your application is located.'))) + ->addOption('paths', array('help' => __('Comma separted list of paths, full paths are needed.'))) + ->addOption('merge', array( + 'help' => __('Merge all domain strings into the default.po file.'), + 'choices' => array('yes', 'no') + )) + ->addOption('output', array('help' => __('Full path to output directory.'))) + ->addOption('files', array('help' => __('Comma separated list of files, full paths are needed.'))) + ->addOption('exclude', array( + 'help' => __('Comma separated list of directories to exclude. Any path containing a path segment with the provided values will be skipped. E.g. test,vendors') + )); + } + /** * Show help options * @@ -484,9 +515,21 @@ class ExtractTask extends Shell { * @access private */ function __searchFiles() { + $pattern = false; + if (!empty($this->_exclude)) { + $pattern = '/[\/\\\\]' . implode('|', $this->_exclude) . '[\/\\\\]/'; + } foreach ($this->__paths as $path) { $Folder = new Folder($path); $files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true); + if (!empty($pattern)) { + foreach ($files as $i => $file) { + if (preg_match($pattern, $file)) { + unset($files[$i]); + } + } + $files = array_values($files); + } $this->__files = array_merge($this->__files, $files); } } diff --git a/cake/console/libs/tasks/fixture.php b/cake/console/shells/tasks/fixture.php similarity index 89% rename from cake/console/libs/tasks/fixture.php rename to cake/console/shells/tasks/fixture.php index c7d9cc8ba..d26144549 100644 --- a/cake/console/libs/tasks/fixture.php +++ b/cake/console/shells/tasks/fixture.php @@ -54,9 +54,38 @@ class FixtureTask extends BakeTask { * Override initialize * */ - public function __construct(&$dispatch) { - parent::__construct($dispatch); - $this->path = $this->params['working'] . DS . 'tests' . DS . 'fixtures' . DS; + public function __construct($stdout = null, $stderr = null, $stdin = null) { + parent::__construct($stdout, $stderr, $stdin); + $this->path = APP . 'tests' . DS . 'fixtures' . DS; + } + +/** + * get the option parser. + * + * @return void + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + __('Generate fixtures for use with the test suite. You can use `bake fixture all` to bake all fixtures.') + )->addArgument('name', array( + 'help' => __('Name of the fixture to bake. Can use Plugin.name to bake plugin fixtures.') + ))->addOption('count', array( + 'help' => __('When using generated data, the number of records to include in the fixture(s).'), + 'short' => 'n', + 'default' => 10 + ))->addOption('connection', array( + 'help' => __('Which database configuration to use for baking.'), + 'short' => 'c', + 'default' => 'default' + ))->addOption('plugin', array( + 'help' => __('CamelCased name of the plugin to bake fixtures for.'), + 'short' => 'p', + ))->addOption('records', array( + 'help' => 'Used with --count and <name>/all commands to pull [n] records from the live tables, where [n] is either --count or the default of 10', + 'short' => 'r', + 'boolean' => true + ))->epilog(__('Omitting all arguments and options will enter into an interactive mode.'));; } /** @@ -66,6 +95,7 @@ class FixtureTask extends BakeTask { * @return void */ public function execute() { + parent::execute(); if (empty($this->args)) { $this->_interactive(); } @@ -382,31 +412,4 @@ class FixtureTask extends BakeTask { return $out; } -/** - * Displays help contents - * - */ - public function help() { - $this->hr(); - $this->out("Usage: cake bake fixture <arg1> <params>"); - $this->hr(); - $this->out('Arguments:'); - $this->out(); - $this->out("<name>"); - $this->out("\tName of the fixture to bake. Can use Plugin.name"); - $this->out("\tas a shortcut for plugin baking."); - $this->out(); - $this->out('Commands:'); - $this->out("\nfixture <name>\n\tbakes fixture with specified name."); - $this->out("\nfixture all\n\tbakes all fixtures."); - $this->out(); - $this->out('Parameters:'); - $this->out("\t-count When using generated data, the number of records to include in the fixture(s)."); - $this->out("\t-connection Which database configuration to use for baking."); - $this->out("\t-plugin CamelCased name of plugin to bake fixtures for."); - $this->out("\t-records Used with -count and <name>/all commands to pull [n] records from the live tables"); - $this->out("\t Where [n] is either -count or the default of 10."); - $this->out(); - $this->_stop(); - } } diff --git a/cake/console/libs/tasks/model.php b/cake/console/shells/tasks/model.php similarity index 97% rename from cake/console/libs/tasks/model.php rename to cake/console/shells/tasks/model.php index 80f5c3717..6bc7c5c6a 100644 --- a/cake/console/libs/tasks/model.php +++ b/cake/console/shells/tasks/model.php @@ -74,6 +74,7 @@ class ModelTask extends BakeTask { */ public function execute() { App::import('Model', 'Model', false); + parent::execute(); if (empty($this->args)) { $this->_interactive(); @@ -873,31 +874,25 @@ class ModelTask extends BakeTask { } /** - * Displays help contents + * get the option parser. * + * @return void */ - public function help() { - $this->hr(); - $this->out("Usage: cake bake model <arg1>"); - $this->hr(); - $this->out('Arguments:'); - $this->out(); - $this->out("<name>"); - $this->out("\tName of the model to bake. Can use Plugin.name"); - $this->out("\tas a shortcut for plugin baking."); - $this->out(); - $this->out('Commands:'); - $this->out(); - $this->out("model"); - $this->out("\tbakes model in interactive mode."); - $this->out(); - $this->out("model <name>"); - $this->out("\tbakes model file with no associations or validation"); - $this->out(); - $this->out("model all"); - $this->out("\tbakes all model files with associations and validation"); - $this->out(); - $this->_stop(); + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + __('Bake models.') + )->addArgument('name', array( + 'help' => __('Name of the model to bake. Can use Plugin.name to bake plugin models.') + ))->addSubcommand('all', array( + 'help' => __('Bake all model files with associations and validation.') + ))->addOption('plugin', array( + 'short' => 'p', + 'help' => __('Plugin to bake the model into.') + ))->addOption('connection', array( + 'short' => 'c', + 'help' => __('The connection the model table is on.') + ))->epilog(__('Omitting all arguments and options will enter into an interactive mode.')); } /** diff --git a/cake/console/libs/tasks/plugin.php b/cake/console/shells/tasks/plugin.php similarity index 64% rename from cake/console/libs/tasks/plugin.php rename to cake/console/shells/tasks/plugin.php index a59dec05d..4fca522dc 100644 --- a/cake/console/libs/tasks/plugin.php +++ b/cake/console/shells/tasks/plugin.php @@ -17,6 +17,7 @@ * @since CakePHP(tm) v 1.2 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ +App::import('Core', 'File'); /** * Task class for creating a plugin @@ -26,12 +27,6 @@ */ class PluginTask extends Shell { -/** - * Tasks - * - */ - public $tasks = array('Model', 'Controller', 'View'); - /** * path to CONTROLLERS directory * @@ -55,45 +50,20 @@ class PluginTask extends Shell { * @return void */ public function execute() { - if (empty($this->params['skel'])) { - $this->params['skel'] = ''; - if (is_dir(CAKE_CORE_INCLUDE_PATH . DS . CAKE . 'console' . DS . 'templates' . DS . 'skel') === true) { - $this->params['skel'] = CAKE_CORE_INCLUDE_PATH . DS . CAKE . 'console' . DS . 'templates' . DS . 'skel'; - } - } $plugin = null; if (isset($this->args[0])) { $plugin = Inflector::camelize($this->args[0]); $pluginPath = $this->_pluginPath($plugin); - $this->Dispatch->shiftArgs(); if (is_dir($pluginPath)) { $this->out(sprintf(__('Plugin: %s'), $plugin)); $this->out(sprintf(__('Path: %s'), $pluginPath)); - } elseif (isset($this->args[0])) { - $this->err(sprintf(__('%s in path %s not found.'), $plugin, $pluginPath)); - $this->_stop(); } else { $this->_interactive($plugin); } } else { return $this->_interactive(); } - - if (isset($this->args[0])) { - $task = Inflector::classify($this->args[0]); - $this->Dispatch->shiftArgs(); - if (in_array($task, $this->tasks)) { - $this->{$task}->plugin = $plugin; - $this->{$task}->path = $pluginPath . Inflector::underscore(Inflector::pluralize($task)) . DS; - - if (!is_dir($this->{$task}->path)) { - $this->err(sprintf(__("%s directory could not be found.\nBe sure you have created %s"), $task, $this->{$task}->path)); - } - $this->{$task}->loadTasks(); - return $this->{$task}->execute(); - } - } } /** @@ -108,7 +78,7 @@ class PluginTask extends Shell { } if (!$this->bake($plugin)) { - $this->err(sprintf(__("An error occured trying to bake: %s in %s"), $plugin, $this->path . Inflector::underscore($pluginPath))); + $this->error(sprintf(__("An error occured trying to bake: %s in %s"), $plugin, $this->path . Inflector::underscore($pluginPath))); } } @@ -127,20 +97,19 @@ class PluginTask extends Shell { $this->findPath($pathOptions); } $this->hr(); - $this->out(sprintf(__("Plugin Name: %s"), $plugin)); - $this->out(sprintf(__("Plugin Directory: %s"), $this->path . $pluginPath)); + $this->out(sprintf(__("<info>Plugin Name:</info> %s"), $plugin)); + $this->out(sprintf(__("<info>Plugin Directory:</info> %s"), $this->path . $pluginPath)); $this->hr(); $looksGood = $this->in(__('Look okay?'), array('y', 'n', 'q'), 'y'); if (strtolower($looksGood) == 'y') { - $verbose = $this->in(__('Do you want verbose output?'), array('y', 'n'), 'n'); - $Folder =& new Folder($this->path . $pluginPath); $directories = array( 'config' . DS . 'schema', 'models' . DS . 'behaviors', 'models' . DS . 'datasources', + 'console' . DS . 'shells' . DS . 'tasks', 'controllers' . DS . 'components', 'libs', 'views' . DS . 'helpers', @@ -152,7 +121,6 @@ class PluginTask extends Shell { 'tests' . DS . 'groups', 'tests' . DS . 'fixtures', 'vendors', - 'vendors' . DS . 'shells' . DS . 'tasks', 'webroot' ); @@ -162,10 +130,8 @@ class PluginTask extends Shell { $File =& new File($dirPath . DS . 'empty', true); } - if (strtolower($verbose) == 'y') { - foreach ($Folder->messages() as $message) { - $this->out($message); - } + foreach ($Folder->messages() as $message) { + $this->out($message, 1, Shell::VERBOSE); } $errors = $Folder->errors(); @@ -190,8 +156,7 @@ class PluginTask extends Shell { $this->createFile($this->path . $pluginPath . DS . $modelFileName, $out); $this->hr(); - $this->out(sprintf(__('Created: %s in %s'), $plugin, $this->path . $pluginPath)); - $this->hr(); + $this->out(sprintf(__('<success>Created:</success> %s in %s'), $plugin, $this->path . $pluginPath), 2); } return true; @@ -219,28 +184,19 @@ class PluginTask extends Shell { } /** - * Help + * get the option parser for the plugin task * * @return void */ - public function help() { - $this->hr(); - $this->out("Usage: cake bake plugin <arg1> <arg2>..."); - $this->hr(); - $this->out('Commands:'); - $this->out(); - $this->out("plugin <name>"); - $this->out("\tbakes plugin directory structure"); - $this->out(); - $this->out("plugin <name> model"); - $this->out("\tbakes model. Run 'cake bake model help' for more info."); - $this->out(); - $this->out("plugin <name> controller"); - $this->out("\tbakes controller. Run 'cake bake controller help' for more info."); - $this->out(); - $this->out("plugin <name> view"); - $this->out("\tbakes view. Run 'cake bake view help' for more info."); - $this->out(); - $this->_stop(); + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + 'Create the directory structure, AppModel and AppController classes for a new plugin. ' . + 'Can create plugins in any of your bootstrapped plugin paths.' + )->addArgument('name', array( + 'help' => __('CamelCased name of the plugin to create.') + )); + } + } diff --git a/cake/console/libs/tasks/project.php b/cake/console/shells/tasks/project.php similarity index 61% rename from cake/console/libs/tasks/project.php rename to cake/console/shells/tasks/project.php index 3cc1270bc..3710a9906 100644 --- a/cake/console/libs/tasks/project.php +++ b/cake/console/shells/tasks/project.php @@ -41,35 +41,34 @@ class ProjectTask extends Shell { * * @param string $project Project path */ - public function execute($project = null) { - if ($project === null) { - if (isset($this->args[0])) { - $project = $this->args[0]; - } + public function execute() { + $project = null; + if (isset($this->args[0])) { + $project = $this->args[0]; } - if ($project) { - $this->Dispatch->parseParams(array('-app', $project)); - $project = $this->params['working']; + if ($project && isset($_SERVER['PWD'])) { + $project = $_SERVER['PWD'] . DS . $project; } if (empty($this->params['skel'])) { - $this->params['skel'] = ''; - if (is_dir(CAKE . 'console' . DS . 'templates' . DS . 'skel') === true) { - $this->params['skel'] = CAKE . 'console' . DS . 'templates' . DS . 'skel'; + $core = App::core('shells'); + $skelPath = dirname($core[0]) . DS . 'templates' . DS . 'skel'; + if (is_dir($skelPath) === true) { + $this->params['skel'] = $skelPath; } } while (!$project) { $prompt = __("What is the full path for this app including the app directory name?\n Example:"); - $default = $this->params['working'] . DS . 'myapp'; + $default = APP_PATH . 'myapp'; $project = $this->in($prompt . $default, null, $default); } if ($project) { $response = false; while ($response == false && is_dir($project) === true && file_exists($project . 'config' . 'core.php')) { - $prompt = sprintf(__('A project already exists in this location: %s Overwrite?'), $project); + $prompt = sprintf(__('<warning>A project already exists in this location:</warning> %s Overwrite?'), $project); $response = $this->in($prompt, array('y','n'), 'n'); if (strtolower($response) === 'n') { $response = $project = false; @@ -77,43 +76,57 @@ class ProjectTask extends Shell { } } + $success = true; if ($this->bake($project)) { $path = Folder::slashTerm($project); if ($this->createHome($path)) { - $this->out(__('Welcome page created')); + $this->out(__(' * Welcome page created')); } else { - $this->out(__('The Welcome page was NOT created')); + $this->err(__('The Welcome page was <error>NOT</error> created')); + $success = false; } - if ($this->securitySalt($path) === true ) { - $this->out(__('Random hash key created for \'Security.salt\'')); + if ($this->securitySalt($path) === true) { + $this->out(__(' * Random hash key created for \'Security.salt\'')); } else { $this->err(sprintf(__('Unable to generate random hash for \'Security.salt\', you should change it in %s'), CONFIGS . 'core.php')); + $success = false; } - if ($this->securityCipherSeed($path) === true ) { - $this->out(__('Random seed created for \'Security.cipherSeed\'')); + if ($this->securityCipherSeed($path) === true) { + $this->out(__(' * Random seed created for \'Security.cipherSeed\'')); } else { $this->err(sprintf(__('Unable to generate random seed for \'Security.cipherSeed\', you should change it in %s'), CONFIGS . 'core.php')); + $success = false; } - $corePath = $this->corePath($path); - if ($corePath === true ) { - $this->out(sprintf(__('CAKE_CORE_INCLUDE_PATH set to %s in webroot/index.php'), CAKE_CORE_INCLUDE_PATH)); - $this->out(sprintf(__('CAKE_CORE_INCLUDE_PATH set to %s in webroot/test.php'), CAKE_CORE_INCLUDE_PATH)); - $this->out(__('Remember to check these value after moving to production server')); - } elseif ($corePath === false) { + if ($this->corePath($path) === true) { + $this->out(sprintf(__(' * CAKE_CORE_INCLUDE_PATH set to %s in webroot/index.php'), CAKE_CORE_INCLUDE_PATH)); + $this->out(sprintf(__(' * CAKE_CORE_INCLUDE_PATH set to %s in webroot/test.php'), CAKE_CORE_INCLUDE_PATH)); + $this->out(__(' * <warning>Remember to check these value after moving to production server</warning>')); + } else { $this->err(sprintf(__('Unable to set CAKE_CORE_INCLUDE_PATH, you should change it in %s'), $path . 'webroot' .DS .'index.php')); + $success = false; } + if ($this->consolePath($path) === true) { + $this->out(__(' * app/console/cake.php path set.')); + } else { + $this->err(__('Unable to set console path for app/console.')); + $success = false; + } + $Folder = new Folder($path); if (!$Folder->chmod($path . 'tmp', 0777)) { $this->err(sprintf(__('Could not set permissions on %s'), $path . DS .'tmp')); $this->out(sprintf(__('chmod -R 0777 %s'), $path . DS .'tmp')); + $success = false; } - - $this->params['working'] = $path; - $this->params['app'] = basename($path); - return true; + if ($success) { + $this->out(__('<success>Project baked successfully!</success>')); + } else { + $this->out(__('Project baked but with <warning>some issues.</warning>.')); + } + return $path; } } @@ -135,7 +148,7 @@ class ProjectTask extends Shell { while (!$skel) { $skel = $this->in(sprintf(__("What is the path to the directory layout you wish to copy?\nExample: %s"), APP, null, ROOT . DS . 'myapp' . DS)); if ($skel == '') { - $this->out(__('The directory path you supplied was empty. Please try again.')); + $this->err(__('The directory path you supplied was empty. Please try again.')); } else { while (is_dir($skel) === false) { $skel = $this->in(__('Directory path does not exist please choose another:')); @@ -145,33 +158,29 @@ class ProjectTask extends Shell { $app = basename($path); - $this->out(__('Bake Project')); - $this->out(__('Skel Directory: ') . $skel); - $this->out(__('Will be copied to: ') . $path); + $this->out(__('<info>Skel Directory</info>: ') . $skel); + $this->out(__('<info>Will be copied to</info>: ') . $path); $this->hr(); $looksGood = $this->in(__('Look okay?'), array('y', 'n', 'q'), 'y'); if (strtolower($looksGood) == 'y') { - $verbose = $this->in(__('Do you want verbose output?'), array('y', 'n'), 'n'); - $Folder = new Folder($skel); if (!empty($this->params['empty'])) { $skip = array(); } + if ($Folder->copy(array('to' => $path, 'skip' => $skip))) { $this->hr(); - $this->out(sprintf(__('Created: %s in %s'), $app, $path)); + $this->out(sprintf(__('<success>Created:</success> %s in %s'), $app, $path)); $this->hr(); } else { - $this->err(sprintf(__(" '%s' could not be created properly"), $app)); + $this->err(sprintf(__("<error>Could not create</error> '%s' properly."), $app)); return false; } - if (strtolower($verbose) == 'y') { - foreach ($Folder->messages() as $message) { - $this->out($message); - } + foreach ($Folder->messages() as $message) { + $this->out(String::wrap(' * ' . $message), 1, Shell::VERBOSE); } return true; @@ -197,6 +206,28 @@ class ProjectTask extends Shell { return $this->createFile($path.'home.ctp', $output); } +/** + * Generates the correct path to the CakePHP libs that are generating the project + * and points app/console/cake.php to the right place + * + * @param string $path Project path. + * @return boolean success + */ + public function consolePath($path) { + $File = new File($path . 'console' . DS . 'cake.php'); + $contents = $File->read(); + if (preg_match('/(__CAKE_PATH__)/', $contents, $match)) { + $path = CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS . 'console' . DS; + $replacement = "'" . str_replace(DS, "' . DIRECTORY_SEPARATOR . '", $path) . "'"; + $result = str_replace($match[0], $replacement, $contents); + if ($File->write($result)) { + return true; + } + return false; + } + return false; + } + /** * Generates and writes 'Security.salt' * @@ -204,9 +235,9 @@ class ProjectTask extends Shell { * @return boolean Success */ public function securitySalt($path) { - $File =& new File($path . 'config' . DS . 'core.php'); + $File = new File($path . 'config' . DS . 'core.php'); $contents = $File->read(); - if (preg_match('/([\\t\\x20]*Configure::write\\(\\\'Security.salt\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) { + if (preg_match('/([\s]*Configure::write\(\'Security.salt\',[\s\'A-z0-9]*\);)/', $contents, $match)) { if (!class_exists('Security')) { require LIBS . 'security.php'; } @@ -220,28 +251,28 @@ class ProjectTask extends Shell { return false; } - /** - * Generates and writes 'Security.cipherSeed' - * - * @param string $path Project path - * @return boolean Success - */ - public function securityCipherSeed($path) { - $File =& new File($path . 'config' . DS . 'core.php'); - $contents = $File->read(); - if (preg_match('/([\\t\\x20]*Configure::write\\(\\\'Security.cipherSeed\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) { - if (!class_exists('Security')) { - require LIBS . 'security.php'; - } - $string = substr(bin2hex(Security::generateAuthKey()), 0, 30); - $result = str_replace($match[0], "\t" . 'Configure::write(\'Security.cipherSeed\', \''.$string.'\');', $contents); - if ($File->write($result)) { - return true; - } - return false; +/** + * Generates and writes 'Security.cipherSeed' + * + * @param string $path Project path + * @return boolean Success + */ + public function securityCipherSeed($path) { + $File = new File($path . 'config' . DS . 'core.php'); + $contents = $File->read(); + if (preg_match('/([\s]*Configure::write\(\'Security.cipherSeed\',[\s\'A-z0-9]*\);)/', $contents, $match)) { + if (!class_exists('Security')) { + require LIBS . 'security.php'; + } + $string = substr(bin2hex(Security::generateAuthKey()), 0, 30); + $result = str_replace($match[0], "\t" . 'Configure::write(\'Security.cipherSeed\', \''.$string.'\');', $contents); + if ($File->write($result)) { + return true; } return false; } + return false; + } /** * Generates and writes CAKE_CORE_INCLUDE_PATH @@ -251,9 +282,9 @@ class ProjectTask extends Shell { */ public function corePath($path) { if (dirname($path) !== CAKE_CORE_INCLUDE_PATH) { - $File =& new File($path . 'webroot' . DS . 'index.php'); + $File = new File($path . 'webroot' . DS . 'index.php'); $contents = $File->read(); - if (preg_match('/([\\t\\x20]*define\\(\\\'CAKE_CORE_INCLUDE_PATH\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) { + if (preg_match('/([\s]*define\(\'CAKE_CORE_INCLUDE_PATH\',[\s\'A-z0-9]*\);)/', $contents, $match)) { $root = strpos(CAKE_CORE_INCLUDE_PATH, '/') === 0 ? " DS . '" : "'"; $result = str_replace($match[0], "\t\tdefine('CAKE_CORE_INCLUDE_PATH', " . $root . str_replace(DS, "' . DS . '", trim(CAKE_CORE_INCLUDE_PATH, DS)) . "');", $contents); if (!$File->write($result)) { @@ -263,9 +294,9 @@ class ProjectTask extends Shell { return false; } - $File =& new File($path . 'webroot' . DS . 'test.php'); + $File = new File($path . 'webroot' . DS . 'test.php'); $contents = $File->read(); - if (preg_match('/([\\t\\x20]*define\\(\\\'CAKE_CORE_INCLUDE_PATH\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) { + if (preg_match('/([\s]*define\(\'CAKE_CORE_INCLUDE_PATH\',[\s\'A-z0-9]*\);)/', $contents, $match)) { $result = str_replace($match[0], "\t\tdefine('CAKE_CORE_INCLUDE_PATH', " . $root . str_replace(DS, "' . DS . '", trim(CAKE_CORE_INCLUDE_PATH, DS)) . "');", $contents); if (!$File->write($result)) { return false; @@ -285,9 +316,9 @@ class ProjectTask extends Shell { */ public function cakeAdmin($name) { $path = (empty($this->configPath)) ? CONFIGS : $this->configPath; - $File =& new File($path . 'core.php'); + $File = new File($path . 'core.php'); $contents = $File->read(); - if (preg_match('%([/\\t\\x20]*Configure::write\(\'Routing.prefixes\',[\\t\\x20\'a-z,\)\(]*\\);)%', $contents, $match)) { + if (preg_match('%([/\s]*Configure::write\(\'Routing.prefixes\',[\s\'a-z,\)\(]*\);)%', $contents, $match)) { $result = str_replace($match[0], "\t" . 'Configure::write(\'Routing.prefixes\', array(\''.$name.'\'));', $contents); if ($File->write($result)) { Configure::write('Routing.prefixes', array($name)); @@ -335,7 +366,7 @@ class ProjectTask extends Shell { $admin = $this->in(__('Enter a routing prefix:'), null, 'admin'); } if ($this->cakeAdmin($admin) !== true) { - $this->out(__('Unable to write to /app/config/core.php.')); + $this->out(__('<error>Unable to write to</error> /app/config/core.php.')); $this->out('You need to enable Configure::write(\'Routing.prefixes\',array(\'admin\')) in /app/config/core.php to use prefix routing.'); $this->_stop(); } @@ -345,21 +376,21 @@ class ProjectTask extends Shell { } /** - * Help + * get the option parser. * - * @return void + * @return ConsoleOptionParser */ - public function help() { - $this->hr(); - $this->out("Usage: cake bake project <arg1>"); - $this->hr(); - $this->out('Commands:'); - $this->out(); - $this->out("project <name>"); - $this->out("\tbakes app directory structure."); - $this->out("\tif <name> begins with '/' path is absolute."); - $this->out(); - $this->_stop(); + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + __('Generate a new CakePHP project skeleton.') + )->addArgument('name', array( + 'help' => __('Application directory to make, if it starts with "/" the path is absolute.') + ))->addOption('empty', array( + 'help' => __('Create empty files in each of the directories. Good if you are using git') + ))->addOption('skel', array( + 'help' => __('The directory layout to use for the new application skeleton. Defaults to cake/console/templates/skel of CakePHP used to create the project.') + )); } } diff --git a/cake/console/libs/tasks/template.php b/cake/console/shells/tasks/template.php similarity index 96% rename from cake/console/libs/tasks/template.php rename to cake/console/shells/tasks/template.php index 4d47b5a10..b9e267087 100644 --- a/cake/console/libs/tasks/template.php +++ b/cake/console/shells/tasks/template.php @@ -17,6 +17,8 @@ * @since CakePHP(tm) v 1.3 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ +App::import('Core', 'Folder'); + class TemplateTask extends Shell { /** @@ -55,7 +57,7 @@ class TemplateTask extends Shell { $paths = App::path('shells'); $core = array_pop($paths); $separator = DS === '/' ? '/' : '\\\\'; - $core = preg_replace('#libs' . $separator . '$#', '', $core); + $core = preg_replace('#shells' . $separator . '$#', '', $core); $paths[] = $core; $Folder =& new Folder($core . 'templates' . DS . 'default'); $contents = $Folder->read(); @@ -63,6 +65,7 @@ class TemplateTask extends Shell { $plugins = App::objects('plugin'); foreach ($plugins as $plugin) { + $paths[] = $this->_pluginPath($plugin) . 'console' . DS . 'shells' . DS; $paths[] = $this->_pluginPath($plugin) . 'vendors' . DS . 'shells' . DS; } @@ -178,7 +181,7 @@ class TemplateTask extends Shell { } $index = $this->in(__('Which bake theme would you like to use?'), range(1, $i - 1), 1); $themeNames = array_keys($this->templatePaths); - $this->Dispatch->params['theme'] = $themeNames[$index - 1]; + $this->params['theme'] = $themeNames[$index - 1]; return $indexedPaths[$index]; } diff --git a/cake/console/libs/tasks/test.php b/cake/console/shells/tasks/test.php similarity index 94% rename from cake/console/libs/tasks/test.php rename to cake/console/shells/tasks/test.php index f64e9bdc1..ce300f058 100644 --- a/cake/console/libs/tasks/test.php +++ b/cake/console/shells/tasks/test.php @@ -60,12 +60,12 @@ class TestTask extends BakeTask { */ protected $_fixtures = array(); - /** * Execution method always used for tasks * */ public function execute() { + parent::execute(); if (empty($this->args)) { $this->_interactive(); } @@ -77,7 +77,7 @@ class TestTask extends BakeTask { if (count($this->args) > 1) { $type = Inflector::underscore($this->args[0]); if ($this->bake($type, $this->args[1])) { - $this->out('done'); + $this->out('<success>Done</success>'); } } } @@ -412,27 +412,21 @@ class TestTask extends BakeTask { } /** - * Show help file. + * get the option parser. * * @return void */ - public function help() { - $this->hr(); - $this->out("Usage: cake bake test <type> <class>"); - $this->hr(); - $this->out('Commands:'); - $this->out(""); - $this->out("test model post\n\tbakes a test case for the post model."); - $this->out(""); - $this->out("test controller comments\n\tbakes a test case for the comments controller."); - $this->out(""); - $this->out('Arguments:'); - $this->out("\t<type> Can be any of the following 'controller', 'model', 'helper',\n\t'component', 'behavior'."); - $this->out("\t<class> Any existing class for the chosen type."); - $this->out(""); - $this->out("Parameters:"); - $this->out("\t-plugin CamelCased name of plugin to bake tests for."); - $this->out(""); - $this->_stop(); + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description(__('Bake test case skeletons for classes.')) + ->addArgument('type', array( + 'help' => __('Type of class to bake, can be any of the following: controller, model, helper, component or behavior.'), + 'choices' => array('controller', 'model', 'helper', 'component', 'behavior') + ))->addArgument('name', array( + 'help' => __('An existing class to bake tests for.') + ))->addOption('plugin', array( + 'short' => 'p', + 'help' => __('CamelCased name of the plugin to bake tests for.') + ))->epilog(__('Omitting all arguments and options will enter into an interactive mode.')); } } diff --git a/cake/console/libs/tasks/view.php b/cake/console/shells/tasks/view.php similarity index 90% rename from cake/console/libs/tasks/view.php rename to cake/console/shells/tasks/view.php index f4f4e95bd..c166a003d 100644 --- a/cake/console/libs/tasks/view.php +++ b/cake/console/shells/tasks/view.php @@ -97,6 +97,7 @@ class ViewTask extends BakeTask { * */ public function execute() { + parent::execute(); if (empty($this->args)) { $this->_interactive(); } @@ -421,43 +422,32 @@ class ViewTask extends BakeTask { } /** - * Displays help contents + * get the option parser for this task * + * @return ConsoleOptionParser */ - public function help() { - $this->hr(); - $this->out("Usage: cake bake view <arg1> <arg2>..."); - $this->hr(); - $this->out('Arguments:'); - $this->out(); - $this->out("<controller>"); - $this->out("\tName of the controller views to bake. Can use Plugin.name"); - $this->out("\tas a shortcut for plugin baking."); - $this->out(); - $this->out("<action>"); - $this->out("\tName of the action view to bake"); - $this->out(); - $this->out('Commands:'); - $this->out(); - $this->out("view <controller>"); - $this->out("\tWill read the given controller for methods"); - $this->out("\tand bake corresponding views."); - $this->out("\tUsing the -admin flag will only bake views for actions"); - $this->out("\tthat begin with Routing.prefixes."); - $this->out("\tIf var scaffold is found it will bake the CRUD actions"); - $this->out("\t(index,view,add,edit)"); - $this->out(); - $this->out("view <controller> <action>"); - $this->out("\tWill bake a template. core templates: (index, add, edit, view)"); - $this->out(); - $this->out("view <controller> <template> <alias>"); - $this->out("\tWill use the template specified"); - $this->out("\tbut name the file based on the alias"); - $this->out(); - $this->out("view all"); - $this->out("\tBake all CRUD action views for all controllers."); - $this->out("\tRequires that models and controllers exist."); - $this->_stop(); + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description( + __('Bake views for a controller, using built-in or custom templates.') + )->addArgument('controller', array( + 'help' => __('Name of the controller views to bake. Can be Plugin.name as a shortcut for plugin baking.') + ))->addArgument('action', array( + 'help' => __("Will bake a single action's file. core templates are (index, add, edit, view)") + ))->addArgument('alias', array( + 'help' => __('Will bake the template in <action> but create the filename after <alias>.') + ))->addOption('plugin', array( + 'short' => 'p', + 'help' => __('Plugin to bake the view into.') + ))->addOption('admin', array( + 'help' => __('Set to only bake views for a prefix in Routing.prefixes'), + 'boolean' => true + ))->addOption('connection', array( + 'short' => 'c', + 'help' => __('The connection the connected model is on.') + ))->addSubcommand('all', array( + 'help' => __('Bake all CRUD action views for all controllers. Requires models and controllers to exist.') + ))->epilog(__('Omitting all arguments and options will enter into an interactive mode.')); } /** diff --git a/cake/console/libs/testsuite.php b/cake/console/shells/testsuite.php similarity index 90% rename from cake/console/libs/testsuite.php rename to cake/console/shells/testsuite.php index 265cf5486..7d9356762 100644 --- a/cake/console/libs/testsuite.php +++ b/cake/console/shells/testsuite.php @@ -28,6 +28,29 @@ class TestSuiteShell extends Shell { */ protected $_dispatcher = null; +/** + * get the option parser for the test suite. + * + * @return void + */ + public function getOptionParser() { + $parser = new ConsoleOptionParser($this->name); + $parser->description(array( + 'The CakePHP Testsuite allows you to run test cases from the command line', + 'If run with no command line arguments, a list of available core test cases will be shown' + ))->addArgument('category', array( + 'help' => __('app, core or name of a plugin.'), + 'required' => true + ))->addArgument('file', array( + 'help' => __('file name with folder prefix and without the test.php suffix.'), + 'required' => true, + ))->addOption('filter', array( + 'help' => __('Filter which tests to run.'), + 'default' => false + )); + return $parser; + } + /** * Initialization method installs Simpletest and loads all plugins * @@ -74,9 +97,6 @@ class TestSuiteShell extends Shell { if (isset($this->args[1])) { $params['case'] = Inflector::underscore($this->args[1]); } - if (isset($this->params['filter'])) { - $this->params['-filter'] = $this->params['filter']; - } return $params; } @@ -87,12 +107,13 @@ class TestSuiteShell extends Shell { */ protected function runnerOptions() { $options = array(); - foreach ($this->params as $param => $value) { - if ($param[0] === '-') { - $options[] = '-' . $param; - if (is_string($value)) { - $options[] = $value; - } + $params = $this->params; + unset($params['help']); + $params = array_filter($params); + foreach ($params as $param => $value) { + $options[] = '--' . $param; + if (is_string($value)) { + $options[] = $value; } } return $options; diff --git a/cake/console/templates/skel/console/cake b/cake/console/templates/skel/console/cake new file mode 100644 index 000000000..d4d067d59 --- /dev/null +++ b/cake/console/templates/skel/console/cake @@ -0,0 +1,26 @@ +#!/bin/bash +################################################################################ +# +# Bake is a shell script for running CakePHP bake script +# PHP 5 +# +# CakePHP(tm) : Rapid Development Framework (http://cakephp.org) +# Copyright 2005-2010, Cake Software Foundation, Inc. +# +# Licensed under The MIT License +# Redistributions of files must retain the above copyright notice. +# +# @copyright Copyright 2005-2010, Cake Software Foundation, Inc. +# @link http://cakephp.org CakePHP(tm) Project +# @package cake +# @subpackage cake.app.console +# @since CakePHP(tm) v 2.0 +# @license MIT License (http://www.opensource.org/licenses/mit-license.php) +# +################################################################################ +LIB=${0/%cake/} +APP=`pwd` + +exec php -q ${LIB}cake.php -working "${APP}" "$@" + +exit; \ No newline at end of file diff --git a/cake/console/templates/skel/console/cake.bat b/cake/console/templates/skel/console/cake.bat new file mode 100644 index 000000000..84ed04a4f --- /dev/null +++ b/cake/console/templates/skel/console/cake.bat @@ -0,0 +1,33 @@ +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Bake is a shell script for running CakePHP bake script +:: PHP 5 +:: +:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org) +:: Copyright 2005-2010, Cake Software Foundation, Inc. +:: +:: Licensed under The MIT License +:: Redistributions of files must retain the above copyright notice. +:: +:: @copyright Copyright 2005-2010, Cake Software Foundation, Inc. +:: @link http://cakephp.org CakePHP(tm) Project +:: @package cake +:: @subpackage cake.app.console +:: @since CakePHP(tm) v 2.0 +:: @license MIT License (http://www.opensource.org/licenses/mit-license.php) +:: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: In order for this script to work as intended, the cake\console\ folder must be in your PATH + +@echo. +@echo off + +SET app=%0 +SET lib=%~dp0 + +php -q "%lib%cake.php" -working "%CD%" %* + +echo. + +exit /B %ERRORLEVEL% \ No newline at end of file diff --git a/cake/console/templates/skel/console/cake.php b/cake/console/templates/skel/console/cake.php new file mode 100644 index 000000000..8ccac0bfb --- /dev/null +++ b/cake/console/templates/skel/console/cake.php @@ -0,0 +1,25 @@ +#!/usr/bin/php -q +<?php +/** + * Command-line code generation utility to automate programmer chores. + * + * Shell dispatcher class + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console + * @since CakePHP(tm) v 1.2.0.5012 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +require_once(__CAKE_PATH__ . 'shell_dispatcher.php'); + +return ShellDispatcher::run($argv); diff --git a/cake/tests/test_app/plugins/test_plugin/vendors/shells/templates/empty b/cake/console/templates/skel/console/shells/tasks/empty similarity index 100% rename from cake/tests/test_app/plugins/test_plugin/vendors/shells/templates/empty rename to cake/console/templates/skel/console/shells/tasks/empty diff --git a/cake/console/templates/skel/tmp/cache/models/empty b/cake/console/templates/skel/tmp/cache/models/empty old mode 100644 new mode 100755 diff --git a/cake/console/templates/skel/tmp/cache/persistent/empty b/cake/console/templates/skel/tmp/cache/persistent/empty old mode 100644 new mode 100755 diff --git a/cake/console/templates/skel/tmp/cache/views/empty b/cake/console/templates/skel/tmp/cache/views/empty old mode 100644 new mode 100755 diff --git a/cake/tests/test_app/plugins/test_plugin_two/vendors/shells/tasks/empty b/cake/console/templates/skel/vendors/empty similarity index 100% rename from cake/tests/test_app/plugins/test_plugin_two/vendors/shells/tasks/empty rename to cake/console/templates/skel/vendors/empty diff --git a/cake/libs/configure.php b/cake/libs/configure.php index bee30774d..9373f907f 100644 --- a/cake/libs/configure.php +++ b/cake/libs/configure.php @@ -587,7 +587,11 @@ class App { 'views' => array(VIEWS), 'helpers' => array(HELPERS), 'locales' => array(APP . 'locale' . DS), - 'shells' => array(APP . 'vendors' . DS . 'shells' . DS, VENDORS . 'shells' . DS), + 'shells' => array( + APP . 'console' . DS . 'shells' . DS, + APP . 'vendors' . DS . 'shells' . DS, + VENDORS . 'shells' . DS + ), 'vendors' => array(APP . 'vendors' . DS, VENDORS), 'plugins' => array(APP . 'plugins' . DS) ); @@ -690,7 +694,9 @@ class App { $paths['helpers'][] = $libs . 'view' . DS . 'helpers' . DS; $paths['plugins'][] = $path . 'plugins' . DS; $paths['vendors'][] = $path . 'vendors' . DS; - $paths['shells'][] = $cake . 'console' . DS . 'libs' . DS; + $paths['shells'][] = $cake . 'console' . DS . 'shells' . DS; + // Provide BC path to vendors/shells + $paths['shells'][] = $path . 'vendors' . DS . 'shells' . DS; Cache::write('core_paths', array_filter($paths), '_cake_core_'); } @@ -1136,6 +1142,15 @@ class App { } return array('class' => $type, 'suffix' => null, 'path' => $path); break; + case 'shell': + if (!class_exists('Shell')) { + App::import($type, 'Shell', false); + } + if ($plugin) { + $path = $pluginPath . DS . 'console' . DS . 'shells' . DS; + } + return array('class' => $type, 'suffix' => null, 'path' => $path); + break; case 'vendor': if ($plugin) { $path = $pluginPath . DS . 'vendors' . DS; diff --git a/cake/libs/exceptions.php b/cake/libs/exceptions.php index fc153a0fe..99cc3a946 100644 --- a/cake/libs/exceptions.php +++ b/cake/libs/exceptions.php @@ -333,6 +333,33 @@ class MissingTaskClassException extends CakeException { protected $_messageTemplate = 'Task class "%s" is missing.'; } +/** + * Used when a shell method cannot be found. + * + * @package cake.libs + */ +class MissingShellMethodException extends CakeException { + protected $_messageTemplate = "Unknown command %1\$s %2\$s.\nFor usage try `cake %1\$s --help`"; +} + +/** + * Used when a shell class cannot be found. + * + * @package cake.libs + */ +class MissingShellClassException extends CakeException { + protected $_messageTemplate = "Shell class %s could not be loaded."; +} + +/** + * Used when a shell class cannot be found. + * + * @package cake.libs + */ +class MissingShellFileException extends CakeException { + protected $_messageTemplate = "Shell file %s could not be loaded."; +} + /** * Exception class to be thrown when a database table is not found in the datasource * diff --git a/cake/libs/string.php b/cake/libs/string.php index 15048af63..affcc263b 100644 --- a/cake/libs/string.php +++ b/cake/libs/string.php @@ -319,4 +319,38 @@ class String { } return $str; } + +/** + * Wraps text to a specific width, can optionally wrap at word breaks. + * + * ### Options + * + * - `width` The width to wrap to. Defaults to 72 + * - `wordWrap` Only wrap on words breaks (spaces) Defaults to true. + * - `indent` String to indent with. Defaults to null. + * - `indentAt` 0 based index to start indenting at. Defaults to 0. + * + * @param string $text Text the text to format. + * @param mixed $options Array of options to use, or an integer to wrap the text to. + * @return string Formatted text. + */ + public static function wrap($text, $options = array()) { + if (is_numeric($options)) { + $options = array('width' => $options); + } + $options += array('width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0); + if ($options['wordWrap']) { + $wrapped = wordwrap($text, $options['width'], "\n"); + } else { + $wrapped = trim(chunk_split($text, $options['width'] - 1, "\n")); + } + if (!empty($options['indent'])) { + $chunks = explode("\n", $wrapped); + for ($i = $options['indentAt'], $len = count($chunks); $i < $len; $i++) { + $chunks[$i] = $options['indent'] . $chunks[$i]; + } + $wrapped = implode("\n", $chunks); + } + return $wrapped; + } } diff --git a/cake/tests/cases/basics.test.php b/cake/tests/cases/basics.test.php index 723254a20..6972e0315 100644 --- a/cake/tests/cases/basics.test.php +++ b/cake/tests/cases/basics.test.php @@ -585,7 +585,7 @@ class BasicsTest extends CakeTestCase { ob_start(); debug('this-is-a-test'); $result = ob_get_clean(); - $pattern = '/.*\>(.+?tests(\/|\\\)cases(\/|\\\)basics\.test\.php|'; + $pattern = '/(.+?tests(\/|\\\)cases(\/|\\\)basics\.test\.php|'; $pattern .= preg_quote(substr(__FILE__, 1), '/') . ')'; $pattern .= '.*line.*' . (__LINE__ - 4) . '.*this-is-a-test.*/s'; $this->assertPattern($pattern, $result); @@ -593,7 +593,7 @@ class BasicsTest extends CakeTestCase { ob_start(); debug('<div>this-is-a-test</div>', true); $result = ob_get_clean(); - $pattern = '/.*\>(.+?tests(\/|\\\)cases(\/|\\\)basics\.test\.php|'; + $pattern = '/(.+?tests(\/|\\\)cases(\/|\\\)basics\.test\.php|'; $pattern .= preg_quote(substr(__FILE__, 1), '/') . ')'; $pattern .= '.*line.*' . (__LINE__ - 4) . '.*<div>this-is-a-test<\/div>.*/s'; $this->assertPattern($pattern, $result); diff --git a/cake/tests/cases/console/all_console.test.php b/cake/tests/cases/console/all_console.test.php new file mode 100644 index 000000000..101a8faba --- /dev/null +++ b/cake/tests/cases/console/all_console.test.php @@ -0,0 +1,46 @@ +<?php +/** + * AllConsoleTest file + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.tests.cases + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +/** + * AllConsoleTest class + * + * This test group will run all console classes. + * + * @package cake + * @subpackage cake.tests.cases.console + */ +class AllConsoleTest extends PHPUnit_Framework_TestSuite { + +/** + * suite method, defines tests for this suite. + * + * @return void + */ + public static function suite() { + $suite = new CakeTestSuite('All console classes'); + + $path = CORE_TEST_CASES . DS . 'console' . DS; + + $suite->addTestFile($path . 'all_console_libs.test.php'); + $suite->addTestFile($path . 'all_shells.test.php'); + $suite->addTestFile($path . 'all_tasks.test.php'); + return $suite; + } +} \ No newline at end of file diff --git a/cake/tests/cases/console/all_console_libs.test.php b/cake/tests/cases/console/all_console_libs.test.php new file mode 100644 index 000000000..70fb8746d --- /dev/null +++ b/cake/tests/cases/console/all_console_libs.test.php @@ -0,0 +1,44 @@ +<?php +/** + * AllConsoleLibsTest file + * + * PHP 5 + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.tests.cases + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +/** + * AllConsoleLibsTest class + * + * This test group will run all console lib classes. + * + * @package cake + * @subpackage cake.tests.cases.console + */ +class AllConsoleLibsTest extends PHPUnit_Framework_TestSuite { + +/** + * suite method, defines tests for this suite. + * + * @return void + */ + public static function suite() { + $suite = new CakeTestSuite('All console lib classes'); + + $path = CORE_TEST_CASES . DS . 'console' . DS . 'libs' . DS; + + $suite->addTestDirectory($path); + return $suite; + } +} \ No newline at end of file diff --git a/cake/tests/cases/console/all_shells.test.php b/cake/tests/cases/console/all_shells.test.php index 98afe5ad7..7f657af82 100644 --- a/cake/tests/cases/console/all_shells.test.php +++ b/cake/tests/cases/console/all_shells.test.php @@ -36,10 +36,8 @@ class AllShellsTest extends PHPUnit_Framework_TestSuite { public static function suite() { $suite = new CakeTestSuite('All shell classes'); - $path = CORE_TEST_CASES . DS . 'console' . DS . 'libs' . DS; + $path = CORE_TEST_CASES . DS . 'console' . DS . 'shells' . DS; - $suite->addTestFile(CORE_TEST_CASES . DS . 'console' . DS . 'cake.test.php'); - $suite->addTestFile(CORE_TEST_CASES . DS . 'console' . DS . 'console_error_handler.test.php'); $suite->addTestDirectory($path); return $suite; } diff --git a/cake/tests/cases/console/all_tasks.test.php b/cake/tests/cases/console/all_tasks.test.php index 7316f490e..51decf3d1 100644 --- a/cake/tests/cases/console/all_tasks.test.php +++ b/cake/tests/cases/console/all_tasks.test.php @@ -36,9 +36,7 @@ class AllTasksTest extends PHPUnit_Framework_TestSuite { public static function suite() { $suite = new CakeTestSuite('All Tasks tests'); - $path = CORE_TEST_CASES . DS . 'console' . DS . 'libs' . DS . 'tasks' . DS; - - $suite->addTestFile(CORE_TEST_CASES . DS . 'console' . DS . 'libs' . DS . 'bake.test.php'); + $path = CORE_TEST_CASES . DS . 'console' . DS . 'shells' . DS . 'tasks' . DS; $suite->addTestDirectory($path); return $suite; } diff --git a/cake/tests/cases/console/cake.test.php b/cake/tests/cases/console/cake.test.php deleted file mode 100644 index 48afb4d6b..000000000 --- a/cake/tests/cases/console/cake.test.php +++ /dev/null @@ -1,932 +0,0 @@ -<?php -/** - * ShellDispatcherTest file - * - * PHP 5 - * - * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing> - * Copyright 2005-2010, Cake Software Foundation, Inc. - * - * Licensed under The MIT License - * Redistributions of files must retain the above copyright notice - * - * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. - * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests - * @package cake - * @subpackage cake.tests.cases.console - * @since CakePHP(tm) v 1.2.0.5432 - * @license MIT License (http://www.opensource.org/licenses/mit-license.php) - */ -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CONSOLE_LIBS . 'shell.php'; - -/** - * TestShellDispatcher class - * - * @package cake - * @subpackage cake.tests.cases.console - */ -class TestShellDispatcher extends ShellDispatcher { - -/** - * params property - * - * @var array - * @access public - */ - public $params = array(); - -/** - * stdout property - * - * @var string - * @access public - */ - public $stdout = ''; - -/** - * stderr property - * - * @var string - * @access public - */ - public $stderr = ''; - -/** - * stopped property - * - * @var string - * @access public - */ - public $stopped = null; - -/** - * TestShell - * - * @var mixed - * @access public - */ - public $TestShell; - -/** - * _initEnvironment method - * - * @return void - */ - protected function _initEnvironment() { - } - -/** - * stderr method - * - * @return void - */ - public function stderr($string) { - $this->stderr .= rtrim($string, ' '); - } - -/** - * stdout method - * - * @return void - */ - public function stdout($string, $newline = true) { - if ($newline) { - $this->stdout .= rtrim($string, ' ') . "\n"; - } else { - $this->stdout .= rtrim($string, ' '); - } - } - -/** - * clear method - * - * @return void - */ - public function clear() { - - } - -/** - * _stop method - * - * @return void - */ - protected function _stop($status = 0) { - $this->stopped = 'Stopped with status: ' . $status; - return $status; - } - -/** - * getShell - * - * @param mixed $plugin - * @return mixed - */ - public function getShell($plugin = null) { - return $this->_getShell($plugin); - } - -/** - * _getShell - * - * @param mixed $plugin - * @return mixed - */ - protected function _getShell($plugin = null) { - if (isset($this->TestShell)) { - return $this->TestShell; - } - return parent::_getShell($plugin); - } -} - -/** - * ShellDispatcherTest - * - * @package cake - * @subpackage cake.tests.cases.libs - */ -class ShellDispatcherTest extends CakeTestCase { - -/** - * setUp method - * - * @return void - */ - public function setUp() { - parent::setUp(); - App::build(array( - 'plugins' => array( - TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS - ), - 'shells' => array( - CORE_PATH ? CONSOLE_LIBS : ROOT . DS . CONSOLE_LIBS, - TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS - ) - ), true); - } - -/** - * testParseParams method - * - * @return void - */ - public function testParseParams() { - $Dispatcher = new TestShellDispatcher(); - - $params = array( - '/cake/1.2.x.x/cake/console/cake.php', - 'bake', - '-app', - 'new', - '-working', - '/var/www/htdocs' - ); - $expected = array( - 'app' => 'new', - 'webroot' => 'webroot', - 'working' => '/var/www/htdocs/new', - 'root' => '/var/www/htdocs' - ); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array('cake.php'); - $expected = array( - 'app' => 'app', - 'webroot' => 'webroot', - 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'app'), - 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array( - 'cake.php', - '-app', - 'new', - ); - $expected = array( - 'app' => 'new', - 'webroot' => 'webroot', - 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), - 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array( - './cake.php', - 'bake', - '-app', - 'new', - '-working', - '/cake/1.2.x.x/cake/console' - ); - - $expected = array( - 'app' => 'new', - 'webroot' => 'webroot', - 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), - 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) - ); - - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array( - './console/cake.php', - 'bake', - '-app', - 'new', - '-working', - '/cake/1.2.x.x/cake' - ); - $expected = array( - 'app' => 'new', - 'webroot' => 'webroot', - 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), - 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array( - './console/cake.php', - 'bake', - '-app', - 'new', - '-dry', - '-working', - '/cake/1.2.x.x/cake' - ); - $expected = array( - 'app' => 'new', - 'dry' => true, - 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), - 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), - 'webroot' => 'webroot' - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEquals($expected, $Dispatcher->params); - - $params = array( - './console/cake.php', - '-working', - '/cake/1.2.x.x/cake', - 'schema', - 'run', - 'create', - '-dry', - '-f', - '-name', - 'DbAcl' - ); - $expected = array( - 'app' => 'app', - 'webroot' => 'webroot', - 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'app'), - 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), - 'dry' => true, - 'f' => true, - 'name' => 'DbAcl' - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $expected = array('./console/cake.php', 'schema', 'run', 'create'); - $this->assertEqual($expected, $Dispatcher->args); - - $params = array( - '/cake/1.2.x.x/cake/console/cake.php', - '-working', - '/cake/1.2.x.x/app', - 'schema', - 'run', - 'create', - '-dry', - '-name', - 'DbAcl' - ); - $expected = array( - 'app' => 'app', - 'webroot' => 'webroot', - 'working' => '/cake/1.2.x.x/app', - 'root' => '/cake/1.2.x.x', - 'dry' => true, - 'name' => 'DbAcl' - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $expected = array('/cake/1.2.x.x/cake/console/cake.php', 'schema', 'run', 'create'); - $this->assertEqual($expected, $Dispatcher->args); - $params = array( - 'cake.php', - '-working', - 'C:/wamp/www/cake/app', - 'bake', - '-app', - 'C:/wamp/www/apps/cake/app', - ); - $expected = array( - 'app' => 'app', - 'webroot' => 'webroot', - 'working' => 'C:\wamp\www\apps\cake\app', - 'root' => 'C:\wamp\www\apps\cake' - ); - - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array( - 'cake.php', - '-working', - 'C:\wamp\www\cake\app', - 'bake', - '-app', - 'C:\wamp\www\apps\cake\app', - ); - $expected = array( - 'app' => 'app', - 'webroot' => 'webroot', - 'working' => 'C:\wamp\www\apps\cake\app', - 'root' => 'C:\wamp\www\apps\cake' - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array( - 'cake.php', - '-working', - 'C:\wamp\www\apps', - 'bake', - '-app', - 'cake\app', - '-url', - 'http://example.com/some/url/with/a/path' - ); - $expected = array( - 'app' => 'app', - 'webroot' => 'webroot', - 'working' => 'C:\wamp\www\apps\cake\app', - 'root' => 'C:\wamp\www\apps\cake', - 'url' => 'http://example.com/some/url/with/a/path' - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array( - '/home/amelo/dev/cake-common/cake/console/cake.php', - '-root', - '/home/amelo/dev/lsbu-vacancy', - '-working', - '/home/amelo/dev/lsbu-vacancy', - '-app', - 'app', - ); - $expected = array( - 'app' => 'app', - 'webroot' => 'webroot', - 'working' => '/home/amelo/dev/lsbu-vacancy/app', - 'root' => '/home/amelo/dev/lsbu-vacancy', - ); - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - - $params = array( - 'cake.php', - '-working', - 'D:\www', - 'bake', - 'my_app', - ); - $expected = array( - 'working' => 'D:\www', - 'app' => 'www', - 'root' => 'D:', - 'webroot' => 'webroot' - ); - - $Dispatcher->params = $Dispatcher->args = array(); - $Dispatcher->parseParams($params); - $this->assertEqual($expected, $Dispatcher->params); - } - -/** - * testBuildPaths method - * - * @return void - */ - public function testBuildPaths() { - $Dispatcher = new TestShellDispatcher(); - - $result = $Dispatcher->shellPaths; - - $expected = array( - TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS . 'vendors' . DS . 'shells' . DS, - TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin_two' . DS . 'vendors' . DS . 'shells' . DS, - APP . 'vendors' . DS . 'shells' . DS, - VENDORS . 'shells' . DS, - CORE_PATH ? CONSOLE_LIBS : ROOT . DS . CONSOLE_LIBS, - TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS, - ); - $this->assertIdentical(array_diff($result, $expected), array()); - $this->assertIdentical(array_diff($expected, $result), array()); - } - -/** - * Verify loading of (plugin-) shells - * - * @return void - */ - public function testGetShell() { - $this->skipIf(class_exists('SampleShell'), '%s SampleShell Class already loaded'); - $this->skipIf(class_exists('ExampleShell'), '%s ExampleShell Class already loaded'); - - $Dispatcher = new TestShellDispatcher(); - - $Dispatcher->shell = 'sample'; - $Dispatcher->shellName = 'Sample'; - $Dispatcher->shellClass = 'SampleShell'; - - $result = $Dispatcher->getShell(); - $this->assertIsA($result, 'SampleShell'); - - $Dispatcher = new TestShellDispatcher(); - - $Dispatcher->shell = 'example'; - $Dispatcher->shellName = 'Example'; - $Dispatcher->shellClass = 'ExampleShell'; - - $result = $Dispatcher->getShell('test_plugin'); - $this->assertIsA($result, 'ExampleShell'); - } - -/** - * Verify correct dispatch of Shell subclasses with a main method - * - * @return void - */ - public function testDispatchShellWithMain() { - $Dispatcher = new TestShellDispatcher(); - $methods = get_class_methods('Shell'); - array_push($methods, 'main', '_secret'); - $Mock = $this->getMock('Shell', $methods, array(&$Dispatcher), 'MockWithMainShell'); - - $Mock->expects($this->once())->method('main')->will($this->returnValue(true)); - $Mock->expects($this->once())->method('initialize'); - $Mock->expects($this->once())->method('loadTasks'); - $Mock->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Mock; - - $Dispatcher->args = array('mock_with_main'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array()); - - $Shell = new MockWithMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main', 'initdb'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('initdb')); - - $Shell = new MockWithMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('startup'); - $Shell->expects($this->once())->method('help'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main', 'help'); - $result = $Dispatcher->dispatch(); - $this->assertNull($result); - $this->assertEqual($Dispatcher->args, array()); - - $Shell = new MockWithMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->never())->method('hr'); - $Shell->expects($this->once())->method('startup'); - $Shell->expects($this->once())->method('main'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main', 'hr'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('hr')); - - $Shell = new MockWithMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main', 'dispatch'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('dispatch')); - - $Shell = new MockWithMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main', 'idontexist'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('idontexist')); - - $Shell = new MockWithMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->never())->method('main'); - $Shell->expects($this->never())->method('startup'); - $Shell->expects($this->never())->method('_secret'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main', '_secret'); - $result = $Dispatcher->dispatch(); - $this->assertFalse($result); - } - -/** - * Verify correct dispatch of Shell subclasses without a main method - * - * @return void - */ - public function testDispatchShellWithoutMain() { - $Dispatcher = new TestShellDispatcher(); - $methods = get_class_methods('Shell'); - array_push($methods, 'initDb', '_secret'); - $Shell = $this->getMock('Shell', $methods, array(&$Dispatcher), 'MockWithoutMainShell'); - - $Shell->expects($this->once())->method('initialize'); - $Shell->expects($this->once())->method('loadTasks'); - $Shell->expects($this->never())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main'); - $result = $Dispatcher->dispatch(); - $this->assertFalse($result); - $this->assertEqual($Dispatcher->args, array()); - - $Shell = new MockWithoutMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('initDb')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('initialize'); - $Shell->expects($this->once())->method('loadTasks'); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main', 'initdb'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array()); - - $Shell = new MockWithoutMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->never())->method('hr'); - $Shell->expects($this->never())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main', 'hr'); - $result = $Dispatcher->dispatch(); - $this->assertFalse($result); - $this->assertEqual($Dispatcher->args, array('hr')); - - $Shell = new MockWithoutMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->never())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main', 'dispatch'); - $result = $Dispatcher->dispatch(); - $this->assertFalse($result); - - $Shell = new MockWithoutMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->never())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main', 'idontexist'); - $result = $Dispatcher->dispatch(); - $this->assertFalse($result); - - $Shell = new MockWithoutMainShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->never())->method('startup'); - $Shell->expects($this->never())->method('_secret'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main', '_secret'); - $result = $Dispatcher->dispatch(); - $this->assertFalse($result); - } - -/** - * Verify correct dispatch of custom classes with a main method - * - * @return void - */ - public function testDispatchNotAShellWithMain() { - $Dispatcher = new TestShellDispatcher(); - $methods = get_class_methods('Object'); - array_push($methods, 'main', 'initdb', 'initialize', 'loadTasks', 'startup', '_secret'); - $Shell = $this->getMock('Object', $methods, array(&$Dispatcher), 'MockWithMainNotAShell'); - - $Shell->expects($this->never())->method('initialize'); - $Shell->expects($this->never())->method('loadTasks'); - $Shell->expects($this->once())->method('startup'); - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main_not_a'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array()); - - $Shell = new MockWithMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('initdb')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main_not_a', 'initdb'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - - $Shell = new MockWithMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main_not_a', 'hr'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('hr')); - - - $Shell = new MockWithMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main_not_a', 'dispatch'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('dispatch')); - - $Shell = new MockWithMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main_not_a', 'idontexist'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('idontexist')); - - $Shell = new MockWithMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->never())->method('_secret'); - $Shell->expects($this->never())->method('main'); - $Shell->expects($this->never())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_with_main_not_a', '_secret'); - $result = $Dispatcher->dispatch(); - $this->assertFalse($result); - } - -/** - * Verify correct dispatch of custom classes without a main method - * - * @return void - */ - public function testDispatchNotAShellWithoutMain() { - $Dispatcher = new TestShellDispatcher(); - $methods = get_class_methods('Object'); - array_push($methods, 'main', 'initdb', 'initialize', 'loadTasks', 'startup', '_secret'); - $Shell = $this->getMock('Object', $methods, array(&$Dispatcher), 'MockWithoutMainNotAShell'); - - $Shell->expects($this->never())->method('initialize'); - $Shell->expects($this->never())->method('loadTasks'); - $Shell->expects($this->once())->method('startup'); - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main_not_a'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array()); - - $Shell = new MockWithoutMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('initdb')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main_not_a', 'initdb'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - - $Shell = new MockWithoutMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main_not_a', 'hr'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('hr')); - - - $Shell = new MockWithoutMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main_not_a', 'dispatch'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('dispatch')); - - $Shell = new MockWithoutMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); - $Shell->expects($this->once())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main_not_a', 'idontexist'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array('idontexist')); - - $Shell = new MockWithoutMainNotAShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->never())->method('_secret'); - $Shell->expects($this->never())->method('main'); - $Shell->expects($this->never())->method('startup'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_without_main_not_a', '_secret'); - $result = $Dispatcher->dispatch(); - $this->assertFalse($result); - } - -/** - * Verify that a task is called instead of the shell if the first arg equals - * the name of the task - * - * @return void - */ - public function testDispatchTask() { - $Dispatcher = new TestShellDispatcher(); - $mainMethods = $executeMethods = get_class_methods('Shell'); - array_push($mainMethods, 'main'); - array_push($executeMethods, 'execute'); - - $Week = $this->getMock('Shell', $mainMethods, array(&$Dispatcher), 'MockWeekShell'); - $Sunday = $this->getMock('Shell', $executeMethods, array(&$Dispatcher), 'MockOnSundayTask'); - - $Shell = new MockWeekShell($Dispatcher); - $this->mockObjects[] = $Shell; - $Shell->expects($this->once())->method('initialize'); - $Shell->expects($this->once())->method('loadTasks'); - $Shell->expects($this->never())->method('startup'); - $Shell->expects($this->never())->method('main'); - - $Task = new MockOnSundayTask($Dispatcher); - $this->mockObjects[] = $Task; - $Task->expects($this->once())->method('execute')->will($this->returnValue(true)); - $Task->expects($this->once())->method('initialize');; - $Task->expects($this->once())->method('loadTasks'); - $Task->expects($this->once())->method('startup'); - $Task->expects($this->once())->method('execute'); - - $Shell->MockOnSunday = $Task; - $Shell->taskNames = array('MockOnSunday'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_week', 'mock_on_sunday'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - $this->assertEqual($Dispatcher->args, array()); - - $Shell = new MockWeekShell($Dispatcher); - $Task = new MockOnSundayTask($Dispatcher); - array_push($this->mockObjects, $Shell, $Task); - - $Task->expects($this->never())->method('execute'); - $Task->expects($this->once())->method('help'); - - $Shell->MockOnSunday = $Task; - $Shell->taskNames = array('MockOnSunday'); - $Dispatcher->TestShell = $Shell; - - $Dispatcher->args = array('mock_week', 'mock_on_sunday', 'help'); - $result = $Dispatcher->dispatch(); - $this->assertTrue($result); - } - -/** - * Verify shifting of arguments - * - * @return void - */ - public function testShiftArgs() { - $Dispatcher = new TestShellDispatcher(); - - $Dispatcher->args = array('a', 'b', 'c'); - $this->assertEqual($Dispatcher->shiftArgs(), 'a'); - $this->assertIdentical($Dispatcher->args, array('b', 'c')); - - $Dispatcher->args = array('a' => 'b', 'c', 'd'); - $this->assertEqual($Dispatcher->shiftArgs(), 'b'); - $this->assertIdentical($Dispatcher->args, array('c', 'd')); - - $Dispatcher->args = array('a', 'b' => 'c', 'd'); - $this->assertEqual($Dispatcher->shiftArgs(), 'a'); - $this->assertIdentical($Dispatcher->args, array('b' => 'c', 'd')); - - $Dispatcher->args = array(0 => 'a', 2 => 'b', 30 => 'c'); - $this->assertEqual($Dispatcher->shiftArgs(), 'a'); - $this->assertIdentical($Dispatcher->args, array(0 => 'b', 1 => 'c')); - - $Dispatcher->args = array(); - $this->assertNull($Dispatcher->shiftArgs()); - $this->assertIdentical($Dispatcher->args, array()); - } - -/** - * testHelpCommand method - * - * @return void - */ - public function testHelpCommand() { - $Dispatcher = new TestShellDispatcher(); - - $expected = "/example \[.*TestPlugin, TestPluginTwo.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/welcome \[.*TestPluginTwo.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/acl \[.*CORE.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/api \[.*CORE.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/bake \[.*CORE.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/console \[.*CORE.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/i18n \[.*CORE.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/schema \[.*CORE.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/testsuite \[.*CORE.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - - $expected = "/sample \[.*test_app.*\]/"; - $this->assertPattern($expected, $Dispatcher->stdout); - } -} diff --git a/cake/tests/cases/console/console_error_handler.test.php b/cake/tests/cases/console/libs/console_error_handler.test.php similarity index 82% rename from cake/tests/cases/console/console_error_handler.test.php rename to cake/tests/cases/console/libs/console_error_handler.test.php index d6e3b261e..81c3b5aae 100644 --- a/cake/tests/cases/console/console_error_handler.test.php +++ b/cake/tests/cases/console/libs/console_error_handler.test.php @@ -17,7 +17,7 @@ * @since CakePHP(tm) v 2.0 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -require_once CAKE . 'console' . DS . 'console_error_handler.php'; +require_once CONSOLE_LIBS . 'console_error_handler.php'; /** * ConsoleErrorHandler Test case. @@ -32,7 +32,9 @@ class ConsoleErrorHandlerTest extends CakeTestCase { * @return Mock object */ function getErrorHandler($exception) { - return $this->getMock('ConsoleErrorHandler', array('stderr'), array($exception)); + $error = new ConsoleErrorHandler($exception); + $error->stderr = $this->getMock('ConsoleOutput', array(), array(), '', false); + return $error; } /** @@ -44,7 +46,7 @@ class ConsoleErrorHandlerTest extends CakeTestCase { $exception = new MissingActionException('Missing action'); $error = $this->getErrorHandler($exception); - $error->expects($this->once())->method('stderr') + $error->stderr->expects($this->once())->method('write') ->with($this->stringContains('Missing action')); $error->render(); @@ -59,7 +61,7 @@ class ConsoleErrorHandlerTest extends CakeTestCase { $exception = new InvalidArgumentException('Too many parameters.'); $error = $this->getErrorHandler($exception); - $error->expects($this->once())->method('stderr') + $error->stderr->expects($this->once())->method('write') ->with($this->stringContains('Too many parameters.')); $error->render(); @@ -74,7 +76,7 @@ class ConsoleErrorHandlerTest extends CakeTestCase { $exception = new NotFoundException('dont use me in cli.'); $error = $this->getErrorHandler($exception); - $error->expects($this->once())->method('stderr') + $error->stderr->expects($this->once())->method('write') ->with($this->stringContains('dont use me in cli.')); $error->render(); @@ -89,7 +91,7 @@ class ConsoleErrorHandlerTest extends CakeTestCase { $exception = new InternalErrorException('dont use me in cli.'); $error = $this->getErrorHandler($exception); - $error->expects($this->once())->method('stderr') + $error->stderr->expects($this->once())->method('write') ->with($this->stringContains('dont use me in cli.')); $error->render(); @@ -104,6 +106,6 @@ class ConsoleErrorHandlerTest extends CakeTestCase { $exception = new InternalErrorException('dont use me in cli.'); $error = new ConsoleErrorHandler($exception); - $this->assertTrue(is_resource($error->stderr), 'No handle.'); + $this->assertType('ConsoleOutput', $error->stderr, 'No handle.'); } } \ No newline at end of file diff --git a/cake/tests/cases/console/libs/console_option_parser.test.php b/cake/tests/cases/console/libs/console_option_parser.test.php new file mode 100644 index 000000000..2b46cd00b --- /dev/null +++ b/cake/tests/cases/console/libs/console_option_parser.test.php @@ -0,0 +1,495 @@ +<?php +/** + * ConsoleOptionParserTest file + * + * PHP 5 + * + * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing> + * Copyright 2005-2010, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.console + * @since CakePHP(tm) v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +require_once CONSOLE_LIBS . 'console_option_parser.php'; + +class ConsoleOptionParserTest extends CakeTestCase { + +/** + * test setting the console description + * + * @return void + */ + function testDescription() { + $parser = new ConsoleOptionParser('test', false); + $result = $parser->description('A test'); + + $this->assertEquals($parser, $result, 'Setting description is not chainable'); + $this->assertEquals('A test', $parser->description(), 'getting value is wrong.'); + + $result = $parser->description(array('A test', 'something')); + $this->assertEquals("A test\nsomething", $parser->description(), 'getting value is wrong.'); + } + +/** + * test setting the console epliog + * + * @return void + */ + function testEpilog() { + $parser = new ConsoleOptionParser('test', false); + $result = $parser->epilog('A test'); + + $this->assertEquals($parser, $result, 'Setting epilog is not chainable'); + $this->assertEquals('A test', $parser->epilog(), 'getting value is wrong.'); + + $result = $parser->epilog(array('A test', 'something')); + $this->assertEquals("A test\nsomething", $parser->epilog(), 'getting value is wrong.'); + } + +/** + * test adding an option returns self. + * + * @return void + */ + function testAddOptionReturnSelf() { + $parser = new ConsoleOptionParser('test', false); + $result = $parser->addOption('test'); + $this->assertEquals($parser, $result, 'Did not return $this from addOption'); + } + +/** + * test adding an option and using the long value for parsing. + * + * @return void + */ + function testAddOptionLong() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test', array( + 'short' => 't' + )); + $result = $parser->parse(array('--test', 'value')); + $this->assertEquals(array('test' => 'value', 'help' => false), $result[0], 'Long parameter did not parse out'); + } + +/** + * test adding an option and using the long value for parsing. + * + * @return void + */ + function testAddOptionLongEquals() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test', array( + 'short' => 't' + )); + $result = $parser->parse(array('--test=value')); + $this->assertEquals(array('test' => 'value', 'help' => false), $result[0], 'Long parameter did not parse out'); + } + +/** + * test adding an option and using the default. + * + * @return void + */ + function testAddOptionDefault() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test', array( + 'default' => 'default value', + )); + $result = $parser->parse(array('--test')); + $this->assertEquals(array('test' => 'default value', 'help' => false), $result[0], 'Default value did not parse out'); + + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test', array( + 'default' => 'default value', + )); + $result = $parser->parse(array()); + $this->assertEquals(array('test' => 'default value', 'help' => false), $result[0], 'Default value did not parse out'); + } + +/** + * test adding an option and using the short value for parsing. + * + * @return void + */ + function testAddOptionShort() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test', array( + 'short' => 't' + )); + $result = $parser->parse(array('-t', 'value')); + $this->assertEquals(array('test' => 'value', 'help' => false), $result[0], 'Short parameter did not parse out'); + } + +/** + * test adding and using boolean options. + * + * @return void + */ + function testAddOptionBoolean() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test', array( + 'boolean' => true, + )); + + $result = $parser->parse(array('--test', 'value')); + $expected = array(array('test' => true, 'help' => false), array('value')); + $this->assertEquals($expected, $result); + + $result = $parser->parse(array('value')); + $expected = array(array('test' => false, 'help' => false), array('value')); + $this->assertEquals($expected, $result); + } + +/** + * test adding an multiple shorts. + * + * @return void + */ + function testAddOptionMultipleShort() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test', array('short' => 't', 'boolean' => true)) + ->addOption('file', array('short' => 'f', 'boolean' => true)) + ->addOption('output', array('short' => 'o', 'boolean' => true)); + + $result = $parser->parse(array('-o', '-t', '-f')); + $expected = array('file' => true, 'test' => true, 'output' => true, 'help' => false); + $this->assertEquals($expected, $result[0], 'Short parameter did not parse out'); + + $result = $parser->parse(array('-otf')); + $this->assertEquals($expected, $result[0], 'Short parameter did not parse out'); + } + +/** + * test multiple options at once. + * + * @return void + */ + function testMultipleOptions() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('test') + ->addOption('connection') + ->addOption('table', array('short' => 't', 'default' => true)); + + $result = $parser->parse(array('--test', 'value', '-t', '--connection', 'postgres')); + $expected = array('test' => 'value', 'table' => true, 'connection' => 'postgres', 'help' => false); + $this->assertEquals($expected, $result[0], 'multiple options did not parse'); + } + +/** + * Test adding multiple options. + * + * @return void + */ + function testAddOptions() { + $parser = new ConsoleOptionParser('something', false); + $result = $parser->addOptions(array( + 'name' => array('help' => 'The name'), + 'other' => array('help' => 'The other arg') + )); + $this->assertEquals($parser, $result, 'addOptions is not chainable.'); + + $result = $parser->options(); + $this->assertEquals(3, count($result), 'Not enough options'); + } + +/** + * test that boolean options work + * + * @return void + */ + function testOptionWithBooleanParam() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('no-commit', array('boolean' => true)) + ->addOption('table', array('short' => 't')); + + $result = $parser->parse(array('--table', 'posts', '--no-commit', 'arg1', 'arg2')); + $expected = array(array('table' => 'posts', 'no-commit' => true, 'help' => false), array('arg1', 'arg2')); + $this->assertEquals($expected, $result, 'Boolean option did not parse correctly.'); + } + +/** + * test parsing options that do not exist. + * + * @expectedException InvalidArgumentException + */ + function testOptionThatDoesNotExist() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('no-commit', array('boolean' => true)); + + $result = $parser->parse(array('--fail', 'other')); + } + +/** + * test that options with choices enforce them. + * + * @expectedException InvalidArgumentException + * @return void + */ + function testOptionWithChoices() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('name', array('choices' => array('mark', 'jose'))); + + $result = $parser->parse(array('--name', 'mark')); + $expected = array('name' => 'mark', 'help' => false); + $this->assertEquals($expected, $result[0], 'Got the correct value.'); + + $result = $parser->parse(array('--name', 'jimmy')); + } + +/** + * test positional argument parsing. + * + * @return void + */ + function testPositionalArgument() { + $parser = new ConsoleOptionParser('test', false); + $result = $parser->addArgument('name', array('help' => 'An argument')); + $this->assertEquals($parser, $result, 'Should returnn this'); + } + +/** + * test overwriting positional arguments. + * + * @return void + */ + function testPositionalArgOverwrite() { + $parser = new ConsoleOptionParser('test', false); + $parser->addArgument('name', array('help' => 'An argument')) + ->addArgument('other', array('index' => 0)); + + $result = $parser->arguments(); + $this->assertEquals(1, count($result), 'Overwrite did not occur'); + } + +/** + * test parsing arguments. + * + * @expectedException InvalidArgumentException + * @return void + */ + function testParseArgumentTooMany() { + $parser = new ConsoleOptionParser('test', false); + $parser->addArgument('name', array('help' => 'An argument')) + ->addArgument('other'); + + $expected = array('one', 'two'); + $result = $parser->parse($expected); + $this->assertEquals($expected, $result[1], 'Arguments are not as expected'); + + $result = $parser->parse(array('one', 'two', 'three')); + } + +/** + * test that when there are not enough arguments an exception is raised + * + * @expectedException RuntimeException + * @return void + */ + function testPositionalArgNotEnough() { + $parser = new ConsoleOptionParser('test', false); + $parser->addArgument('name', array('required' => true)) + ->addArgument('other', array('required' => true)); + + $parser->parse(array('one')); + } + +/** + * test that arguments with choices enforce them. + * + * @expectedException InvalidArgumentException + * @return void + */ + function testPositionalArgWithChoices() { + $parser = new ConsoleOptionParser('test', false); + $parser->addArgument('name', array('choices' => array('mark', 'jose'))) + ->addArgument('alias', array('choices' => array('cowboy', 'samurai'))) + ->addArgument('weapon', array('choices' => array('gun', 'sword'))); + + $result = $parser->parse(array('mark', 'samurai', 'sword')); + $expected = array('mark', 'samurai', 'sword'); + $this->assertEquals($expected, $result[1], 'Got the correct value.'); + + $result = $parser->parse(array('jose', 'coder')); + } + +/** + * Test adding multiple arguments. + * + * @return void + */ + function testAddArguments() { + $parser = new ConsoleOptionParser('test', false); + $result = $parser->addArguments(array( + 'name' => array('help' => 'The name'), + 'other' => array('help' => 'The other arg') + )); + $this->assertEquals($parser, $result, 'addArguments is not chainable.'); + + $result = $parser->arguments(); + $this->assertEquals(2, count($result), 'Not enough arguments'); + } + +/** + * test setting a subcommand up. + * + * @return void + */ + function testSubcommand() { + $parser = new ConsoleOptionParser('test', false); + $result = $parser->addSubcommand('initdb', array( + 'help' => 'Initialize the database' + )); + $this->assertEquals($parser, $result, 'Adding a subcommand is not chainable'); + } + +/** + * test adding multiple subcommands + * + * @return void + */ + function testAddSubcommands() { + $parser = new ConsoleOptionParser('test', false); + $result = $parser->addSubcommands(array( + 'initdb' => array('help' => 'Initialize the database'), + 'create' => array('help' => 'Create something') + )); + $this->assertEquals($parser, $result, 'Adding a subcommands is not chainable'); + $result = $parser->subcommands(); + $this->assertEquals(2, count($result), 'Not enough subcommands'); + } + +/** + * test that no exception is triggered when help is being generated + * + * @return void + */ + function testHelpNoExceptionWhenGettingHelp() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addOption('test', array('help' => 'A test option.')) + ->addArgument('model', array('help' => 'The model to make.', 'required' => true)); + + $result = $parser->parse(array('--help')); + $this->assertTrue($result[0]['help']); + } + +/** + * test that help() with a command param shows the help for a subcommand + * + * @return void + */ + function testHelpSubcommandHelp() { + $subParser = new ConsoleOptionParser('method', false); + $subParser->addOption('connection', array('help' => 'Db connection.')); + + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addSubcommand('method', array( + 'help' => 'This is another command', + 'parser' => $subParser + )) + ->addOption('test', array('help' => 'A test option.')); + + $result = $parser->help('method'); + $expected = <<<TEXT +<info>Usage:</info> +cake mycommand method [-h] [--connection] + +<info>Options:</info> + +--help, -h Display this help. +--connection Db connection. + +TEXT; + $this->assertEquals($expected, $result, 'Help is not correct.'); + } + +/** + * test building a parser from an array. + * + * @return void + */ + function testBuildFromArray() { + $spec = array( + 'command' => 'test', + 'arguments' => array( + 'name' => array('help' => 'The name'), + 'other' => array('help' => 'The other arg') + ), + 'options' => array( + 'name' => array('help' => 'The name'), + 'other' => array('help' => 'The other arg') + ), + 'subcommands' => array( + 'initdb' => array('help' => 'make database') + ), + 'description' => 'description text', + 'epilog' => 'epilog text' + ); + $parser = ConsoleOptionParser::buildFromArray($spec); + + $this->assertEquals($spec['description'], $parser->description()); + $this->assertEquals($spec['epilog'], $parser->epilog()); + + $options = $parser->options(); + $this->assertTrue(isset($options['name'])); + $this->assertTrue(isset($options['other'])); + + $args = $parser->arguments(); + $this->assertEquals(2, count($args)); + + $commands = $parser->subcommands(); + $this->assertEquals(1, count($commands)); + } + +/** + * test that create() returns instances + * + * @return void + */ + function testCreateFactory() { + $parser = ConsoleOptionParser::create('factory', false); + $this->assertInstanceOf('ConsoleOptionParser', $parser); + $this->assertEquals('factory', $parser->command()); + } + +/** + * test that parse() takes a subcommand argument, and that the subcommand parser + * is used. + * + * @return void + */ + function testParsingWithSubParser() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('primary') + ->addArgument('one', array('required' => true, 'choices' => array('a', 'b'))) + ->addArgument('two', array('required' => true)) + ->addSubcommand('sub', array( + 'parser' => array( + 'options' => array( + 'secondary' => array('boolean' => true), + 'fourth' => array('help' => 'fourth option') + ), + 'arguments' => array( + 'sub_arg' => array('choices' => array('c', 'd')) + ) + ) + )); + + $result = $parser->parse(array('--secondary', '--fourth', '4', 'c'), 'sub'); + $expected = array(array( + 'secondary' => true, + 'fourth' => '4', + 'help' => false, + 'verbose' => false, + 'quiet' => false), array('c')); + $this->assertEquals($expected, $result, 'Sub parser did not parse request.'); + } + +} \ No newline at end of file diff --git a/cake/tests/cases/console/libs/console_output.test.php b/cake/tests/cases/console/libs/console_output.test.php new file mode 100644 index 000000000..37fee8f5c --- /dev/null +++ b/cake/tests/cases/console/libs/console_output.test.php @@ -0,0 +1,228 @@ +<?php +/** + * ConsoleOutputTest file + * + * PHP 5 + * + * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing> + * Copyright 2005-2010, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.console + * @since CakePHP(tm) v 1.2.0.5432 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +require_once CONSOLE_LIBS . 'console_output.php'; + +class ConsoleOutputTest extends CakeTestCase { + +/** + * setup + * + * @return void + */ + function setUp() { + parent::setUp(); + $this->output = $this->getMock('ConsoleOutput', array('_write')); + } + +/** + * tearDown + * + * @return void + */ + function tearDown() { + unset($this->output); + } + +/** + * test writing with no new line + * + * @return void + */ + function testWriteNoNewLine() { + $this->output->expects($this->once())->method('_write') + ->with('Some output'); + + $this->output->write('Some output', false); + } + +/** + * test writing with no new line + * + * @return void + */ + function testWriteNewLine() { + $this->output->expects($this->once())->method('_write') + ->with('Some output' . PHP_EOL); + + $this->output->write('Some output'); + } + +/** + * test write() with multiple new lines + * + * @return void + */ + function testWriteMultipleNewLines() { + $this->output->expects($this->once())->method('_write') + ->with('Some output' . PHP_EOL . PHP_EOL . PHP_EOL . PHP_EOL); + + $this->output->write('Some output', 4); + } + +/** + * test writing an array of messages. + * + * @return void + */ + function testWriteArray() { + $this->output->expects($this->once())->method('_write') + ->with('Line' . PHP_EOL . 'Line' . PHP_EOL . 'Line' . PHP_EOL); + + $this->output->write(array('Line', 'Line', 'Line')); + } + +/** + * test getting a style. + * + * @return void + */ + function testStylesGet() { + $result = $this->output->styles('error'); + $expected = array('text' => 'red', 'underline' => true); + $this->assertEqual($result, $expected); + + $this->assertNull($this->output->styles('made_up_goop')); + + $result = $this->output->styles(); + $this->assertNotEmpty($result, 'error', 'Error is missing'); + $this->assertNotEmpty($result, 'warning', 'Warning is missing'); + } + +/** + * test adding a style. + * + * @return void + */ + function testStylesAdding() { + $this->output->styles('test', array('text' => 'red', 'background' => 'black')); + $result = $this->output->styles('test'); + $expected = array('text' => 'red', 'background' => 'black'); + $this->assertEquals($expected, $result); + + $this->assertTrue($this->output->styles('test', false), 'Removing a style should return true.'); + $this->assertNull($this->output->styles('test'), 'Removed styles should be null.'); + } + +/** + * test formatting text with styles. + * + * @return void + */ + function testFormattingSimple() { + $this->output->expects($this->once())->method('_write') + ->with("\033[31;4mError:\033[0m Something bad"); + + $this->output->write('<error>Error:</error> Something bad', false); + } + +/** + * test that formatting doesn't eat tags it doesn't know about. + * + * @return void + */ + function testFormattingNotEatingTags() { + $this->output->expects($this->once())->method('_write') + ->with("<red> Something bad"); + + $this->output->write('<red> Something bad', false); + } + +/** + * test formatting with custom styles. + * + * @return void + */ + function testFormattingCustom() { + $this->output->styles('annoying', array( + 'text' => 'magenta', + 'background' => 'cyan', + 'blink' => true, + 'underline' => true + )); + + $this->output->expects($this->once())->method('_write') + ->with("\033[35;46;5;4mAnnoy:\033[0m Something bad"); + + $this->output->write('<annoying>Annoy:</annoying> Something bad', false); + } + +/** + * test formatting text with missing styles. + * + * @return void + */ + function testFormattingMissingStyleName() { + $this->output->expects($this->once())->method('_write') + ->with("<not_there>Error:</not_there> Something bad"); + + $this->output->write('<not_there>Error:</not_there> Something bad', false); + } + +/** + * test formatting text with multiple styles. + * + * @return void + */ + function testFormattingMultipleStylesName() { + $this->output->expects($this->once())->method('_write') + ->with("\033[31;4mBad\033[0m \033[33mWarning\033[0m Regular"); + + $this->output->write('<error>Bad</error> <warning>Warning</warning> Regular', false); + } + +/** + * test that multiple tags of the same name work in one string. + * + * @return void + */ + function testFormattingMultipleSameTags() { + $this->output->expects($this->once())->method('_write') + ->with("\033[31;4mBad\033[0m \033[31;4mWarning\033[0m Regular"); + + $this->output->write('<error>Bad</error> <error>Warning</error> Regular', false); + } + +/** + * test raw output not getting tags replaced. + * + * @return void + */ + function testOutputAsRaw() { + $this->output->outputAs(ConsoleOutput::RAW); + $this->output->expects($this->once())->method('_write') + ->with('<error>Bad</error> Regular'); + + $this->output->write('<error>Bad</error> Regular', false); + } + +/** + * test plain output. + * + * @return void + */ + function testOutputAsPlain() { + $this->output->outputAs(ConsoleOutput::PLAIN); + $this->output->expects($this->once())->method('_write') + ->with('Bad Regular'); + + $this->output->write('<error>Bad</error> Regular', false); + } +} \ No newline at end of file diff --git a/cake/tests/cases/console/libs/help_formatter.test.php b/cake/tests/cases/console/libs/help_formatter.test.php new file mode 100644 index 000000000..a957843b1 --- /dev/null +++ b/cake/tests/cases/console/libs/help_formatter.test.php @@ -0,0 +1,440 @@ +<?php + +require_once CONSOLE_LIBS . 'console_option_parser.php'; +require_once CONSOLE_LIBS . 'help_formatter.php'; + +class HelpFormatterTest extends CakeTestCase { + +/** + * test that the console max width is respected when generating help. + * + * @return void + */ + function testWidthFormatting() { + $parser = new ConsoleOptionParser('test', false); + $parser->description(__('This is fifteen This is fifteen This is fifteen')) + ->addOption('four', array('help' => 'this is help text this is help text')) + ->addArgument('four', array('help' => 'this is help text this is help text')) + ->addSubcommand('four', array('help' => 'this is help text this is help text')); + + $formatter = new HelpFormatter($parser); + $result = $formatter->text(30); + $expected = <<<TEXT +This is fifteen This is +fifteen This is fifteen + +<info>Usage:</info> +cake test [subcommand] [-h] [--four] [<four>] + +<info>Subcommands:</info> + +four this is help text this + is help text + +To see help on a subcommand use <info>`cake test [subcommand] --help`</info> + +<info>Options:</info> + +--help, -h Display this help. +--four this is help text + this is help text + +<info>Arguments:</info> + +four this is help text this + is help text + <comment>(optional)</comment> + +TEXT; + $this->assertEquals($expected, $result, 'Generated help is too wide'); + } + +/** + * test help() with options and arguments that have choices. + * + * @return void + */ + function testHelpWithChoices() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addOption('test', array('help' => 'A test option.', 'choices' => array('one', 'two'))) + ->addArgument('type', array( + 'help' => 'Resource type.', + 'choices' => array('aco', 'aro'), + 'required' => true + )) + ->addArgument('other_longer', array('help' => 'Another argument.')); + + $formatter = new HelpFormatter($parser); + $result = $formatter->text(); + $expected = <<<TEXT +<info>Usage:</info> +cake mycommand [-h] [--test one|two] <aco|aro> [<other_longer>] + +<info>Options:</info> + +--help, -h Display this help. +--test A test option. <comment>(choices: one|two)</comment> + +<info>Arguments:</info> + +type Resource type. <comment>(choices: aco|aro)</comment> +other_longer Another argument. <comment>(optional)</comment> + +TEXT; + $this->assertEquals($expected, $result, 'Help does not match'); + } + +/** + * test description and epilog in the help + * + * @return void + */ + function testHelpDescriptionAndEpilog() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->description('Description text') + ->epilog('epilog text') + ->addOption('test', array('help' => 'A test option.')) + ->addArgument('model', array('help' => 'The model to make.', 'required' => true)); + + $formatter = new HelpFormatter($parser); + $result = $formatter->text(); + $expected = <<<TEXT +Description text + +<info>Usage:</info> +cake mycommand [-h] [--test] <model> + +<info>Options:</info> + +--help, -h Display this help. +--test A test option. + +<info>Arguments:</info> + +model The model to make. + +epilog text + +TEXT; + $this->assertEquals($expected, $result, 'Help is wrong.'); + } + +/** + * test that help() outputs subcommands. + * + * @return void + */ + function testHelpSubcommand() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addSubcommand('method', array('help' => 'This is another command')) + ->addOption('test', array('help' => 'A test option.')); + + $formatter = new HelpFormatter($parser); + $result = $formatter->text(); + $expected = <<<TEXT +<info>Usage:</info> +cake mycommand [subcommand] [-h] [--test] + +<info>Subcommands:</info> + +method This is another command + +To see help on a subcommand use <info>`cake mycommand [subcommand] --help`</info> + +<info>Options:</info> + +--help, -h Display this help. +--test A test option. + +TEXT; + $this->assertEquals($expected, $result, 'Help is not correct.'); + } + +/** + * test getting help with defined options. + * + * @return void + */ + function testHelpWithOptions() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addOption('test', array('help' => 'A test option.')) + ->addOption('connection', array( + 'short' => 'c', 'help' => 'The connection to use.', 'default' => 'default' + )); + + $formatter = new HelpFormatter($parser); + $result = $formatter->text(); + $expected = <<<TEXT +<info>Usage:</info> +cake mycommand [-h] [--test] [-c default] + +<info>Options:</info> + +--help, -h Display this help. +--test A test option. +--connection, -c The connection to use. <comment>(default: + default)</comment> + +TEXT; + $this->assertEquals($expected, $result, 'Help does not match'); + } + +/** + * test getting help with defined options. + * + * @return void + */ + function testHelpWithOptionsAndArguments() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addOption('test', array('help' => 'A test option.')) + ->addArgument('model', array('help' => 'The model to make.', 'required' => true)) + ->addArgument('other_longer', array('help' => 'Another argument.')); + + $formatter = new HelpFormatter($parser); + $result = $formatter->text(); + $expected = <<<TEXT +<info>Usage:</info> +cake mycommand [-h] [--test] <model> [<other_longer>] + +<info>Options:</info> + +--help, -h Display this help. +--test A test option. + +<info>Arguments:</info> + +model The model to make. +other_longer Another argument. <comment>(optional)</comment> + +TEXT; + $this->assertEquals($expected, $result, 'Help does not match'); + } + +/** + * test help() with options and arguments that have choices. + * + * @return void + */ + function testXmlHelpWithChoices() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addOption('test', array('help' => 'A test option.', 'choices' => array('one', 'two'))) + ->addArgument('type', array( + 'help' => 'Resource type.', + 'choices' => array('aco', 'aro'), + 'required' => true + )) + ->addArgument('other_longer', array('help' => 'Another argument.')); + + $formatter = new HelpFormatter($parser); + $result = $formatter->xml(); + $expected = <<<TEXT +<?xml version="1.0"?> +<shell> +<name>mycommand</name> +<description>Description text</description> +<subcommands /> +<options> + <option name="--help" short="-h" help="Display this help." boolean="1"> + <default></default> + <choices></choices> + </option> + <option name="--test" short="" help="A test option." boolean="0"> + <default></default> + <choices> + <choice>one</choice> + <choice>two</choice> + </choices> + </option> +</options> +<arguments> + <argument name="type" help="Resource type." required="1"> + <choices> + <choice>aco</choice> + <choice>aro</choice> + </choices> + </argument> +</arguments> +<epilog>epilog text</epilog> +</shell> +TEXT; + $this->assertEquals(new DomDocument($expected), new DomDocument($result), 'Help does not match'); + } + +/** + * test description and epilog in the help + * + * @return void + */ + function testXmlHelpDescriptionAndEpilog() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->description('Description text') + ->epilog('epilog text') + ->addOption('test', array('help' => 'A test option.')) + ->addArgument('model', array('help' => 'The model to make.', 'required' => true)); + + $formatter = new HelpFormatter($parser); + $result = $formatter->xml(); + $expected = <<<TEXT +<?xml version="1.0"?> +<shell> +<name>mycommand</name> +<description>Description text</description> +<subcommands /> +<options> + <option name="--help" short="-h" help="Display this help." boolean="1"> + <default></default> + <choices></choices> + </option> + <option name="--test" short="" help="A test option." boolean="0"> + <default></default> + <choices></choices> + </option> +</options> +<arguments> + <argument name="model" help="The model to make." required="1"> + <choices></choices> + </argument> +</arguments> +<epilog>epilog text</epilog> +</shell> +TEXT; + $this->assertEquals(new DomDocument($expected), new DomDocument($result), 'Help does not match'); + } + +/** + * test that help() outputs subcommands. + * + * @return void + */ + function testXmlHelpSubcommand() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addSubcommand('method', array('help' => 'This is another command')) + ->addOption('test', array('help' => 'A test option.')); + + $formatter = new HelpFormatter($parser); + $result = $formatter->xml(); + $expected = <<<TEXT +<?xml version="1.0"?> +<shell> +<name>mycommand</name> +<description/> +<subcommands> + <command name="method" help="This is another command" /> +</subcommands> +<options> + <option name="--help" short="-h" help="Display this help." boolean="1"> + <default></default> + <choices></choices> + </option> + <option name="--test" short="" help="A test option." boolean="0"> + <default></default> + <choices></choices> + </option> +</options> +<arguments/> +<epilog/> +</shell> +TEXT; + $this->assertEquals(new DomDocument($expected), new DomDocument($result), 'Help does not match'); + } + +/** + * test getting help with defined options. + * + * @return void + */ + function testXmlHelpWithOptions() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addOption('test', array('help' => 'A test option.')) + ->addOption('connection', array( + 'short' => 'c', 'help' => 'The connection to use.', 'default' => 'default' + )); + + $formatter = new HelpFormatter($parser); + $result = $formatter->xml(); + $expected = <<<TEXT +<?xml version="1.0"?> +<shell> +<name>mycommand</name> +<description/> +<subcommands/> +<options> + <option name="--help" short="-h" help="Display this help." boolean="1"> + <default></default> + <choices></choices> + </option> + <option name="--test" short="" help="A test option." boolean="0"> + <default></default> + <choices></choices> + </option> + <option name="--connection" short="-c" help="The connection to use." boolean="0"> + <default>default</default> + <choices></choices> + </option> +</options> +<arguments/> +<epilog/> +</shell> +TEXT; + $this->assertEquals(new DomDocument($expected), new DomDocument($result), 'Help does not match'); + } + +/** + * test getting help with defined options. + * + * @return void + */ + function testXmlHelpWithOptionsAndArguments() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addOption('test', array('help' => 'A test option.')) + ->addArgument('model', array('help' => 'The model to make.', 'required' => true)) + ->addArgument('other_longer', array('help' => 'Another argument.')); + + $formatter = new HelpFormatter($parser); + $result = $formatter->xml(); + $expected = <<<TEXT +<?xml version="1.0"?> +<shell> + <name>mycommand</name> + <description/> + <subcommands/> + <options> + <option name="--help" short="-h" help="Display this help." boolean="1"> + <default></default> + <choices></choices> + </option> + <option name="--test" short="" help="A test option." boolean="0"> + <default></default> + <choices></choices> + </option> + </options> + <arguments> + <argument name="model" help="The model to make." required="1"> + <choices></choices> + </argument> + <argument name="other_longer" help="Another argument." required="0"> + <choices></choices> + </argument> + </arguments> + <epilog/> +</shell> +TEXT; + $this->assertEquals(new DomDocument($expected), new DomDocument($result), 'Help does not match'); + } + +/** + * Test xml help as object + * + * @return void + */ + function testXmlHelpAsObject() { + $parser = new ConsoleOptionParser('mycommand', false); + $parser->addOption('test', array('help' => 'A test option.')) + ->addArgument('model', array('help' => 'The model to make.', 'required' => true)) + ->addArgument('other_longer', array('help' => 'Another argument.')); + + $formatter = new HelpFormatter($parser); + $result = $formatter->xml(false); + $this->assertType('SimpleXmlElement', $result); + } +} diff --git a/cake/tests/cases/console/libs/shell.test.php b/cake/tests/cases/console/libs/shell.test.php index 157701079..f76d07623 100644 --- a/cake/tests/cases/console/libs/shell.test.php +++ b/cake/tests/cases/console/libs/shell.test.php @@ -22,18 +22,7 @@ App::import('Core', 'Folder'); App::import('Shell', 'Shell', false); - -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * TestShell class @@ -50,6 +39,7 @@ class TestShell extends Shell { * @access public */ public $name = 'TestShell'; + /** * stopped property * @@ -67,6 +57,18 @@ class TestShell extends Shell { protected function _stop($status = 0) { $this->stopped = $status; } + + public function do_something() { + + } + + public function _secret() { + + } + + protected function no_access() { + + } } /** @@ -114,21 +116,10 @@ class ShellTest extends CakeTestCase { public function setUp() { parent::setUp(); - $this->Dispatcher = $this->getMock( - 'ShellDispatcher', - array('getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear') - ); - $this->Shell =& new TestShell($this->Dispatcher); - } - -/** - * tearDown method - * - * @return void - */ - public function tearDown() { - parent::tearDown(); - ClassRegistry::flush(); + $output = $this->getMock('ConsoleOutput', array(), array(), '', false); + $error = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Shell =& new TestShell($output, $error, $in); } /** @@ -137,9 +128,10 @@ class ShellTest extends CakeTestCase { * @return void */ public function testConstruct() { - $this->assertEquals($this->Dispatcher, $this->Shell->Dispatch); $this->assertEqual($this->Shell->name, 'TestShell'); - $this->assertEqual($this->Shell->alias, 'TestShell'); + $this->assertType('ConsoleInput', $this->Shell->stdin); + $this->assertType('ConsoleOutput', $this->Shell->stdout); + $this->assertType('ConsoleOutput', $this->Shell->stderr); } /** @@ -166,11 +158,6 @@ class ShellTest extends CakeTestCase { $this->assertIsA($this->Shell->Comment, 'Comment'); $this->assertEqual($this->Shell->modelClass, 'Comment'); - $this->Shell->uses = true; - $this->Shell->initialize(); - $this->assertTrue(isset($this->Shell->AppModel)); - $this->assertIsA($this->Shell->AppModel, 'AppModel'); - App::build(); } @@ -180,29 +167,24 @@ class ShellTest extends CakeTestCase { * @return void */ public function testIn() { - $this->Dispatcher->expects($this->at(0)) - ->method('getInput') - ->with('Just a test?', array('y', 'n'), 'n') + $this->Shell->stdin->expects($this->at(0)) + ->method('read') ->will($this->returnValue('n')); - $this->Dispatcher->expects($this->at(1)) - ->method('getInput') - ->with('Just a test?', array('y', 'n'), 'n') + $this->Shell->stdin->expects($this->at(1)) + ->method('read') ->will($this->returnValue('Y')); - $this->Dispatcher->expects($this->at(2)) - ->method('getInput') - ->with('Just a test?', 'y,n', 'n') + $this->Shell->stdin->expects($this->at(2)) + ->method('read') ->will($this->returnValue('y')); - $this->Dispatcher->expects($this->at(3)) - ->method('getInput') - ->with('Just a test?', 'y/n', 'n') + $this->Shell->stdin->expects($this->at(3)) + ->method('read') ->will($this->returnValue('y')); - $this->Dispatcher->expects($this->at(4)) - ->method('getInput') - ->with('Just a test?', 'y', 'y') + $this->Shell->stdin->expects($this->at(4)) + ->method('read') ->will($this->returnValue('y')); $result = $this->Shell->in('Just a test?', array('y', 'n'), 'n'); @@ -232,21 +214,21 @@ class ShellTest extends CakeTestCase { * @return void */ public function testOut() { - $this->Shell->Dispatch->expects($this->at(0)) - ->method('stdout') - ->with("Just a test\n", false); + $this->Shell->stdout->expects($this->at(0)) + ->method('write') + ->with("Just a test", 1); - $this->Shell->Dispatch->expects($this->at(1)) - ->method('stdout') - ->with("Just\na\ntest\n", false); + $this->Shell->stdout->expects($this->at(1)) + ->method('write') + ->with(array('Just', 'a', 'test'), 1); - $this->Shell->Dispatch->expects($this->at(2)) - ->method('stdout') - ->with("Just\na\ntest\n\n", false); + $this->Shell->stdout->expects($this->at(2)) + ->method('write') + ->with(array('Just', 'a', 'test'), 2); - $this->Shell->Dispatch->expects($this->at(3)) - ->method('stdout') - ->with("\n", false); + $this->Shell->stdout->expects($this->at(3)) + ->method('write') + ->with('', 1); $this->Shell->out('Just a test'); @@ -257,27 +239,65 @@ class ShellTest extends CakeTestCase { $this->Shell->out(); } +/** + * test that verbose and quiet output levels work + * + * @return void + */ + function testVerboseOutput() { + $this->Shell->stdout->expects($this->at(0))->method('write') + ->with('Verbose', 1); + $this->Shell->stdout->expects($this->at(1))->method('write') + ->with('Normal', 1); + $this->Shell->stdout->expects($this->at(2))->method('write') + ->with('Quiet', 1); + + $this->Shell->params['verbose'] = true; + $this->Shell->params['quiet'] = false; + + $this->Shell->out('Verbose', 1, Shell::VERBOSE); + $this->Shell->out('Normal', 1, Shell::NORMAL); + $this->Shell->out('Quiet', 1, Shell::QUIET); + } + +/** + * test that verbose and quiet output levels work + * + * @return void + */ + function testQuietOutput() { + $this->Shell->stdout->expects($this->once())->method('write') + ->with('Quiet', 1); + + $this->Shell->params['verbose'] = false; + $this->Shell->params['quiet'] = true; + + $this->Shell->out('Verbose', 1, Shell::VERBOSE); + $this->Shell->out('Normal', 1, Shell::NORMAL); + $this->Shell->out('Quiet', 1, Shell::QUIET); + } + /** * testErr method * * @return void */ public function testErr() { - $this->Shell->Dispatch->expects($this->at(0)) - ->method('stderr') - ->with("Just a test\n"); + $this->Shell->stderr->expects($this->at(0)) + ->method('write') + ->with("Just a test", 1); - $this->Shell->Dispatch->expects($this->at(1)) - ->method('stderr') - ->with("Just\na\ntest\n"); + $this->Shell->stderr->expects($this->at(1)) + ->method('write') + ->with(array('Just', 'a', 'test'), 1); - $this->Shell->Dispatch->expects($this->at(2)) - ->method('stderr') - ->with("Just\na\ntest\n\n"); + $this->Shell->stderr->expects($this->at(2)) + ->method('write') + ->with(array('Just', 'a', 'test'), 2); - $this->Shell->Dispatch->expects($this->at(3)) - ->method('stderr') - ->with("\n"); + $this->Shell->stderr->expects($this->at(3)) + ->method('write') + ->with('', 1); $this->Shell->err('Just a test'); @@ -309,17 +329,17 @@ class ShellTest extends CakeTestCase { public function testHr() { $bar = '---------------------------------------------------------------'; - $this->Shell->Dispatch->expects($this->at(0))->method('stdout')->with('', false); - $this->Shell->Dispatch->expects($this->at(1))->method('stdout')->with($bar . "\n", false); - $this->Shell->Dispatch->expects($this->at(2))->method('stdout')->with('', false); + $this->Shell->stdout->expects($this->at(0))->method('write')->with('', 0); + $this->Shell->stdout->expects($this->at(1))->method('write')->with($bar, 1); + $this->Shell->stdout->expects($this->at(2))->method('write')->with('', 0); - $this->Shell->Dispatch->expects($this->at(3))->method('stdout')->with("\n", false); - $this->Shell->Dispatch->expects($this->at(4))->method('stdout')->with($bar . "\n", false); - $this->Shell->Dispatch->expects($this->at(5))->method('stdout')->with("\n", false); + $this->Shell->stdout->expects($this->at(3))->method('write')->with("", true); + $this->Shell->stdout->expects($this->at(4))->method('write')->with($bar, 1); + $this->Shell->stdout->expects($this->at(5))->method('write')->with("", true); - $this->Shell->Dispatch->expects($this->at(6))->method('stdout')->with("\n\n", false); - $this->Shell->Dispatch->expects($this->at(7))->method('stdout')->with($bar . "\n", false); - $this->Shell->Dispatch->expects($this->at(8))->method('stdout')->with("\n\n", false); + $this->Shell->stdout->expects($this->at(6))->method('write')->with("", 2); + $this->Shell->stdout->expects($this->at(7))->method('write')->with($bar, 1); + $this->Shell->stdout->expects($this->at(8))->method('write')->with("", 2); $this->Shell->hr(); @@ -334,17 +354,17 @@ class ShellTest extends CakeTestCase { * @return void */ public function testError() { - $this->Shell->Dispatch->expects($this->at(0)) - ->method('stderr') - ->with("Error: Foo Not Found\n"); + $this->Shell->stderr->expects($this->at(0)) + ->method('write') + ->with("<error>Error:</error> Foo Not Found", 1); - $this->Shell->Dispatch->expects($this->at(1)) - ->method('stderr') - ->with("Error: Foo Not Found\n"); + $this->Shell->stderr->expects($this->at(1)) + ->method('write') + ->with("<error>Error:</error> Foo Not Found", 1); - $this->Shell->Dispatch->expects($this->at(2)) - ->method('stderr') - ->with("Searched all...\n"); + $this->Shell->stderr->expects($this->at(2)) + ->method('write') + ->with("Searched all...", 1); $this->Shell->error('Foo Not Found'); $this->assertIdentical($this->Shell->stopped, 1); @@ -377,19 +397,37 @@ class ShellTest extends CakeTestCase { $this->Shell->tasks = array('TestApple'); $this->assertTrue($this->Shell->loadTasks()); - $this->assertIsA($this->Shell->TestApple, 'TestAppleTask'); + $this->assertInstanceOf('TestAppleTask', $this->Shell->TestApple); $this->Shell->tasks = 'TestBanana'; $this->assertTrue($this->Shell->loadTasks()); - $this->assertIsA($this->Shell->TestApple, 'TestAppleTask'); - $this->assertIsA($this->Shell->TestBanana, 'TestBananaTask'); + $this->assertInstanceOf('TestAppleTask', $this->Shell->TestApple); + $this->assertInstanceOf('TestBananaTask', $this->Shell->TestBanana); unset($this->Shell->ShellTestApple, $this->Shell->TestBanana); $this->Shell->tasks = array('TestApple', 'TestBanana'); $this->assertTrue($this->Shell->loadTasks()); - $this->assertIsA($this->Shell->TestApple, 'TestAppleTask'); - $this->assertIsA($this->Shell->TestBanana, 'TestBananaTask'); + $this->assertInstanceOf('TestAppleTask', $this->Shell->TestApple); + $this->assertInstanceOf('TestBananaTask', $this->Shell->TestBanana); + } + +/** + * test that __get() makes args and params references + * + * @return void + */ + function test__getArgAndParamReferences() { + $this->Shell->tasks = array('TestApple'); + $this->Shell->args = array('one'); + $this->Shell->params = array('help' => false); + $this->Shell->loadTasks(); + $result = $this->Shell->TestApple; + + $this->Shell->args = array('one', 'two'); + + $this->assertSame($this->Shell->args, $result->args); + $this->assertSame($this->Shell->params, $result->params); } /** @@ -477,14 +515,12 @@ class ShellTest extends CakeTestCase { $this->Shell->interactive = true; - $this->Shell->Dispatch->expects($this->at(5)) - ->method('getInput') - ->withAnyParameters() + $this->Shell->stdin->expects($this->at(0)) + ->method('read') ->will($this->returnValue('n')); - $this->Shell->Dispatch->expects($this->at(9)) - ->method('getInput') - ->withAnyParameters() + $this->Shell->stdin->expects($this->at(1)) + ->method('read') ->will($this->returnValue('y')); @@ -578,4 +614,161 @@ class ShellTest extends CakeTestCase { $Folder->delete(); } + +/** + * test hasTask method + * + * @return void + */ + function testHasTask() { + $this->Shell->tasks = array('Extract', 'DbConfig'); + $this->Shell->loadTasks(); + + $this->assertTrue($this->Shell->hasTask('extract')); + $this->assertTrue($this->Shell->hasTask('Extract')); + $this->assertFalse($this->Shell->hasTask('random')); + + $this->assertTrue($this->Shell->hasTask('db_config')); + $this->assertTrue($this->Shell->hasTask('DbConfig')); + } + +/** + * test the hasMethod + * + * @return void + */ + function testHasMethod() { + $this->assertTrue($this->Shell->hasMethod('do_something')); + $this->assertFalse($this->Shell->hasMethod('hr'), 'hr is callable'); + $this->assertFalse($this->Shell->hasMethod('_secret'), '_secret is callable'); + $this->assertFalse($this->Shell->hasMethod('no_access'), 'no_access is callable'); + } + +/** + * test run command calling main. + * + * @return void + */ + function testRunCommandMain() { + $methods = get_class_methods('Shell'); + $Mock = $this->getMock('Shell', array('main', 'startup'), array(), '', false); + + $Mock->expects($this->once())->method('main')->will($this->returnValue(true)); + $result = $Mock->runCommand(null, array()); + $this->assertTrue($result); + } + +/** + * test run command calling a legit method. + * + * @return void + */ + function testRunCommandWithMethod() { + $methods = get_class_methods('Shell'); + $Mock = $this->getMock('Shell', array('hit_me', 'startup'), array(), '', false); + + $Mock->expects($this->once())->method('hit_me')->will($this->returnValue(true)); + $result = $Mock->runCommand('hit_me', array()); + $this->assertTrue($result); + } + +/** + * test run command causing exception on Shell method. + * + * @return void + */ + function testRunCommandBaseclassMethod() { + $Mock = $this->getMock('Shell', array('startup', 'getOptionParser', 'out'), array(), '', false); + $Parser = $this->getMock('ConsoleOptionParser', array(), array(), '', false); + + $Parser->expects($this->once())->method('help'); + $Mock->expects($this->once())->method('getOptionParser') + ->will($this->returnValue($Parser)); + $Mock->expects($this->never())->method('hr'); + $Mock->expects($this->once())->method('out'); + + $result = $Mock->runCommand('hr', array()); + } + +/** + * test run command causing exception on Shell method. + * + * @return void + */ + function testRunCommandMissingMethod() { + $methods = get_class_methods('Shell'); + $Mock = $this->getMock('Shell', array('startup', 'getOptionParser', 'out'), array(), '', false); + $Parser = $this->getMock('ConsoleOptionParser', array(), array(), '', false); + + $Parser->expects($this->once())->method('help'); + $Mock->expects($this->never())->method('idontexist'); + $Mock->expects($this->once())->method('getOptionParser') + ->will($this->returnValue($Parser)); + $Mock->expects($this->once())->method('out'); + + + $result = $Mock->runCommand('idontexist', array()); + } + +/** + * test that a --help causes help to show. + * + * @return void + */ + function testRunCommandTriggeringHelp() { + $Parser = $this->getMock('ConsoleOptionParser', array(), array(), '', false); + $Parser->expects($this->once())->method('parse') + ->with(array('--help')) + ->will($this->returnValue(array(array('help' => true), array()))); + $Parser->expects($this->once())->method('help'); + + $Shell = $this->getMock('Shell', array('getOptionParser', 'out', 'startup', '_welcome'), array(), '', false); + $Shell->expects($this->once())->method('getOptionParser') + ->will($this->returnValue($Parser)); + $Shell->expects($this->once())->method('out'); + + $Shell->runCommand(null, array('--help')); + } + +/** + * test that runCommand will call runCommand on the task. + * + * @return void + */ + function testRunCommandHittingTask() { + $Shell = $this->getMock('Shell', array('hasTask', 'startup'), array(), '', false); + $task = $this->getMock('Shell', array('execute', 'runCommand'), array(), '', false); + $task->expects($this->any())->method('runCommand') + ->with('execute', array('one', 'value')); + + $Shell->expects($this->once())->method('startup'); + $Shell->expects($this->any())->method('hasTask')->will($this->returnValue(true)); + $Shell->RunCommand = $task; + + $Shell->runCommand('run_command', array('run_command', 'one', 'value')); + } + +/** + * test wrapBlock wrapping text. + * + * @return void + */ + function testWrapText() { + $text = 'This is the song that never ends. This is the song that never ends. This is the song that never ends.'; + $result = $this->Shell->wrapText($text, 33); + $expected = <<<TEXT +This is the song that never ends. +This is the song that never ends. +This is the song that never ends. +TEXT; + $this->assertEquals($expected, $result, 'Text not wrapped.'); + + $result = $this->Shell->wrapText($text, array('indent' => ' ', 'width' => 33)); + $expected = <<<TEXT + This is the song that never ends. + This is the song that never ends. + This is the song that never ends. +TEXT; + $this->assertEquals($expected, $result, 'Text not wrapped.'); + } } diff --git a/cake/tests/cases/console/libs/task_collection.test.php b/cake/tests/cases/console/libs/task_collection.test.php index e9f9f69c8..da7d6eaef 100644 --- a/cake/tests/cases/console/libs/task_collection.test.php +++ b/cake/tests/cases/console/libs/task_collection.test.php @@ -17,15 +17,7 @@ * @since CakePHP(tm) v 2.0 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} + App::import('Shell', 'TaskCollection', false); App::import('Shell', 'Shell', false); @@ -36,9 +28,9 @@ class TaskCollectionTest extends CakeTestCase { * @return void */ function setup() { + $shell = $this->getMock('Shell', array(), array(), '', false); $dispatcher = $this->getMock('ShellDispatcher', array(), array(), '', false); - $dispatcher->shellPaths = App::path('shells'); - $this->Tasks = new TaskCollection($dispatcher); + $this->Tasks = new TaskCollection($shell, $dispatcher); } /** @@ -95,9 +87,11 @@ class TaskCollectionTest extends CakeTestCase { */ function testLoadPluginTask() { $dispatcher = $this->getMock('ShellDispatcher', array(), array(), '', false); - $dispatcher->shellPaths = App::path('shells'); - $dispatcher->shellPaths[] = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS . 'test_plugin' . DS . 'vendors' . DS . 'shells' . DS; - $this->Tasks = new TaskCollection($dispatcher); + $shell = $this->getMock('Shell', array(), array(), '', false); + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS) + )); + $this->Tasks = new TaskCollection($shell, $dispatcher); $result = $this->Tasks->load('TestPlugin.OtherTask'); $this->assertType('OtherTaskTask', $result, 'Task class is wrong.'); diff --git a/cake/tests/cases/console/libs/tasks/plugin.test.php b/cake/tests/cases/console/libs/tasks/plugin.test.php deleted file mode 100644 index 5e75194f1..000000000 --- a/cake/tests/cases/console/libs/tasks/plugin.test.php +++ /dev/null @@ -1,249 +0,0 @@ -<?php -/** - * PluginTask Test file - * - * Test Case for plugin generation shell task - * - * PHP 5 - * - * CakePHP : Rapid Development Framework (http://cakephp.org) - * Copyright 2006-2010, Cake Software Foundation, Inc. - * - * Licensed under The MIT License - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright 2006-2010, Cake Software Foundation, Inc. - * @link http://cakephp.org CakePHP Project - * @package cake - * @subpackage cake.tests.cases.console.libs.tasks - * @since CakePHP v 1.3.0 - * @license MIT License (http://www.opensource.org/licenses/mit-license.php) - */ -App::import('Shell', 'Shell', false); -App::import('Core', array('File')); - -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'plugin.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'model.php'; - -/** - * PluginTaskPlugin class - * - * @package cake - * @subpackage cake.tests.cases.console.libs.tasks - */ -class PluginTaskTest extends CakeTestCase { - -/** - * setup method - * - * @return void - */ - public function setUp() { - parent::setUp(); - - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); - $this->Task = $this->getMock('PluginTask', - array('in', 'err', 'createFile', '_stop'), - array(&$this->Dispatcher) - ); - $this->Task->path = TMP . 'tests' . DS; - - $this->_paths = $paths = App::path('plugins'); - $this->_testPath = array_push($paths, TMP . 'tests' . DS); - App::build(array('plugins' => $paths)); - } - -/** - * teardown - * - * @return void - */ - public function tearDown() { - parent::tearDown(); - App::build(array('plugins' => $this->_paths)); - } - -/** - * test bake() - * - * @return void - */ - public function testBakeFoldersAndFiles() { - $this->Task->expects($this->at(0))->method('in')->will($this->returnValue($this->_testPath)); - $this->Task->expects($this->at(1))->method('in')->will($this->returnValue('y')); - - $path = $this->Task->path . 'bake_test_plugin'; - - $file = $path . DS . 'bake_test_plugin_app_controller.php'; - $this->Task->expects($this->at(3))->method('createFile') - ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); - - $file = $path . DS . 'bake_test_plugin_app_model.php'; - $this->Task->expects($this->at(4))->method('createFile') - ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); - - $this->Task->bake('BakeTestPlugin'); - - $path = $this->Task->path . 'bake_test_plugin'; - $this->assertTrue(is_dir($path), 'No plugin dir %s'); - - $this->assertTrue(is_dir($path . DS . 'config'), 'No config dir %s'); - $this->assertTrue(is_dir($path . DS . 'config' . DS . 'schema'), 'No schema dir %s'); - $this->assertTrue(file_exists($path . DS . 'config' . DS . 'schema' . DS . 'empty'), 'No empty file %s'); - - $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir %s'); - $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir %s'); - $this->assertTrue(file_exists($path . DS . 'controllers' . DS . 'components' . DS . 'empty'), 'No empty file %s'); - - $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir %s'); - $this->assertTrue(file_exists($path . DS . 'models' . DS . 'behaviors' . DS . 'empty'), 'No empty file %s'); - $this->assertTrue(is_dir($path . DS . 'models' . DS . 'datasources'), 'No datasources dir %s'); - $this->assertTrue(file_exists($path . DS . 'models' . DS . 'datasources' . DS . 'empty'), 'No empty file %s'); - - $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir %s'); - $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir %s'); - $this->assertTrue(file_exists($path . DS . 'views' . DS . 'helpers' . DS . 'empty'), 'No empty file %s'); - - $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir %s'); - - $this->assertTrue( - is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'components'), 'No components cases dir %s' - ); - $this->assertTrue( - file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'components' . DS . 'empty'), 'No empty file %s' - ); - - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'behaviors'), 'No behaviors cases dir %s'); - $this->assertTrue( - file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'behaviors' . DS . 'empty'), 'No empty file %s' - ); - - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'helpers'), 'No helpers cases dir %s'); - $this->assertTrue( - file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'helpers' . DS . 'empty'), 'No empty file %s' - ); - - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'models'), 'No models cases dir %s'); - $this->assertTrue( - file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'models' . DS . 'empty'), 'No empty file %s' - ); - - $this->assertTrue( - is_dir($path . DS . 'tests' . DS . 'cases' . DS . 'controllers'), - 'No controllers cases dir %s' - ); - $this->assertTrue( - file_exists($path . DS . 'tests' . DS . 'cases' . DS . 'controllers' . DS . 'empty'), 'No empty file %s' - ); - - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'groups'), 'No groups dir %s'); - $this->assertTrue(file_exists($path . DS . 'tests' . DS . 'groups' . DS . 'empty'), 'No empty file %s'); - - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir %s'); - $this->assertTrue(file_exists($path . DS . 'tests' . DS . 'fixtures' . DS . 'empty'), 'No empty file %s'); - - $this->assertTrue(is_dir($path . DS . 'vendors'), 'No vendors dir %s'); - - $this->assertTrue(is_dir($path . DS . 'vendors' . DS . 'shells'), 'No vendors shells dir %s'); - $this->assertTrue(is_dir($path . DS . 'vendors' . DS . 'shells' . DS . 'tasks'), 'No vendors shells tasks dir %s'); - $this->assertTrue(file_exists($path . DS . 'vendors' . DS . 'shells' . DS . 'tasks' . DS . 'empty'), 'No empty file %s'); - $this->assertTrue(is_dir($path . DS . 'libs'), 'No libs dir %s'); - $this->assertTrue(is_dir($path . DS . 'webroot'), 'No webroot dir %s'); - - $Folder = new Folder($this->Task->path . 'bake_test_plugin'); - $Folder->delete(); - } - -/** - * test execute with no args, flowing into interactive, - * - * @return void - */ - public function testExecuteWithNoArgs() { - $this->Task->expects($this->at(0))->method('in')->will($this->returnValue('TestPlugin')); - $this->Task->expects($this->at(1))->method('in')->will($this->returnValue('3')); - $this->Task->expects($this->at(2))->method('in')->will($this->returnValue('y')); - $this->Task->expects($this->at(3))->method('in')->will($this->returnValue('n')); - - $path = $this->Task->path . 'test_plugin'; - $file = $path . DS . 'test_plugin_app_controller.php'; - $this->Task->expects($this->at(4))->method('createFile') - ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); - - $file = $path . DS . 'test_plugin_app_model.php'; - $this->Task->expects($this->at(5))->method('createFile') - ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); - - $this->Task->args = array(); - $this->Task->execute(); - - $Folder = new Folder($path); - $Folder->delete(); - } - -/** - * Test Execute - * - * @return void - */ - public function testExecuteWithOneArg() { - $this->Task->expects($this->at(0))->method('in') - ->will($this->returnValue($this->_testPath)); - $this->Task->expects($this->at(1))->method('in') - ->will($this->returnValue('y')); - - $path = $this->Task->path . 'bake_test_plugin'; - $file = $path . DS . 'bake_test_plugin_app_controller.php'; - $this->Task->expects($this->at(3))->method('createFile') - ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); - - $path = $this->Task->path . 'bake_test_plugin'; - $file = $path . DS . 'bake_test_plugin_app_model.php'; - $this->Task->expects($this->at(4))->method('createFile') - ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); - - $this->Task->Dispatch->args = array('BakeTestPlugin'); - $this->Task->args =& $this->Task->Dispatch->args; - - $this->Task->execute(); - - $Folder = new Folder($this->Task->path . 'bake_test_plugin'); - $Folder->delete(); - } - -/** - * test execute chaining into MVC parts - * - * @return void - */ - public function testExecuteWithTwoArgs() { - $this->Task->Model = $this->getMock('ModelTask', array(), array(&$this->Dispatcher)); - - $this->Task->expects($this->at(0))->method('in')->will($this->returnValue($this->_testPath)); - - $this->Task->Model->expects($this->once())->method('loadTasks'); - $this->Task->Model->expects($this->once())->method('execute'); - - $Folder = new Folder($this->Task->path . 'bake_test_plugin', true); - - $this->Task->Dispatch->args = array('BakeTestPlugin', 'model'); - $this->Task->args = $this->Task->Dispatch->args; - - $this->Task->execute(); - $Folder->delete(); - } -} diff --git a/cake/tests/cases/console/shell_dispatcher.test.php b/cake/tests/cases/console/shell_dispatcher.test.php new file mode 100644 index 000000000..2da5bbe81 --- /dev/null +++ b/cake/tests/cases/console/shell_dispatcher.test.php @@ -0,0 +1,544 @@ +<?php +/** + * ShellDispatcherTest file + * + * PHP 5 + * + * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing> + * Copyright 2005-2010, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice + * + * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. + * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests + * @package cake + * @subpackage cake.tests.cases.console + * @since CakePHP(tm) v 1.2.0.5432 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; + +/** + * TestShellDispatcher class + * + * @package cake + * @subpackage cake.tests.cases.console + */ +class TestShellDispatcher extends ShellDispatcher { + +/** + * params property + * + * @var array + * @access public + */ + public $params = array(); + +/** + * stopped property + * + * @var string + * @access public + */ + public $stopped = null; + +/** + * TestShell + * + * @var mixed + * @access public + */ + public $TestShell; + +/** + * _initEnvironment method + * + * @return void + */ + protected function _initEnvironment() { + } + +/** + * clear method + * + * @return void + */ + public function clear() { + + } + +/** + * _stop method + * + * @return void + */ + protected function _stop($status = 0) { + $this->stopped = 'Stopped with status: ' . $status; + return $status; + } + +/** + * getShell + * + * @param mixed $shell + * @return mixed + */ + public function getShell($shell) { + return $this->_getShell($shell); + } + +/** + * _getShell + * + * @param mixed $plugin + * @return mixed + */ + protected function _getShell($shell) { + if (isset($this->TestShell)) { + return $this->TestShell; + } + return parent::_getShell($shell); + } +} + +/** + * ShellDispatcherTest + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class ShellDispatcherTest extends CakeTestCase { + +/** + * setUp method + * + * @return void + */ + public function setUp() { + parent::setUp(); + App::build(array( + 'plugins' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS + ), + 'shells' => array( + CORE_PATH ? CONSOLE_LIBS : ROOT . DS . CONSOLE_LIBS, + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'console' . DS . 'shells' . DS + ) + ), true); + } + +/** + * testParseParams method + * + * @return void + */ + public function testParseParams() { + $Dispatcher = new TestShellDispatcher(); + + $params = array( + '/cake/1.2.x.x/cake/console/cake.php', + 'bake', + '-app', + 'new', + '-working', + '/var/www/htdocs' + ); + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => '/var/www/htdocs/new', + 'root' => '/var/www/htdocs' + ); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array('cake.php'); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'app'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-app', + 'new', + ); + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + './cake.php', + 'bake', + '-app', + 'new', + '-working', + '/cake/1.2.x.x/cake/console' + ); + + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) + ); + + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + './console/cake.php', + 'bake', + '-app', + 'new', + '-working', + '/cake/1.2.x.x/cake' + ); + $expected = array( + 'app' => 'new', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH) + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + './console/cake.php', + 'bake', + '-app', + 'new', + '-dry', + '-working', + '/cake/1.2.x.x/cake' + ); + $expected = array( + 'app' => 'new', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'new'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), + 'webroot' => 'webroot' + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEquals($expected, $Dispatcher->params); + + $params = array( + './console/cake.php', + '-working', + '/cake/1.2.x.x/cake', + 'schema', + 'run', + 'create', + '-dry', + '-f', + '-name', + 'DbAcl' + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH . DS . 'app'), + 'root' => str_replace('\\', '/', CAKE_CORE_INCLUDE_PATH), + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $expected = array( + './console/cake.php', 'schema', 'run', 'create', '-dry', '-f', '-name', 'DbAcl' + ); + $this->assertEqual($expected, $Dispatcher->args); + + $params = array( + '/cake/1.2.x.x/cake/console/cake.php', + '-working', + '/cake/1.2.x.x/app', + 'schema', + 'run', + 'create', + '-dry', + '-name', + 'DbAcl' + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => '/cake/1.2.x.x/app', + 'root' => '/cake/1.2.x.x', + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-working', + 'C:/wamp/www/cake/app', + 'bake', + '-app', + 'C:/wamp/www/apps/cake/app', + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => 'C:\wamp\www\apps\cake\app', + 'root' => 'C:\wamp\www\apps\cake' + ); + + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-working', + 'C:\wamp\www\cake\app', + 'bake', + '-app', + 'C:\wamp\www\apps\cake\app', + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => 'C:\wamp\www\apps\cake\app', + 'root' => 'C:\wamp\www\apps\cake' + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-working', + 'C:\wamp\www\apps', + 'bake', + '-app', + 'cake\app', + '-url', + 'http://example.com/some/url/with/a/path' + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => 'C:\wamp\www\apps\cake\app', + 'root' => 'C:\wamp\www\apps\cake', + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + '/home/amelo/dev/cake-common/cake/console/cake.php', + '-root', + '/home/amelo/dev/lsbu-vacancy', + '-working', + '/home/amelo/dev/lsbu-vacancy', + '-app', + 'app', + ); + $expected = array( + 'app' => 'app', + 'webroot' => 'webroot', + 'working' => '/home/amelo/dev/lsbu-vacancy/app', + 'root' => '/home/amelo/dev/lsbu-vacancy', + ); + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + + $params = array( + 'cake.php', + '-working', + 'D:\www', + 'bake', + 'my_app', + ); + $expected = array( + 'working' => 'D:\www', + 'app' => 'www', + 'root' => 'D:', + 'webroot' => 'webroot' + ); + + $Dispatcher->params = $Dispatcher->args = array(); + $Dispatcher->parseParams($params); + $this->assertEqual($expected, $Dispatcher->params); + } + +/** + * Verify loading of (plugin-) shells + * + * @return void + */ + public function testGetShell() { + $this->skipIf(class_exists('SampleShell'), '%s SampleShell Class already loaded'); + $this->skipIf(class_exists('ExampleShell'), '%s ExampleShell Class already loaded'); + + $Dispatcher = new TestShellDispatcher(); + + $result = $Dispatcher->getShell('sample'); + $this->assertInstanceOf('SampleShell', $result); + + $Dispatcher = new TestShellDispatcher(); + $result = $Dispatcher->getShell('test_plugin.example'); + $this->assertInstanceOf('ExampleShell', $result); + } + +/** + * Verify correct dispatch of Shell subclasses with a main method + * + * @return void + */ + public function testDispatchShellWithMain() { + $Dispatcher = new TestShellDispatcher(); + $Mock = $this->getMock('Shell', array(), array(&$Dispatcher), 'MockWithMainShell'); + + $Mock->expects($this->once())->method('initialize'); + $Mock->expects($this->once())->method('loadTasks'); + $Mock->expects($this->once())->method('runCommand') + ->with(null, array()) + ->will($this->returnValue(true)); + + $Dispatcher->TestShell = $Mock; + + $Dispatcher->args = array('mock_with_main'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array()); + } + +/** + * Verify correct dispatch of Shell subclasses without a main method + * + * @return void + */ + public function testDispatchShellWithoutMain() { + $Dispatcher = new TestShellDispatcher(); + $Shell = $this->getMock('Shell', array(), array(&$Dispatcher), 'MockWithoutMainShell'); + + $Shell = new MockWithoutMainShell($Dispatcher); + $this->mockObjects[] = $Shell; + + $Shell->expects($this->once())->method('initialize'); + $Shell->expects($this->once())->method('loadTasks'); + $Shell->expects($this->once())->method('runCommand') + ->with('initdb', array('initdb')) + ->will($this->returnValue(true)); + + $Dispatcher->TestShell = $Shell; + + $Dispatcher->args = array('mock_without_main', 'initdb'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + } + +/** + * Verify correct dispatch of custom classes with a main method + * + * @return void + */ + public function testDispatchNotAShellWithMain() { + $Dispatcher = new TestShellDispatcher(); + $methods = get_class_methods('Object'); + array_push($methods, 'main', 'initdb', 'initialize', 'loadTasks', 'startup', '_secret'); + $Shell = $this->getMock('Object', $methods, array(), 'MockWithMainNotAShell'); + + $Shell->expects($this->never())->method('initialize'); + $Shell->expects($this->never())->method('loadTasks'); + $Shell->expects($this->once())->method('startup'); + $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); + $Dispatcher->TestShell = $Shell; + + $Dispatcher->args = array('mock_with_main_not_a'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWithMainNotAShell($Dispatcher); + $this->mockObjects[] = $Shell; + $Shell->expects($this->once())->method('initdb')->will($this->returnValue(true)); + $Shell->expects($this->once())->method('startup'); + $Dispatcher->TestShell = $Shell; + + $Dispatcher->args = array('mock_with_main_not_a', 'initdb'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + } + +/** + * Verify correct dispatch of custom classes without a main method + * + * @return void + */ + public function testDispatchNotAShellWithoutMain() { + $Dispatcher = new TestShellDispatcher(); + $methods = get_class_methods('Object'); + array_push($methods, 'main', 'initdb', 'initialize', 'loadTasks', 'startup', '_secret'); + $Shell = $this->getMock('Object', $methods, array(&$Dispatcher), 'MockWithoutMainNotAShell'); + + $Shell->expects($this->never())->method('initialize'); + $Shell->expects($this->never())->method('loadTasks'); + $Shell->expects($this->once())->method('startup'); + $Shell->expects($this->once())->method('main')->will($this->returnValue(true)); + $Dispatcher->TestShell = $Shell; + + $Dispatcher->args = array('mock_without_main_not_a'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + $this->assertEqual($Dispatcher->args, array()); + + $Shell = new MockWithoutMainNotAShell($Dispatcher); + $this->mockObjects[] = $Shell; + $Shell->expects($this->once())->method('initdb')->will($this->returnValue(true)); + $Shell->expects($this->once())->method('startup'); + $Dispatcher->TestShell = $Shell; + + $Dispatcher->args = array('mock_without_main_not_a', 'initdb'); + $result = $Dispatcher->dispatch(); + $this->assertTrue($result); + } + +/** + * Verify shifting of arguments + * + * @return void + */ + public function testShiftArgs() { + $Dispatcher = new TestShellDispatcher(); + + $Dispatcher->args = array('a', 'b', 'c'); + $this->assertEqual($Dispatcher->shiftArgs(), 'a'); + $this->assertIdentical($Dispatcher->args, array('b', 'c')); + + $Dispatcher->args = array('a' => 'b', 'c', 'd'); + $this->assertEqual($Dispatcher->shiftArgs(), 'b'); + $this->assertIdentical($Dispatcher->args, array('c', 'd')); + + $Dispatcher->args = array('a', 'b' => 'c', 'd'); + $this->assertEqual($Dispatcher->shiftArgs(), 'a'); + $this->assertIdentical($Dispatcher->args, array('b' => 'c', 'd')); + + $Dispatcher->args = array(0 => 'a', 2 => 'b', 30 => 'c'); + $this->assertEqual($Dispatcher->shiftArgs(), 'a'); + $this->assertIdentical($Dispatcher->args, array(0 => 'b', 1 => 'c')); + + $Dispatcher->args = array(); + $this->assertNull($Dispatcher->shiftArgs()); + $this->assertIdentical($Dispatcher->args, array()); + } + +} diff --git a/cake/tests/cases/console/libs/acl.test.php b/cake/tests/cases/console/shells/acl.test.php similarity index 82% rename from cake/tests/cases/console/libs/acl.test.php rename to cake/tests/cases/console/shells/acl.test.php index 5df62fdde..305ee1ebc 100644 --- a/cake/tests/cases/console/libs/acl.test.php +++ b/cake/tests/cases/console/shells/acl.test.php @@ -18,21 +18,9 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', 'Acl'); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -if (!class_exists('AclShell')) { - require CAKE . 'console' . DS . 'libs' . DS . 'acl.php'; -} +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * AclShellTest class @@ -61,14 +49,13 @@ class AclShellTest extends CakeTestCase { Configure::write('Acl.database', 'test'); Configure::write('Acl.classname', 'DbAcl'); - $this->Dispatcher = $this->getMock( - 'ShellDispatcher', - array('getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'dispatch', 'clear') - ); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock( 'AclShell', - array('in', 'out', 'hr', 'createFile', 'error', 'err'), - array(&$this->Dispatcher) + array('in', 'out', 'hr', 'createFile', 'error', 'err', 'clear', 'dispatchShell'), + array($out, $out, $in) ); $collection = new ComponentCollection(); $this->Task->Acl = new AclComponent($collection); @@ -145,9 +132,9 @@ class AclShellTest extends CakeTestCase { */ public function testCreate() { $this->Task->args = array('aro', 'root', 'User.1'); - $this->Task->expects($this->at(0))->method('out')->with("New Aro 'User.1' created.\n", true); - $this->Task->expects($this->at(1))->method('out')->with("New Aro 'User.3' created.\n", true); - $this->Task->expects($this->at(2))->method('out')->with("New Aro 'somealias' created.\n", true); + $this->Task->expects($this->at(0))->method('out')->with("<success>New Aro</success> 'User.1' created.", 2); + $this->Task->expects($this->at(1))->method('out')->with("<success>New Aro</success> 'User.3' created.", 2); + $this->Task->expects($this->at(2))->method('out')->with("<success>New Aro</success> 'somealias' created.", 2); $this->Task->create(); @@ -187,7 +174,7 @@ class AclShellTest extends CakeTestCase { public function testDelete() { $this->Task->args = array('aro', 'AuthUser.1'); $this->Task->expects($this->at(0))->method('out') - ->with("Aro deleted.\n", true); + ->with("<success>Aro deleted.</success>", 2); $this->Task->delete(); $Aro = ClassRegistry::init('Aro'); @@ -217,7 +204,7 @@ class AclShellTest extends CakeTestCase { public function testGrant() { $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', 'create'); $this->Task->expects($this->at(0))->method('out') - ->with(new PHPUnit_Framework_Constraint_PCREMatch('/Permission granted/'), true); + ->with($this->matchesRegularExpression('/granted/'), true); $this->Task->grant(); $node = $this->Task->Acl->Aro->read(null, 4); @@ -249,13 +236,13 @@ class AclShellTest extends CakeTestCase { */ public function testCheck() { $this->Task->expects($this->at(0))->method('out') - ->with(new PHPUnit_Framework_Constraint_PCREMatch('/not allowed/'), true); + ->with($this->matchesRegularExpression('/not allowed/'), true); $this->Task->expects($this->at(1))->method('out') - ->with(new PHPUnit_Framework_Constraint_PCREMatch('/Permission granted/'), true); + ->with($this->matchesRegularExpression('/granted/'), true); $this->Task->expects($this->at(2))->method('out') - ->with(new PHPUnit_Framework_Constraint_PCREMatch('/is allowed/'), true); + ->with($this->matchesRegularExpression('/is.*allowed/'), true); $this->Task->expects($this->at(3))->method('out') - ->with(new PHPUnit_Framework_Constraint_PCREMatch('/not allowed/'), true); + ->with($this->matchesRegularExpression('/not.*allowed/'), true); $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', '*'); $this->Task->check(); @@ -277,9 +264,9 @@ class AclShellTest extends CakeTestCase { */ public function testInherit() { $this->Task->expects($this->at(0))->method('out') - ->with(new PHPUnit_Framework_Constraint_PCREMatch('/Permission granted/'), true); + ->with($this->matchesRegularExpression('/Permission .*granted/'), true); $this->Task->expects($this->at(1))->method('out') - ->with(new PHPUnit_Framework_Constraint_PCREMatch('/Permission inherited/'), true); + ->with($this->matchesRegularExpression('/Permission .*inherited/'), true); $this->Task->args = array('AuthUser.2', 'ROOT/Controller1', 'create'); $this->Task->grant(); @@ -311,9 +298,9 @@ class AclShellTest extends CakeTestCase { * @return void */ function testInitDb() { - $this->Task->Dispatch->expects($this->once())->method('dispatch'); + $this->Task->expects($this->once())->method('dispatchShell') + ->with('schema create DbAcl'); + $this->Task->initdb(); - - $this->assertEqual($this->Task->Dispatch->args, array('schema', 'create', 'DbAcl')); } } diff --git a/cake/tests/cases/console/libs/api.test.php b/cake/tests/cases/console/shells/api.test.php similarity index 82% rename from cake/tests/cases/console/libs/api.test.php rename to cake/tests/cases/console/shells/api.test.php index fda9b1ba7..109cf57f7 100644 --- a/cake/tests/cases/console/libs/api.test.php +++ b/cake/tests/cases/console/shells/api.test.php @@ -18,21 +18,10 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', 'Api'); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -if (!class_exists('ApiShell')) { - require CAKE . 'console' . DS . 'libs' . DS . 'api.php'; -} /** * ApiShellTest class @@ -49,14 +38,13 @@ class ApiShellTest extends CakeTestCase { */ public function setUp() { parent::setUp(); - $this->Dispatcher = $this->getMock( - 'ShellDispatcher', - array('getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'dispatch', 'clear') - ); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Shell = $this->getMock( 'ApiShell', array('in', 'out', 'createFile', 'hr', '_stop'), - array(&$this->Dispatcher) + array( $out, $out, $in) ); } diff --git a/cake/tests/cases/console/libs/bake.test.php b/cake/tests/cases/console/shells/bake.test.php similarity index 79% rename from cake/tests/cases/console/libs/bake.test.php rename to cake/tests/cases/console/shells/bake.test.php index 10cec6576..fc5e79163 100644 --- a/cake/tests/cases/console/libs/bake.test.php +++ b/cake/tests/cases/console/shells/bake.test.php @@ -19,23 +19,13 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', 'Bake', false); +App::import('Shell', 'tasks/model', false); +App::import('Shell', 'tasks/controller', false); +App::import('Shell', 'tasks/db_config', false); + App::import('Core', 'Controller'); - -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'bake.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'model.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'controller.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'db_config.php'; +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; if (!class_exists('UsersController')) { class UsersController extends Controller { @@ -60,16 +50,14 @@ class BakeShellTest extends CakeTestCase { */ public function setUp() { parent::setUp(); - $this->Dispatcher = $this->getMock( - 'ShellDispatcher', - array('getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear') - ); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Shell = $this->getMock( 'BakeShell', array('in', 'out', 'hr', 'err', 'createFile', '_stop', '_checkUnitTest'), - array(&$this->Dispatcher) + array($out, $out, $in) ); - $this->Shell->Dispatch->shellPaths = App::path('shells'); } /** diff --git a/cake/tests/cases/console/shells/command_list.test.php b/cake/tests/cases/console/shells/command_list.test.php new file mode 100644 index 000000000..bdabc2230 --- /dev/null +++ b/cake/tests/cases/console/shells/command_list.test.php @@ -0,0 +1,156 @@ +<?php +/** + * CommandList file + * + * PHP 5 + * + * CakePHP : Rapid Development Framework (http://cakephp.org) + * Copyright 2006-2010, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2006-2010, Cake Software Foundation, Inc. + * @link http://cakephp.org CakePHP Project + * @package cake + * @subpackage cake.tests.cases.console.libs + * @since CakePHP v 2.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +App::import('Shell', 'CommandList', false); + + +class TestStringOutput extends ConsoleOutput { + public $output = ''; + + protected function _write($message) { + $this->output .= $message; + } +} + +class CommandListTest extends CakeTestCase { +/** + * setUp method + * + * @return void + */ + public function setUp() { + parent::setUp(); + App::build(array( + 'plugins' => array( + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS + ), + 'shells' => array( + CORE_PATH ? + CORE_PATH . CAKE . 'console' . DS . 'shells' . DS : + CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS . 'console' . DS . 'shells' .DS, + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'console' . DS . 'shells' . DS + ) + ), true); + App::objects('plugin', null, false); + + $out = new TestStringOutput(); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + + $this->Shell = $this->getMock( + 'CommandListShell', + array('in', '_stop', 'clear'), + array($out, $out, $in) + ); + } + +/** + * teardown + * + * @return void + */ + function tearDown() { + parent::tearDown(); + unset($this->Shell); + } + +/** + * test that main finds core shells. + * + * @return void + */ + function testMain() { + $this->Shell->main(); + $output = $this->Shell->stdout->output; + + $expected = "/example \[.*TestPlugin, TestPluginTwo.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/welcome \[.*TestPluginTwo.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/acl \[.*CORE.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/api \[.*CORE.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/bake \[.*CORE.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/console \[.*CORE.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/i18n \[.*CORE.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/schema \[.*CORE.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/testsuite \[.*CORE.*\]/"; + $this->assertPattern($expected, $output); + + $expected = "/sample \[.*app.*\]/"; + $this->assertPattern($expected, $output); + } + +/** + * Test the sort param + * + * @return void + */ + function testSortPlugin() { + $this->Shell->params['sort'] = true; + $this->Shell->main(); + + $output = $this->Shell->stdout->output; + + $expected = "/\[.*App.*\]\n[ ]+sample/"; + $this->assertPattern($expected, $output); + + $expected = "/\[.*TestPluginTwo.*\]\n[ ]+example, welcome/"; + $this->assertPattern($expected, $output); + + $expected = "/\[.*TestPlugin.*\]\n[ ]+example/"; + $this->assertPattern($expected, $output); + + $expected = "/\[.*Core.*\]\n[ ]+acl, api, bake, command_list, console, i18n, schema, testsuite/"; + $this->assertPattern($expected, $output); + } + +/** + * test xml output. + * + * @return void + */ + function testMainXml() { + $this->Shell->params['xml'] = true; + $this->Shell->main(); + + $output = $this->Shell->stdout->output; + + $find = '<shell name="sample" call_as="sample" provider="app" help="sample -h"/>'; + $this->assertContains($find, $output); + + $find = '<shell name="bake" call_as="bake" provider="CORE" help="bake -h"/>'; + $this->assertContains($find, $output); + + $find = '<shell name="welcome" call_as="test_plugin_two.welcome" provider="TestPluginTwo" help="test_plugin_two.welcome -h"/>'; + $this->assertContains($find, $output); + } +} diff --git a/cake/tests/cases/console/libs/schema.test.php b/cake/tests/cases/console/shells/schema.test.php similarity index 94% rename from cake/tests/cases/console/libs/schema.test.php rename to cake/tests/cases/console/shells/schema.test.php index 33c43260e..5fd76e63d 100644 --- a/cake/tests/cases/console/libs/schema.test.php +++ b/cake/tests/cases/console/shells/schema.test.php @@ -18,22 +18,12 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', 'Schema', false); App::import('Model', 'CakeSchema', false); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} -if (!class_exists('SchemaShell')) { - require CAKE . 'console' . DS . 'libs' . DS . 'schema.php'; -} /** * Test for Schema database management @@ -119,15 +109,15 @@ class SchemaShellTest extends CakeTestCase { * * @return void */ - public function setup() { - $this->Dispatcher = $this->getMock( - 'ShellDispatcher', - array('getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear') - ); + public function setUp() { + parent::setUp(); + + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); $this->Shell = $this->getMock( 'SchemaShell', array('in', 'out', 'hr', 'createFile', 'error', 'err', '_stop'), - array(&$this->Dispatcher) + array($out, $out, $in) ); } @@ -136,13 +126,12 @@ class SchemaShellTest extends CakeTestCase { * * @return void */ - public function teardown() { - ClassRegistry::flush(); + public function tearDown() { + parent::tearDown(); if (!empty($this->file) && $this->file instanceof File) { $this->file->delete(); unset($this->file); } - App::build(); } /** @@ -157,7 +146,7 @@ class SchemaShellTest extends CakeTestCase { $this->assertEqual(strtolower($this->Shell->Schema->name), strtolower(APP_DIR)); $this->assertEqual($this->Shell->Schema->file, 'schema.php'); - unset($this->Shell->Schema); + $this->Shell->Schema = null; $this->Shell->params = array( 'name' => 'TestSchema' ); @@ -167,7 +156,7 @@ class SchemaShellTest extends CakeTestCase { $this->assertEqual($this->Shell->Schema->connection, 'default'); $this->assertEqual($this->Shell->Schema->path, APP . 'config' . DS . 'schema'); - unset($this->Shell->Schema); + $this->Shell->Schema = null; $this->Shell->params = array( 'file' => 'other_file.php', 'connection' => 'test', @@ -264,7 +253,7 @@ class SchemaShellTest extends CakeTestCase { $this->file =& new File(TMP . 'tests' . DS . 'dump_test.sql'); $contents = $this->file->read(); - $this->assertPattern('/CREATE TABLE `acos`/', $contents); + $this->assertPattern('/CREATE TABLE `test_plugin_acos`/', $contents); $this->assertPattern('/id/', $contents); $this->assertPattern('/model/', $contents); @@ -419,6 +408,9 @@ class SchemaShellTest extends CakeTestCase { $this->assertTrue(in_array($db->config['prefix'] . 'acos', $sources), 'acos should be present.'); $this->assertFalse(in_array($db->config['prefix'] . 'aros', $sources), 'aros should not be found.'); $this->assertFalse(in_array('aros_acos', $sources), 'aros_acos should not be found.'); + + $schema = new DbAclSchema(); + $db->execute($db->dropSchema($schema, 'acos')); } /** @@ -435,7 +427,7 @@ class SchemaShellTest extends CakeTestCase { $this->Shell->params = array( 'connection' => 'test', - 'f' => true + 'force' => true ); $this->Shell->args = array('SchemaShellTest', 'articles'); $this->Shell->startup(); @@ -483,6 +475,9 @@ class SchemaShellTest extends CakeTestCase { $db =& ConnectionManager::getDataSource('test'); $sources = $db->listSources(); - $this->assertTrue(in_array($db->config['prefix'] . 'acos', $sources)); + $this->assertTrue(in_array($db->config['prefix'] . 'test_plugin_acos', $sources)); + + $schema = new TestPluginAppSchema(); + $db->execute($db->dropSchema($schema, 'test_plugin_acos')); } } diff --git a/cake/tests/cases/console/libs/tasks/controller.test.php b/cake/tests/cases/console/shells/tasks/controller.test.php similarity index 94% rename from cake/tests/cases/console/libs/tasks/controller.test.php rename to cake/tests/cases/console/shells/tasks/controller.test.php index 67384215f..ba8ef1fef 100644 --- a/cake/tests/cases/console/libs/tasks/controller.test.php +++ b/cake/tests/cases/console/shells/tasks/controller.test.php @@ -20,24 +20,15 @@ App::import('Core', 'ClassRegistry'); App::import('View', 'Helper', false); App::import('Shell', 'Shell', false); +App::import('Shell', array( + 'tasks/project', + 'tasks/controller', + 'tasks/model', + 'tasks/template', + 'tasks/test' +)); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'project.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'controller.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'model.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'template.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'test.php'; - +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; $imported = App::import('Model', 'BakeArticle'); $imported = $imported || App::import('Model', 'BakeComment'); @@ -77,27 +68,25 @@ class ControllerTaskTest extends CakeTestCase { * @return void */ public function setUp() { - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); $this->Task = $this->getMock('ControllerTask', array('in', 'out', 'err', 'hr', 'createFile', '_stop', '_checkUnitTest'), - array(&$this->Dispatcher) + array($out, $out, $in) ); - $this->Task->name = 'ControllerTask'; - $this->Task->Dispatch->shellPaths = App::path('shells'); - $this->Task->Template =& new TemplateTask($this->Task->Dispatch); + $this->Task->name = 'Controller'; + $this->Task->Template = new TemplateTask($out, $out, $in); $this->Task->Template->params['theme'] = 'default'; $this->Task->Model = $this->getMock('ModelTask', array('in', 'out', 'err', 'createFile', '_stop', '_checkUnitTest'), - array(&$this->Dispatcher) + array($out, $out, $in) ); $this->Task->Project = $this->getMock('ProjectTask', array('in', 'out', 'err', 'createFile', '_stop', '_checkUnitTest', 'getPrefix'), - array(&$this->Dispatcher) + array($out, $out, $in) ); - $this->Task->Test = $this->getMock('TestTask', array(), array(&$this->Dispatcher)); + $this->Task->Test = $this->getMock('TestTask', array(), array($out, $out, $in)); } /** @@ -106,7 +95,7 @@ class ControllerTaskTest extends CakeTestCase { * @return void */ public function teardown() { - unset($this->Task, $this->Dispatcher); + unset($this->Task); ClassRegistry::flush(); } @@ -600,7 +589,8 @@ class ControllerTaskTest extends CakeTestCase { } $this->Task->connection = 'test'; $this->Task->path = '/my/path/'; - $this->Task->args = array('BakeArticles', 'public'); + $this->Task->args = array('BakeArticles'); + $this->Task->params = array('public' => true); $filename = '/my/path/bake_articles_controller.php'; $expected = new PHPUnit_Framework_Constraint_Not(new PHPUnit_Framework_Constraint_PCREMatch('/\$scaffold/')); @@ -622,7 +612,8 @@ class ControllerTaskTest extends CakeTestCase { $this->Task->Project->expects($this->any())->method('getPrefix')->will($this->returnValue('admin_')); $this->Task->connection = 'test'; $this->Task->path = '/my/path/'; - $this->Task->args = array('BakeArticles', 'public', 'admin'); + $this->Task->args = array('BakeArticles'); + $this->Task->params = array('public' => true, 'admin' => true); $filename = '/my/path/bake_articles_controller.php'; $this->Task->expects($this->once())->method('createFile')->with( @@ -643,7 +634,8 @@ class ControllerTaskTest extends CakeTestCase { $this->Task->Project->expects($this->any())->method('getPrefix')->will($this->returnValue('admin_')); $this->Task->connection = 'test'; $this->Task->path = '/my/path/'; - $this->Task->args = array('BakeArticles', 'admin'); + $this->Task->args = array('BakeArticles'); + $this->Task->params = array('admin' => true); $filename = '/my/path/bake_articles_controller.php'; $this->Task->expects($this->once())->method('createFile')->with( diff --git a/cake/tests/cases/console/libs/tasks/db_config.test.php b/cake/tests/cases/console/shells/tasks/db_config.test.php similarity index 76% rename from cake/tests/cases/console/libs/tasks/db_config.test.php rename to cake/tests/cases/console/shells/tasks/db_config.test.php index 358d4d125..aca87ac94 100644 --- a/cake/tests/cases/console/libs/tasks/db_config.test.php +++ b/cake/tests/cases/console/shells/tasks/db_config.test.php @@ -18,19 +18,9 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', 'tasks/DbConfig'); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'db_config.php'; +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; class TEST_DATABASE_CONFIG { @@ -70,16 +60,15 @@ class DbConfigTaskTest extends CakeTestCase { */ public function setUp() { parent::setUp(); - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock('DbConfigTask', array('in', 'out', 'err', 'hr', 'createFile', '_stop', '_checkUnitTest', '_verify'), - array(&$this->Dispatcher) + array($out, $out, $in) ); - $this->Task->Dispatch->shellPaths = App::path('shells'); - $this->Task->params['working'] = rtrim(APP, DS); + $this->Task->path = APP . 'config' . DS; $this->Task->databaseClassName = 'TEST_DATABASE_CONFIG'; } @@ -90,7 +79,7 @@ class DbConfigTaskTest extends CakeTestCase { */ public function tearDown() { parent::tearDown(); - unset($this->Task, $this->Dispatcher); + unset($this->Task); } /** @@ -101,7 +90,7 @@ class DbConfigTaskTest extends CakeTestCase { public function testGetConfig() { $this->Task->expects($this->at(0))->method('in')->will($this->returnValue('otherOne')); $result = $this->Task->getConfig(); - $this->assertEqual($result, 'otherOne'); + $this->assertEquals('otherOne', $result); } /** @@ -110,11 +99,9 @@ class DbConfigTaskTest extends CakeTestCase { * @return void */ public function testInitialize() { - $this->assertTrue(empty($this->Task->path)); $this->Task->initialize(); $this->assertFalse(empty($this->Task->path)); - $this->assertEqual($this->Task->path, APP . 'config' . DS); - + $this->assertEquals(APP . 'config' . DS, $this->Task->path); } /** @@ -124,7 +111,13 @@ class DbConfigTaskTest extends CakeTestCase { */ public function testExecuteIntoInteractive() { $this->Task->initialize(); - $this->Task = $this->getMock('DbConfigTask', array('in', '_stop', 'createFile'), array(&$this->Dispatcher)); + + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock( + 'DbConfigTask', + array('in', '_stop', 'createFile', 'bake'), array($out, $out, $in) + ); $this->Task->expects($this->once())->method('_stop'); $this->Task->expects($this->at(0))->method('in')->will($this->returnValue('default')); //name @@ -139,6 +132,22 @@ class DbConfigTaskTest extends CakeTestCase { $this->Task->expects($this->at(12))->method('in')->will($this->returnValue('n')); //encoding $this->Task->expects($this->at(13))->method('in')->will($this->returnValue('y')); //looks good $this->Task->expects($this->at(14))->method('in')->will($this->returnValue('n')); //another + $this->Task->expects($this->at(15))->method('bake') + ->with(array( + array( + 'name' => 'default', + 'driver' => 'mysql', + 'persistent' => 'false', + 'host' => 'localhost', + 'login' => 'root', + 'password' => 'password', + 'database' => 'cake_test', + 'prefix' => null, + 'encoding' => null, + 'port' => '', + 'schema' => null + ) + )); $result = $this->Task->execute(); } diff --git a/cake/tests/cases/console/libs/tasks/extract.test.php b/cake/tests/cases/console/shells/tasks/extract.test.php similarity index 71% rename from cake/tests/cases/console/libs/tasks/extract.test.php rename to cake/tests/cases/console/shells/tasks/extract.test.php index 44898b72b..21993e0d4 100644 --- a/cake/tests/cases/console/libs/tasks/extract.test.php +++ b/cake/tests/cases/console/shells/tasks/extract.test.php @@ -21,19 +21,9 @@ */ App::import('Core', 'Folder'); App::import('Shell', 'Shell', false); +App::import('Shell', 'tasks/Extract', false); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'extract.php'; +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * ExtractTaskTest class @@ -49,10 +39,16 @@ class ExtractTaskTest extends CakeTestCase { * @return void */ public function setUp() { - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); - $this->Task =& new ExtractTask($this->Dispatcher); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + + $this->Task = $this->getMock( + 'ExtractTask', + array('in', 'out', 'err', '_stop'), + array($out, $out, $in) + ); + $this->path = TMP . 'tests' . DS . 'extract_task_test'; + $Folder = new Folder($this->path . DS . 'locale', true); } /** @@ -61,7 +57,11 @@ class ExtractTaskTest extends CakeTestCase { * @return void */ public function tearDown() { - ClassRegistry::flush(); + parent::tearDown(); + unset($this->Task); + + $Folder = new Folder($this->path); + $Folder->delete(); } /** @@ -70,19 +70,18 @@ class ExtractTaskTest extends CakeTestCase { * @return void */ public function testExecute() { - $path = TMP . 'tests' . DS . 'extract_task_test'; - new Folder($path . DS . 'locale', true); - $this->Task->interactive = false; $this->Task->params['paths'] = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'pages'; - $this->Task->params['output'] = $path . DS; - $this->Dispatcher->expects($this->never())->method('stderr'); - $this->Dispatcher->expects($this->never())->method('_stop'); + $this->Task->params['output'] = $this->path . DS; + $this->Task->expects($this->never())->method('err'); + $this->Task->expects($this->any())->method('in') + ->will($this->returnValue('y')); + $this->Task->expects($this->never())->method('_stop'); $this->Task->execute(); - $this->assertTrue(file_exists($path . DS . 'default.pot')); - $result = file_get_contents($path . DS . 'default.pot'); + $this->assertTrue(file_exists($this->path . DS . 'default.pot')); + $result = file_get_contents($this->path . DS . 'default.pot'); $pattern = '/"Content-Type\: text\/plain; charset\=utf-8/'; $this->assertPattern($pattern, $result); @@ -134,7 +133,7 @@ class ExtractTaskTest extends CakeTestCase { $this->assertPattern($pattern, $result); // extract.ctp - reading the domain.pot - $result = file_get_contents($path . DS . 'domain.pot'); + $result = file_get_contents($this->path . DS . 'domain.pot'); $pattern = '/msgid "You have %d new message."\nmsgid_plural "You have %d new messages."/'; $this->assertNoPattern($pattern, $result); @@ -145,9 +144,32 @@ class ExtractTaskTest extends CakeTestCase { $this->assertPattern($pattern, $result); $pattern = '/msgid "You deleted %d message \(domain\)."\nmsgid_plural "You deleted %d messages \(domain\)."/'; $this->assertPattern($pattern, $result); + } - $Folder = new Folder($path); - $Folder->delete(); +/** + * test exclusions + * + * @return void + */ + function testExtractWithExclude() { + $this->Task->interactive = false; + + $this->Task->params['paths'] = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'; + $this->Task->params['output'] = $this->path . DS; + $this->Task->params['exclude'] = 'pages,layouts'; + + $this->Task->expects($this->any())->method('in') + ->will($this->returnValue('y')); + + $this->Task->execute(); + $this->assertTrue(file_exists($this->path . DS . 'default.pot')); + $result = file_get_contents($this->path . DS . 'default.pot'); + + $pattern = '/\#: .*extract\.ctp:6\n/'; + $this->assertNotRegExp($pattern, $result); + + $pattern = '/\#: .*default\.ctp:26\n/'; + $this->assertNotRegExp($pattern, $result); } /** @@ -156,21 +178,18 @@ class ExtractTaskTest extends CakeTestCase { * @return void */ function testExtractMultiplePaths() { - $path = TMP . 'tests' . DS . 'extract_task_test'; - new Folder($path . DS . 'locale', true); - $this->Task->interactive = false; $this->Task->params['paths'] = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'pages,' . TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'posts'; - $this->Task->params['output'] = $path . DS; - $this->Task->Dispatch->expects($this->never())->method('stderr'); - $this->Task->Dispatch->expects($this->never())->method('_stop'); + $this->Task->params['output'] = $this->path . DS; + $this->Task->expects($this->never())->method('err'); + $this->Task->expects($this->never())->method('_stop'); $this->Task->execute(); - $result = file_get_contents($path . DS . 'default.pot'); + $result = file_get_contents($this->path . DS . 'default.pot'); $pattern = '/msgid "Add User"/'; $this->assertPattern($pattern, $result); diff --git a/cake/tests/cases/console/libs/tasks/fixture.test.php b/cake/tests/cases/console/shells/tasks/fixture.test.php similarity index 90% rename from cake/tests/cases/console/libs/tasks/fixture.test.php rename to cake/tests/cases/console/shells/tasks/fixture.test.php index e31401e35..7121f7fd1 100644 --- a/cake/tests/cases/console/libs/tasks/fixture.test.php +++ b/cake/tests/cases/console/shells/tasks/fixture.test.php @@ -18,20 +18,13 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', array( + 'tasks/fixture', + 'tasks/template', + 'tasks/db_config' +)); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'template.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'fixture.php'; +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * FixtureTaskTest class @@ -56,19 +49,19 @@ class FixtureTaskTest extends CakeTestCase { */ public function setUp() { parent::setUp(); - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock('FixtureTask', - array('in', 'err', 'createFile', '_stop'), - array(&$this->Dispatcher) + array('in', 'err', 'createFile', '_stop', 'clear'), + array($out, $out, $in) ); $this->Task->Model = $this->getMock('Shell', - array('in', 'out', 'erro', 'createFile', 'getName', 'getTable', 'listAll'), - array(&$this->Dispatcher) + array('in', 'out', 'error', 'createFile', 'getName', 'getTable', 'listAll'), + array($out, $out, $in) ); - $this->Task->Template =& new TemplateTask($this->Dispatcher); - $this->Task->Dispatch->shellPaths = App::path('shells'); + $this->Task->Template = new TemplateTask($out, $out, $in); + $this->Task->DbConfig = $this->getMock('DbConfigTask', array(), array($out, $out, $in)); $this->Task->Template->initialize(); } @@ -79,7 +72,7 @@ class FixtureTaskTest extends CakeTestCase { */ public function tearDown() { parent::tearDown(); - unset($this->Task, $this->Dispatcher); + unset($this->Task); } /** @@ -88,11 +81,11 @@ class FixtureTaskTest extends CakeTestCase { * @return void */ public function testConstruct() { - $this->Dispatcher->params['working'] = DS . 'my' . DS . 'path'; - $Task = new FixtureTask($this->Dispatcher); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); - $expected = DS . 'my' . DS . 'path' . DS . 'tests' . DS . 'fixtures' . DS; - $this->assertEqual($Task->path, $expected); + $Task = new FixtureTask($out, $out, $in); + $this->assertEqual($Task->path, APP . 'tests' . DS . 'fixtures' . DS); } /** @@ -143,7 +136,7 @@ class FixtureTaskTest extends CakeTestCase { * * @return void */ - public function testImportRecordsFromDatabaseWithConditions() { + public function testImportRecordsFromDatabaseWithConditionsPoo() { $this->Task->interactive = true; $this->Task->expects($this->at(0))->method('in') ->will($this->returnValue('WHERE 1=1 LIMIT 10')); diff --git a/cake/tests/cases/console/libs/tasks/model.test.php b/cake/tests/cases/console/shells/tasks/model.test.php similarity index 96% rename from cake/tests/cases/console/libs/tasks/model.test.php rename to cake/tests/cases/console/shells/tasks/model.test.php index 105acb6e2..b2072fdeb 100644 --- a/cake/tests/cases/console/libs/tasks/model.test.php +++ b/cake/tests/cases/console/shells/tasks/model.test.php @@ -20,21 +20,13 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', array( + 'tasks/model', + 'tasks/fixture', + 'tasks/template' +)); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'model.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'fixture.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'template.php'; +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * ModelTaskTest class @@ -59,12 +51,12 @@ class ModelTaskTest extends CakeTestCase { */ public function setUp() { parent::setUp(); - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock('ModelTask', array('in', 'err', 'createFile', '_stop', '_checkUnitTest'), - array(&$this->Dispatcher) + array($out, $out, $in) ); $this->_setupOtherMocks(); } @@ -75,9 +67,12 @@ class ModelTaskTest extends CakeTestCase { * @return void */ protected function _useMockedOut() { + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock('ModelTask', array('in', 'out', 'err', 'hr', 'createFile', '_stop', '_checkUnitTest'), - array(&$this->Dispatcher) + array($out, $out, $in) ); $this->_setupOtherMocks(); } @@ -88,13 +83,15 @@ class ModelTaskTest extends CakeTestCase { * @return void */ protected function _setupOtherMocks() { - $this->Task->Fixture = $this->getMock('FixtureTask', array(), array(&$this->Dispatcher)); - $this->Task->Test = $this->getMock('FixtureTask', array(), array(&$this->Dispatcher)); - $this->Task->Template =& new TemplateTask($this->Task->Dispatch); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); - $this->Task->name = 'ModelTask'; + $this->Task->Fixture = $this->getMock('FixtureTask', array(), array($out, $out, $in)); + $this->Task->Test = $this->getMock('FixtureTask', array(), array($out, $out, $in)); + $this->Task->Template = new TemplateTask($out, $out, $in); + + $this->Task->name = 'Model'; $this->Task->interactive = true; - $this->Task->Dispatch->shellPaths = App::path('shells'); } /** @@ -104,7 +101,7 @@ class ModelTaskTest extends CakeTestCase { */ public function tearDown() { parent::tearDown(); - unset($this->Task, $this->Dispatcher); + unset($this->Task); } /** diff --git a/cake/tests/cases/console/shells/tasks/plugin.test.php b/cake/tests/cases/console/shells/tasks/plugin.test.php new file mode 100644 index 000000000..327b258c2 --- /dev/null +++ b/cake/tests/cases/console/shells/tasks/plugin.test.php @@ -0,0 +1,166 @@ +<?php +/** + * PluginTask Test file + * + * Test Case for plugin generation shell task + * + * PHP 5 + * + * CakePHP : Rapid Development Framework (http://cakephp.org) + * Copyright 2006-2009, Cake Software Foundation, Inc. + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2006-2009, Cake Software Foundation, Inc. + * @link http://cakephp.org CakePHP Project + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + * @since CakePHP v 1.3.0 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +App::import('Shell', 'Shell', false); +App::import('Shell', array( + 'tasks/plugin', + 'tasks/model' +)); + +App::import('Core', array('File')); + +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; + +/** + * PluginTaskPlugin class + * + * @package cake + * @subpackage cake.tests.cases.console.libs.tasks + */ +class PluginTaskTest extends CakeTestCase { + +/** + * setup method + * + * @return void + */ + public function setUp() { + parent::setUp(); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + + $this->Task = $this->getMock('PluginTask', + array('in', 'err', 'createFile', '_stop', 'clear'), + array($out, $out, $in) + ); + $this->Task->path = TMP . 'tests' . DS; + + $this->_paths = $paths = App::path('plugins'); + $this->_testPath = array_push($paths, TMP . 'tests' . DS); + App::build(array('plugins' => $paths)); + } + +/** + * test bake() + * + * @return void + */ + public function testBakeFoldersAndFiles() { + $this->Task->expects($this->at(0))->method('in')->will($this->returnValue($this->_testPath)); + $this->Task->expects($this->at(1))->method('in')->will($this->returnValue('y')); + + $path = $this->Task->path . 'bake_test_plugin'; + + $file = $path . DS . 'bake_test_plugin_app_controller.php'; + $this->Task->expects($this->at(2))->method('createFile') + ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); + + $file = $path . DS . 'bake_test_plugin_app_model.php'; + $this->Task->expects($this->at(3))->method('createFile') + ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); + + $this->Task->bake('BakeTestPlugin'); + + $path = $this->Task->path . 'bake_test_plugin'; + $this->assertTrue(is_dir($path), 'No plugin dir %s'); + + $directories = array( + 'config' . DS . 'schema', + 'models' . DS . 'behaviors', + 'models' . DS . 'datasources', + 'console' . DS . 'shells' . DS . 'tasks', + 'controllers' . DS . 'components', + 'libs', + 'views' . DS . 'helpers', + 'tests' . DS . 'cases' . DS . 'components', + 'tests' . DS . 'cases' . DS . 'helpers', + 'tests' . DS . 'cases' . DS . 'behaviors', + 'tests' . DS . 'cases' . DS . 'controllers', + 'tests' . DS . 'cases' . DS . 'models', + 'tests' . DS . 'groups', + 'tests' . DS . 'fixtures', + 'vendors', + 'webroot' + ); + foreach ($directories as $dir) { + $this->assertTrue(is_dir($path . DS . $dir), 'Missing directory for ' . $dir); + } + + $Folder = new Folder($this->Task->path . 'bake_test_plugin'); + $Folder->delete(); + } + +/** + * test execute with no args, flowing into interactive, + * + * @return void + */ + public function testExecuteWithNoArgs() { + $this->Task->expects($this->at(0))->method('in')->will($this->returnValue('TestPlugin')); + $this->Task->expects($this->at(1))->method('in')->will($this->returnValue('3')); + $this->Task->expects($this->at(2))->method('in')->will($this->returnValue('y')); + + $path = $this->Task->path . 'test_plugin'; + $file = $path . DS . 'test_plugin_app_controller.php'; + $this->Task->expects($this->at(3))->method('createFile') + ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); + + $file = $path . DS . 'test_plugin_app_model.php'; + $this->Task->expects($this->at(4))->method('createFile') + ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); + + $this->Task->args = array(); + $this->Task->execute(); + + $Folder = new Folder($path); + $Folder->delete(); + } + +/** + * Test Execute + * + * @return void + */ + public function testExecuteWithOneArg() { + $this->Task->expects($this->at(0))->method('in') + ->will($this->returnValue($this->_testPath)); + $this->Task->expects($this->at(1))->method('in') + ->will($this->returnValue('y')); + + $path = $this->Task->path . 'bake_test_plugin'; + $file = $path . DS . 'bake_test_plugin_app_controller.php'; + $this->Task->expects($this->at(2))->method('createFile') + ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); + + $path = $this->Task->path . 'bake_test_plugin'; + $file = $path . DS . 'bake_test_plugin_app_model.php'; + $this->Task->expects($this->at(3))->method('createFile') + ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); + + $this->Task->args = array('BakeTestPlugin'); + + $this->Task->execute(); + + $Folder = new Folder($this->Task->path . 'bake_test_plugin'); + $Folder->delete(); + } + +} diff --git a/cake/tests/cases/console/libs/tasks/project.test.php b/cake/tests/cases/console/shells/tasks/project.test.php similarity index 57% rename from cake/tests/cases/console/libs/tasks/project.test.php rename to cake/tests/cases/console/shells/tasks/project.test.php index 104102b23..e9044d992 100644 --- a/cake/tests/cases/console/libs/tasks/project.test.php +++ b/cake/tests/cases/console/shells/tasks/project.test.php @@ -20,21 +20,11 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', 'tasks/project'); + App::import('Core', 'File'); - -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'project.php'; +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * ProjectTask Test class @@ -51,14 +41,13 @@ class ProjectTaskTest extends CakeTestCase { */ public function setUp() { parent::setUp(); - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock('ProjectTask', array('in', 'err', 'createFile', '_stop'), - array(&$this->Dispatcher) + array($out, $out, $in) ); - $this->Dispatcher->shellPaths = App::path('shells'); $this->Task->path = TMP . 'tests' . DS; } @@ -72,7 +61,7 @@ class ProjectTaskTest extends CakeTestCase { $Folder = new Folder($this->Task->path . 'bake_test_app'); $Folder->delete(); - unset($this->Dispatcher, $this->Task); + unset($this->Task); } /** @@ -83,7 +72,6 @@ class ProjectTaskTest extends CakeTestCase { protected function _setupTestProject() { $skel = CAKE_CORE_INCLUDE_PATH . DS . CAKE . 'console' . DS . 'templates' . DS . 'skel'; $this->Task->expects($this->at(0))->method('in')->will($this->returnValue('y')); - $this->Task->expects($this->at(1))->method('in')->will($this->returnValue('n')); $this->Task->bake($this->Task->path . 'bake_test_app', $skel); } @@ -94,18 +82,32 @@ class ProjectTaskTest extends CakeTestCase { */ public function testBake() { $this->_setupTestProject(); - $path = $this->Task->path . 'bake_test_app'; + $this->assertTrue(is_dir($path), 'No project dir %s'); - $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir %s'); - $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir %s'); - $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir %s'); - $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir %s'); - $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'groups'), 'No groups dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir %s'); + $dirs = array( + 'config', + 'config' . DS . 'schema', + 'console', + 'console' . DS . 'shells', + 'console' . DS . 'shells' . DS . 'tasks', + 'controllers', + 'models', + 'views', + 'views' . DS . 'helpers', + 'tests', + 'tests' . DS . 'cases', + 'tests' . DS . 'cases' . DS . 'models', + 'tests' . DS . 'cases', + 'tests' . DS . 'fixtures', + 'tmp', + 'webroot', + 'webroot' . DS . 'js', + 'webroot' . DS . 'css', + ); + foreach ($dirs as $dir) { + $this->assertTrue(is_dir($path . DS . $dir), 'Missing ' . $dir); + } } /** @@ -117,36 +119,24 @@ class ProjectTaskTest extends CakeTestCase { $this->Task->params['empty'] = true; $this->_setupTestProject(); $path = $this->Task->path . 'bake_test_app'; - $this->assertTrue(is_dir($path), 'No project dir %s'); - $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir %s'); - $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir %s'); - $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir %s'); - $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir %s'); - $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'groups'), 'No groups dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir %s'); - - $this->assertTrue(is_file($path . DS . 'controllers' . DS .'components' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'locale' . DS . 'eng' . DS . 'LC_MESSAGES' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'models' . DS . 'behaviors' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'models' . DS . 'datasources' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'plugins' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'behaviors' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'components' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'controllers' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'datasources' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'helpers' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'models' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'cases' . DS . 'shells' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'fixtures' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'tests' . DS . 'groups' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'vendors' . DS . 'shells' . DS . 'tasks' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'views' . DS . 'errors' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'views' . DS . 'helpers' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'views' . DS . 'scaffolds' . DS . 'empty'), 'No empty file in dir %s'); - $this->assertTrue(is_file($path . DS . 'webroot' . DS . 'js' . DS . 'empty'), 'No empty file in dir %s'); + + $empty = array( + 'console' . DS . 'shells' . DS . 'tasks', + 'controllers' . DS . 'components', + 'models' . DS . 'behaviors', + 'views' . DS . 'helpers', + 'views' . DS . 'errors', + 'views' . DS . 'scaffolds', + 'tests' . DS . 'cases' . DS . 'models', + 'tests' . DS . 'cases' . DS . 'controllers', + 'tests' . DS . 'cases' . DS . 'helpers', + 'tests' . DS . 'fixtures', + 'webroot' . DS . 'js' + ); + + foreach ($empty as $dir) { + $this->assertTrue(is_file($path . DS . $dir . DS . 'empty'), 'Missing empty file in ' . $dir); + } } /** @@ -275,14 +265,30 @@ class ProjectTaskTest extends CakeTestCase { $this->Task->execute(); $this->assertTrue(is_dir($path), 'No project dir %s'); - $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir %s'); - $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir %s'); - $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir %s'); - $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir %s'); - $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'groups'), 'No groups dir %s'); - $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir %s'); + $this->assertTrue(is_dir($path . DS . 'controllers'), 'No controllers dir '); + $this->assertTrue(is_dir($path . DS . 'controllers' . DS .'components'), 'No components dir '); + $this->assertTrue(is_dir($path . DS . 'models'), 'No models dir'); + $this->assertTrue(is_dir($path . DS . 'views'), 'No views dir'); + $this->assertTrue(is_dir($path . DS . 'views' . DS . 'helpers'), 'No helpers dir'); + $this->assertTrue(is_dir($path . DS . 'tests'), 'No tests dir'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'cases'), 'No cases dir'); + $this->assertTrue(is_dir($path . DS . 'tests' . DS . 'fixtures'), 'No fixtures dir'); + } + +/** + * test console path + * + * @return void + */ + function testConsolePath() { + $this->_setupTestProject(); + + $path = $this->Task->path . 'bake_test_app' . DS; + $result = $this->Task->consolePath($path); + $this->assertTrue($result); + + $file = new File($path . 'console' . DS . 'cake.php'); + $contents = $file->read(); + $this->assertNoPattern('/__CAKE_PATH__/', $contents, 'Console path placeholder left behind.'); } } diff --git a/cake/tests/cases/console/libs/tasks/template.test.php b/cake/tests/cases/console/shells/tasks/template.test.php similarity index 83% rename from cake/tests/cases/console/libs/tasks/template.test.php rename to cake/tests/cases/console/shells/tasks/template.test.php index ec35cf986..9d970a092 100644 --- a/cake/tests/cases/console/libs/tasks/template.test.php +++ b/cake/tests/cases/console/shells/tasks/template.test.php @@ -21,19 +21,9 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', 'tasks/template'); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'template.php'; +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * TemplateTaskTest class @@ -49,14 +39,14 @@ class TemplateTaskTest extends CakeTestCase { * @return void */ public function setup() { - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); + parent::setUp(); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock('TemplateTask', - array('in', 'err', 'createFile', '_stop'), - array(&$this->Dispatcher) + array('in', 'err', 'createFile', '_stop', 'clear'), + array($out, $out, $in) ); - $this->Task->Dispatch->shellPaths = App::path('shells'); } /** @@ -64,10 +54,9 @@ class TemplateTaskTest extends CakeTestCase { * * @return void */ - public function teardown() { - unset($this->Task, $this->Dispatcher); - ClassRegistry::flush(); - App::build(); + public function tearDown() { + parent::tearDown(); + unset($this->Task); } /** @@ -100,7 +89,6 @@ class TemplateTaskTest extends CakeTestCase { */ public function testFindingInstalledThemesForBake() { $consoleLibs = CAKE_CORE_INCLUDE_PATH . DS . CAKE . 'console' . DS; - $this->Task->Dispatch->shellPaths = array($consoleLibs); $this->Task->initialize(); $this->assertEqual($this->Task->templatePaths, array('default' => $consoleLibs . 'templates' . DS . 'default' . DS)); } @@ -128,7 +116,7 @@ class TemplateTaskTest extends CakeTestCase { $this->Task->params = array(); $result = $this->Task->getThemePath(); $this->assertEqual($result, $defaultTheme); - $this->assertEqual($this->Dispatcher->params['theme'], 'default'); + $this->assertEqual($this->Task->params['theme'], 'default'); } /** @@ -139,7 +127,7 @@ class TemplateTaskTest extends CakeTestCase { public function testGenerate() { App::build(array( 'shells' => array( - TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'console' . DS ) )); $this->Task->initialize(); @@ -159,7 +147,7 @@ class TemplateTaskTest extends CakeTestCase { public function testGenerateWithTemplateFallbacks() { App::build(array( 'shells' => array( - TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'vendors' . DS . 'shells' . DS, + TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'console' . DS, CAKE_CORE_INCLUDE_PATH . DS . 'console' . DS ) )); diff --git a/cake/tests/cases/console/libs/tasks/test.test.php b/cake/tests/cases/console/shells/tasks/test.test.php similarity index 94% rename from cake/tests/cases/console/libs/tasks/test.test.php rename to cake/tests/cases/console/shells/tasks/test.test.php index 6d6942ced..d8fa87ead 100644 --- a/cake/tests/cases/console/libs/tasks/test.test.php +++ b/cake/tests/cases/console/shells/tasks/test.test.php @@ -20,22 +20,15 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', array( + 'tasks/test', + 'tasks/template' +)); + App::import('Controller', 'Controller', false); App::import('Model', 'Model', false); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'test.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'template.php'; +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * Test Article model @@ -249,16 +242,15 @@ class TestTaskTest extends CakeTestCase { */ public function setup() { parent::setup(); - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock('TestTask', array('in', 'err', 'createFile', '_stop', 'isLoadableClass'), - array(&$this->Dispatcher) + array($out, $out, $in) ); - $this->Dispatcher->shellPaths = App::path('shells'); - $this->Task->name = 'TestTask'; - $this->Task->Template = new TemplateTask($this->Dispatcher); + $this->Task->name = 'Test'; + $this->Task->Template = new TemplateTask($out, $out, $in); } /** @@ -266,9 +258,9 @@ class TestTaskTest extends CakeTestCase { * * @return void */ - public function teardown() { - parent::teardown(); - ClassRegistry::flush(); + public function tearDown() { + parent::tearDown(); + unset($this->Task); } /** @@ -277,8 +269,8 @@ class TestTaskTest extends CakeTestCase { * @return void */ public function testFilePathGenerationModelRepeated() { - $this->Dispatcher->expects($this->never())->method('stderr'); - $this->Dispatcher->expects($this->never())->method('_stop'); + $this->Task->expects($this->never())->method('err'); + $this->Task->expects($this->never())->method('_stop'); $file = TESTS . 'cases' . DS . 'models' . DS . 'my_class.test.php'; diff --git a/cake/tests/cases/console/libs/tasks/view.test.php b/cake/tests/cases/console/shells/tasks/view.test.php similarity index 94% rename from cake/tests/cases/console/libs/tasks/view.test.php rename to cake/tests/cases/console/shells/tasks/view.test.php index 39c1e6e65..5a2dfcce5 100644 --- a/cake/tests/cases/console/libs/tasks/view.test.php +++ b/cake/tests/cases/console/shells/tasks/view.test.php @@ -20,23 +20,15 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ App::import('Shell', 'Shell', false); +App::import('Shell', array( + 'tasks/view', + 'tasks/controller', + 'tasks/template', + 'tasks/project', + 'tasks/db_config' +)); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} - -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'view.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'controller.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'template.php'; -require_once CAKE . 'console' . DS . 'libs' . DS . 'tasks' . DS . 'project.php'; - +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; /** * Test View Task Comment Model @@ -232,18 +224,18 @@ class ViewTaskTest extends CakeTestCase { */ public function setUp() { parent::setUp(); - $this->Dispatcher = $this->getMock('ShellDispatcher', array( - 'getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear' - )); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Task = $this->getMock('ViewTask', array('in', 'err', 'createFile', '_stop'), - array(&$this->Dispatcher) + array($out, $out, $in) ); - $this->Task->Template = new TemplateTask($this->Dispatcher); - $this->Task->Controller = $this->getMock('ControllerTask', array(), array(&$this->Dispatcher)); - $this->Task->Project = $this->getMock('ProjectTask', array(), array(&$this->Dispatcher)); + $this->Task->Template = new TemplateTask($out, $out, $in); + $this->Task->Controller = $this->getMock('ControllerTask', array(), array($out, $out, $in)); + $this->Task->Project = $this->getMock('ProjectTask', array(), array($out, $out, $in)); + $this->Task->DbConfig = $this->getMock('DbConfigTask', array(), array($out, $out, $in)); - $this->Dispatcher->shellPaths = App::path('shells'); $this->Task->path = TMP; $this->Task->Template->params['theme'] = 'default'; } @@ -401,7 +393,7 @@ class ViewTaskTest extends CakeTestCase { $this->Task->controllerName = 'ViewTaskComments'; $this->Task->controllerPath = 'view_task_comments'; $this->Task->plugin = 'TestTest'; - $this->Task->name = 'ViewTask'; + $this->Task->name = 'View'; $path = APP . 'plugins' . DS . 'test_test' . DS . 'views' . DS . 'view_task_comments' . DS . 'view.ctp'; $this->Task->expects($this->once())->method('createFile') diff --git a/cake/tests/cases/console/libs/testsuite.test.php b/cake/tests/cases/console/shells/testsuite.test.php similarity index 79% rename from cake/tests/cases/console/libs/testsuite.test.php rename to cake/tests/cases/console/shells/testsuite.test.php index b790e72a6..b132aa557 100644 --- a/cake/tests/cases/console/libs/testsuite.test.php +++ b/cake/tests/cases/console/shells/testsuite.test.php @@ -19,19 +19,10 @@ */ App::import('Shell', 'Shell', false); +App::import('Shell', 'Testsuite'); -if (!defined('DISABLE_AUTO_DISPATCH')) { - define('DISABLE_AUTO_DISPATCH', true); -} +require_once CAKE . 'console' . DS . 'shell_dispatcher.php'; -if (!class_exists('ShellDispatcher')) { - ob_start(); - $argv = false; - require CAKE . 'console' . DS . 'cake.php'; - ob_end_clean(); -} - -require_once CAKE . 'console' . DS . 'libs' . DS . 'testsuite.php'; class TestSuiteShellTest extends CakeTestCase { @@ -42,16 +33,14 @@ class TestSuiteShellTest extends CakeTestCase { * @return void */ public function setUp() { - $this->Dispatcher = $this->getMock( - 'ShellDispatcher', - array('getInput', 'stdout', 'stderr', '_stop', '_initEnvironment', 'clear') - ); + $out = $this->getMock('ConsoleOutput', array(), array(), '', false); + $in = $this->getMock('ConsoleInput', array(), array(), '', false); + $this->Shell = $this->getMock( 'TestSuiteShell', - array('in', 'out', 'hr', 'help', 'error', 'err', '_stop', 'initialize', 'run'), - array(&$this->Dispatcher) + array('in', 'out', 'hr', 'help', 'error', 'err', '_stop', 'initialize', 'run', 'clear'), + array($out, $out, $in) ); - $this->Shell->Dispatch->shellPaths = App::path('shells'); } /** @@ -107,12 +96,12 @@ class TestSuiteShellTest extends CakeTestCase { public function testRunnerOptions() { $this->Shell->startup(); $this->Shell->args = array('core', 'Basics'); - $this->Shell->params = array('filter' => 'myFilter', '-colors' => null, '-verbose' => null); + $this->Shell->params = array('filter' => 'myFilter', 'colors' => true, 'verbose' => true); $this->Shell->expects($this->once())->method('run') ->with( array('app' => false, 'plugin' => null, 'output' => 'text', 'case' => 'basics'), - array('--colors', '--verbose', '--filter', 'myFilter') + array('--filter', 'myFilter', '--colors', '--verbose') ); $this->Shell->main(); } diff --git a/cake/tests/cases/libs/all_tests.test.php b/cake/tests/cases/libs/all_tests.test.php index 6982a1d3c..c3a580103 100644 --- a/cake/tests/cases/libs/all_tests.test.php +++ b/cake/tests/cases/libs/all_tests.test.php @@ -38,6 +38,7 @@ class AllTests extends PHPUnit_Framework_TestSuite { $path = CORE_TEST_CASES . DS . 'libs' . DS; $console = CORE_TEST_CASES . DS . 'console' . DS; + $suite->addTestFile($console . 'all_console_libs.test.php'); $suite->addTestFile($console . 'all_shells.test.php'); $suite->addTestFile($console . 'all_tasks.test.php'); diff --git a/cake/tests/cases/libs/model/cake_schema.test.php b/cake/tests/cases/libs/model/cake_schema.test.php index b2e056cf1..5750054f5 100644 --- a/cake/tests/cases/libs/model/cake_schema.test.php +++ b/cake/tests/cases/libs/model/cake_schema.test.php @@ -969,7 +969,7 @@ class CakeSchemaTest extends CakeTestCase { )); $Other =& $this->Schema->load(array('name' => 'TestPluginApp', 'plugin' => 'TestPlugin')); $this->assertEqual($Other->name, 'TestPluginApp'); - $this->assertEqual(array_keys($Other->tables), array('acos')); + $this->assertEqual(array_keys($Other->tables), array('test_plugin_acos')); App::build(); } diff --git a/cake/tests/cases/libs/string.test.php b/cake/tests/cases/libs/string.test.php index ef062956f..1e5e84d54 100644 --- a/cake/tests/cases/libs/string.test.php +++ b/cake/tests/cases/libs/string.test.php @@ -307,4 +307,46 @@ class StringTest extends CakeTestCase { $result = String::insert($string, array('b' => 2, 'c' => 3), array('clean' => true)); $this->assertEqual($expected, $result); } + +/** + * test wrap method. + * + * @return void + */ + function testWrap() { + $text = 'This is the song that never ends. This is the song that never ends. This is the song that never ends.'; + $result = String::wrap($text, 33); + $expected = <<<TEXT +This is the song that never ends. +This is the song that never ends. +This is the song that never ends. +TEXT; + $this->assertEquals($expected, $result, 'Text not wrapped.'); + + $result = String::wrap($text, array('width' => 20, 'wordWrap' => false)); + $expected = <<<TEXT +This is the song th +at never ends. This + is the song that n +ever ends. This is +the song that never + ends. +TEXT; + $this->assertEquals($expected, $result, 'Text not wrapped.'); + } + +/** + * test wrap() indenting + * + * @return void + */ + function testWrapIndent() { + $text = 'This is the song that never ends. This is the song that never ends. This is the song that never ends.'; + $result = String::wrap($text, array('width' => 33, 'indent' => "\t", 'indentAt' => 1)); + $expected = <<<TEXT +This is the song that never ends. + This is the song that never ends. + This is the song that never ends. +TEXT; + } } diff --git a/cake/tests/test_app/vendors/shells/sample.php b/cake/tests/test_app/console/shells/sample.php similarity index 100% rename from cake/tests/test_app/vendors/shells/sample.php rename to cake/tests/test_app/console/shells/sample.php diff --git a/cake/tests/test_app/plugins/test_plugin_two/vendors/shells/templates/empty b/cake/tests/test_app/console/shells/tasks/empty similarity index 100% rename from cake/tests/test_app/plugins/test_plugin_two/vendors/shells/templates/empty rename to cake/tests/test_app/console/shells/tasks/empty diff --git a/cake/tests/test_app/vendors/shells/templates/test/classes/test_object.ctp b/cake/tests/test_app/console/templates/test/classes/test_object.ctp similarity index 100% rename from cake/tests/test_app/vendors/shells/templates/test/classes/test_object.ctp rename to cake/tests/test_app/console/templates/test/classes/test_object.ctp diff --git a/cake/tests/test_app/plugins/test_plugin/config/schema/schema.php b/cake/tests/test_app/plugins/test_plugin/config/schema/schema.php index e3aa8087d..fd3a283c8 100644 --- a/cake/tests/test_app/plugins/test_plugin/config/schema/schema.php +++ b/cake/tests/test_app/plugins/test_plugin/config/schema/schema.php @@ -23,7 +23,7 @@ class TestPluginAppSchema extends CakeSchema { public $name = 'TestPluginApp'; - public $acos = array( + public $test_plugin_acos = array( 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), 'model' => array('type'=>'string', 'null' => true), diff --git a/cake/tests/test_app/plugins/test_plugin/vendors/shells/example.php b/cake/tests/test_app/plugins/test_plugin/console/shells/example.php similarity index 100% rename from cake/tests/test_app/plugins/test_plugin/vendors/shells/example.php rename to cake/tests/test_app/plugins/test_plugin/console/shells/example.php diff --git a/cake/tests/test_app/vendors/shells/tasks/empty b/cake/tests/test_app/plugins/test_plugin/console/shells/tasks/empty similarity index 100% rename from cake/tests/test_app/vendors/shells/tasks/empty rename to cake/tests/test_app/plugins/test_plugin/console/shells/tasks/empty diff --git a/cake/tests/test_app/plugins/test_plugin/vendors/shells/tasks/other_task.php b/cake/tests/test_app/plugins/test_plugin/console/shells/tasks/other_task.php similarity index 100% rename from cake/tests/test_app/plugins/test_plugin/vendors/shells/tasks/other_task.php rename to cake/tests/test_app/plugins/test_plugin/console/shells/tasks/other_task.php diff --git a/cake/tests/test_app/plugins/test_plugin/console/templates/empty b/cake/tests/test_app/plugins/test_plugin/console/templates/empty new file mode 100644 index 000000000..e69de29bb diff --git a/cake/tests/test_app/plugins/test_plugin_two/vendors/shells/example.php b/cake/tests/test_app/plugins/test_plugin_two/console/shells/example.php similarity index 100% rename from cake/tests/test_app/plugins/test_plugin_two/vendors/shells/example.php rename to cake/tests/test_app/plugins/test_plugin_two/console/shells/example.php diff --git a/cake/tests/test_app/plugins/test_plugin_two/console/shells/tasks/empty b/cake/tests/test_app/plugins/test_plugin_two/console/shells/tasks/empty new file mode 100644 index 000000000..e69de29bb diff --git a/cake/tests/test_app/plugins/test_plugin_two/vendors/shells/welcome.php b/cake/tests/test_app/plugins/test_plugin_two/console/shells/welcome.php similarity index 100% rename from cake/tests/test_app/plugins/test_plugin_two/vendors/shells/welcome.php rename to cake/tests/test_app/plugins/test_plugin_two/console/shells/welcome.php diff --git a/cake/tests/test_app/plugins/test_plugin_two/console/templates/empty b/cake/tests/test_app/plugins/test_plugin_two/console/templates/empty new file mode 100644 index 000000000..e69de29bb