code == 200) { $function_matches = array(); preg_match_all('!^[a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)\(.*\n.*$!m', $response->data, $function_matches, PREG_SET_ORDER); foreach ($function_matches as $function_match) { $docblock = array( 'object_name' => $function_match[1], 'branch_name' => 'php', 'object_type' => 'function', 'file_name' => $location, 'title' => $function_match[1], 'summary' => api_documentation_summary($function_match[0]), 'documentation' => $function_match[0], 'code' => ''); api_save_documentation($docblock); } } } /** * Read in the file at the given path and pass it off to the appropriate handler * for parsing. */ function api_parse_file($file_path, $branch_name, $file_name) { if (preg_match('!\.(php|module|inc|install|engine|theme)$!', $file_name)) { watchdog('api', t('Parsing %path...', array('%path' => $file_path))); api_parse_php_file($file_path, $branch_name, $file_name); return TRUE; } if (preg_match('!\.(txt)$!', $file_name)) { watchdog('api', t('Parsing %path...', array('%path' => $file_path))); api_parse_text_file($file_path, $branch_name, $file_name); return TRUE; } if (preg_match('!\.(htm|html)$!', $file_name)) { watchdog('api', t('Parsing %path...', array('%path' => $file_path))); api_parse_html_file($file_path, $branch_name, $file_name); return TRUE; } return FALSE; } /** * Read in the file at the given path and parse it as if it consisted entirely of * documentation. */ function api_parse_text_file($file_path, $branch_name, $file_name) { $source = file_get_contents($file_path); $docblock_matches = array(); $docblocks = array(); // Set up documentation block for file, in case it is not explicitly defined. $docblocks[0] = array( 'object_name' => $file_name, 'branch_name' => $branch_name, 'object_type' => 'file', 'file_name' => $file_name, 'title' => strpos($file_name, '/') ? substr($file_name, strrpos($file_name, '/') + 1) : $file_name, 'summary' => api_documentation_summary(api_format_documentation($source, $branch_name)), 'documentation' => api_format_documentation($source, $branch_name), 'code' => api_format_php($source)); $docblocks[0]['version'] = ''; $version_match = array(); if (preg_match('!\$'.'Id: .*?,v (.*?) (.*?) (.*?) (.*?) Exp \$!', $source, $version_match)) { $docblocks[0]['version'] = $version_match[1] .' (checked in on '. $version_match[2] .' at '. $version_match[3] .' by '. $version_match[4] .')'; } $docblocks[0]['modified'] = filemtime($file_path); $old_dids = array(); $result = db_query("SELECT did FROM {api_documentation} WHERE branch_name = '%s' AND file_name = '%s'", $branch_name, $file_name); while ($object = db_fetch_object($result)) { $old_dids[] = $object->did; } $dids = array(); foreach ($docblocks as $docblock) { $dids[] = api_save_documentation($docblock); } $old_dids = array_diff($old_dids, $dids); if (count($old_dids) > 0) { $old_dids = implode(',', $old_dids); db_query('DELETE FROM {api_documentation} WHERE did IN (%s)', $old_dids); db_query('DELETE FROM {api_file} WHERE did IN (%s)', $old_dids); db_query('DELETE FROM {api_function} WHERE did IN (%s)', $old_dids); db_query('DELETE FROM {api_reference_storage} WHERE from_did IN (%s) OR to_did IN (%s)', $old_dids, $old_dids); } } /** * Read in the file at the given path and parse it as an HTML file. */ function api_parse_html_file($file_path, $branch_name, $file_name) { $source = file_get_contents($file_path); $docblocks = array(); // Set up documentation block for file, in case it is not explicitly defined. $docblocks[0] = array( 'object_name' => $file_name, 'branch_name' => $branch_name, 'object_type' => 'file', 'file_name' => $file_name, 'title' => strpos($file_name, '/') ? substr($file_name, strrpos($file_name, '/') + 1) : $file_name, 'summary' => '', 'documentation' => '', 'code' => '
'. htmlspecialchars($source) .'', ); $title_match = array(); if (preg_match('!
function ')), $function_call_matches, PREG_SET_ORDER);
foreach ($function_call_matches as $function_call_match) {
$docblock['function calls'][$function_call_match[1]] = $function_call_match[1];
}
// Determine group membership.
$group_matches = array();
preg_match_all('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches);
$docblock['groups'] = $group_matches[2];
$docblock['content'] = preg_replace('!@ingroup.*?\n!', '', $docblock['content']);
foreach ($nested_groups as $group_id) {
if (!empty($group_id)) {
$docblock['groups'][] = $group_id;
}
}
}
else if (substr($source, $code_start, 6) == 'define') {
$constant_matches = array();
$docblock['object_type'] = 'constant';
preg_match('!^define\([\'"]([a-zA-Z0-9_]+)[\'"]!', substr($source, $code_start), $constant_matches);
$docblock['object_name'] = $constant_matches[1];
$docblock['title'] = $constant_matches[1];
$code_end = strpos($source, ';', $code_start) + 1;
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
$docblock['code'] = api_format_php("");
$docblock['start_line'] = substr_count(substr($source, 0, $code_start), "\n");
// Determine group membership.
$group_matches = array();
preg_match_all('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches);
$docblock['groups'] = $group_matches[2];
$docblock['content'] = preg_replace('!@ingroup.*?\n!', '', $docblock['content']);
foreach ($nested_groups as $group_id) {
if (!empty($group_id)) {
$docblock['groups'][] = $group_id;
}
}
}
else if (strpos($docblock['content'], '@mainpage') !== FALSE) {
$mainpage_matches = array();
preg_match('!@mainpage (.*?)\n!', $docblock['content'], $mainpage_matches);
$docblock['title'] = $mainpage_matches[1];
$docblock['content'] = preg_replace('!@mainpage.*?\n!', '', $docblock['content']);
$docblock['object_type'] = 'mainpage';
$docblock['object_name'] = $branch_name;
}
else if (strpos($docblock['content'], '@file') !== FALSE) {
$docblocks[0]['content'] = str_replace('@file', '', $docblock['content']);
$docblocks[0]['documentation'] = api_format_documentation($docblocks[0]['content'], $branch_name);
$docblocks[0]['summary'] = api_documentation_summary($docblocks[0]['documentation']);
}
else if (strpos($docblock['content'], '@defgroup') !== FALSE) {
$group_matches = array();
preg_match('!@defgroup ([a-zA-Z0-9_]+) +(.*?)\n!', $docblock['content'], $group_matches);
$docblock['object_name'] = $group_matches[1];
$docblock['title'] = $group_matches[2];
$docblock['content'] = preg_replace('!@defgroup.*?\n!', '', $docblock['content']);
$docblock['object_type'] = 'group';
}
// Handle nested function groups.
if (strpos($docblock['content'], '@{') !== FALSE) {
if ($docblock['object_type'] == 'group') {
array_push($nested_groups, $docblock['object_name']);
}
else {
$group_matches = array();
if (preg_match('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches)) {
array_push($nested_groups, $group_matches[2]);
}
else {
array_push($nested_groups, '');
}
}
}
if (strpos($docblock['content'], '@}') !== FALSE) {
array_pop($nested_groups);
}
if ($docblock['object_type'] != '') {
$docblock['documentation'] = api_format_documentation($docblock['content'], $branch_name);
$docblock['summary'] = api_documentation_summary($docblock['documentation']);
$docblocks[] = $docblock;
}
}
// Find undocumented functions.
$function_matches = array();
preg_match_all('%(? $function_match[2][0],
'branch_name' => $branch_name,
'object_type' => 'function',
'file_name' => $file_name,
'title' => $function_match[2][0],
'summary' => '',
'documentation' => '',
'code' => '');
$docblock['signature'] = $function_match[1][0];
$docblock['parameters'] = '';
$docblock['return'] = '';
$docblock['groups'] = array();
$code_start = $function_match[0][1];
$code_end = strpos($source, "\n}", $code_start) + 2;
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
$docblock['code'] = api_format_php("");
$docblock['start_line'] = substr_count(substr($source, 0, $code_start), "\n");
$docblocks[] = $docblock;
}
// Find undocumented constants.
$constant_matches = array();
preg_match_all('%(? $constant_match[1][0],
'branch_name' => $branch_name,
'object_type' => 'constant',
'file_name' => $file_name,
'title' => $constant_match[1][0],
'summary' => '',
'documentation' => '',
'code' => '');
$docblock['groups'] = array();
$code_start = $constant_match[0][1];
$code_end = strpos($source, ';', $code_start) + 1;
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
$docblock['code'] = api_format_php("");
$docblock['start_line'] = substr_count(substr($source, 0, $code_start), "\n");
$docblocks[] = $docblock;
}
$old_dids = array();
$result = db_query("SELECT did FROM {api_documentation} WHERE branch_name = '%s' AND file_name = '%s'", $branch_name, $file_name);
while ($object = db_fetch_object($result)) {
$old_dids[] = $object->did;
}
$dids = array();
foreach ($docblocks as $docblock) {
$dids[] = api_save_documentation($docblock);
}
$old_dids = array_diff($old_dids, $dids);
if (count($old_dids) > 0) {
$old_dids = implode(',', $old_dids);
db_query('DELETE FROM {api_documentation} WHERE did IN (%s)', $old_dids);
db_query('DELETE FROM {api_file} WHERE did IN (%s)', $old_dids);
db_query('DELETE FROM {api_function} WHERE did IN (%s)', $old_dids);
db_query('DELETE FROM {api_reference_storage} WHERE from_did IN (%s) OR to_did IN (%s)', $old_dids, $old_dids);
}
}
/**
* Save a documentation block into the database.
*
* @param &$docblock
* An array containing information about the documentation block.
* @return
* The documentation ID of the inserted/updated construct.
*/
function api_save_documentation(&$docblock) {
$result = db_query("SELECT did FROM {api_documentation} WHERE object_name = '%s' AND branch_name = '%s' AND object_type = '%s'", $docblock['object_name'], $docblock['branch_name'], $docblock['object_type']);
if (db_num_rows($result) > 0) {
$did = db_result($result);
db_query("UPDATE {api_documentation} SET title = '%s', file_name = '%s', summary = '%s', documentation = '%s', code = '%s' WHERE did = %d", $docblock['title'], $docblock['file_name'], $docblock['summary'], $docblock['documentation'], $docblock['code'], $did);
}
else {
$did = db_next_id('{api_documentation}_did');
db_query("INSERT INTO {api_documentation} (did, object_name, branch_name, object_type, title, file_name, summary, documentation, code) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $did, $docblock['object_name'], $docblock['branch_name'], $docblock['object_type'], $docblock['title'], $docblock['file_name'], $docblock['summary'], $docblock['documentation'], $docblock['code']);
}
switch ($docblock['object_type']) {
case 'function':
db_query('DELETE FROM {api_function} WHERE did = %d', $did);
db_query("INSERT INTO {api_function} (did, signature, start_line, parameters, `return`) VALUES (%d, '%s', %d, '%s', '%s')", $did, $docblock['signature'], $docblock['start_line'], $docblock['parameters'], $docblock['return']);
if (is_array($docblock['function calls'])) {
foreach ($docblock['function calls'] as $function_name) {
api_reference($docblock['branch_name'], 'function', $function_name, $did);
}
}
break;
case 'file':
db_query('DELETE FROM {api_file} WHERE did = %d', $did);
db_query("INSERT INTO {api_file} (did, modified, version) VALUES (%d, %d, '%s')", $did, $docblock['modified'], $docblock['version']);
break;
}
if (is_array($docblock['groups'])) {
db_query("DELETE FROM {api_reference_storage} WHERE branch_name = '%s' AND object_type = 'group' AND from_did = %d", $docblock['branch_name'], $did);
foreach ($docblock['groups'] as $group_name) {
api_reference($docblock['branch_name'], 'group', $group_name, $did);
}
}
return $did;
}
/**
* Format a documentation block as HTML.
*/
function api_format_documentation($documentation, $branch_name) {
// Don't do processing on empty text (so we don't end up with empty paragraphs).
$documentation = trim($documentation);
if (empty($documentation)) {
return '';
}
$documentation = check_plain($documentation);
// Process the @link tag.
$documentation = preg_replace('!@link ((http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://)([a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+*~#&=/;-])) (.*?) @endlink!', '$4', $documentation);
$documentation = preg_replace('!@link '. API_RE_FILENAME .' (.*?) @endlink!', str_replace('%24', '$', l('$2', 'api/file/$1/'. $branch_name)), $documentation);
$documentation = preg_replace('!@link ([a-zA-Z0-9_]+)\(\) (.*?) @endlink!', str_replace('%24', '$', l('$2', 'api/function/$1/'. $branch_name)), $documentation);
$documentation = preg_replace('!@link /([a-zA-Z0-9_/-]+) (.*?) @endlink!', str_replace('%24', '$', l('$2', '$1')), $documentation);
$documentation = preg_replace('!@link ([a-zA-Z0-9_]+) (.*?) @endlink!', str_replace('%24', '$', l('$2', 'api/group/$1/'. $branch_name)), $documentation);
// Process the @see tag
$documentation = preg_replace('!\n@see (.*[^.])\.?!', 'See also
$1
', $documentation);
// Replace left over curly braces
$documentation = preg_replace('!@[{}]!', '', $documentation);
// Process the @code @endcode tags.
$documentation = preg_replace_callback('!@code(.+?)@endcode!s', 'api_format_embedded_php', $documentation);
// Format lists
$documentation = api_format_documentation_lists($documentation);
// Convert newlines into paragraphs.
$documentation = preg_replace('|\n*$|', '', $documentation) ."\n\n"; // just to make things a little easier, pad the end
$documentation = preg_replace('!\n?(.+?)(?:\n\s*\n|\z)!s', "$1
\n", $documentation); // make paragraphs, including one at the end
$documentation = preg_replace('!\s*?
!', '', $documentation); // under certain strange conditions it could create a P of entirely whitespace
return $documentation;
}
/**
* Regexp replace callback for code tags.
*/
function api_format_embedded_php($matches) {
return "\n\n". api_format_php("") ."\n\n";
}
/*
* Parse a block of text for lists that use hyphens or asterisks as bullets, and
* format them as proper HTML lists.
*/
function api_format_documentation_lists($documentation) {
$lines = explode("\n", $documentation);
$output = '';
$bullet_indents = array(-1);
foreach ($lines as $line) {
$matches = array();
preg_match('!^( *)([*-] )?(.*)$!', $line, $matches);
$indent = strlen($matches[1]);
$bullet_exists = $matches[2];
if ($indent < $bullet_indents[0]) {
// First close off any lists that have completed.
while ($indent < $bullet_indents[0]) {
array_shift($bullet_indents);
$output .= '';
}
}
if ($indent == $bullet_indents[0]) {
if ($bullet_exists) {
// A new bullet at the same indent means a new list item.
$output .= '';
}
else {
// If the indent is the same, but there is no bullet, that also
// signifies the end of the list.
array_shift($bullet_indents);
$output .= ' ';
}
}
if ($indent > $bullet_indents[0] && $bullet_exists) {
// A new list at a lower level.
array_unshift($bullet_indents, $indent);
$output .= '- ';
}
$output .= $matches[3] ."\n";
}
// Clean up any unclosed lists.
array_pop($bullet_indents);
foreach ($bullet_indents as $indent) {
$output .= '
';
}
return $output;
}
/**
* Retrieve a summary from a documentation block.
*/
function api_documentation_summary($documentation) {
$pos = strpos($documentation, "");
if ($pos !== FALSE) {
$documentation = substr($documentation, 0, $pos);
}
$documentation = trim(strip_tags($documentation));
if (strlen($documentation) > 255) {
return substr($documentation, 0, strrpos(substr($documentation, 0, 252), ' ')) .'...';
}
else {
return $documentation;
}
}
/**
* Colorize and format a PHP script.
*/
function api_format_php($code) {
$output = '';
if (!defined('T_ML_COMMENT')) {
define('T_ML_COMMENT', T_COMMENT);
}
if (!defined('T_DOC_COMMENT')) {
define('T_DOC_COMMENT', T_COMMENT);
}
$tokens = token_get_all($code);
$in_string = FALSE;
foreach ($tokens as $token) {
if ($in_string) {
if ($token == '"') {
$output .= '"';
$in_string = FALSE;
}
else {
$output .= is_array($token) ? htmlspecialchars($token[1]) : htmlspecialchars($token);
}
continue;
}
else if ($token == '"') {
$output .= '"';
$in_string = TRUE;
continue;
}
if (is_array($token)) {
$type = $token[0];
$value = htmlspecialchars($token[1]);
switch ($type) {
case T_OPEN_TAG:
case T_CLOSE_TAG:
$output .= ''. $value .'';
break;
case T_COMMENT:
case T_ML_COMMENT:
case T_DOC_COMMENT:
$output .= ''. $value .'';
break;
case T_VARIABLE:
$output .= ''. $value .'';
break;
case T_CONSTANT_ENCAPSED_STRING:
case T_INLINE_HTML:
$output .= ''. $value .'';
break;
case T_STRING:
$output .= ''. $value .'';
break;
case T_LNUMBER:
case T_DNUMBER:
$output .= ''. $value .'';
break;
case T_ARRAY_CAST: case T_ARRAY: case T_AS: case T_BOOL_CAST:
case T_BREAK: case T_CASE: case T_CLASS: case T_CONST:
case T_CONTINUE: case T_DECLARE: case T_DEFAULT: case T_DO:
case T_DOUBLE_CAST: case T_ECHO: case T_ELSE: case T_ELSEIF:
case T_EMPTY: case T_ENDDECLARE: case T_ENDFOR: case T_ENDFOREACH:
case T_ENDIF: case T_ENDSWITCH: case T_ENDWHILE: case T_EVAL:
case T_EXIT: case T_EXTENDS: case T_FOR: case T_FOREACH:
case T_FUNCTION: case T_GLOBAL: case T_IF: case T_INCLUDE_ONCE:
case T_INCLUDE: case T_INT_CAST: case T_ISSET: case T_LIST:
case T_NEW: case T_OBJECT_CAST: case T_PRINT:
case T_REQUIRE_ONCE: case T_REQUIRE: case T_RETURN: case T_STATIC:
case T_STRING_CAST: case T_SWITCH: case T_UNSET_CAST: case T_UNSET:
case T_USE: case T_VAR: case T_WHILE:
$output .= ''. $value .'';
break;
default:
$output .= $value;
}
}
else {
$output .= $token;
}
}
// Manage whitespace:
return ''. trim($output) .'
';
}
/**
* Since we may parse a file containing a reference before we have parsed the
* file containing the referenced object, we keep track of the references
* using a scratch table and save the references to the database table after the
* referenced object has been parsed.
*/
function api_reference($branch_name, $to_type, $to_name, $from_did) {
static $is_php_function = array();
if ($to_type == 'function' && !isset($is_php_function[$to_name])) {
$is_php_function[$to_name] = (db_num_rows(db_query("SELECT object_name FROM {api_documentation} WHERE object_name = '%s' AND branch_name = 'php'", $to_name)) == 1);
}
if ($to_type != 'function' || !$is_php_function[$to_name]) {
db_query("INSERT INTO {api_reference_storage} (object_name, branch_name, object_type, from_did) VALUES ('%s', '%s', '%s', %d)", $to_name, $branch_name, $to_type, $from_did);
}
}
/**
* Update the references that were collected.
*/
function api_update_references() {
// First, figure out all the dids of the object/branch/types.
db_query('UPDATE {api_reference_storage} r, {api_documentation} d SET r.to_did = d.did WHERE r.object_name = d.object_name AND r.branch_name = d.branch_name AND r.object_type = d.object_type');
}