* @link http://www.yiiframework.com/ * @copyright Copyright © 2008-2011 Yii Software LLC * @license http://www.yiiframework.com/license/ */ Yii::import('application.commands.api.ApiModel'); /** * 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 * @version $Id$ * @package system.build * @since 1.0 */ class ApiCommand extends CConsoleCommand { const URL_PATTERN='/\{\{([^\}]+)\|([^\}]+)\}\}/'; public $classes; public $packages; public $pageTitle; public $themePath; public $currentClass; public $baseSourceUrl="http://code.google.com/p/yii/source/browse"; public $version; public function getHelp() { return << [mode] build api check DESCRIPTION This command generates offline API documentation for the Yii framework. PARAMETERS * output-path: required, the directory where the generated documentation would be saved. * mode: optional, either 'online' or 'offline' (default). Indicates whether the generated documentation are for online or offline use. * check: check PHPDoc for proper @param syntax EXAMPLES * build api yii/doc online - builds api ONLINE documentation in folder yii/doc * build api yii/doc - builds api OFFLINE (default) documentation in folder yii/doc * build api check - cheks PHPDoc @param directives EOD; } /** * Execute the action. * @param array command line parameters specific for this command */ public function run($args) { $options=array( 'fileTypes'=>array('php'), 'exclude'=>array( '.svn', '/yiilite.php', '/yiit.php', '/cli', '/i18n/data', '/messages', '/vendors', '/views', '/web/js', '/web/widgets/views', '/utils/mimeTypes.php', '/gii/assets', '/gii/components', '/gii/controllers', '/gii/generators', '/gii/models', '/gii/views', ), ); if(!isset($args[0])) $this->usageError('the output directory is not specified.'); if($args[0]=='check') { $checkFiles=CFileHelper::findFiles(YII_PATH,$options); $model=new ApiModel; $model->check($checkFiles); exit(); } if(!is_dir($docPath=$args[0])) $this->usageError("the output directory {$docPath} does not exist."); $offline=true; if(isset($args[1]) && $args[1]==='online') $offline=false; $this->version=Yii::getVersion(); /* * development version - link to trunk * release version link to tags */ if(substr($this->version,-3)=='dev') $this->baseSourceUrl .= '/trunk/framework'; else $this->baseSourceUrl .= '/tags/'.$this->version.'/framework'; $this->pageTitle='Yii Framework Class Reference'; $themePath=dirname(__FILE__).'/api'; echo "\nBuilding.. : " . $this->pageTitle."\n"; echo "Type...... : " . ( $offline ? "offline" : "online" ). "\n"; echo "Version... : " . $this->version."\n"; echo "Source URL : " . $this->baseSourceUrl."\n\n"; echo "Building model...\n"; $model=$this->buildModel(YII_PATH,$options); $this->classes=$model->classes; $this->packages=$model->packages; echo "Building pages...\n"; if($offline) $this->buildOfflinePages($docPath.DIRECTORY_SEPARATOR.'api',$themePath); else { $this->buildOnlinePages($docPath.DIRECTORY_SEPARATOR.'api',$themePath); $this->buildKeywords($docPath); $this->buildPackages($docPath); } echo "Done.\n\n"; } protected function buildPackages($docPath) { file_put_contents($docPath.'/api/packages.txt',serialize($this->packages)); } protected function buildKeywords($docPath) { $keywords=array(); foreach($this->classes as $class) $keywords[]=$class->name; foreach($this->classes as $class) { $name=$class->name; foreach($class->properties as $property) { if(!$property->isInherited) $keywords[]=$name.'.'.$property->name; } foreach($class->methods as $method) { if(!$method->isInherited) $keywords[]=$name.'.'.$method->name.'()'; } } file_put_contents($docPath.'/api/keywords.txt',implode(',',$keywords)); } public function render($view,$data=null,$return=false,$layout='main') { $viewFile=$this->themePath."/views/{$view}.php"; $layoutFile=$this->themePath."/layouts/{$layout}.php"; $content=$this->renderFile($viewFile,$data,true); return $this->renderFile($layoutFile,array('content'=>$content),$return); } public function renderPartial($view,$data=null,$return=false) { $viewFile=$this->themePath."/views/{$view}.php"; return $this->renderFile($viewFile,$data,$return); } public function renderSourceLink($sourcePath,$line=null) { if($line===null) return CHtml::link('framework'.$sourcePath,$this->baseSourceUrl.$sourcePath,array('class'=>'sourceLink')); else return CHtml::link('framework'.$sourcePath.'#'.$line, $this->baseSourceUrl.$sourcePath.'#'.$line,array('class'=>'sourceLink')); } public function highlight($code,$limit=20) { $code=preg_replace("/^ /m",'',rtrim(str_replace("\t"," ",$code))); $code=highlight_string("/','',$code,1); } protected function buildOfflinePages($docPath,$themePath) { $this->themePath=$themePath; @mkdir($docPath); $content=$this->render('index',null,true); $content=preg_replace_callback(self::URL_PATTERN,array($this,'fixOfflineLink'),$content); file_put_contents($docPath.'/index.html',$content); foreach($this->classes as $name=>$class) { $this->currentClass=$name; $this->pageTitle=$name; $content=$this->render('class',array('class'=>$class),true); $content=preg_replace_callback(self::URL_PATTERN,array($this,'fixOfflineLink'),$content); file_put_contents($docPath.'/'.$name.'.html',$content); } CFileHelper::copyDirectory($this->themePath.'/assets',$docPath,array('exclude'=>array('.svn'))); $content=$this->renderPartial('chmProject',null,true); file_put_contents($docPath.'/manual.hhp',$content); $content=$this->renderPartial('chmIndex',null,true); file_put_contents($docPath.'/manual.hhk',$content); $content=$this->renderPartial('chmContents',null,true); file_put_contents($docPath.'/manual.hhc',$content); } protected function buildOnlinePages($docPath,$themePath) { $this->themePath=$themePath; @mkdir($docPath); $content=$this->renderPartial('index',null,true); $content=preg_replace_callback(self::URL_PATTERN,array($this,'fixOnlineLink'),$content); file_put_contents($docPath.'/index.html',$content); foreach($this->classes as $name=>$class) { $this->currentClass=$name; $this->pageTitle=$name; $content=$this->renderPartial('class',array('class'=>$class),true); $content=preg_replace_callback(self::URL_PATTERN,array($this,'fixOnlineLink'),$content); file_put_contents($docPath.'/'.$name.'.html',$content); } } protected function buildModel($sourcePath,$options) { $files=CFileHelper::findFiles($sourcePath,$options); $model=new ApiModel; $model->build($files); return $model; } public function renderInheritance($class) { $parents=array($class->signature); foreach($class->parentClasses as $parent) { if(isset($this->classes[$parent])) $parents[]='{{'.$parent.'|'.$parent.'}}'; else $parents[]=$parent; } return implode(" »\n",$parents); } public function renderImplements($class) { $interfaces=array(); foreach($class->interfaces as $interface) { if(isset($this->classes[$interface])) $interfaces[]='{{'.$interface.'|'.$interface.'}}'; else $interfaces[]=$interface; } return implode(', ',$interfaces); } public function renderSubclasses($class) { $subclasses=array(); foreach($class->subclasses as $subclass) { if(isset($this->classes[$subclass])) $subclasses[]='{{'.$subclass.'|'.$subclass.'}}'; else $subclasses[]=$subclass; } return implode(', ',$subclasses); } public function renderTypeUrl($type) { if(isset($this->classes[$type]) && $type!==$this->currentClass) return '{{'.$type.'|'.$type.'}}'; else return $type; } public function renderSubjectUrl($type,$subject,$text=null) { if($text===null) $text=$subject; if(isset($this->classes[$type])) { return '{{'.$type.'::'.$subject.'-detail'.'|'.$text.'}}'; } else return $text; } public function renderPropertySignature($property) { if(!empty($property->signature)) return $property->signature; $sig=''; if(!empty($property->getter)) $sig=$property->getter->signature; if(!empty($property->setter)) { if($sig!=='') $sig.='
'; $sig.=$property->setter->signature; } return $sig; } public function fixMethodAnchor($class,$name) { if(isset($this->classes[$class]->properties[$name])) return $name."()"; else return $name; } protected function fixOfflineLink($matches) { if(($pos=strpos($matches[1],'::'))!==false) { $className=substr($matches[1],0,$pos); $method=substr($matches[1],$pos+2); return "{$matches[2]}"; } else return "{$matches[2]}"; } protected function fixOnlineLink($matches) { if(($pos=strpos($matches[1],'::'))!==false) { $className=substr($matches[1],0,$pos); $method=substr($matches[1],$pos+2); if($className==='index') return "{$matches[2]}"; else return "{$matches[2]}"; } else { if($matches[1]==='index') return "{$matches[2]}"; else return "{$matches[2]}"; } } }