root/trunk/framework/prado-cli.php

Revision 2498, 19.8 kB (checked in by knut, 3 months ago)

fixed #911

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1<?php
2
3/**
4 * Prado command line developer tools.
5 *
6 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
7 * @link http://www.pradosoft.com/
8 * @copyright Copyright &copy; 2005-2008 PradoSoft
9 * @license http://www.pradosoft.com/license/
10 * @version $Id$
11 */
12
13if(!isset($_SERVER['argv']) || php_sapi_name()!=='cli')
14    die('Must be run from the command line');
15
16require_once(dirname(__FILE__).'/prado.php');
17
18//stub application class
19class PradoShellApplication extends TApplication
20{
21    public function run()
22    {
23        $this->initApplication();
24    }
25}
26
27restore_exception_handler();
28
29//config PHP shell
30if(count($_SERVER['argv']) > 1 && strtolower($_SERVER['argv'][1])==='shell')
31{
32    function __shell_print_var($shell,$var)
33    {
34        if(!$shell->has_semicolon) echo Prado::varDump($var);
35    }
36    include_once(dirname(__FILE__).'/3rdParty/PhpShell/php-shell-init.php');
37}
38
39
40//register action classes
41PradoCommandLineInterpreter::getInstance()->addActionClass('PradoCommandLineCreateProject');
42PradoCommandLineInterpreter::getInstance()->addActionClass('PradoCommandLineCreateTests');
43PradoCommandLineInterpreter::getInstance()->addActionClass('PradoCommandLinePhpShell');
44PradoCommandLineInterpreter::getInstance()->addActionClass('PradoCommandLineUnitTest');
45PradoCommandLineInterpreter::getInstance()->addActionClass('PradoCommandLineActiveRecordGen');
46
47//run it;
48PradoCommandLineInterpreter::getInstance()->run($_SERVER['argv']);
49
50/**************** END CONFIGURATION **********************/
51
52/**
53 * PradoCommandLineInterpreter Class
54 *
55 * Command line interface, configures the action classes and dispatches the command actions.
56 *
57 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
58 * @version $Id$
59 * @since 3.0.5
60 */
61class PradoCommandLineInterpreter
62{
63    /**
64     * @var array command action classes
65     */
66    protected $_actions=array();
67
68    /**
69     * @param string action class name
70     */
71    public function addActionClass($class)
72    {
73        $this->_actions[$class] = new $class;
74    }
75
76    /**
77     * @return PradoCommandLineInterpreter static instance
78     */
79    public static function getInstance()
80    {
81        static $instance;
82        if($instance === null)
83            $instance = new self;
84        return $instance;
85    }
86
87    /**
88     * Dispatch the command line actions.
89     * @param array command line arguments
90     */
91    public function run($args)
92    {
93        echo "Command line tools for Prado ".Prado::getVersion().".\n";
94
95        if(count($args) > 1)
96            array_shift($args);
97        $valid = false;
98        foreach($this->_actions as $class => $action)
99        {
100            if($action->isValidAction($args))
101            {
102                $valid |= $action->performAction($args);
103                break;
104            }
105            else
106            {
107                $valid = false;
108            }
109        }
110        if(!$valid)
111            $this->printHelp();
112    }
113
114    /**
115     * Print command line help, default action.
116     */
117    public function printHelp()
118    {
119        echo "usage: php prado-cli.php action <parameter> [optional]\n";
120        echo "example: php prado-cli.php -c mysite\n\n";
121        echo "actions:\n";
122        foreach($this->_actions as $action)
123            echo $action->renderHelp();
124    }
125}
126
127/**
128 * Base class for command line actions.
129 *
130 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
131 * @version $Id$
132 * @since 3.0.5
133 */
134abstract class PradoCommandLineAction
135{
136    /**
137     * Execute the action.
138     * @param array command line parameters
139     * @return boolean true if action was handled
140     */
141    public abstract function performAction($args);
142
143    protected function createDirectory($dir, $mask)
144    {
145        if(!is_dir($dir))
146        {
147            mkdir($dir);
148            echo "creating $dir\n";
149        }
150        if(is_dir($dir))
151            chmod($dir, $mask);
152    }
153
154    protected function createFile($filename, $content)
155    {
156        if(!is_file($filename))
157        {
158            file_put_contents($filename, $content);
159            echo "creating $filename\n";
160        }
161    }
162
163    public function isValidAction($args)
164    {
165        return strtolower($args[0]) === $this->action &&
166                count($args)-1 >= count($this->parameters);
167    }
168
169    public function renderHelp()
170    {
171        $params = array();
172        foreach($this->parameters as $v)
173            $params[] = '<'.$v.'>';
174        $parameters = join($params, ' ');
175        $options = array();
176        foreach($this->optional as $v)
177            $options[] = '['.$v.']';
178        $optional = (strlen($parameters) ? ' ' : ''). join($options, ' ');
179        $description='';
180        foreach(explode("\n", wordwrap($this->description,65)) as $line)
181            $description .= '    '.$line."\n";
182        return <<<EOD
183  {$this->action} {$parameters}{$optional}
184{$description}
185
186EOD;
187    }
188
189    protected function initializePradoApplication($directory)
190    {
191        $app_dir = realpath($directory.'/protected/');
192        if($app_dir !== false && is_dir($app_dir))
193        {
194            if(Prado::getApplication()===null)
195            {
196                $app = new PradoShellApplication($app_dir);
197                $app->run();
198                $dir = substr(str_replace(realpath('./'),'',$app_dir),1);
199                $initialized=true;
200                echo '** Loaded PRADO appplication in directory "'.$dir."\".\n";
201            }
202
203            return Prado::getApplication();
204        }
205        else
206        {
207            echo '+'.str_repeat('-',77)."+\n";
208            echo '** Unable to load PRADO application in directory "'.$directory."\".\n";
209            echo '+'.str_repeat('-',77)."+\n";
210        }
211        return false;
212    }
213
214}
215
216/**
217 * Create a Prado project skeleton, including directories and files.
218 *
219 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
220 * @version $Id$
221 * @since 3.0.5
222 */
223class PradoCommandLineCreateProject extends PradoCommandLineAction
224{
225    protected $action = '-c';
226    protected $parameters = array('directory');
227    protected $optional = array();
228    protected $description = 'Creates a Prado project skeleton for the given <directory>.';
229
230    public function performAction($args)
231    {
232        $this->createNewPradoProject($args[1]);
233        return true;
234    }
235
236    /**
237     * Functions to create new prado project.
238     */
239    protected function createNewPradoProject($dir)
240    {
241        if(strlen(trim($dir)) == 0)
242            return;
243
244        $rootPath = realpath(dirname(trim($dir)));
245
246        if(basename($dir)!=='.')
247            $basePath = $rootPath.DIRECTORY_SEPARATOR.basename($dir);
248        else
249            $basePath = $rootPath;
250        $appName = basename($basePath);
251        $assetPath = $basePath.DIRECTORY_SEPARATOR.'assets';
252        $protectedPath  = $basePath.DIRECTORY_SEPARATOR.'protected';
253        $runtimePath = $basePath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'runtime';
254        $pagesPath = $protectedPath.DIRECTORY_SEPARATOR.'pages';
255
256        $indexFile = $basePath.DIRECTORY_SEPARATOR.'index.php';
257        $htaccessFile = $protectedPath.DIRECTORY_SEPARATOR.'.htaccess';
258        $configFile = $protectedPath.DIRECTORY_SEPARATOR.'application.xml';
259        $defaultPageFile = $pagesPath.DIRECTORY_SEPARATOR.'Home.page';
260
261        $this->createDirectory($basePath, 0755);
262        $this->createDirectory($assetPath,0777);
263        $this->createDirectory($protectedPath,0755);
264        $this->createDirectory($runtimePath,0777);
265        $this->createDirectory($pagesPath,0755);
266
267        $this->createFile($indexFile, $this->renderIndexFile());
268        $this->createFile($configFile, $this->renderConfigFile($appName));
269        $this->createFile($htaccessFile, $this->renderHtaccessFile());
270        $this->createFile($defaultPageFile, $this->renderDefaultPage());
271    }
272
273    protected function renderIndexFile()
274    {
275        $framework = realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR.'prado.php';
276return '<?php
277
278$frameworkPath=\''.$framework.'\';
279
280// The following directory checks may be removed if performance is required
281$basePath=dirname(__FILE__);
282$assetsPath=$basePath.\'/assets\';
283$runtimePath=$basePath.\'/protected/runtime\';
284
285if(!is_file($frameworkPath))
286    die("Unable to find prado framework path $frameworkPath.");
287if(!is_writable($assetsPath))
288    die("Please make sure that the directory $assetsPath is writable by Web server process.");
289if(!is_writable($runtimePath))
290    die("Please make sure that the directory $runtimePath is writable by Web server process.");
291
292
293require_once($frameworkPath);
294
295$application=new TApplication;
296$application->run();
297
298?>';
299    }
300
301    protected function renderConfigFile($appName)
302    {
303        return <<<EOD
304<?xml version="1.0" encoding="utf-8"?>
305
306<application id="$appName" mode="Debug">
307  <!-- alias definitions and namespace usings
308  <paths>
309    <alias id="myalias" path="./lib" />
310    <using namespace="Application.common.*" />
311  </paths>
312  -->
313
314  <!-- configurations for modules -->
315  <modules>
316    <!-- Remove this comment mark to enable caching
317    <module id="cache" class="System.Caching.TDbCache" />
318    -->
319
320    <!-- Remove this comment mark to enable PATH url format
321    <module id="request" class="THttpRequest" UrlFormat="Path" />
322    -->
323
324    <!-- Remove this comment mark to enable logging
325    <module id="log" class="System.Util.TLogRouter">
326      <route class="TBrowserLogRoute" Categories="System" />
327    </module>
328    -->
329  </modules>
330
331  <!-- configuration for available services -->
332  <services>
333    <service id="page" class="TPageService" DefaultPage="Home" />
334  </services>
335
336  <!-- application parameters
337  <parameters>
338    <parameter id="param1" value="value1" />
339    <parameter id="param2" value="value2" />
340  </parameters>
341  -->
342</application>
343EOD;
344    }
345
346    protected function renderHtaccessFile()
347    {
348        return 'deny from all';
349    }
350
351
352    protected function renderDefaultPage()
353    {
354return <<<EOD
355<html>
356<head>
357  <title>Welcome to PRADO</title>
358</head>
359<body>
360<h1>Welcome to PRADO!</h1>
361</body>
362</html>
363EOD;
364    }
365}
366
367/**
368 * Creates test fixtures for a Prado application.
369 *
370 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
371 * @version $Id$
372 * @since 3.0.5
373 */
374class PradoCommandLineCreateTests extends PradoCommandLineAction
375{
376    protected $action = '-t';
377    protected $parameters = array('directory');
378    protected $optional = array();
379    protected $description = 'Create test fixtures in the given <directory>.';
380
381    public function performAction($args)
382    {
383        $this->createTestFixtures($args[1]);
384        return true;
385    }
386
387    protected function createTestFixtures($dir)
388    {
389        if(strlen(trim($dir)) == 0)
390            return;
391
392        $rootPath = realpath(dirname(trim($dir)));
393        $basePath = $rootPath.'/'.basename($dir);
394
395        $tests = $basePath.'/tests';
396        $unit_tests = $tests.'/unit';
397        $functional_tests = $tests.'/functional';
398
399        $this->createDirectory($tests,0755);
400        $this->createDirectory($unit_tests,0755);
401        $this->createDirectory($functional_tests,0755);
402
403        $unit_test_index = $tests.'/unit.php';
404        $functional_test_index = $tests.'/functional.php';
405
406        $this->createFile($unit_test_index, $this->renderUnitTestFixture());
407        $this->createFile($functional_test_index, $this->renderFunctionalTestFixture());
408    }
409
410    protected function renderUnitTestFixture()
411    {
412        $tester = realpath(dirname(__FILE__).'/../tests/test_tools/unit_tests.php');
413return '<?php
414
415include_once \''.$tester.'\';
416
417$app_directory = "../protected";
418$test_cases = dirname(__FILE__)."/unit";
419
420$tester = new PradoUnitTester($test_cases, $app_directory);
421$tester->run(new HtmlReporter());
422
423?>';
424    }
425
426    protected function renderFunctionalTestFixture()
427    {
428        $tester = realpath(dirname(__FILE__).'/../tests/test_tools/functional_tests.php');
429return '<?php
430
431include_once \''.$tester.'\';
432
433$test_cases = dirname(__FILE__)."/functional";
434
435$tester=new PradoFunctionalTester($test_cases);
436$tester->run(new SimpleReporter());
437
438?>';
439    }
440
441}
442
443/**
444 * Creates and run a Prado application in a PHP Shell.
445 *
446 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
447 * @version $Id$
448 * @since 3.0.5
449 */
450class PradoCommandLinePhpShell extends PradoCommandLineAction
451{
452    protected $action = 'shell';
453    protected $parameters = array();
454    protected $optional = array('directory');
455    protected $description = 'Runs a PHP interactive interpreter. Initializes the Prado application in the given [directory].';
456
457    public function performAction($args)
458    {
459        if(count($args) > 1)
460            $app = $this->initializePradoApplication($args[1]);
461        return true;
462    }
463}
464
465/**
466 * Runs unit test cases.
467 *
468 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
469 * @version $Id$
470 * @since 3.0.5
471 */
472class PradoCommandLineUnitTest extends PradoCommandLineAction
473{
474    protected $action = 'test';
475    protected $parameters = array('directory');
476    protected $optional = array('testcase ...');
477    protected $description = 'Runs all unit test cases in the given <directory>. Use [testcase] option to run specific test cases.';
478
479    protected $matches = array();
480
481    public function performAction($args)
482    {
483        $dir = realpath($args[1]);
484        if($dir !== false && is_dir($dir))
485            $this->runUnitTests($dir,$args);
486        else
487            echo '** Unable to find directory "'.$args[1]."\".\n";
488        return true;
489    }
490
491    protected function initializeTestRunner()
492    {
493        $TEST_TOOLS = realpath(dirname(__FILE__).'/../tests/test_tools/');
494
495        require_once($TEST_TOOLS.'/simpletest/unit_tester.php');
496        require_once($TEST_TOOLS.'/simpletest/web_tester.php');
497        require_once($TEST_TOOLS.'/simpletest/mock_objects.php');
498        require_once($TEST_TOOLS.'/simpletest/reporter.php');
499    }
500
501    protected function runUnitTests($dir, $args)
502    {
503        $app_dir = $this->getAppDir($dir);
504        if($app_dir !== false)
505            $this->initializePradoApplication($app_dir.'/../');
506
507        $this->initializeTestRunner();
508        $test_dir = $this->getTestDir($dir);
509        if($test_dir !== false)
510        {
511            $test =$this->getUnitTestCases($test_dir,$args);
512            $running_dir = substr(str_replace(realpath('./'),'',$test_dir),1);
513            echo 'Running unit tests in directory "'.$running_dir."\":\n";
514            $test->run(new TextReporter());
515        }
516        else
517        {
518            $running_dir = substr(str_replace(realpath('./'),'',$dir),1);
519            echo '** Unable to find test directory "'.$running_dir.'/unit" or "'.$running_dir.'/tests/unit".'."\n";
520        }
521    }
522
523    protected function getAppDir($dir)
524    {
525        $app_dir = realpath($dir.'/protected');
526        if($app_dir !== false && is_dir($app_dir))
527            return $app_dir;
528        return realpath($dir.'/../protected');
529    }
530
531    protected function getTestDir($dir)
532    {
533        $test_dir = realpath($dir.'/unit');
534        if($test_dir !== false && is_dir($test_dir))
535            return $test_dir;
536        return realpath($dir.'/tests/unit/');
537    }
538
539    protected function getUnitTestCases($dir,$args)
540    {
541        $matches = null;
542        if(count($args) > 2)
543            $matches = array_slice($args,2);
544        $test=new GroupTest(' ');
545        $this->addTests($test,$dir,true,$matches);
546        $test->setLabel(implode(' ',$this->matches));
547        return $test;
548    }
549
550    protected function addTests($test,$path,$recursive=true,$match=null)
551    {
552        $dir=opendir($path);
553        while(($entry=readdir($dir))!==false)
554        {
555            if(is_file($path.'/'.$entry) && (preg_match('/[^\s]*test[^\s]*\.php/', strtolower($entry))))
556            {
557                if($match==null||($match!=null && $this->hasMatch($match,$entry)))
558                    $test->addTestFile($path.'/'.$entry);
559            }
560            if($entry!=='.' && $entry!=='..' && $entry!=='.svn' && is_dir($path.'/'.$entry) && $recursive)
561                $this->addTests($test,$path.'/'.$entry,$recursive,$match);
562        }
563        closedir($dir);
564    }
565
566    protected function hasMatch($match,$entry)
567    {
568        $file = strtolower(substr($entry,0,strrpos($entry,'.')));
569        foreach($match as $m)
570        {
571            if(strtolower($m) === $file)
572            {
573                $this->matches[] = $m;
574                return true;
575            }
576        }
577        return false;
578    }
579}
580
581/**
582 * Create active record skeleton
583 *
584 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
585 * @version $Id$
586 * @since 3.1
587 */
588class PradoCommandLineActiveRecordGen extends PradoCommandLineAction
589{
590    protected $action = 'generate';
591    protected $parameters = array('table', 'output');
592    protected $optional = array('directory', 'soap');
593    protected $description = 'Generate Active Record skeleton for <table> to <output> file using application.xml in [directory]. May also generate [soap] properties.';
594    private $_soap=false;
595
596    public function performAction($args)
597    {
598        $app_dir = count($args) > 3 ? $this->getAppDir($args[3]) : $this->getAppDir();
599        $this->_soap = count($args) > 4;
600        if($app_dir