Commit 5f3e24d8 by Qiang Xue

working on console app.

parent 083ca919
...@@ -73,7 +73,7 @@ use yii\base\Exception; ...@@ -73,7 +73,7 @@ use yii\base\Exception;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Application extends Module abstract class Application extends Module
{ {
/** /**
* @var string the application name. Defaults to 'My Application'. * @var string the application name. Defaults to 'My Application'.
...@@ -95,7 +95,12 @@ class Application extends Module ...@@ -95,7 +95,12 @@ class Application extends Module
* to ensure errors and exceptions can be handled nicely. * to ensure errors and exceptions can be handled nicely.
*/ */
public $preload = array('errorHandler'); public $preload = array('errorHandler');
/**
* @var Controller the currently active controller instance
*/
public $controller;
// todo
public $localeDataPath = '@yii/i18n/data'; public $localeDataPath = '@yii/i18n/data';
private $_runtimePath; private $_runtimePath;
...@@ -118,9 +123,8 @@ class Application extends Module ...@@ -118,9 +123,8 @@ class Application extends Module
} }
/** /**
* Initializes the module. * Initializes the application by loading components declared in [[preload]].
* This method is called after the module is created and initialized with property values * If you override this method, make sure the parent implementation is invoked.
* given in configuration.
*/ */
public function init() public function init()
{ {
...@@ -129,16 +133,10 @@ class Application extends Module ...@@ -129,16 +133,10 @@ class Application extends Module
/** /**
* Runs the application. * Runs the application.
* This method loads static application components. Derived classes usually overrides this * This is the main entrance of an application. Derived classes must implement this method.
* method to do more application-specific tasks. * @return integer the exit status (0 means normal, non-zero values mean abnormal)
* Remember to call the parent implementation so that static application components are loaded.
*/ */
public function run() abstract public function run();
{
$this->beforeRequest();
$this->processRequest();
$this->afterRequest();
}
/** /**
* Terminates the application. * Terminates the application.
...@@ -167,20 +165,33 @@ class Application extends Module ...@@ -167,20 +165,33 @@ class Application extends Module
} }
/** /**
* Processes the request. * Raises the [[afterRequest]] event right AFTER the application processes the request.
* This is the place where the actual request processing work is done.
* Derived classes should override this method.
*/ */
public function processRequest() public function afterRequest()
{ {
$this->trigger('afterRequest');
} }
/** /**
* Raises the [[afterRequest]] event right AFTER the application processes the request. * Processes the request.
* The request is represented in terms of a controller route and action parameters.
* @param string $route the route of the request. It may contain module ID, controller ID, and/or action ID.
* @param array $params parameters to be bound to the controller action.
* @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal)
* @throws Exception if the route cannot be resolved into a controller
*/ */
public function afterRequest() public function processRequest($route, $params = array())
{ {
$this->trigger('afterRequest'); $result = $this->parseRoute($route);
if ($result === null) {
throw new Exception(\Yii::t('yii', 'Unable to resolve the request.'));
}
list($controller, $action) = $result;
$oldController = $this->controller;
$this->controller = $controller;
$status = $controller->run($action, $params);
$this->controller = $oldController;
return $status;
} }
/** /**
...@@ -189,12 +200,10 @@ class Application extends Module ...@@ -189,12 +200,10 @@ class Application extends Module
*/ */
public function getRuntimePath() public function getRuntimePath()
{ {
if ($this->_runtimePath !== null) { if ($this->_runtimePath === null) {
return $this->_runtimePath;
} else {
$this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime'); $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
return $this->_runtimePath;
} }
return $this->_runtimePath;
} }
/** /**
...@@ -205,7 +214,7 @@ class Application extends Module ...@@ -205,7 +214,7 @@ class Application extends Module
public function setRuntimePath($path) public function setRuntimePath($path)
{ {
if (!is_dir($path) || !is_writable($path)) { if (!is_dir($path) || !is_writable($path)) {
throw new \yii\base\Exception("Application runtime path \"$path\" is invalid. Please make sure it is a directory writable by the Web server process."); throw new Exception("Application runtime path \"$path\" is invalid. Please make sure it is a directory writable by the Web server process.");
} }
$this->_runtimePath = $path; $this->_runtimePath = $path;
} }
...@@ -226,7 +235,7 @@ class Application extends Module ...@@ -226,7 +235,7 @@ class Application extends Module
* By default, [[language]] and [[sourceLanguage]] are the same. * By default, [[language]] and [[sourceLanguage]] are the same.
* Do not set this property unless your application needs to support multiple languages. * Do not set this property unless your application needs to support multiple languages.
* @param string $language the user language (e.g. 'en_US', 'zh_CN'). * @param string $language the user language (e.g. 'en_US', 'zh_CN').
* If it is null, the {@link sourceLanguage} will be used. * If it is null, the [[sourceLanguage]] will be used.
*/ */
public function setLanguage($language) public function setLanguage($language)
{ {
...@@ -256,40 +265,6 @@ class Application extends Module ...@@ -256,40 +265,6 @@ class Application extends Module
} }
/** /**
* Returns the localized version of a specified file.
*
* The searching is based on the specified language code. In particular,
* a file with the same name will be looked for under the subdirectory
* named as the locale ID. For example, given the file "path/to/view.php"
* and locale ID "zh_cn", the localized file will be looked for as
* "path/to/zh_cn/view.php". If the file is not found, the original file
* will be returned.
*
* For consistency, it is recommended that the locale ID is given
* in lower case and in the format of LanguageID_RegionID (e.g. "en_us").
*
* @param string $srcFile the original file
* @param string $srcLanguage the language that the original file is in. If null, the application {@link sourceLanguage source language} is used.
* @param string $language the desired language that the file should be localized to. If null, the {@link getLanguage application language} will be used.
* @return string the matching localized file. The original file is returned if no localized version is found
* or if source language is the same as the desired language.
*/
public function findLocalizedFile($srcFile, $srcLanguage = null, $language = null)
{
if ($srcLanguage === null) {
$srcLanguage = $this->sourceLanguage;
}
if ($language === null) {
$language = $this->getLanguage();
}
if ($language === $srcLanguage) {
return $srcFile;
}
$desiredFile = dirname($srcFile) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($srcFile);
return is_file($desiredFile) ? $desiredFile : $srcFile;
}
/**
* Returns the locale instance. * Returns the locale instance.
* @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used. * @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used.
* @return CLocale the locale instance * @return CLocale the locale instance
...@@ -346,15 +321,6 @@ class Application extends Module ...@@ -346,15 +321,6 @@ class Application extends Module
} }
/** /**
* Returns the state persister component.
* @return CStatePersister the state persister application component.
*/
public function getStatePersister()
{
return $this->getComponent('statePersister');
}
/**
* Returns the cache component. * Returns the cache component.
* @return \yii\caching\Cache the cache application component. Null if the component is not enabled. * @return \yii\caching\Cache the cache application component. Null if the component is not enabled.
*/ */
...@@ -420,9 +386,6 @@ class Application extends Module ...@@ -420,9 +386,6 @@ class Application extends Module
'securityManager' => array( 'securityManager' => array(
'class' => 'yii\base\SecurityManager', 'class' => 'yii\base\SecurityManager',
), ),
'statePersister' => array(
'class' => 'yii\base\StatePersister',
),
)); ));
} }
} }
...@@ -27,7 +27,7 @@ namespace yii\base; ...@@ -27,7 +27,7 @@ namespace yii\base;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
abstract class Controller extends Component implements Initable class Controller extends Component implements Initable
{ {
/** /**
* @var string ID of this controller * @var string ID of this controller
...@@ -98,36 +98,33 @@ abstract class Controller extends Component implements Initable ...@@ -98,36 +98,33 @@ abstract class Controller extends Component implements Initable
} }
/** /**
* Creates an action with the specified ID and runs it. * Runs the controller with the specified action and parameters.
* If the action does not exist, [[missingAction()]] will be invoked. * @param Action|string $action the action to be executed. This can be either an action object
* @param string $actionID action ID * or the ID of the action.
* @param array $params the parameters to be passed to the action.
* If null, the result of [[getActionParams()]] will be used as action parameters.
* Note that the parameters must be name-value pairs with the names corresponding to
* the parameter names as declared by the action.
* @return integer the exit status of the action. 0 means normal, other values mean abnormal. * @return integer the exit status of the action. 0 means normal, other values mean abnormal.
* @see createAction
* @see runAction
* @see missingAction * @see missingAction
* @see createAction
*/ */
public function run($actionID) public function run($action, $params = null)
{ {
if (($action = $this->createAction($actionID)) !== null) { if (is_string($action)) {
return $this->runAction($action); if (($a = $this->createAction($action)) !== null) {
$action = $a;
} else { } else {
$this->missingAction($actionID); $this->missingAction($action);
return 1; return 1;
} }
} }
/**
* Runs the action.
* @param Action $action action to run
* @return integer the exit status of the action. 0 means normal, other values mean abnormal.
*/
public function runAction($action)
{
$priorAction = $this->action; $priorAction = $this->action;
$this->action = $action; $this->action = $action;
$exitStatus = 1; $exitStatus = 1;
if ($this->authorize($action)) { if ($this->authorize($action)) {
$params = $action->normalizeParams($this->getActionParams()); $params = $action->normalizeParams($params === null ? $this->getActionParams() : $params);
if ($params !== false) { if ($params !== false) {
if ($this->beforeAction($action)) { if ($this->beforeAction($action)) {
$exitStatus = (int)call_user_func_array(array($action, 'run'), $params); $exitStatus = (int)call_user_func_array(array($action, 'run'), $params);
...@@ -223,17 +220,21 @@ abstract class Controller extends Component implements Initable ...@@ -223,17 +220,21 @@ abstract class Controller extends Component implements Initable
* @param string $route the route of the new controller action. This can be an action ID, or a complete route * @param string $route the route of the new controller action. This can be an action ID, or a complete route
* with module ID (optional in the current module), controller ID and action ID. If the former, * with module ID (optional in the current module), controller ID and action ID. If the former,
* the action is assumed to be located within the current controller. * the action is assumed to be located within the current controller.
* @param array $params the parameters to be passed to the action.
* If null, the result of [[getActionParams()]] will be used as action parameters.
* Note that the parameters must be name-value pairs with the names corresponding to
* the parameter names as declared by the action.
* @param boolean $exit whether to end the application after this call. Defaults to true. * @param boolean $exit whether to end the application after this call. Defaults to true.
*/ */
public function forward($route, $exit = true) public function forward($route, $params = array(), $exit = true)
{ {
if (strpos($route, '/') === false) { if (strpos($route, '/') === false) {
$status = $this->run($route); $status = $this->run($route, $params);
} else { } else {
if ($route[0] !== '/' && !$this->module instanceof Application) { if ($route[0] !== '/' && !$this->module instanceof Application) {
$route = '/' . $this->module->getUniqueId() . '/' . $route; $route = '/' . $this->module->getUniqueId() . '/' . $route;
} }
$status = \Yii::$application->runController($route); $status = \Yii::$application->dispatch($route, $params);
} }
if ($exit) { if ($exit) {
\Yii::$application->end($status); \Yii::$application->end($status);
......
...@@ -43,10 +43,54 @@ abstract class Module extends Component implements Initable ...@@ -43,10 +43,54 @@ abstract class Module extends Component implements Initable
* @var Module the parent module of this module. Null if this module does not have a parent. * @var Module the parent module of this module. Null if this module does not have a parent.
*/ */
public $module; public $module;
/**
private $_basePath; * @var array mapping from controller ID to controller configurations.
private $_modules = array(); * Each name-value pair specifies the configuration of a single controller.
private $_components = array(); * A controller configuration can be either a string or an array.
* If the former, the string should be the class name or path alias of the controller.
* If the latter, the array must contain a 'class' element which specifies
* the controller's class name or path alias, and the rest of the name-value pairs
* in the array are used to initialize the corresponding controller properties. For example,
*
* ~~~
* array(
* 'account' => '@application/controllers/UserController',
* 'article' => array(
* 'class' => '@application/controllers/PostController',
* 'pageTitle' => 'something new',
* ),
* )
* ~~~
*/
public $controllers = array();
/**
* @return string the default route of this module. Defaults to 'default'.
* The route may consist of child module ID, controller ID, and/or action ID.
* For example, `help`, `post/create`, `admin/post/create`.
* If action ID is not given, it will take the default value as specified in
* [[Controller::defaultAction]].
*/
public $defaultRoute = 'default';
/**
* @var string the root directory of the module.
* @see getBasePath
* @see setBasePath
*/
protected $_basePath;
/**
* @var string the directory containing controller classes in the module.
* @see getControllerPath
* @see setControllerPath
*/
protected $_controllerPath;
/**
* @var array child modules of this module
*/
protected $_modules = array();
/**
* @var array application components of this module
*/
protected $_components = array();
/** /**
* Constructor. * Constructor.
...@@ -118,7 +162,8 @@ abstract class Module extends Component implements Initable ...@@ -118,7 +162,8 @@ abstract class Module extends Component implements Initable
/** /**
* Returns the root directory of the module. * Returns the root directory of the module.
* @return string the root directory of the module. Defaults to the directory containing the module class file. * It defaults to the directory containing the module class file.
* @return string the root directory of the module.
*/ */
public function getBasePath() public function getBasePath()
{ {
...@@ -132,7 +177,7 @@ abstract class Module extends Component implements Initable ...@@ -132,7 +177,7 @@ abstract class Module extends Component implements Initable
/** /**
* Sets the root directory of the module. * Sets the root directory of the module.
* This method can only be invoked at the beginning of the constructor. * This method can only be invoked at the beginning of the constructor.
* @param string $path the root directory of the module. * @param string $path the root directory of the module. This can be either a directory name or a path alias.
* @throws Exception if the directory does not exist. * @throws Exception if the directory does not exist.
*/ */
public function setBasePath($path) public function setBasePath($path)
...@@ -146,6 +191,36 @@ abstract class Module extends Component implements Initable ...@@ -146,6 +191,36 @@ abstract class Module extends Component implements Initable
} }
/** /**
* Returns the directory that contains the controller classes.
* Defaults to "[[basePath]]/controllers".
* @return string the directory that contains the controller classes.
*/
public function getControllerPath()
{
if ($this->_controllerPath !== null) {
return $this->_controllerPath;
} else {
return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers';
}
}
/**
* Sets the directory that contains the controller classes.
* @param string $path the directory that contains the controller classes.
* This can be either a directory name or a path alias.
* @throws Exception if the directory is invalid
*/
public function setControllerPath($path)
{
$p = \Yii::getAlias($path);
if ($p === false || !is_dir($p)) {
throw new Exception('Invalid controller path: ' . $path);
} else {
$this->_controllerPath = realpath($p);
}
}
/**
* Imports the specified path aliases. * Imports the specified path aliases.
* This method is provided so that you can import a set of path aliases when configuring a module. * This method is provided so that you can import a set of path aliases when configuring a module.
* The path aliases will be imported by calling [[\Yii::import()]]. * The path aliases will be imported by calling [[\Yii::import()]].
...@@ -404,4 +479,83 @@ abstract class Module extends Component implements Initable ...@@ -404,4 +479,83 @@ abstract class Module extends Component implements Initable
$this->getComponent($id); $this->getComponent($id);
} }
} }
/**
* Parses a given route into controller and action ID.
* The parsing process follows the following algorithm:
*
* 1. Get the first segment in route
* 2. If the segment matches
* - an ID in [[controllers]], create a controller instance using the corresponding configuration,
* and return the controller with the rest part of the route;
* - a controller class under [[controllerPath]], create the controller instance, and return it
* with the rest part of the route;
* - an ID in [[modules]], let the corresponding module to parse the rest part of the route.
*
* @param string $route the route which may consist module ID, controller ID and/or action ID (e.g. `post/create`)
* @return array|null the controller instance and action ID. Null if the route cannot be resolved.
*/
public function parseRoute($route)
{
if (($route = trim($route, '/')) === '') {
$route = $this->defaultRoute;
}
if (($pos = strpos($route, '/')) !== false) {
$id = substr($route, 0, $pos);
$route = (string)substr($route, $pos + 1);
} else {
$id = $route;
$route = '';
}
$controller = $this->createController($id);
if ($controller !== null) {
return array($controller, $route);
}
if (($module = $this->getModule($id)) !== null) {
return $module->parseRoute($route);
}
return null;
}
/**
* Creates a controller instance according to the specified controller ID.
* For security reasons, the controller ID must start with a lower-case letter
* and consist of word characters only.
*
* This method will first match the controller ID in [[controllers]]. If found,
* it will create an instance of controller using the corresponding configuration.
* If not found, it will look for a controller class named `XyzController` under
* the [[controllerPath]] directory, where `xyz` is the controller ID with the first
* letter in upper-case.
* @param string $id the controller ID
* @return Controller|null the created controller instance. Null if the controller ID is invalid.
*/
public function createController($id)
{
// Controller IDs must start with a lower-case letter and consist of word characters only
if (!preg_match('/^[a-z][a-zA-Z0-9_]*$/', $id)) {
return null;
}
if (isset($this->controllers[$id])) {
return \Yii::createObject($this->controllers[$id], $id, $this);
}
$className = ucfirst($id) . 'Controller';
$classFile = $this->getControllerPath() . DIRECTORY_SEPARATOR . $className . '.php';
if (is_file($classFile)) {
if (!class_exists($className, false)) {
require($classFile);
}
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
return $className::newInstance(array(), $id, $this);
}
}
return null;
}
} }
<?php
/**
* CConsoleCommandRunner class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console;
/**
* CConsoleCommandRunner manages commands and executes the requested command.
*
* @property string $scriptName The entry script name.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CommandRunner extends \yii\base\Component
{
/**
* @var array list of all available commands (command name=>command configuration).
* Each command configuration can be either a string or an array.
* If the former, the string should be the class name or
* {@link YiiBase::getPathOfAlias class path alias} of the command.
* If the latter, the array must contain a 'class' element which specifies
* the command's class name or {@link YiiBase::getPathOfAlias class path alias}.
* The rest name-value pairs in the array are used to initialize
* the corresponding command properties. For example,
* <pre>
* array(
* 'email'=>array(
* 'class'=>'path.to.Mailer',
* 'interval'=>3600,
* ),
* 'log'=>'path.to.LoggerCommand',
* )
* </pre>
*/
public $commands=array();
private $_scriptName;
/**
* Executes the requested command.
* @param array $args list of user supplied parameters (including the entry script name and the command name).
*/
public function run($args)
{
$this->_scriptName=$args[0];
array_shift($args);
if(isset($args[0]))
{
$name=$args[0];
array_shift($args);
} else
$name='help';
if(($command=$this->createCommand($name))===null)
$command=$this->createCommand('help');
$command->init();
$command->run($args);
}
/**
* @return string the entry script name
*/
public function getScriptName()
{
return $this->_scriptName;
}
/**
* Searches for commands under the specified directory.
* @param string $path the directory containing the command class files.
* @return array list of commands (command name=>command class file)
*/
public function findCommands($path)
{
if(($dir=@opendir($path))===false)
return array();
$commands=array();
while(($name=readdir($dir))!==false)
{
$file=$path.DIRECTORY_SEPARATOR.$name;
if(!strcasecmp(substr($name,-11),'Command.php') && is_file($file))
$commands[strtolower(substr($name,0,-11))]=$file;
}
closedir($dir);
return $commands;
}
/**
* Adds commands from the specified command path.
* If a command already exists, the new one will be ignored.
* @param string $path the alias of the directory containing the command class files.
*/
public function addCommands($path)
{
if(($commands=$this->findCommands($path))!==array())
{
foreach($commands as $name=>$file)
{
if(!isset($this->commands[$name]))
$this->commands[$name]=$file;
}
}
}
/**
* @param string $name command name (case-insensitive)
* @return \yii\console\Command the command object. Null if the name is invalid.
*/
public function createCommand($name)
{
$name=strtolower($name);
if(isset($this->commands[$name]))
{
if(is_string($this->commands[$name])) // class file path or alias
{
if(strpos($this->commands[$name],'/')!==false || strpos($this->commands[$name],'\\')!==false)
{
$className=substr(basename($this->commands[$name]),0,-4);
if(!class_exists($className,false))
require_once($this->commands[$name]);
} else // an alias
$className=\Yii::import($this->commands[$name]);
return new $className($name,$this);
} else // an array configuration
return \Yii::create($this->commands[$name],$name,$this);
} else if($name==='help')
return new HelpCommand('help',$this);
else
return null;
}
}
\ No newline at end of file
<?php
/**
* WebAppCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @version $Id$
*/
/**
* WebAppCommand creates an Yii Web application at the specified location.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package system.cli.commands
* @since 1.0
*/
class WebAppCommand extends CConsoleCommand
{
private $_rootPath;
public function getHelp()
{
return <<<EOD
USAGE
yiic webapp <app-path>
DESCRIPTION
This command generates an Yii Web Application at the specified location.
PARAMETERS
* app-path: required, the directory where the new application will be created.
If the directory does not exist, it will be created. After the application
is created, please make sure the directory can be accessed by Web users.
EOD;
}
/**
* Execute the action.
* @param array command line parameters specific for this command
*/
public function run($args)
{
if(!isset($args[0]))
$this->usageError('the Web application location is not specified.');
$path=strtr($args[0],'/\\',DIRECTORY_SEPARATOR);
if(strpos($path,DIRECTORY_SEPARATOR)===false)
$path='.'.DIRECTORY_SEPARATOR.$path;
$dir=rtrim(realpath(dirname($path)),'\\/');
if($dir===false || !is_dir($dir))
$this->usageError("The directory '$path' is not valid. Please make sure the parent directory exists.");
if(basename($path)==='.')
$this->_rootPath=$path=$dir;
else
$this->_rootPath=$path=$dir.DIRECTORY_SEPARATOR.basename($path);
if($this->confirm("Create a Web application under '$path'?"))
{
$sourceDir=realpath(dirname(__FILE__).'/../views/webapp');
if($sourceDir===false)
die("\nUnable to locate the source directory.\n");
$list=$this->buildFileList($sourceDir,$path);
$list['index.php']['callback']=array($this,'generateIndex');
$list['index-test.php']['callback']=array($this,'generateIndex');
$list['protected/tests/bootstrap.php']['callback']=array($this,'generateTestBoostrap');
$list['protected/yiic.php']['callback']=array($this,'generateYiic');
$this->copyFiles($list);
@chmod($path.'/assets',0777);
@chmod($path.'/protected/runtime',0777);
@chmod($path.'/protected/data',0777);
@chmod($path.'/protected/data/testdrive.db',0777);
@chmod($path.'/protected/yiic',0755);
echo "\nYour application has been created successfully under {$path}.\n";
}
}
public function generateIndex($source,$params)
{
$content=file_get_contents($source);
$yii=realpath(dirname(__FILE__).'/../../yii.php');
$yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'index.php');
$yii=str_replace('\\','\\\\',$yii);
return preg_replace('/\$yii\s*=(.*?);/',"\$yii=$yii;",$content);
}
public function generateTestBoostrap($source,$params)
{
$content=file_get_contents($source);
$yii=realpath(dirname(__FILE__).'/../../yiit.php');
$yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'bootstrap.php');
$yii=str_replace('\\','\\\\',$yii);
return preg_replace('/\$yiit\s*=(.*?);/',"\$yiit=$yii;",$content);
}
public function generateYiic($source,$params)
{
$content=file_get_contents($source);
$yiic=realpath(dirname(__FILE__).'/../../yiic.php');
$yiic=$this->getRelativePath($yiic,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'yiic.php');
$yiic=str_replace('\\','\\\\',$yiic);
return preg_replace('/\$yiic\s*=(.*?);/',"\$yiic=$yiic;",$content);
}
protected function getRelativePath($path1,$path2)
{
$segs1=explode(DIRECTORY_SEPARATOR,$path1);
$segs2=explode(DIRECTORY_SEPARATOR,$path2);
$n1=count($segs1);
$n2=count($segs2);
for($i=0;$i<$n1 && $i<$n2;++$i)
{
if($segs1[$i]!==$segs2[$i])
break;
}
if($i===0)
return "'".$path1."'";
$up='';
for($j=$i;$j<$n2-1;++$j)
$up.='/..';
for(;$i<$n1-1;++$i)
$up.='/'.$segs1[$i];
return 'dirname(__FILE__).\''.$up.'/'.basename($path1).'\'';
}
}
\ No newline at end of file
<?php
/**
* MessageCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* MessageCommand extracts messages to be translated from source files.
* The extracted messages are saved as PHP message source files
* under the specified directory.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package system.cli.commands
* @since 1.0
*/
class MessageCommand extends CConsoleCommand
{
public function getHelp()
{
return <<<EOD
USAGE
yiic message <config-file>
DESCRIPTION
This command searches for messages to be translated in the specified
source files and compiles them into PHP arrays as message source.
PARAMETERS
* config-file: required, the path of the configuration file. You can find
an example in framework/messages/config.php.
The file can be placed anywhere and must be a valid PHP script which
returns an array of name-value pairs. Each name-value pair represents
a configuration option.
The following options are available:
- sourcePath: string, root directory of all source files.
- messagePath: string, root directory containing message translations.
- languages: array, list of language codes that the extracted messages
should be translated to. For example, array('zh_cn','en_au').
- fileTypes: array, a list of file extensions (e.g. 'php', 'xml').
Only the files whose extension name can be found in this list
will be processed. If empty, all files will be processed.
- exclude: array, a list of directory and file exclusions. Each
exclusion can be either a name or a path. If a file or directory name
or path matches the exclusion, it will not be copied. For example,
an exclusion of '.svn' will exclude all files and directories whose
name is '.svn'. And an exclusion of '/a/b' will exclude file or
directory 'sourcePath/a/b'.
- translator: the name of the function for translating messages.
Defaults to 'Yii::t'. This is used as a mark to find messages to be
translated.
- overwrite: if message file must be overwritten with the merged messages.
- removeOld: if message no longer needs translation it will be removed,
instead of being enclosed between a pair of '@@' marks.
- sort: sort messages by key when merging, regardless of their translation
state (new, obsolete, translated.)
EOD;
}
/**
* Execute the action.
* @param array command line parameters specific for this command
*/
public function run($args)
{
if(!isset($args[0]))
$this->usageError('the configuration file is not specified.');
if(!is_file($args[0]))
$this->usageError("the configuration file {$args[0]} does not exist.");
$config=require_once($args[0]);
$translator='Yii::t';
extract($config);
if(!isset($sourcePath,$messagePath,$languages))
$this->usageError('The configuration file must specify "sourcePath", "messagePath" and "languages".');
if(!is_dir($sourcePath))
$this->usageError("The source path $sourcePath is not a valid directory.");
if(!is_dir($messagePath))
$this->usageError("The message path $messagePath is not a valid directory.");
if(empty($languages))
$this->usageError("Languages cannot be empty.");
if(!isset($overwrite))
$overwrite = false;
if(!isset($removeOld))
$removeOld = false;
if(!isset($sort))
$sort = false;
$options=array();
if(isset($fileTypes))
$options['fileTypes']=$fileTypes;
if(isset($exclude))
$options['exclude']=$exclude;
$files=CFileHelper::findFiles(realpath($sourcePath),$options);
$messages=array();
foreach($files as $file)
$messages=array_merge_recursive($messages,$this->extractMessages($file,$translator));
foreach($languages as $language)
{
$dir=$messagePath.DIRECTORY_SEPARATOR.$language;
if(!is_dir($dir))
@mkdir($dir);
foreach($messages as $category=>$msgs)
{
$msgs=array_values(array_unique($msgs));
$this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort);
}
}
}
protected function extractMessages($fileName,$translator)
{
echo "Extracting messages from $fileName...\n";
$subject=file_get_contents($fileName);
$n=preg_match_all('/\b'.$translator.'\s*\(\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*,\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*[,\)]/s',$subject,$matches,PREG_SET_ORDER);
$messages=array();
for($i=0;$i<$n;++$i)
{
if(($pos=strpos($matches[$i][1],'.'))!==false)
$category=substr($matches[$i][1],$pos+1,-1);
else
$category=substr($matches[$i][1],1,-1);
$message=$matches[$i][2];
$messages[$category][]=eval("return $message;"); // use eval to eliminate quote escape
}
return $messages;
}
protected function generateMessageFile($messages,$fileName,$overwrite,$removeOld,$sort)
{
echo "Saving messages to $fileName...";
if(is_file($fileName))
{
$translated=require($fileName);
sort($messages);
ksort($translated);
if(array_keys($translated)==$messages)
{
echo "nothing new...skipped.\n";
return;
}
$merged=array();
$untranslated=array();
foreach($messages as $message)
{
if(!empty($translated[$message]))
$merged[$message]=$translated[$message];
else
$untranslated[]=$message;
}
ksort($merged);
sort($untranslated);
$todo=array();
foreach($untranslated as $message)
$todo[$message]='';
ksort($translated);
foreach($translated as $message=>$translation)
{
if(!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld)
{
if(substr($translation,0,2)==='@@' && substr($translation,-2)==='@@')
$todo[$message]=$translation;
else
$todo[$message]='@@'.$translation.'@@';
}
}
$merged=array_merge($todo,$merged);
if($sort)
ksort($merged);
if($overwrite === false)
$fileName.='.merged';
echo "translation merged.\n";
}
else
{
$merged=array();
foreach($messages as $message)
$merged[$message]='';
ksort($merged);
echo "saved.\n";
}
$array=str_replace("\r",'',var_export($merged,true));
$content=<<<EOD
<?php
/**
* Message translations.
*
* This file is automatically generated by 'yiic message' command.
* It contains the localizable messages extracted from source code.
* You may modify this file by translating the extracted messages.
*
* Each array element represents the translation (value) of a message (key).
* If the value is empty, the message is considered as not translated.
* Messages that no longer need translation will have their translations
* enclosed between a pair of '@@' marks.
*
* Message string can be used with plural forms format. Check i18n section
* of the guide for details.
*
* NOTE, this file must be saved in UTF-8 encoding.
*
* @version \$Id: \$
*/
return $array;
EOD;
file_put_contents($fileName, $content);
}
}
<?php
/**
* ShellCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @version $Id$
*/
/**
* ShellCommand executes the specified Web application and provides a shell for interaction.
*
* @property string $help The help information for the shell command.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package system.cli.commands
* @since 1.0
*/
class ShellCommand extends CConsoleCommand
{
/**
* @return string the help information for the shell command
*/
public function getHelp()
{
return <<<EOD
USAGE
yiic shell [entry-script | config-file]
DESCRIPTION
This command allows you to interact with a Web application
on the command line. It also provides tools to automatically
generate new controllers, views and data models.
It is recommended that you execute this command under
the directory that contains the entry script file of
the Web application.
PARAMETERS
* entry-script | config-file: optional, the path to
the entry script file or the configuration file for
the Web application. If not given, it is assumed to be
the 'index.php' file under the current directory.
EOD;
}
/**
* Execute the action.
* @param array $args command line parameters specific for this command
*/
public function run($args)
{
if(!isset($args[0]))
$args[0]='index.php';
$entryScript=isset($args[0]) ? $args[0] : 'index.php';
if(($entryScript=realpath($args[0]))===false || !is_file($entryScript))
$this->usageError("{$args[0]} does not exist or is not an entry script file.");
// fake the web server setting
$cwd=getcwd();
chdir(dirname($entryScript));
$_SERVER['SCRIPT_NAME']='/'.basename($entryScript);
$_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME'];
$_SERVER['SCRIPT_FILENAME']=$entryScript;
$_SERVER['HTTP_HOST']='localhost';
$_SERVER['SERVER_NAME']='localhost';
$_SERVER['SERVER_PORT']=80;
// reset context to run the web application
restore_error_handler();
restore_exception_handler();
Yii::setApplication(null);
Yii::setPathOfAlias('application',null);
ob_start();
$config=require($entryScript);
ob_end_clean();
// oops, the entry script turns out to be a config file
if(is_array($config))
{
chdir($cwd);
$_SERVER['SCRIPT_NAME']='/index.php';
$_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME'];
$_SERVER['SCRIPT_FILENAME']=$cwd.DIRECTORY_SEPARATOR.'index.php';
Yii::createWebApplication($config);
}
restore_error_handler();
restore_exception_handler();
$yiiVersion=Yii::getVersion();
echo <<<EOD
Yii Interactive Tool v1.1 (based on Yii v{$yiiVersion})
Please type 'help' for help. Type 'exit' to quit.
EOD;
$this->runShell();
}
protected function runShell()
{
// disable E_NOTICE so that the shell is more friendly
error_reporting(E_ALL ^ E_NOTICE);
$_runner_=new CConsoleCommandRunner;
$_runner_->addCommands(dirname(__FILE__).'/shell');
$_runner_->addCommands(Yii::getPathOfAlias('application.commands.shell'));
if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false)
$_runner_->addCommands($_path_);
$_commands_=$_runner_->commands;
$log=Yii::app()->log;
while(($_line_=$this->prompt("\n>>"))!==false)
{
$_line_=trim($_line_);
if($_line_==='exit')
return;
try
{
$_args_=preg_split('/[\s,]+/',rtrim($_line_,';'),-1,PREG_SPLIT_NO_EMPTY);
if(isset($_args_[0]) && isset($_commands_[$_args_[0]]))
{
$_command_=$_runner_->createCommand($_args_[0]);
array_shift($_args_);
$_command_->init();
$_command_->run($_args_);
}
else
echo eval($_line_.';');
}
catch(Exception $e)
{
if($e instanceof ShellException)
echo $e->getMessage();
else
echo $e;
}
}
}
}
class ShellException extends CException
{
}
\ No newline at end of file
...@@ -5,10 +5,9 @@ ...@@ -5,10 +5,9 @@
* *
* This is the bootstrap script for running yiic on Unix/Linux. * This is the bootstrap script for running yiic on Unix/Linux.
* *
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2012 Yii Software LLC * @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
require_once(__DIR__.'/yiic.php'); require_once(__DIR__ . '/yiic.php');
...@@ -7,23 +7,12 @@ ...@@ -7,23 +7,12 @@
* @license http://www.yiiframework.com/license/ * @license http://www.yiiframework.com/license/
*/ */
// fcgi doesn't have STDIN defined by default require(__DIR__ . '/yii.php');
defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
require(__DIR__.'/yii.php'); $config = array(
'controllerPath' => '@yii/console/commands',
);
$id = 'yiic';
$basePath = __DIR__ . '/console';
if(isset($config)) yii\console\Application::newInstance($config, $id, $basePath)->run();
{ \ No newline at end of file
$app=new \yii\console\Application($config);
$app->commandRunner->addCommands(YII_PATH.'/cli/commands');
$env=@getenv('YII_CONSOLE_COMMANDS');
if(!empty($env))
$app->commandRunner->addCommands($env);
} else
{
$app=new \yii\console\Application(array(
'basePath'=>__DIR__.'/cli',
));
}
$app->run();
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment