shell.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: shell_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 /**
00004  * Base class for Shells
00005  *
00006  * Long description for file
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00011  * Copyright 2005-2008, Cake Software Foundation, Inc.
00012  *                              1785 E. Sahara Avenue, Suite 490-204
00013  *                              Las Vegas, Nevada 89104
00014  *
00015  * Licensed under The MIT License
00016  * Redistributions of files must retain the above copyright notice.
00017  *
00018  * @filesource
00019  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00020  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00021  * @package         cake
00022  * @subpackage      cake.cake.console.libs
00023  * @since           CakePHP(tm) v 1.2.0.5012
00024  * @version         $Revision: 580 $
00025  * @modifiedby      $LastChangedBy: gwoo $
00026  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00027  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00028  */
00029 /**
00030  * Base class for command-line utilities for automating programmer chores.
00031  *
00032  * @package     cake
00033  * @subpackage  cake.cake.console.libs
00034  */
00035 class Shell extends Object {
00036 /**
00037  * An instance of the ShellDispatcher object that loaded this script
00038  *
00039  * @var object
00040  * @access public
00041  */
00042     var $Dispatch = null;
00043 /**
00044  * If true, the script will ask for permission to perform actions.
00045  *
00046  * @var boolean
00047  * @access public
00048  */
00049     var $interactive = true;
00050 /**
00051  * Holds the DATABASE_CONFIG object for the app. Null if database.php could not be found,
00052  * or the app does not exist.
00053  *
00054  * @var object
00055  * @access public
00056  */
00057     var $DbConfig = null;
00058 /**
00059  * Contains command switches parsed from the command line.
00060  *
00061  * @var array
00062  * @access public
00063  */
00064     var $params = array();
00065 /**
00066  * Contains arguments parsed from the command line.
00067  *
00068  * @var array
00069  * @access public
00070  */
00071     var $args = array();
00072 /**
00073  * The file name of the shell that was invoked.
00074  *
00075  * @var string
00076  * @access public
00077  */
00078     var $shell = null;
00079 /**
00080  * The class name of the shell that was invoked.
00081  *
00082  * @var string
00083  * @access public
00084  */
00085     var $className = null;
00086 /**
00087  * The command called if public methods are available.
00088  *
00089  * @var string
00090  * @access public
00091  */
00092     var $command = null;
00093 /**
00094  * The name of the shell in camelized.
00095  *
00096  * @var string
00097  * @access public
00098  */
00099     var $name = null;
00100 /**
00101  * Contains tasks to load and instantiate
00102  *
00103  * @var array
00104  * @access public
00105  */
00106     var $tasks = array();
00107 /**
00108  * Contains the loaded tasks
00109  *
00110  * @var array
00111  * @access public
00112  */
00113     var $taskNames = array();
00114 /**
00115  * Contains models to load and instantiate
00116  *
00117  * @var array
00118  * @access public
00119  */
00120     var $uses = array();
00121 /**
00122  *  Constructs this Shell instance.
00123  *
00124  */
00125     function __construct(&$dispatch) {
00126         $vars = array('params', 'args', 'shell', 'shellCommand'=> 'command');
00127         foreach ($vars as $key => $var) {
00128             if (is_string($key)) {
00129                 $this->{$var} =& $dispatch->{$key};
00130             } else {
00131                 $this->{$var} =& $dispatch->{$var};
00132             }
00133         }
00134 
00135         $this->className = get_class($this);
00136 
00137         if ($this->name == null) {
00138             $this->name = str_replace(array('shell', 'Shell', 'task', 'Task'), '', $this->className);
00139         }
00140 
00141         $shellKey = Inflector::underscore($this->className);
00142         ClassRegistry::addObject($shellKey, $this);
00143         ClassRegistry::map($shellKey, $shellKey);
00144 
00145         if (!PHP5 && isset($this->args[0])) {
00146             if(strpos($this->className, low(Inflector::camelize($this->args[0]))) !== false) {
00147                 $dispatch->shiftArgs();
00148             }
00149             if (low($this->command) == low(Inflector::variable($this->args[0])) && method_exists($this, $this->command)) {
00150                 $dispatch->shiftArgs();
00151             }
00152         }
00153 
00154         $this->Dispatch =& $dispatch;
00155     }
00156 /**
00157  * Initializes the Shell
00158  * acts as constructor for subclasses
00159  * allows configuration of tasks prior to shell execution
00160  *
00161  * @access public
00162  */
00163     function initialize() {
00164         $this->_loadModels();
00165     }
00166 /**
00167  * Starts up the the Shell
00168  * allows for checking and configuring prior to command or main execution
00169  * can be overriden in subclasses
00170  *
00171  * @access public
00172  */
00173     function startup() {
00174         $this->_welcome();
00175     }
00176 /**
00177  * Displays a header for the shell
00178  *
00179  * @access protected
00180  */
00181     function _welcome() {
00182         $this->out("\nWelcome to CakePHP v" . Configure::version() . " Console");
00183         $this->out("---------------------------------------------------------------");
00184         $this->out('App : '. $this->params['app']);
00185         $this->out('Path: '. $this->params['working']);
00186         $this->hr();
00187     }
00188 /**
00189  * Loads database file and constructs DATABASE_CONFIG class
00190  * makes $this->DbConfig available to subclasses
00191  *
00192  * @return bool
00193  * @access protected
00194  */
00195     function _loadDbConfig() {
00196         if (config('database') && class_exists('DATABASE_CONFIG')) {
00197             $this->DbConfig =& new DATABASE_CONFIG();
00198             return true;
00199         }
00200         $this->err('Database config could not be loaded');
00201         $this->out('Run \'bake\' to create the database configuration');
00202         return false;
00203     }
00204 /**
00205  * if var $uses = true
00206  * Loads AppModel file and constructs AppModel class
00207  * makes $this->AppModel available to subclasses
00208  * if var $uses is an array of models will load those models
00209  *
00210  * @return bool
00211  * @access protected
00212  */
00213     function _loadModels() {
00214         if ($this->uses === null || $this->uses === false) {
00215             return;
00216         }
00217 
00218         if ($this->uses === true && App::import('Model', 'AppModel')) {
00219             $this->AppModel =& new AppModel(false, false, false);
00220             return true;
00221         }
00222 
00223         if ($this->uses !== true && !empty($this->uses)) {
00224             $uses = is_array($this->uses) ? $this->uses : array($this->uses);
00225             $this->modelClass = $uses[0];
00226 
00227             foreach ($uses as $modelClass) {
00228                 if (PHP5) {
00229                     $this->{$modelClass} = ClassRegistry::init($modelClass);
00230                 } else {
00231                     $this->{$modelClass} =& ClassRegistry::init($modelClass);
00232                 }
00233             }
00234             return true;
00235         }
00236         return false;
00237     }
00238 /**
00239  * Loads tasks defined in var $tasks
00240  *
00241  * @return bool
00242  * @access public
00243  */
00244     function loadTasks() {
00245         if ($this->tasks === null || $this->tasks === false) {
00246             return;
00247         }
00248 
00249         if ($this->tasks !== true && !empty($this->tasks)) {
00250 
00251             $tasks = $this->tasks;
00252             if (!is_array($tasks)) {
00253                 $tasks = array($tasks);
00254             }
00255 
00256             foreach ($tasks as $taskName) {
00257                 $task = Inflector::underscore($taskName);
00258                 $taskClass = Inflector::camelize($taskName.'Task');
00259                 $taskKey = Inflector::underscore($taskClass);
00260 
00261                 if (!class_exists($taskClass)) {
00262                     foreach ($this->Dispatch->shellPaths as $path) {
00263                         $taskPath = $path . 'tasks' . DS . $task.'.php';
00264                         if (file_exists($taskPath)) {
00265                             require_once $taskPath;
00266                             break;
00267                         }
00268                     }
00269                 }
00270                 if (ClassRegistry::isKeySet($taskKey)) {
00271                     $this->taskNames[] = $taskName;
00272                     if (!PHP5) {
00273                         $this->{$taskName} =& ClassRegistry::getObject($taskKey);
00274                         ClassRegistry::map($taskName, $taskKey);
00275                     } else {
00276                         $this->{$taskName} = ClassRegistry::getObject($taskKey);
00277                         ClassRegistry::map($taskName, $taskKey);
00278                     }
00279                 } else {
00280 
00281                     $this->taskNames[] = $taskName;
00282                     if (!PHP5) {
00283                         $this->{$taskName} =& new $taskClass($this->Dispatch);
00284                     } else {
00285                         $this->{$taskName} = new $taskClass($this->Dispatch);
00286                     }
00287                 }
00288 
00289                 if (!isset($this->{$taskName})) {
00290                     $this->err("Task '".$taskName."' could not be loaded");
00291                     $this->_stop();
00292                 }
00293             }
00294         }
00295 
00296         return false;
00297     }
00298 /**
00299  * Prompts the user for input, and returns it.
00300  *
00301  * @param string $prompt Prompt text.
00302  * @param mixed $options Array or string of options.
00303  * @param string $default Default input value.
00304  * @return Either the default value, or the user-provided input.
00305  * @access public
00306  */
00307     function in($prompt, $options = null, $default = null) {
00308         if (!$this->interactive) {
00309             return $default;
00310         }
00311         $in = $this->Dispatch->getInput($prompt, $options, $default);
00312 
00313         if ($options && is_string($options)) {
00314             if (strpos($options, ',')) {
00315                 $options = explode(',', $options);
00316             } elseif (strpos($options, '/')) {
00317                 $options = explode('/', $options);
00318             } else {
00319                 $options = array($options);
00320             }
00321         }
00322         if (is_array($options)) {
00323             while ($in == '' || ($in && (!in_array(low($in), $options) && !in_array(up($in), $options)) && !in_array($in, $options))) {
00324                 $in = $this->Dispatch->getInput($prompt, $options, $default);
00325             }
00326         }
00327         if ($in) {
00328             return $in;
00329         }
00330     }
00331 /**
00332  * Outputs to the stdout filehandle.
00333  *
00334  * @param string $string String to output.
00335  * @param boolean $newline If true, the outputs gets an added newline.
00336  * @access public
00337  */
00338     function out($string, $newline = true) {
00339         if (is_array($string)) {
00340             $str = '';
00341             foreach($string as $message) {
00342                 $str .= $message ."\n";
00343             }
00344             $string = $str;
00345         }
00346         return $this->Dispatch->stdout($string, $newline);
00347     }
00348 /**
00349  * Outputs to the stderr filehandle.
00350  *
00351  * @param string $string Error text to output.
00352  * @access public
00353  */
00354     function err($string) {
00355         if (is_array($string)) {
00356             $str = '';
00357             foreach($string as $message) {
00358                 $str .= $message ."\n";
00359             }
00360             $string = $str;
00361         }
00362         return $this->Dispatch->stderr($string."\n");
00363     }
00364 /**
00365  * Outputs a series of minus characters to the standard output, acts as a visual separator.
00366  *
00367  * @param boolean $newline If true, the outputs gets an added newline.
00368  * @access public
00369  */
00370     function hr($newline = false) {
00371         if ($newline) {
00372             $this->out("\n");
00373         }
00374         $this->out('---------------------------------------------------------------');
00375         if ($newline) {
00376             $this->out("\n");
00377         }
00378     }
00379 /**
00380  * Displays a formatted error message and exits the application
00381  *
00382  * @param string $title Title of the error message
00383  * @param string $msg Error message
00384  * @access public
00385  */
00386     function error($title, $msg) {
00387         $out  = "$title\n";
00388         $out .= "$msg\n";
00389         $out .= "\n";
00390         $this->err($out);
00391         $this->_stop();
00392     }
00393 /**
00394  * Will check the number args matches otherwise throw an error
00395  *
00396  * @param integer $expectedNum Expected number of paramters
00397  * @param string $command Command
00398  * @access protected
00399  */
00400     function _checkArgs($expectedNum, $command = null) {
00401         if (!$command) {
00402             $command = $this->command;
00403         }
00404         if (count($this->args) < $expectedNum) {
00405             $this->error("Wrong number of parameters: ".count($this->args), "Expected: {$expectedNum}\nPlease type 'cake {$this->shell} help' for help on usage of the {$this->name} {$command}");
00406         }
00407     }
00408 /**
00409  * Creates a file at given path
00410  *
00411  * @param string $path Where to put the file.
00412  * @param string $contents Content to put in the file.
00413  * @return boolean Success
00414  * @access public
00415  */
00416     function createFile ($path, $contents) {
00417         $path = str_replace(DS . DS, DS, $path);
00418         $this->out("\n" . sprintf(__("Creating file %s", true), $path));
00419         if (is_file($path) && $this->interactive === true) {
00420             $key = $this->in(__("File exists, overwrite?", true). " {$path}",  array('y', 'n', 'q'), 'n');
00421             if (low($key) == 'q') {
00422                 $this->out(__("Quitting.", true) ."\n");
00423                 exit;
00424             } elseif (low($key) != 'y') {
00425                 $this->out(__("Skip", true) ." {$path}\n");
00426                 return false;
00427             }
00428         }
00429         if (!class_exists('File')) {
00430             uses('file');
00431         }
00432 
00433         if ($File = new File($path, true)) {
00434             $data = $File->prepare($contents);
00435             $File->write($data);
00436             $this->out(__("Wrote", true) ." {$path}");
00437             return true;
00438         } else {
00439             $this->err(__("Error! Could not write to", true)." {$path}.\n");
00440             return false;
00441         }
00442     }
00443 /**
00444  * Outputs usage text on the standard output. Implement it in subclasses.
00445  *
00446  * @access public
00447  */
00448     function help() {
00449         if ($this->command != null) {
00450             $this->err("Unknown {$this->name} command '$this->command'.\nFor usage, try 'cake {$this->shell} help'.\n\n");
00451         } else {
00452             $this->Dispatch->help();
00453         }
00454     }
00455 /**
00456  * Action to create a Unit Test
00457  *
00458  * @return boolean Success
00459  * @access protected
00460  */
00461     function _checkUnitTest() {
00462         if (is_dir(VENDORS.'simpletest') || is_dir(ROOT.DS.APP_DIR.DS.'vendors'.DS.'simpletest')) {
00463             return true;
00464         }
00465         $unitTest = $this->in('Cake test suite not installed.  Do you want to bake unit test files anyway?', array('y','n'), 'y');
00466         $result = low($unitTest) == 'y' || low($unitTest) == 'yes';
00467 
00468         if ($result) {
00469             $this->out("\nYou can download the Cake test suite from http://cakeforge.org/projects/testsuite/", true);
00470         }
00471         return $result;
00472     }
00473 /**
00474  * Makes absolute file path easier to read
00475  *
00476  * @param string $file Absolute file path
00477  * @return sting short path
00478  * @access public
00479  */
00480     function shortPath($file) {
00481         $shortPath = str_replace(ROOT, null, $file);
00482         $shortPath = str_replace('..'.DS, '', $shortPath);
00483         return r(DS.DS, DS, $shortPath);
00484     }
00485 /**
00486  * Checks for Configure::read('Routing.admin') and forces user to input it if not enabled
00487  *
00488  * @return string Admin route to use
00489  * @access public
00490  */
00491     function getAdmin() {
00492         $admin = '';
00493         $cakeAdmin = null;
00494         $adminRoute = Configure::read('Routing.admin');
00495         if (!empty($adminRoute)) {
00496             $cakeAdmin = $adminRoute . '_';
00497         } else {
00498             $this->out('You need to enable Configure::write(\'Routing.admin\',\'admin\') in /app/config/core.php to use admin routing.');
00499             $this->out('What would you like the admin route to be?');
00500             $this->out('Example: www.example.com/admin/controller');
00501             while ($admin == '') {
00502                 $admin = $this->in("What would you like the admin route to be?", null, 'admin');
00503             }
00504             if ($this->Project->cakeAdmin($admin) !== true) {
00505                 $this->out('Unable to write to /app/config/core.php.');
00506                 $this->out('You need to enable Configure::write(\'Routing.admin\',\'admin\') in /app/config/core.php to use admin routing.');
00507                 $this->_stop();
00508             } else {
00509                 $cakeAdmin = $admin . '_';
00510             }
00511         }
00512         return $cakeAdmin;
00513     }
00514 /**
00515  * Creates the proper controller path for the specified controller class name
00516  *
00517  * @param string $name Controller class name
00518  * @return string Path to controller
00519  * @access protected
00520  */
00521     function _controllerPath($name) {
00522         return low(Inflector::underscore($name));
00523     }
00524 /**
00525  * Creates the proper controller plural name for the specified controller class name
00526  *
00527  * @param string $name Controller class name
00528  * @return string Controller plural name
00529  * @access protected
00530  */
00531     function _controllerName($name) {
00532         return Inflector::pluralize(Inflector::camelize($name));
00533     }
00534 /**
00535  * Creates the proper controller camelized name (singularized) for the specified name
00536  *
00537  * @param string $name Name
00538  * @return string Camelized and singularized controller name
00539  * @access protected
00540  */
00541     function _modelName($name) {
00542         return Inflector::camelize(Inflector::singularize($name));
00543     }
00544 /**
00545  * Creates the proper singular model key for associations
00546  *
00547  * @param string $name Controller class name
00548  * @return string Singular model key
00549  * @access protected
00550  */
00551     function _modelKey($name) {
00552         return Inflector::underscore(Inflector::singularize($name)).'_id';
00553     }
00554 /**
00555  * Creates the proper model name from a foreign key
00556  *
00557  * @param string $key Foreign key
00558  * @return string Model name
00559  * @access protected
00560  */
00561     function _modelNameFromKey($key) {
00562         $name = str_replace('_id', '',$key);
00563         return Inflector::camelize($name);
00564     }
00565 /**
00566  * creates the singular name for use in views.
00567  *
00568  * @param string $name
00569  * @return string $name
00570  * @access protected
00571  */
00572     function _singularName($name) {
00573         return Inflector::variable(Inflector::singularize($name));
00574     }
00575 /**
00576  * Creates the plural name for views
00577  *
00578  * @param string $name Name to use
00579  * @return string Plural name for views
00580  * @access protected
00581  */
00582     function _pluralName($name) {
00583         return Inflector::variable(Inflector::pluralize($name));
00584     }
00585 /**
00586  * Creates the singular human name used in views
00587  *
00588  * @param string $name Controller name
00589  * @return string Singular human name
00590  * @access protected
00591  */
00592     function _singularHumanName($name) {
00593         return Inflector::humanize(Inflector::underscore(Inflector::singularize($name)));
00594     }
00595 /**
00596  * Creates the plural human name used in views
00597  *
00598  * @param string $name Controller name
00599  * @return string Plural human name
00600  * @access protected
00601  */
00602     function _pluralHumanName($name) {
00603         return Inflector::humanize(Inflector::underscore(Inflector::pluralize($name)));
00604     }
00605 }
00606 ?>