#!/usr/bin/php This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ define('VERSION', '1.4'); if (!defined('T_COMMENT') || !extension_loaded('tokenizer') || version_compare(phpversion(), '4.3.0', '<')) { error('You need PHP version 4.3.0 or greater with the "tokenizer" extension to run this script, please upgrade'); exit; } if (!isset($argv[0])) { // not being run command line error('This program must be run from the command line using the CLI version of PHP'); exit; } elseif (!isset($argv[1])) { // use default config file if (is_file('default.ini')) { warning('Using default config file "default.ini"'); $argv[1] = 'default.ini'; } else { error('Usage: phpdoc.php [config_file]'); exit; } } /** Path PHPDoctor is running from */ define('PATH', dirname($argv[0])); /** Default template path */ define('DEFAULT_TEMPLATE', 'templates/default'); // read config file if (is_file($argv[1])) { $settings = @parse_ini_file($argv[1]); if (count($settings) == 0) { error('Could not parse configuration file "'.$argv[1].'"'); exit; } } else { error('Could not find configuration file "'.$argv[1].'"'); exit; } // record start time $startTime = getTime(); // set config directives if (isset($settings['files'])) $in_file = $settings['files']; if (isset($settings['file_path'])) { define('FILE_PATH', prefixFilename('', $settings['file_path'])); } else { define('FILE_PATH', prefixFilename('', dirname($argv[1]))); } if (isset($settings['output_dir'])) { define('OUT_DIR', prefixFilename(pathAppendSlash($settings['output_dir']), FILE_PATH)); } else { define('OUT_DIR', prefixFilename('apidocs/', FILE_PATH)); } if (isset($settings['title'])) define('LONG_TITLE', $settings['title']); else define('LONG_TITLE', 'Noname'); if (isset($settings['short_title'])) define('SHORT_TITLE', $settings['short_title']); else define('SHORT_TITLE', LONG_TITLE); if (isset($settings['subdirs'])) define('SUBDIRS', $settings['subdirs']); else define('SUBDIRS', FALSE); if (isset($settings['file_list'])) define('FILE_LIST', $settings['file_list']); if (isset($settings['template'])) define('TEMPLATE', 'templates/'.$settings['template']); else define('TEMPLATE', DEFAULT_TEMPLATE); if (isset($settings['globals'])) define('NO_GLOBAL', !$settings['globals']); else define('NO_GLOBAL', FALSE); if (isset($settings['constants'])) define('NO_CONSTANT', !$settings['constants']); else define('NO_CONSTANT', FALSE); if (isset($settings['tree'])) define('NO_TREE', !$settings['tree']); else define('NO_TREE', FALSE); if (isset($settings['default_package'])) define('DEFAULT_PACKAGE', $settings['default_package']); else define('DEFAULT_PACKAGE', strtolower(SHORT_TITLE)); if (isset($settings['warnings'])) define('DISPLAY_WARNINGS', $settings['warnings']); else define('DISPLAY_WARNINGS', TRUE); if (isset($settings['footer'])) define('FOOTER', $settings['footer']); else define('FOOTER', 'This document was generated by PHPDoctor: The PHP Documentation Creator v'.VERSION.'.'); if (isset($settings['overview_comment'])) define('OVERVIEW_COMMENT', prefixFilename($settings['overview_comment'], FILE_PATH)); else define('OVERVIEW_COMMENT', prefixFilename('overview.html', FILE_PATH)); if (isset($settings['package_comment_dir'])) define('PACKAGE_COMMENT_DIR', prefixFilename(pathAppendSlash($settings['package_comment_dir']), FILE_PATH)); else define('PACKAGE_COMMENT_DIR', FILE_PATH); if (isset($settings['hide_private'])) define('HIDE_PRIVATE', $settings['hide_private']); else define('HIDE_PRIVATE', FALSE); // define tokienizer constants if not defined so we don't throw undefined constant errors later on if (!defined('T_DOC_COMMENT')) define('T_DOC_COMMENT', 0); if (!defined('T_ML_COMMENT')) define('T_ML_COMMENT', 0); if (!defined('T_PRIVATE')) define('T_PRIVATE', 0); if (!defined('T_PROTECTED')) define('T_PROTECTED', 0); if (!defined('T_PUBLIC')) define('T_PUBLIC', 0); if (!defined('T_ABSTRACT')) define('T_ABSTRACT', 0); if (!defined('T_FINAL')) define('T_FINAL', 0); if (!defined('T_INTERFACE')) define('T_INTERFACE', 0); if (!defined('T_IMPLEMENTS')) define('T_IMPLEMENTS', 0); if (!defined('T_CONST')) define('T_CONST', 0); if (defined('FILE_LIST')) { $filename = FILE_LIST; echo 'Reading file list ', $filename, "...\n"; $filename = prefixFilename($filename, FILE_PATH); if (!is_file($filename)) { error('Could not open file '.$filename); exit; } $files = file($filename); $fileList = ''; foreach ($files as $file) { $fileList .= rtrim($file).','; } $fileList = substr($fileList, 0, -1); if (isset($in_file)) { $in_file .= ','.$fileList; } else { $in_file = $fileList; } } if (!isset($in_file) || $in_file == '') { error('No files specified to parse in config file, halting'); exit; } $objects = array(); $packages = array(); $summary = array(); $classTree = array(); $interfaceTree = array(); $pos = strpos($in_file, ','); if ($pos !== FALSE) { $inFiles = explode(',', $in_file); } else { $inFiles = array($in_file); } echo 'Output directory set as ', OUT_DIR, "\n"; if (is_dir(OUT_DIR)) { warning('Output directory already exists, overwriting existing files.'); } echo 'Documentation title set as ', LONG_TITLE, "\n"; echo 'Documentation short title set as ', SHORT_TITLE, "\n"; if (NO_GLOBAL) echo "Globals will not be parsed.\n"; if (NO_CONSTANT) echo "Constants will not be parsed.\n"; readFiles($inFiles, FILE_PATH); replaceInlineLinks($objects); setSeeLinks($objects); //dumpToFile($objects); foreach ($objects as $package_name => $package) { $package_summary = array(); $package_classTree = array(); $package_interfaceTree = array(); //set package dir $objects[$package_name]['dir'] = packageToDir($package_name); $package['package'] = $package_name; echo 'Writing package ', $package_name, "...\n"; if (isset($package['params'])) { echo ' ', count($package['params']), " globals parsed.\n"; } if (isset($package['constants'])) { echo ' ', count($package['constants']), " constants parsed.\n"; } if (isset($package['classes'])) { echo ' ', count($package['classes']), " classes parsed.\n"; } if (isset($package['interfaces'])) { echo ' ', count($package['interfaces']), " interfaces parsed.\n"; } if (isset($package['functions'])) { echo ' ', count($package['functions']), " functions parsed.\n"; } if (isset($package['classes'])) { // we have classes, output them echo " Writing classes...\n"; ksort($package['classes']); foreach ($package['classes'] as $class) { if (isset($class['shortDescription'])) { $description = $class['shortDescription']; } else { $description = 'No description'; } addToSummary($package_summary, 'classes', $class['name'], $description, $package_name); if (isset($class['extends'])) { addToTree($package_classTree, $class['name'], $package_name, $class['extends']); } else { addToTree($package_classTree, $class['name'], $package_name); } echo ' Writing ', $class['name'], "\n"; $classDetails = array('this' => &$class); if (isset($class['extends'])) { getParentsDetails('classes', $class['extends'], $classDetails, $objects); } if (isset($class['implements'])) { foreach ($class['implements'] as $interface) { getParentsDetails('interfaces', $interface, $classDetails, $objects); } } $contents = plugTemplate($classDetails, 'class', $package_name); writeFile($contents, $class['name'].'.html', $package_name); } } if (isset($package['interfaces'])) { // we have interfaces, output them echo " Writing interfaces...\n"; ksort($package['interfaces']); foreach ($package['interfaces'] as $interface) { if (isset($interface['shortDescription'])) { $description = $interface['shortDescription']; } else { $description = 'No description'; } addToSummary($package_summary, 'interfaces', $interface['name'], $description, $package_name); if (isset($interface['extends'])) { addToTree($package_interfaceTree, $interface['name'], $package_name, $interface['extends']); } else { addToTree($package_interfaceTree, $interface['name'], $package_name); } echo ' Writing ', $interface['name'], "\n"; $interfaceDetails = array('this' => &$interface); if (isset($interface['extends'])) { getParentsDetails('interfaces', $interface['extends'], $interfaceDetails, $objects); } if (isset($interface['implements'])) { foreach ($interface['implements'] as $inter) { getParentsDetails('interfaces', $inter, $interfaceDetails, $objects); } } $contents = plugTemplate($interfaceDetails, 'interface', $package_name); writeFile($contents, $interface['name'].'.html', $package_name); } } if (isset($package['functions'])) { // we have functions, output them echo " Writing functions...\n"; ksort($package['functions']); foreach ($package['functions'] as $function) { if (isset($function['shortDescription'])) { $description = $function['shortDescription']; } else { $description = 'No description'; } addToSummary($package_summary, 'functions', $function['name'], $description, $package_name); } $contents = plugTemplate($package['functions'], 'package-functions', $package_name); writeFile($contents, 'package-functions.html', $package_name); } if (isset($package['params'])) { // we have globals, output them echo " Writing globals...\n"; ksort($package['params']); foreach ($package['params'] as $key => $global) { foreach ($global as $key2 => $item) { // since we can have multiple occurances of the same global, we'll pick it up more than once, so we have to consolidate these occurances into a single item if (is_array($item) && isset($item[0])) { $global[$key2] = $item[0]; $package['params'][$key][$key2] = $item[0]; } } if (isset($global['shortDescription'])) { $description = $global['shortDescription']; } else { $description = 'No description'; } addToSummary($package_summary, 'globals', $global['name'], $description, $package_name); } $contents = plugTemplate($package['params'], 'package-globals', $package_name); writeFile($contents, 'package-globals.html', $package_name); } if (isset($package['constants'])) { // we have globals, output them echo " Writing constants...\n"; ksort($package['constants']); foreach ($package['constants'] as $constant) { if (isset($constant['shortDescription'])) { $description = $constant['shortDescription']; } else { $description = 'No description'; } addToSummary($package_summary, 'constants', $constant['name'], $description, $package_name); } $contents = plugTemplate($package['constants'], 'package-constants', $package_name); writeFile($contents, 'package-constants.html', $package_name); } echo " Writing package summary...\n"; echo " DEBUG: package_name: " . PACKAGE_COMMENT_DIR.$package_name.'.html' . "\n"; $comment_file = PACKAGE_COMMENT_DIR.$package_name.'.html'; if (is_file($comment_file)) { echo ' Reading package comment file ', $comment_file, "\n"; $comment = getHTMLContents($comment_file); } else { $comment = FALSE; } $contents = plugTemplate($package, 'package-summary', $package_name, $comment); writeFile($contents, 'package-summary.html', $package_name); echo " Writing package index...\n"; $contents = plugTemplate($package_summary, 'package-frame', $package_name); writeFile($contents, 'package-frame.html', $package_name); if (!NO_TREE) { echo " Writing package class tree...\n"; $tree = array('classes' => &$package_classTree, 'interfaces' => &$package_interfaceTree); $contents = plugTemplate($tree, 'package-tree', $package_name); writeFile($contents, 'package-tree.html', $package_name); } $summary = mergeArrays($summary, $package_summary); $classTree = mergeArrays($classTree, $package_classTree); $interfaceTree = mergeArrays($interfaceTree, $package_interfaceTree); } // write indexes and stuff echo "Writing package list...\n"; $contents = plugTemplate($objects, 'overview-frame', FALSE); writeFile($contents, 'overview-frame.html'); echo "Writing overview summary...\n"; if (is_file(OVERVIEW_COMMENT)) { echo ' Reading overview file ', OVERVIEW_COMMENT, "\n"; $comment = getHTMLContents(OVERVIEW_COMMENT); } else { $comment = FALSE; } $contents = plugTemplate($objects, 'overview-summary', FALSE, $comment); writeFile($contents, 'overview-summary.html'); echo "Writing overview index...\n"; $contents = plugTemplate($summary, 'package-frame', FALSE); writeFile($contents, 'allitems-frame.html'); if (NO_TREE) { echo "Not generating class tree\n"; } else { echo "Writing class tree...\n"; $tree = array('classes' => &$classTree, 'interfaces' => &$interfaceTree); $contents = plugTemplate($tree, 'overview-tree', FALSE); writeFile($contents, 'overview-tree.html'); } echo "Writing frameset...\n"; $contents = plugTemplate($summary, 'index', FALSE); writeFile($contents, 'index.html'); echo "Copying stylesheet...\n"; copy(PATH.'/'.getTemplateFile('stylesheet.css'), OUT_DIR.'stylesheet.css'); echo "Done\n"; echo 'Documentation generated in ', round(getTime() - $startTime, 2), " seconds\n"; /** * Read input files and parse them into tokens. This function is * recursively called by itself if PHPDoctor is searching sub * directories. * * @param array inFiles Array of input files to parse */ function readFiles($inFiles, $dir) { global $objects, $tokens; foreach ($inFiles as $filename) { $filename = prefixFilename($filename, $dir); if (!is_file($filename)) { $globResults = glob(str_replace('\\', '/', $filename)); // switch slashes since old versions of glob need forward slashes if ($globResults) { foreach ($globResults as $filename) { if (!is_dir($filename)) { echo 'Reading file ', $filename, "\n"; $fileString = @file_get_contents($filename); if ($fileString) { $tokens = token_get_all($fileString); $objects = mergeArrays($objects, parseTokens($tokens)); } else { error('Could not read file '.$filename); } } } } else { if (!SUBDIRS) { error('Could not find file '.$filename); } } } else { echo 'Reading file ', $filename, "\n"; $fileString = @file_get_contents($filename); if ($fileString) { $tokens = token_get_all($fileString); $objects = mergeArrays($objects, parseTokens($tokens)); } else { error('Could not read file '.$filename); } } } if (SUBDIRS) { // recurse into subdir $globResults = glob($dir.'*', GLOB_ONLYDIR); // get subdirs if ($globResults) { foreach ($globResults as $filename) { echo 'Reading directory ', $filename, "...\n"; readFiles($inFiles, $filename); } } } } /** * Write doc tags to output. Doc tag results are encased in HTML definition list tags. * * @param array object Object to write tags for */ function writeTags(&$object) { if (isset($object['params'])) { echo '
', $param['name'], '';
if (isset($param['description'])) {
echo ' - ', $param['description'], ''.parseLink($link, $packageName).'';
$lastPos = $match[1] + strlen($match[0]);
}
$text = $newtext.substr($text, $lastPos);
}
$matches = array();
preg_match_all('/{@linkplain .*}/U', $text, $matches, PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER);
if (isset($matches[0]) && count($matches[0]) > 0) {
$newtext = '';
$lastPos = 0;
foreach ($matches[0] as $match) {
$link = substr($match[0], 12, strlen($match[0]) - 13);
$newtext .= substr($text, $lastPos, $match[1] - $lastPos).parseLink($link, $packageName);
$lastPos = $match[1] + strlen($match[0]);
}
$text = $newtext.substr($text, $lastPos);
}
$items[$key] = $text;
}
}
return $items;
}
/**
* Replace see doc tags with HTML tags. This function recursively traverses an array looking for see entries to convert.
*
* @param array items Object array to traverse for links
* @return array The parsed array
*/
function setSeeLinks(&$items) {
foreach ($items as $key => $item) {
if ($key === 'see' && is_array($item)) {
if (isset($items['package']) && !is_array($items['package'])) {
$packageName = $items['package'];
} else {
$packageName = DEFAULT_PACKAGE;
}
foreach ($item as $seekey => $see) {
$items[$key][$seekey] = parseLink(trim($see), $packageName);
}
} elseif (is_array($item)) {
$items[$key] = setSeeLinks($item);
}
}
return $items;
}
/**
* Parse the token array into objects.
*
* @param array tokens Token array
* @return array Object array
*/
function parseTokens(&$tokens) {
echo "Parsing tokens...\n";
$objects = array();
$docComment = array();
$access = 'public';
$abstract = FALSE;
$final = FALSE;
$static = FALSE;
$var = FALSE;
$const = FALSE;
$object = &$objects[];
$object['inBody'] = 0;
$open_curly_braces = FALSE;
$in_parsed_string = FALSE;
foreach ($tokens as $key => $token) {
if (!$in_parsed_string && is_array($token)) {
switch ($token[0]) {
case T_COMMENT: // read comment
case T_ML_COMMENT: // and multiline comment (deprecated I think)
case T_DOC_COMMENT: // and catch PHP5 doc comment token too
$result = processDocComment($token[1]);
if ($result) $docComment = mergeArrays($docComment, $result);
break;
case T_CLASS: // read class
$subObject = array();
$subObject['..'] = &$object;
$subObject['package'] = DEFAULT_PACKAGE;
$subObject['name'] = getNext($tokens, $key, T_STRING);
$subObject['abstract'] = $abstract;
$subObject['final'] = $final;
$subObject['docComment'] = $docComment;
if (isset($docComment['package'])) $subObject['package'] = $docComment['package'];
$subObject['dir'] = packageToDir($subObject['package']);
$object = &$object[$subObject['package']]['classes'][$subObject['name']];
if ($object) {
$object = array_merge($object, $subObject);
} else {
$object = $subObject;
}
$docComment = array();
$abstract = FALSE;
$final = FALSE;
break;
case T_INTERFACE: // read interface
$subObject = array();
$subObject['..'] = &$object;
$subObject['package'] = DEFAULT_PACKAGE;
$subObject['name'] = getNext($tokens, $key, T_STRING);
$subObject['abstract'] = $abstract;
$subObject['final'] = $final;
$subObject['docComment'] = $docComment;
if (isset($docComment['package'])) $subObject['package'] = $docComment['package'];
$subObject['dir'] = packageToDir($subObject['package']);
$object = &$object[$subObject['package']]['interfaces'][$subObject['name']];
if ($object) {
$object = array_merge($object, $subObject);
} else {
$object = $subObject;
}
$docComment = array();
$abstract = FALSE;
$final = FALSE;
break;
case T_EXTENDS: // get extends clause
$object['extends'] = getNext($tokens, $key, T_STRING);
break;
case T_IMPLEMENTS: // get implements clause
while($tokens[++$key] != '{') {
if ($tokens[$key][0] == T_STRING) {
$object['implements'][] = $tokens[$key][1];
}
}
break;
case T_PRIVATE:
$access = 'private';
break;
case T_PROTECTED:
$access = 'protected';
break;
case T_ABSTRACT:
$abstract = TRUE;
break;
case T_FINAL:
$final = TRUE;
break;
case T_STATIC:
$static = TRUE;
break;
case T_VAR:
$var = TRUE;
break;
case T_CONST:
$const = TRUE;
break;
case T_FUNCTION: // read function
$subObject = array();
$subObject['..'] = &$object;
if (!isset($object['name'])) { // only set package name for top level functions
$subObject['package'] = DEFAULT_PACKAGE;
}
$subObject['name'] = getNext($tokens, $key, T_STRING);
$subObject['access'] = $access;
if (substr($subObject['name'], 0, 1) == '_' && $subObject['access'] == 'public') $subObject['access'] = 'private';
$subObject['abstract'] = $abstract;
$subObject['final'] = $final;
$subObject['static'] = $static;
$subObject['return']['type'] = 'void';
$subObject['docComment'] = $docComment;
if (isset($docComment['package'])) $subObject['package'] = $docComment['package'];
if (!HIDE_PRIVATE || ($subObject['access'] != 'private' && (!isset($docComment['access']) || $docComment['access'] != 'private'))) {
if (isset($subObject['package'])) {
$subObject['dir'] = packageToDir($subObject['package']);
$object = &$object[$subObject['package']]['functions'][$subObject['name']];
} else {
$object = &$object['functions'][$subObject['name']];
}
if ($object) {
$object = array_merge($object, $subObject);
} else {
$object = $subObject;
}
}
$docComment = array();
$access = 'public';
$abstract = FALSE;
$final = FALSE;
$static = FALSE;
break;
case T_STRING:
if (!NO_CONSTANT && $token[1] == 'define') { // read global constant
$subObject = array();
if (!isset($object['classes']) || !isset($object['interfaces'])) { // only set package name for top level functions
$subObject['package'] = DEFAULT_PACKAGE;
}
$subObject['name'] = trim(getNext($tokens, $key, T_CONSTANT_ENCAPSED_STRING), '\'"');
$subObject['type'] = 'const';
addDocComment($subObject, $docComment);
if (isset($docComment['package'])) $subObject['package'] = $docComment['package'];
if (isset($subObject['package'])) {
$subObject['dir'] = packageToDir($subObject['package']);
if (isset($object['params'][$subObject['name']])) {
$object[$subObject['package']]['constants'][$subObject['name']] = array_merge($object['params'][$subObject['name']], $subObject);
} else {
$object[$subObject['package']]['constants'][$subObject['name']] = $subObject;
}
} else {
if (isset($object['params'][$subObject['name']])) {
$object['constants'][$subObject['name']] = array_merge($object['params'][$subObject['name']], $subObject);
} else {
$object['constants'][$subObject['name']] = $subObject;
}
}
$docComment = array();
} elseif ($const) { // member constant
$subObject = array();
$subObject['final'] = $final;
$subObject['static'] = $static;
$subObject['type'] = 'const';
addDocComment($subObject, $docComment);
do {
if ($tokens[$key][0] == T_STRING) {
$subObject['access'] = $access;
$subObject['name'] = $tokens[$key][1];
$object['vars'][] = $subObject;
}
$key++;
} while($tokens[$key] != ';');
$docComment = array();
$final = FALSE;
$static = FALSE;
$const = FALSE;
}
break;
case T_VARIABLE: // read variables / parameters
if ($var || $access == 'private' || $access == 'protected') { // member var
$subObject = array();
$subObject['final'] = $final;
$subObject['static'] = $static;
$subObject['type'] = 'var';
$subObject['default'] = '';
addDocComment($subObject, $docComment);
$default = FALSE;
do {
$key++;
if ($tokens[$key] == '=') {
$default = TRUE;
} elseif ($tokens[$key] == ',' || $tokens[$key] == ';') {
$subObject['access'] = $access;
$subObject['name'] = getPrev($tokens, $key, T_VARIABLE);
if (substr($subObject['name'], 1, 1) == '_' && $subObject['access'] == 'public') $subObject['access'] = 'private';
$subObject['default'] = trim($subObject['default']);
if (!HIDE_PRIVATE || $subObject['access'] != 'private') {
$object['vars'][] = $subObject;
}
$subObject['default'] = '';
$default = FALSE;
} elseif($default) {
if (is_array($tokens[$key])) {
$subObject['default'] .= $tokens[$key][1];
} else {
$subObject['default'] .= $tokens[$key];
}
}
} while($tokens[$key] != ';');
$docComment = array();
$access = 'public';
$final = FALSE;
$static = FALSE;
$var = FALSE;
} elseif ((!isset($object['inBody']) || $object['inBody'] == 0) && (!NO_GLOBAL || isset($object['..']))) {
$subObject = array();
if (!isset($object['name'])) { // only set package name for top level functions
$subObject['package'] = DEFAULT_PACKAGE;
}
$subObject['name'] = $tokens[$key][1];
if (isset($tokens[$key - 1][0]) && is_array($tokens[$key - 2]) && $tokens[$key - 2][0] == T_STRING && $tokens[$key - 1][0] == T_WHITESPACE) {
$subObject['type'] = $tokens[$key - 2][1];
} else {
$subObject['type'] = 'var';
}
addDocComment($subObject, $docComment);
if ($tokens[$key + 1] == '=' || $tokens[$key + 2] == '=') {
$subObject['default'] = '';
$key2 = $key + 1;
do {
if (is_array($tokens[$key2])) {
if ($tokens[$key2][1] != '=') $subObject['default'] .= $tokens[$key2][1];
} else {
if ($tokens[$key2] != '=') $subObject['default'] .= $tokens[$key2];
}
$key2++;
} while(isset($tokens[$key2]) && $tokens[$key2] != ';' && $tokens[$key2] != ',' && $tokens[$key2] != ')');
$subObject['default'] = trim($subObject['default'], ' ()');
}
if (isset($docComment['package'])) $subObject['package'] = $docComment['package'];
if (isset($subObject['package'])) {
$subObject['dir'] = packageToDir($subObject['package']);
if (isset($object['params'][substr($tokens[$key][1], 1)])) {
$object[$subObject['package']]['params'][substr($tokens[$key][1], 1)] = array_merge($subObject, $object['params'][substr($tokens[$key][1], 1)]);
} else {
$object[$subObject['package']]['params'][substr($tokens[$key][1], 1)] = $subObject;
}
} else {
if (isset($object['params'][substr($tokens[$key][1], 1)])) {
$object['params'][substr($tokens[$key][1], 1)] = array_merge($subObject, $object['params'][substr($tokens[$key][1], 1)]);
} else {
$object['params'][substr($tokens[$key][1], 1)] = $subObject;
}
}
$docComment = array();
}
break;
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES: // we must catch this so we don't accidently step out of the current block
$open_curly_braces = TRUE;
break;
}
} else {
switch ($token) {
case '{':
if (!$in_parsed_string) {
if (!isset($object['inBody'])) $object['inBody'] = 0;
$object['inBody']++;
}
break;
case '}':
if (!$in_parsed_string) {
if ($open_curly_braces) { // end of var curly brace syntax
$open_curly_braces = FALSE;
} else {
$object['inBody']--;
if ($object['inBody'] == 0 && isset($object['..'])) {
addDocComment($object, $object['docComment']);
$object = &$object['..'];
$docComment = array();
$access = 'public';
$abstract = FALSE;
$final = FALSE;
$static = FALSE;
}
}
}
break;
case ';': // case for closing abstract functions
if (!$in_parsed_string && !isset($object['inBody'])) {
addDocComment($object, $object['docComment']);
$object = &$object['..'];
$docComment = array();
$access = 'public';
$abstract = FALSE;
$final = FALSE;
$static = FALSE;
}
break;
case '"': // catch parsed strings so as to ignore tokens within
$in_parsed_string = !$in_parsed_string;
break;
}
}
}
//print_r(get_defined_constants());
//dumpToFile($tokens);
//dumpToFile($objects);
return cleanObjectArray($objects[0]);
}
/**
* Get next token of a certain type from token array
*
* @param array tokens Token array to search
* @param int key Key to start searching from
* @param int whatToGet Type of token to look for
* @return str Value of found token
*/
function getNext(&$tokens, $key, $whatToGet) {
$key++;
while(!is_array($tokens[$key]) || $tokens[$key][0] != $whatToGet) {
$key++;
if (!isset($tokens[$key])) return FALSE;
}
return $tokens[$key][1];
}
/**
* Get previous token of a certain type from token array
*
* @param array tokens Token array to search
* @param int key Key to start searching from
* @param int whatToGet Type of token to look for
* @return str Value of found token
*/
function getPrev(&$tokens, $key, $whatToGet) {
$key--;
while(!is_array($tokens[$key]) || $tokens[$key][0] != $whatToGet) {
$key--;
if (!isset($tokens[$key])) return FALSE;
}
return $tokens[$key][1];
}
/**
* Clean up object array after generation. This function removes work fields from the array.
*
* @param array items Object array to tidy
* @return Tidied object array
*/
function cleanObjectArray($items) {
if (is_array($items)) {
foreach ($items as $key => $item) {
if ($key === 'inBody' || $key === '..' || $key === 'docComment') {
unset($items[$key]);
} else {
$items[$key] = cleanObjectArray($item);
}
}
}
return $items;
}
/**
* Turn an array into a space seperated string
*
* @param array array Array to turn into a string
* @param int startIndex Ignore items before this index
*/
function arrayToString(&$array, $startIndex) {
$string = '';
foreach ($array as $index => $item) {
if ($index >= $startIndex) $string .= $item.' ';
}
return $string;
}
/**
* Process a doc comment into a doc tag array.
*
* @param str comment The comment to process
* @return array Array of doc comment data
*/
function processDocComment($comment) {
$docComment = array();
$comment = explode("\n", $comment);
if (substr(trim($comment[0]), 0, 3) != '/**') return FALSE;
$tagName = 'shortDescription';
$paramName = FALSE;
$tagIndex = FALSE;
foreach ($comment as $line) {
$line = trim($line, "\t\r /*"); // strip whitespace and asterix's from beginning
$words = array();
foreach (explode(' ', $line) as $word) { // explode line into words and eat whitespace
if ($word != '') $words[] = trim($word, "\t ");
}
if (isset($words[0])) {
if (substr($words[0], 0, 1) == '@') { // first word is tag
$tagName = $words[0]; // set tag name
$paramName = FALSE;
$tagIndex = FALSE;
}
switch ($tagName) { // get tag contents
case '@package':
if (isset($words[1])) $docComment['package'] = rtrim(arrayToString($words, 1));
break;
case '@access':
if (isset($words[1])) $docComment['access'] = $words[1];
break;
case '@param':
if ($paramName && isset($docComment['params'][$paramName]['description'])) {
$docComment['params'][$paramName]['description'] .= arrayToString($words, 0);
} elseif (isset($words[2])) {
if (substr($words[2], 0, 1) == '$') {
$paramName = substr($words[2], 1);
} elseif (substr($words[2], 0, 2) == '&$') {
$paramName = substr($words[2], 2);
} else {
$paramName = $words[2];
}
$docComment['params'][$paramName]['name'] = $paramName;
$docComment['params'][$paramName]['type'] = $words[1];
if (isset($words[3])) $docComment['params'][$paramName]['description'] = arrayToString($words, 3);
}
break;
case '@return':
if (isset($docComment['return']['description'])) {
$docComment['return']['description'] .= arrayToString($words, 0);
} elseif (isset($words[1])) {
$docComment['return']['type'] = $words[1];
if (isset($words[2])) $docComment['return']['description'] = arrayToString($words, 2);
}
break;
case '@see':
if (isset($words[1])) $docComment['see'][] = arrayToString($words, 1);
break;
case '@var':
case '@type':
if (isset($docComment['type']) && isset($docComment['description'])) {
$docComment['description'] .= arrayToString($words, 0);
} elseif (isset($words[1])) {
$docComment['type'] = $words[1];
if (isset($words[2])) $docComment['description'] = arrayToString($words, 2);
}
break;
case 'shortDescription':
case 'description':
if (!isset($docComment[$tagName])) $docComment[$tagName] = '';
foreach ($words as $word) {
$docComment[$tagName] .= $word.' ';
if ($tagName == 'shortDescription') {
if (substr($word, -1, 1) == '.') {
$docComment['description'] = '';
$tagName = 'description';
}
}
}
$docComment[$tagName] = $docComment[$tagName];
break;
default:
$tag = substr($tagName, 1);
if ($tagIndex && isset($docComment[$tag][$tagIndex])) {
$docComment[$tag][$tagIndex] .= arrayToString($words, 0);
} else {
$docComment[$tag][] = arrayToString($words, 1);
$tagIndex = count($docComment[$tag]);
}
}
}
}
return $docComment;
}
/**
* Add a doc comment to an object.
*
* @param array subObject Object to add doc comment data to
* @param array docComment Doc comment to add
*/
function addDocComment(&$subObject, $docComment) {
if (isset($docComment['shortDescription'])) {
$subObject['shortDescription'] = $docComment['shortDescription'];
}
if (isset($docComment['description'])) {
$subObject['description'] = $docComment['description'];
}
foreach ($docComment as $tag => $value) {
if ($tag == 'params') {
foreach($value as $name => $param) {
if (!isset($subObject['params'][$name])) { // param doc comment found for non-defined parameter
$warningStr = 'Unknown param "'.$name.'" found for method "';
if (isset($subObject['..']['name'])) $warningStr .= $subObject['..']['name'].'::';
$warningStr .= $subObject['name'].'".';
warning($warningStr);
}
}
}
if (isset($subObject[$tag]) && is_array($value)) { // merge it
$subObject[$tag] = mergeArrays($subObject[$tag], $value);
} else { // just add it
$subObject[$tag] = $value;
}
}
}
/**
* Recursively merge two arrays into a single array. Same values are overwritten by the values in array two.
*
* @param array one Array one
* @param array two Array two
*
* @return array Merged array
*/
function mergeArrays($one, $two) {
foreach ($two as $key => $item) {
if (isset($one[$key]) && is_array($one[$key]) && is_array($item)) {
$one[$key] = mergeArrays($one[$key], $item);
} else {
$one[$key] = $item;
}
}
return $one;
}
/**
* Generate the class tree. This function is called recursively for each class with children.
*
* @param array t Object array
* @param str name Name of parent class
* @param int depth Current depth of recursion
*/
function classTree(&$t, $path, $name = NULL, $depth = 2) {
if ($name == NULL) {
$class = $t['this'];
} elseif (isset($t[$name])) {
$class = $t[$name];
}
if (isset($class['extends'])) {
$depth = classTree($t, $path, $class['extends'], $depth);
}
if ($name == NULL) {
echo '', $class['package'], '.', $class['name'], '