| 1 |
<?php
|
| 2 |
// $Id: parser.inc,v 1.40 2008/12/27 00:02:49 drumm Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* The PHP documentation parser that generates content for api.module.
|
| 7 |
*/
|
| 8 |
|
| 9 |
/**
|
| 10 |
* Parse out function definitions from the PHP manual.
|
| 11 |
*/
|
| 12 |
function api_parse_php_manual($location) {
|
| 13 |
$response = drupal_http_request($location);
|
| 14 |
if ($response->code == 200) {
|
| 15 |
$function_matches = array();
|
| 16 |
preg_match_all('!^[a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)\(.*\n.*$!m', $response->data, $function_matches, PREG_SET_ORDER);
|
| 17 |
foreach ($function_matches as $function_match) {
|
| 18 |
$docblock = array(
|
| 19 |
'object_name' => $function_match[1],
|
| 20 |
'branch_name' => 'php',
|
| 21 |
'object_type' => 'function',
|
| 22 |
'file_name' => $location,
|
| 23 |
'title' => $function_match[1],
|
| 24 |
'summary' => api_documentation_summary($function_match[0]),
|
| 25 |
'documentation' => $function_match[0],
|
| 26 |
'code' => '',
|
| 27 |
'signature' => '',
|
| 28 |
'start_line' => 0,
|
| 29 |
'parameters' => '',
|
| 30 |
'return_value' => '',
|
| 31 |
);
|
| 32 |
api_save_documentation(array($docblock));
|
| 33 |
}
|
| 34 |
}
|
| 35 |
}
|
| 36 |
|
| 37 |
/**
|
| 38 |
* Read in the file at the given path and parse it as if it consisted entirely of
|
| 39 |
* documentation.
|
| 40 |
*/
|
| 41 |
function api_parse_text_file($file_path, $branch_name, $file_name) {
|
| 42 |
$source = file_get_contents($file_path);
|
| 43 |
|
| 44 |
// Set up documentation block for file, in case it is not explicitly defined.
|
| 45 |
$docblock = array(
|
| 46 |
'object_name' => $file_name,
|
| 47 |
'branch_name' => $branch_name,
|
| 48 |
'object_type' => 'file',
|
| 49 |
'file_name' => $file_name,
|
| 50 |
'title' => strpos($file_name, '/') ? substr($file_name, strrpos($file_name, '/') + 1) : $file_name,
|
| 51 |
'summary' => api_documentation_summary(api_format_documentation($source, $branch_name)),
|
| 52 |
'documentation' => api_format_documentation($source, $branch_name),
|
| 53 |
'code' => api_format_php($source),
|
| 54 |
'version' => '',
|
| 55 |
'modified' => filemtime($file_path),
|
| 56 |
);
|
| 57 |
$version_match = array();
|
| 58 |
if (preg_match('!\$'.'Id: .*?,v (.*?) (.*?) (.*?) (.*?) Exp \$!', $source, $version_match)) {
|
| 59 |
$docblock['version'] = $version_match[1] .' (checked in on '. $version_match[2] .' at '. $version_match[3] .' by '. $version_match[4] .')';
|
| 60 |
}
|
| 61 |
|
| 62 |
api_save_documentation(array($docblock), $branch_name, $file_name);
|
| 63 |
}
|
| 64 |
|
| 65 |
/**
|
| 66 |
* Read in the file at the given path and parse it as an HTML file.
|
| 67 |
*/
|
| 68 |
function api_parse_html_file($file_path, $branch_name, $file_name) {
|
| 69 |
$source = file_get_contents($file_path);
|
| 70 |
|
| 71 |
// Set up documentation block for file, in case it is not explicitly defined.
|
| 72 |
$docblock = array(
|
| 73 |
'object_name' => $file_name,
|
| 74 |
'branch_name' => $branch_name,
|
| 75 |
'object_type' => 'file',
|
| 76 |
'file_name' => $file_name,
|
| 77 |
'title' => strpos($file_name, '/') ? substr($file_name, strrpos($file_name, '/') + 1) : $file_name,
|
| 78 |
'summary' => '',
|
| 79 |
'documentation' => '',
|
| 80 |
'code' => '<pre>'. htmlspecialchars($source) .'</pre>',
|
| 81 |
'version' => '',
|
| 82 |
'modified' => filemtime($file_path),
|
| 83 |
);
|
| 84 |
$title_match = array();
|
| 85 |
if (preg_match('!<title>(.*)</title>!is', $source, $title_match)) {
|
| 86 |
$docblock['title'] = $title_match[1];
|
| 87 |
}
|
| 88 |
$documentation_match = array();
|
| 89 |
if (preg_match('!<body>(.*?</h1>)?(.*)</body>!is', $source, $documentation_match)) {
|
| 90 |
$docblock['documentation'] = $documentation_match[2];
|
| 91 |
$docblock['summary'] = api_documentation_summary($documentation_match[2]);
|
| 92 |
}
|
| 93 |
$version_match = array();
|
| 94 |
if (preg_match('!\$'.'Id: .*?,v (.*?) (.*?) (.*?) (.*?) Exp \$!', $source, $version_match)) {
|
| 95 |
$docblock['version'] = $version_match[1] .' (checked in on '. $version_match[2] .' at '. $version_match[3] .' by '. $version_match[4] .')';
|
| 96 |
}
|
| 97 |
|
| 98 |
api_save_documentation(array($docblock), $branch_name, $file_name);
|
| 99 |
}
|
| 100 |
|
| 101 |
/**
|
| 102 |
* Read in the file at the given path and parse its documentation.
|
| 103 |
*/
|
| 104 |
function api_parse_php_file($file_path, $branch_name, $file_name) {
|
| 105 |
$source = file_get_contents($file_path);
|
| 106 |
|
| 107 |
// Convert Mac/Win line breaks to Unix format.
|
| 108 |
$source = str_replace("\r\n", "\n", $source);
|
| 109 |
$source = str_replace("\r", "\n", $source);
|
| 110 |
|
| 111 |
$docblocks = array();
|
| 112 |
|
| 113 |
// Set up documentation block for file, in case it is not explicitly defined.
|
| 114 |
$docblocks[0] = array(
|
| 115 |
'object_name' => $file_name,
|
| 116 |
'branch_name' => $branch_name,
|
| 117 |
'object_type' => 'file',
|
| 118 |
'file_name' => $file_name,
|
| 119 |
'title' => strpos($file_name, '/') ? substr($file_name, strrpos($file_name, '/') + 1) : $file_name,
|
| 120 |
'summary' => '',
|
| 121 |
'documentation' => '',
|
| 122 |
'code' => api_format_php($source),
|
| 123 |
'version' => '',
|
| 124 |
'modified' => filemtime($file_path),
|
| 125 |
);
|
| 126 |
$version_match = array();
|
| 127 |
if (preg_match('!\$'.'Id: .*?,v (.*?) (.*?) (.*?) (.*?) Exp \$!', $source, $version_match)) {
|
| 128 |
$docblocks[0]['version'] = $version_match[1] .' (checked in on '. $version_match[2] .' at '. $version_match[3] .' by '. $version_match[4] .')';
|
| 129 |
}
|
| 130 |
|
| 131 |
$nested_groups = array();
|
| 132 |
|
| 133 |
$docblock_matches = array();
|
| 134 |
preg_match_all('!/\*\*(.*?)\*/!s', $source, $docblock_matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
|
| 135 |
|
| 136 |
foreach ($docblock_matches as $docblock_match) {
|
| 137 |
$docblock = array(
|
| 138 |
'object_name' => '',
|
| 139 |
'branch_name' => $branch_name,
|
| 140 |
'object_type' => '',
|
| 141 |
'file_name' => $file_name,
|
| 142 |
'title' => '',
|
| 143 |
'summary' => '',
|
| 144 |
'documentation' => '',
|
| 145 |
'code' => '',
|
| 146 |
);
|
| 147 |
$docblock['content'] = str_replace(array("\n *", "\n "), array("\n", "\n"), $docblock_match[1][0]);
|
| 148 |
$docblock['start'] = $docblock_match[0][1];
|
| 149 |
$docblock['length'] = strlen($docblock_match[0][0]);
|
| 150 |
$code_start = $docblock['start'] + $docblock['length'] + 1;
|
| 151 |
$docblock['start_line'] = substr_count(substr($source, 0, $code_start), "\n");
|
| 152 |
|
| 153 |
// Determine what kind of documentation block this is.
|
| 154 |
if (substr($source, $code_start, 8) == 'function') {
|
| 155 |
$function_matches = array();
|
| 156 |
|
| 157 |
$docblock['object_type'] = 'function';
|
| 158 |
preg_match('!^function (&?([a-zA-Z0-9_]+)\(.*?)\s*\{!', substr($source, $code_start), $function_matches);
|
| 159 |
$docblock['object_name'] = $function_matches[2];
|
| 160 |
$docblock['title'] = $function_matches[2];
|
| 161 |
$docblock['signature'] = $function_matches[1];
|
| 162 |
|
| 163 |
// We rely on the Drupal coding convention that functions are closed in column 1.
|
| 164 |
$code_end = strpos($source, "\n}", $code_start) + 2;
|
| 165 |
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
|
| 166 |
$docblock['code'] = api_format_php("<?php\n". $docblock['code'] ."\n?>");
|
| 167 |
|
| 168 |
// Find parameter definitions.
|
| 169 |
$param_match = array();
|
| 170 |
$offset = 0;
|
| 171 |
$docblock['parameters'] = '';
|
| 172 |
while (preg_match('!@param(.*?)(?=\n@|\n\n|$)!s', substr($docblock['content'], $offset), $param_match, PREG_OFFSET_CAPTURE)) {
|
| 173 |
$docblock['content'] = str_replace($param_match[0][0], '', $docblock['content']);
|
| 174 |
$docblock['parameters'] .= "\n\n". $param_match[1][0];
|
| 175 |
$offset = $param_match[0][1];
|
| 176 |
}
|
| 177 |
$docblock['parameters'] = api_format_documentation($docblock['parameters'], $branch_name);
|
| 178 |
|
| 179 |
// Find return value definitions.
|
| 180 |
$return_matches = array();
|
| 181 |
$docblock['return_value'] = '';
|
| 182 |
preg_match_all('!@return(.*?)(\n@|\n\n|$)!s', $docblock['content'], $return_matches, PREG_SET_ORDER);
|
| 183 |
foreach ($return_matches as $return_match) {
|
| 184 |
$docblock['content'] = str_replace($return_match[0], '', $docblock['content']);
|
| 185 |
$docblock['return_value'] .= "\n\n". $return_match[1];
|
| 186 |
}
|
| 187 |
$docblock['return_value'] = api_format_documentation($docblock['return_value'], $branch_name);
|
| 188 |
|
| 189 |
$docblock['function calls'] = api_parse_function_calls($docblock['code']);
|
| 190 |
|
| 191 |
// Determine group membership.
|
| 192 |
$group_matches = array();
|
| 193 |
preg_match_all('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches);
|
| 194 |
$docblock['groups'] = $group_matches[2];
|
| 195 |
$docblock['content'] = preg_replace('!@ingroup.*?\n!', '', $docblock['content']);
|
| 196 |
|
| 197 |
foreach ($nested_groups as $group_id) {
|
| 198 |
if (!empty($group_id)) {
|
| 199 |
$docblock['groups'][] = $group_id;
|
| 200 |
}
|
| 201 |
}
|
| 202 |
}
|
| 203 |
else if (substr($source, $code_start, 6) == 'define') {
|
| 204 |
$constant_matches = array();
|
| 205 |
|
| 206 |
$docblock['object_type'] = 'constant';
|
| 207 |
preg_match('!^define\([\'"]([a-zA-Z0-9_]+)[\'"]!', substr($source, $code_start), $constant_matches);
|
| 208 |
$docblock['object_name'] = $constant_matches[1];
|
| 209 |
$docblock['title'] = $constant_matches[1];
|
| 210 |
|
| 211 |
$code_end = strpos($source, ';', $code_start) + 1;
|
| 212 |
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
|
| 213 |
$docblock['code'] = api_format_php("<?php\n". $docblock['code'] ."\n?>");
|
| 214 |
|
| 215 |
// Determine group membership.
|
| 216 |
$group_matches = array();
|
| 217 |
preg_match_all('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches);
|
| 218 |
$docblock['groups'] = $group_matches[2];
|
| 219 |
$docblock['content'] = preg_replace('!@ingroup.*?\n!', '', $docblock['content']);
|
| 220 |
|
| 221 |
foreach ($nested_groups as $group_id) {
|
| 222 |
if (!empty($group_id)) {
|
| 223 |
$docblock['groups'][] = $group_id;
|
| 224 |
}
|
| 225 |
}
|
| 226 |
}
|
| 227 |
else if (substr($source, $code_start, 6) == 'global') {
|
| 228 |
$global_matches = array();
|
| 229 |
$docblock['object_type'] = 'global';
|
| 230 |
preg_match('!^global (\$[a-zA-Z0-9_]+)!', substr($source, $code_start), $global_matches);
|
| 231 |
|
| 232 |
$docblock['object_name'] = substr($global_matches[1], 1);
|
| 233 |
$docblock['title'] = $global_matches[1];
|
| 234 |
|
| 235 |
$code_end = strpos($source, ';', $code_start) + 1;
|
| 236 |
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
|
| 237 |
$docblock['code'] = api_format_php("<?php\n". $docblock['code'] ."\n?>");
|
| 238 |
|
| 239 |
$docblock['start_line'] = substr_count(substr($source, 0, $code_start), "\n");
|
| 240 |
|
| 241 |
// Determine group membership.
|
| 242 |
$group_matches = array();
|
| 243 |
preg_match_all('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches);
|
| 244 |
$docblock['groups'] = $group_matches[2];
|
| 245 |
$docblock['content'] = preg_replace('!@ingroup.*?\n!', '', $docblock['content']);
|
| 246 |
|
| 247 |
foreach ($nested_groups as $group_id) {
|
| 248 |
if (!empty($group_id)) {
|
| 249 |
$docblock['groups'][] = $group_id;
|
| 250 |
}
|
| 251 |
}
|
| 252 |
}
|
| 253 |
else if (strpos($docblock['content'], '@mainpage') !== FALSE) {
|
| 254 |
$mainpage_matches = array();
|
| 255 |
preg_match('!@mainpage (.*?)\n!', $docblock['content'], $mainpage_matches);
|
| 256 |
$docblock['title'] = $mainpage_matches[1];
|
| 257 |
$docblock['content'] = preg_replace('!@mainpage.*?\n!', '', $docblock['content']);
|
| 258 |
$docblock['object_type'] = 'mainpage';
|
| 259 |
$docblock['object_name'] = $branch_name;
|
| 260 |
}
|
| 261 |
else if (strpos($docblock['content'], '@file') !== FALSE) {
|
| 262 |
$docblocks[0]['content'] = str_replace('@file', '', $docblock['content']);
|
| 263 |
$docblocks[0]['documentation'] = api_format_documentation($docblocks[0]['content'], $branch_name);
|
| 264 |
$docblocks[0]['summary'] = api_documentation_summary($docblocks[0]['documentation']);
|
| 265 |
}
|
| 266 |
else if (strpos($docblock['content'], '@defgroup') !== FALSE) {
|
| 267 |
$group_matches = array();
|
| 268 |
if (preg_match('!@defgroup ([a-zA-Z0-9_.-]+) +(.*?)\n!', $docblock['content'], $group_matches)) {
|
| 269 |
$docblock['object_name'] = $group_matches[1];
|
| 270 |
$docblock['title'] = $group_matches[2];
|
| 271 |
$docblock['content'] = preg_replace('!@defgroup.*?\n!', '', $docblock['content']);
|
| 272 |
$docblock['object_type'] = 'group';
|
| 273 |
}
|
| 274 |
else {
|
| 275 |
watchdog('api', 'Malformed @defgroup in %file at line %line.', array('%file' => $file_path, '%line' => $docblock['start_line']), WATCHDOG_NOTICE);
|
| 276 |
}
|
| 277 |
}
|
| 278 |
|
| 279 |
// Handle nested function groups.
|
| 280 |
if (strpos($docblock['content'], '@{') !== FALSE) {
|
| 281 |
if ($docblock['object_type'] == 'group') {
|
| 282 |
array_push($nested_groups, $docblock['object_name']);
|
| 283 |
}
|
| 284 |
else {
|
| 285 |
$group_matches = array();
|
| 286 |
if (preg_match('!@(ingroup|addtogroup) ([a-zA-Z0-9_]+)!', $docblock['content'], $group_matches)) {
|
| 287 |
array_push($nested_groups, $group_matches[2]);
|
| 288 |
}
|
| 289 |
else {
|
| 290 |
array_push($nested_groups, '');
|
| 291 |
}
|
| 292 |
}
|
| 293 |
}
|
| 294 |
if (strpos($docblock['content'], '@}') !== FALSE) {
|
| 295 |
array_pop($nested_groups);
|
| 296 |
}
|
| 297 |
|
| 298 |
if ($docblock['object_type'] != '') {
|
| 299 |
$docblock['documentation'] = api_format_documentation($docblock['content'], $branch_name);
|
| 300 |
$docblock['summary'] = api_documentation_summary($docblock['documentation']);
|
| 301 |
$docblocks[] = $docblock;
|
| 302 |
}
|
| 303 |
}
|
| 304 |
|
| 305 |
// Find undocumented functions.
|
| 306 |
$function_matches = array();
|
| 307 |
preg_match_all('%(?<!\*/\n)^function (&?([a-zA-Z0-9_]+)\(.*?)\s*\{%sm', $source, $function_matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
|
| 308 |
foreach ($function_matches as $function_match) {
|
| 309 |
$docblock = array(
|
| 310 |
'object_name' => $function_match[2][0],
|
| 311 |
'branch_name' => $branch_name,
|
| 312 |
'object_type' => 'function',
|
| 313 |
'file_name' => $file_name,
|
| 314 |
'title' => $function_match[2][0],
|
| 315 |
'summary' => '',
|
| 316 |
'documentation' => '',
|
| 317 |
'code' => '');
|
| 318 |
$docblock['signature'] = $function_match[1][0];
|
| 319 |
$docblock['parameters'] = '';
|
| 320 |
$docblock['return_value'] = '';
|
| 321 |
$docblock['groups'] = array();
|
| 322 |
|
| 323 |
$code_start = $function_match[0][1];
|
| 324 |
$code_end = strpos($source, "\n}", $code_start) + 2;
|
| 325 |
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
|
| 326 |
$docblock['code'] = api_format_php("<?php\n". $docblock['code'] ."\n?>");
|
| 327 |
|
| 328 |
$docblock['function calls'] = api_parse_function_calls($docblock['code']);
|
| 329 |
|
| 330 |
$docblock['start_line'] = substr_count(substr($source, 0, $code_start), "\n");
|
| 331 |
|
| 332 |
$docblocks[] = $docblock;
|
| 333 |
}
|
| 334 |
|
| 335 |
// Find undocumented constants.
|
| 336 |
$constant_matches = array();
|
| 337 |
preg_match_all('%(?<!\*/\n)^define\([\'"]([a-zA-Z0-9_]+)[\'"]%sm', $source, $constant_matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
|
| 338 |
foreach ($constant_matches as $constant_match) {
|
| 339 |
$docblock = array(
|
| 340 |
'object_name' => $constant_match[1][0],
|
| 341 |
'branch_name' => $branch_name,
|
| 342 |
'object_type' => 'constant',
|
| 343 |
'file_name' => $file_name,
|
| 344 |
'title' => $constant_match[1][0],
|
| 345 |
'summary' => '',
|
| 346 |
'documentation' => '',
|
| 347 |
'code' => '');
|
| 348 |
$docblock['groups'] = array();
|
| 349 |
|
| 350 |
$code_start = $constant_match[0][1];
|
| 351 |
$code_end = strpos($source, ';', $code_start) + 1;
|
| 352 |
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
|
| 353 |
$docblock['code'] = api_format_php("<?php\n". $docblock['code'] ."\n?>");
|
| 354 |
|
| 355 |
$docblock['start_line'] = substr_count(substr($source, 0, $code_start), "\n");
|
| 356 |
|
| 357 |
$docblocks[] = $docblock;
|
| 358 |
}
|
| 359 |
|
| 360 |
// Find undocumented globals.
|
| 361 |
$global_matches = array();
|
| 362 |
preg_match_all('%(?<!\*/\n)^global (\$[a-zA-Z0-9_]+)%sm', $source, $global_matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
|
| 363 |
foreach ($global_matches as $global_match) {
|
| 364 |
$docblock = array(
|
| 365 |
'object_name' => $global_match[1][0],
|
| 366 |
'branch_name' => $branch_name,
|
| 367 |
'object_type' => 'global',
|
| 368 |
'file_name' => $file_name,
|
| 369 |
'title' => $global_match[1][0],
|
| 370 |
'summary' => '',
|
| 371 |
'documentation' => '',
|
| 372 |
'code' => '',
|
| 373 |
);
|
| 374 |
$docblock['groups'] = array();
|
| 375 |
|
| 376 |
$code_start = $global_match[0][1];
|
| 377 |
$code_end = strpos($source, ';', $code_start) + 1;
|
| 378 |
$docblock['code'] = substr($source, $code_start, $code_end - $code_start);
|
| 379 |
$docblock['code'] = api_format_php("<?php\n". $docblock['code'] ."\n?>");
|
| 380 |
|
| 381 |
$docblock['start_line'] = substr_count(substr($source, 0, $code_start), "\n");
|
| 382 |
|
| 383 |
$docblocks[] = $docblock;
|
| 384 |
}
|
| 385 |
|
| 386 |
api_save_documentation($docblocks, $branch_name, $file_name);
|
| 387 |
}
|
| 388 |
|
| 389 |
/**
|
| 390 |
* Find functions called in a formatted block of code.
|
| 391 |
*/
|
| 392 |
function api_parse_function_calls($code) {
|
| 393 |
$function_call_matches = array();
|
| 394 |
$function_calls = array();
|
| 395 |
preg_match_all('!<span class="php-function-or-constant">([a-zA-Z0-9_]+)</span>\(!', $code, $function_call_matches, PREG_SET_ORDER);
|
| 396 |
array_shift($function_call_matches); // Remove the first match, the function declaration itself.
|
| 397 |
foreach ($function_call_matches as $function_call_match) {
|
| 398 |
$function_calls[$function_call_match[1]] = $function_call_match[1];
|
| 399 |
}
|
| 400 |
|
| 401 |
return $function_calls;
|
| 402 |
}
|
| 403 |
|
| 404 |
/**
|
| 405 |
* Save a documentation block into the database.
|
| 406 |
*
|
| 407 |
* @param $docblocks
|
| 408 |
* An array containing information about the documentation block.
|
| 409 |
* @param $branch_name
|
| 410 |
* If set, old documentation will be removed for the branch and file name.
|
| 411 |
* @param $file_name
|
| 412 |
* If set, old documentation will be removed for the branch and file name.
|
| 413 |
* @return
|
| 414 |
* The documentation ID of the inserted/updated construct.
|
| 415 |
*/
|
| 416 |
function api_save_documentation($docblocks, $branch_name = NULL, $file_name = NULL) {
|
| 417 |
if (!is_null($branch_name)) {
|
| 418 |
$old_dids = array();
|
| 419 |
$result = db_query("SELECT did FROM {api_documentation} WHERE branch_name = '%s' AND file_name = '%s'", $branch_name, $file_name);
|
| 420 |
while ($object = db_fetch_object($result)) {
|
| 421 |
$old_dids[] = $object->did;
|
| 422 |
}
|
| 423 |
}
|
| 424 |
|
| 425 |
$dids = array();
|
| 426 |
foreach ($docblocks as $docblock) {
|
| 427 |
$did = db_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'], $docblock['file_name']));
|
| 428 |
if ($did > 0) {
|
| 429 |
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);
|
| 430 |
}
|
| 431 |
else {
|
| 432 |
db_query("INSERT INTO {api_documentation} (object_name, branch_name, object_type, title, file_name, summary, documentation, code) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $docblock['object_name'], $docblock['branch_name'], $docblock['object_type'], $docblock['title'], $docblock['file_name'], $docblock['summary'], $docblock['documentation'], $docblock['code']);
|
| 433 |
$did = db_last_insert_id('api_documentation', 'did');
|
| 434 |
}
|
| 435 |
|
| 436 |
switch ($docblock['object_type']) {
|
| 437 |
case 'function':
|
| 438 |
db_query('DELETE FROM {api_function} WHERE did = %d', $did);
|
| 439 |
db_query("INSERT INTO {api_function} (did, signature, start_line, parameters, return_value) VALUES (%d, '%s', %d, '%s', '%s')", $did, $docblock['signature'], $docblock['start_line'], $docblock['parameters'], $docblock['return_value']);
|
| 440 |
|
| 441 |
if (isset($docblock['function calls'])) {
|
| 442 |
db_query("DELETE FROM {api_reference_storage} WHERE branch_name = '%s' AND object_type = 'function' AND from_did = %d", $docblock['branch_name'], $did);
|
| 443 |
foreach ($docblock['function calls'] as $function_name) {
|
| 444 |
api_reference($docblock['branch_name'], 'function', $function_name, $did);
|
| 445 |
}
|
| 446 |
}
|
| 447 |
break;
|
| 448 |
|
| 449 |
case 'file':
|
| 450 |
db_query('DELETE FROM {api_file} WHERE did = %d', $did);
|
| 451 |
db_query("INSERT INTO {api_file} (did, modified, version) VALUES (%d, %d, '%s')", $did, $docblock['modified'], $docblock['version']);
|
| 452 |
break;
|
| 453 |
}
|
| 454 |
|
| 455 |
if (isset($docblock['groups'])) {
|
| 456 |
db_query("DELETE FROM {api_reference_storage} WHERE branch_name = '%s' AND object_type = 'group' AND from_did = %d", $docblock['branch_name'], $did);
|
| 457 |
foreach ($docblock['groups'] as $group_name) {
|
| 458 |
api_reference($docblock['branch_name'], 'group', $group_name, $did);
|
| 459 |
}
|
| 460 |
}
|
| 461 |
|
| 462 |
$dids[] = $did;
|
| 463 |
}
|
| 464 |
|
| 465 |
if (!is_null($branch_name)) {
|
| 466 |
$old_dids = array_diff($old_dids, $dids);
|
| 467 |
if (count($old_dids) > 0) {
|
| 468 |
$old_dids = implode(',', $old_dids);
|
| 469 |
db_query('DELETE FROM {api_documentation} WHERE did IN (%s)', $old_dids);
|
| 470 |
db_query('DELETE FROM {api_file} WHERE did IN (%s)', $old_dids);
|
| 471 |
db_query('DELETE FROM {api_function} WHERE did IN (%s)', $old_dids);
|
| 472 |
db_query('DELETE FROM {api_reference_storage} WHERE from_did IN (%s) OR to_did IN (%s)', $old_dids, $old_dids);
|
| 473 |
}
|
| 474 |
}
|
| 475 |
|
| 476 |
api_schedule_update_references();
|
| 477 |
api_schedule_cache_clear();
|
| 478 |
}
|
| 479 |
|
| 480 |
/**
|
| 481 |
* Format a documentation block as HTML.
|
| 482 |
*/
|
| 483 |
function api_format_documentation($documentation, $branch_name) {
|
| 484 |
// Don't do processing on empty text (so we don't end up with empty paragraphs).
|
| 485 |
$documentation = trim($documentation);
|
| 486 |
if (empty($documentation)) {
|
| 487 |
return '';
|
| 488 |
}
|
| 489 |
|
| 490 |
$documentation = check_plain($documentation);
|
| 491 |
|
| 492 |
// @link full URLs.
|
| 493 |
$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!', '<a href="$1">$4</a>', $documentation);
|
| 494 |
// Site URLs.
|
| 495 |
$documentation = preg_replace('!@link /([a-zA-Z0-9_/-]+) (.*?) @endlink!', str_replace('%24', '$', l('$2', '$1')), $documentation);
|
| 496 |
//$documentation = preg_replace('!@link ([a-zA-Z0-9_]+) (.*?) @endlink!', str_replace('%24', '$', l('$2', 'api/group/$1/'. $branch_name)), $documentation);
|
| 497 |
|
| 498 |
// Process the @see tag
|
| 499 |
$documentation = preg_replace('!\n@see (.*[^.])\.?!', '<h3>See also</h3><p>$1</p>', $documentation);
|
| 500 |
|
| 501 |
// Replace left over curly braces
|
| 502 |
$documentation = preg_replace('!@[{}]!', '', $documentation);
|
| 503 |
|
| 504 |
// Process the @code @endcode tags.
|
| 505 |
$documentation = preg_replace_callback('!@code(.+?)@endcode!s', 'api_format_embedded_php', $documentation);
|
| 506 |
|
| 507 |
// Format lists
|
| 508 |
$documentation = api_format_documentation_lists($documentation);
|
| 509 |
|
| 510 |
// Convert newlines into paragraphs.
|
| 511 |
$documentation = preg_replace('|\n*$|', '', $documentation) ."\n\n"; // just to make things a little easier, pad the end
|
| 512 |
$documentation = preg_replace('!\n?(.+?)(?:\n\s*\n|\z)!s', "<p>$1</p>\n", $documentation); // make paragraphs, including one at the end
|
| 513 |
$documentation = preg_replace('!<p>\s*?</p>!', '', $documentation); // under certain strange conditions it could create a P of entirely whitespace
|
| 514 |
|
| 515 |
return $documentation;
|
| 516 |
}
|
| 517 |
|
| 518 |
/**
|
| 519 |
* Regexp replace callback for code tags.
|
| 520 |
*/
|
| 521 |
function api_format_embedded_php($matches) {
|
| 522 |
return "\n\n". api_format_php("<?php\n". decode_entities($matches[1]) ."\n?>") ."\n\n";
|
| 523 |
}
|
| 524 |
|
| 525 |
/*
|
| 526 |
* Parse a block of text for lists that use hyphens or asterisks as bullets, and
|
| 527 |
* format them as proper HTML lists.
|
| 528 |
*/
|
| 529 |
function api_format_documentation_lists($documentation) {
|
| 530 |
$lines = explode("\n", $documentation);
|
| 531 |
$output = '';
|
| 532 |
$bullet_indents = array(-1);
|
| 533 |
|
| 534 |
foreach ($lines as $line) {
|
| 535 |
$matches = array();
|
| 536 |
preg_match('!^( *)([*-] )?(.*)$!', $line, $matches);
|
| 537 |
$indent = strlen($matches[1]);
|
| 538 |
$bullet_exists = $matches[2];
|
| 539 |
|
| 540 |
if ($indent < $bullet_indents[0]) {
|
| 541 |
// First close off any lists that have completed.
|
| 542 |
while ($indent < $bullet_indents[0]) {
|
| 543 |
array_shift($bullet_indents);
|
| 544 |
$output .= '</li></ul>';
|
| 545 |
}
|
| 546 |
}
|
| 547 |
|
| 548 |
if ($indent == $bullet_indents[0]) {
|
| 549 |
if ($bullet_exists) {
|
| 550 |
// A new bullet at the same indent means a new list item.
|
| 551 |
$output .= '</li><li>';
|
| 552 |
}
|
| 553 |
else {
|
| 554 |
// If the indent is the same, but there is no bullet, that also
|
| 555 |
// signifies the end of the list.
|
| 556 |
array_shift($bullet_indents);
|
| 557 |
$output .= '</li></ul>';
|
| 558 |
}
|
| 559 |
}
|
| 560 |
|
| 561 |
if ($indent > $bullet_indents[0] && $bullet_exists) {
|
| 562 |
// A new list at a lower level.
|
| 563 |
array_unshift($bullet_indents, $indent);
|
| 564 |
$output .= '<ul><li>';
|
| 565 |
}
|
| 566 |
|
| 567 |
$output .= $matches[3] ."\n";
|
| 568 |
}
|
| 569 |
|
| 570 |
// Clean up any unclosed lists.
|
| 571 |
array_pop($bullet_indents);
|
| 572 |
foreach ($bullet_indents as $indent) {
|
| 573 |
$output .= '</li></ul>';
|
| 574 |
}
|
| 575 |
|
| 576 |
return $output;
|
| 577 |
}
|
| 578 |
|
| 579 |
/**
|
| 580 |
* Retrieve a summary from a documentation block.
|
| 581 |
*/
|
| 582 |
function api_documentation_summary($documentation) {
|
| 583 |
$pos = strpos($documentation, "</p>");
|
| 584 |
if ($pos !== FALSE) {
|
| 585 |
$documentation = substr($documentation, 0, $pos);
|
| 586 |
}
|
| 587 |
$documentation = trim(strip_tags($documentation));
|
| 588 |
|
| 589 |
if (strlen($documentation) > 255) {
|
| 590 |
return substr($documentation, 0, strrpos(substr($documentation, 0, 252), ' ')) .'...';
|
| 591 |
}
|
| 592 |
else {
|
| 593 |
return $documentation;
|
| 594 |
}
|
| 595 |
}
|
| 596 |
|
| 597 |
/**
|
| 598 |
* Colorize and format a PHP script.
|
| 599 |
*/
|
| 600 |
function api_format_php($code) {
|
| 601 |
$output = '';
|
| 602 |
|
| 603 |
if (!defined('T_ML_COMMENT')) {
|
| 604 |
define('T_ML_COMMENT', T_COMMENT);
|
| 605 |
}
|
| 606 |
if (!defined('T_DOC_COMMENT')) {
|
| 607 |
define('T_DOC_COMMENT', T_COMMENT);
|
| 608 |
}
|
| 609 |
|
| 610 |
$tokens = token_get_all($code);
|
| 611 |
|
| 612 |
$in_string = FALSE;
|
| 613 |
|
| 614 |
foreach ($tokens as $token) {
|
| 615 |
if ($in_string) {
|
| 616 |
if ($token == '"') {
|
| 617 |
$output .= '"</span>';
|
| 618 |
$in_string = FALSE;
|
| 619 |
}
|
| 620 |
else {
|
| 621 |
$output .= is_array($token) ? htmlspecialchars($token[1]) : htmlspecialchars($token);
|
| 622 |
}
|
| 623 |
continue;
|
| 624 |
}
|
| 625 |
else if ($token == '"') {
|
| 626 |
$output .= '<span class="php-string">"';
|
| 627 |
$in_string = TRUE;
|
| 628 |
continue;
|
| 629 |
}
|
| 630 |
|
| 631 |
if (is_array($token)) {
|
| 632 |
$type = $token[0];
|
| 633 |
$value = htmlspecialchars($token[1]);
|
| 634 |
|
| 635 |
switch ($type) {
|
| 636 |
case T_OPEN_TAG:
|
| 637 |
case T_CLOSE_TAG:
|
| 638 |
$output .= '<span class="php-boundry">'. $value .'</span>';
|
| 639 |
break;
|
| 640 |
|
| 641 |
case T_COMMENT:
|
| 642 |
case T_ML_COMMENT:
|
| 643 |
case T_DOC_COMMENT:
|
| 644 |
$output .= '<span class="php-comment">'. $value .'</span>';
|
| 645 |
break;
|
| 646 |
|
| 647 |
case T_VARIABLE:
|
| 648 |
$output .= '<span class="php-variable">'. $value .'</span>';
|
| 649 |
break;
|
| 650 |
|
| 651 |
case T_CONSTANT_ENCAPSED_STRING:
|
| 652 |
case T_INLINE_HTML:
|
| 653 |
$output .= '<span class="php-string">'. $value .'</span>';
|
| 654 |
break;
|
| 655 |
|
| 656 |
case T_STRING:
|
| 657 |
$output .= '<span class="php-function-or-constant">'. $value .'</span>';
|
| 658 |
break;
|
| 659 |
|
| 660 |
case T_LNUMBER:
|
| 661 |
case T_DNUMBER:
|
| 662 |
$output .= '<span class="php-constant">'. $value .'</span>';
|
| 663 |
break;
|
| 664 |
|
| 665 |
case T_ARRAY_CAST: case T_ARRAY: case T_AS: case T_BOOL_CAST:
|
| 666 |
case T_BREAK: case T_CASE: case T_CLASS: case T_CONST:
|
| 667 |
case T_CONTINUE: case T_DECLARE: case T_DEFAULT: case T_DO:
|
| 668 |
case T_DOUBLE_CAST: case T_ECHO: case T_ELSE: case T_ELSEIF:
|
| 669 |
case T_EMPTY: case T_ENDDECLARE: case T_ENDFOR: case T_ENDFOREACH:
|
| 670 |
case T_ENDIF: case T_ENDSWITCH: case T_ENDWHILE: case T_EVAL:
|
| 671 |
case T_EXIT: case T_EXTENDS: case T_FOR: case T_FOREACH:
|
| 672 |
case T_FUNCTION: case T_GLOBAL: case T_IF: case T_INCLUDE_ONCE:
|
| 673 |
case T_INCLUDE: case T_INT_CAST: case T_ISSET: case T_LIST:
|
| 674 |
case T_NEW: case T_OBJECT_CAST: case T_PRINT:
|
| 675 |
case T_REQUIRE_ONCE: case T_REQUIRE: case T_RETURN: case T_STATIC:
|
| 676 |
case T_STRING_CAST: case T_SWITCH: case T_UNSET_CAST: case T_UNSET:
|
| 677 |
case T_USE: case T_VAR: case T_WHILE:
|
| 678 |
$output .= '<span class="php-keyword">'. $value .'</span>';
|
| 679 |
break;
|
| 680 |
|
| 681 |
default:
|
| 682 |
$output .= $value;
|
| 683 |
}
|
| 684 |
}
|
| 685 |
else {
|
| 686 |
$output .= $token;
|
| 687 |
}
|
| 688 |
}
|
| 689 |
|
| 690 |
// Manage whitespace:
|
| 691 |
return '<pre class="php"><code>'. trim($output) .'</code></pre>';
|
| 692 |
}
|
| 693 |
|
| 694 |
/**
|
| 695 |
* Since we may parse a file containing a reference before we have parsed the
|
| 696 |
* file containing the referenced object, we keep track of the references
|
| 697 |
* using a scratch table and save the references to the database table after the
|
| 698 |
* referenced object has been parsed.
|
| 699 |
*/
|
| 700 |
function api_reference($branch_name, $to_type, $to_name, $from_did) {
|
| 701 |
static $is_php_function = array();
|
| 702 |
|
| 703 |
if ($to_type == 'function' && !isset($is_php_function[$to_name])) {
|
| 704 |
$is_php_function[$to_name] = (db_result(db_query("SELECT COUNT(*) FROM {api_documentation} WHERE object_name = '%s' AND branch_name = 'php'", $to_name)) == 1);
|
| 705 |
}
|
| 706 |
|
| 707 |
if ($to_type != 'function' || !$is_php_function[$to_name]) {
|
| 708 |
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);
|
| 709 |
}
|
| 710 |
}
|
| 711 |
|
| 712 |
/**
|
| 713 |
* References do not need to be repeatedly updated, just at the end of a
|
| 714 |
* request that involved parsing.
|
| 715 |
*/
|
| 716 |
function api_schedule_update_references() {
|
| 717 |
static $scheduled = FALSE;
|
| 718 |
|
| 719 |
if (!$scheduled) {
|
| 720 |
register_shutdown_function('api_update_references');
|
| 721 |
$scheduled = TRUE;
|
| 722 |
}
|
| 723 |
}
|
| 724 |
|
| 725 |
/**
|
| 726 |
* Update the references that were collected.
|
| 727 |
*/
|
| 728 |
function api_update_references() {
|
| 729 |
// Figure out all the dids of the object/branch/types.
|
| 730 |
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');
|
| 731 |
}
|
| 732 |
|
| 733 |
function api_schedule_cache_clear() {
|
| 734 |
register_shutdown_function('cache_clear_all');
|
| 735 |
}
|
| 736 |
|
| 737 |
function api_update_all_branches() {
|
| 738 |
foreach (api_get_branches() as $branch) {
|
| 739 |
api_update_branch($branch);
|
| 740 |
}
|
| 741 |
}
|
| 742 |
|
| 743 |
function api_update_branch($branch) {
|
| 744 |
static $parse_functions = array(
|
| 745 |
'php' => 'api_parse_php_file',
|
| 746 |
'module' => 'api_parse_php_file',
|
| 747 |
'inc' => 'api_parse_php_file',
|
| 748 |
'install' => 'api_parse_php_file',
|
| 749 |
'engine' => 'api_parse_php_file',
|
| 750 |
'theme' => 'api_parse_php_file',
|
| 751 |
'profile' => 'api_parse_php_file',
|
| 752 |
|
| 753 |
'txt' => 'api_parse_text_file',
|
| 754 |
|
| 755 |
'htm' => 'api_parse_html_file',
|
| 756 |
'html' => 'api_parse_html_file',
|
| 757 |
);
|
| 758 |
|
| 759 |
// List all documented files for the branch.
|
| 760 |
$files = array();
|
| 761 |
$result = db_query("SELECT f.did, f.modified, d.object_name FROM {api_documentation} d INNER JOIN {api_file} f ON d.did = f.did WHERE d.branch_name = '%s' AND d.object_type = 'file'", $branch->branch_name);
|
| 762 |
while ($file = db_fetch_object($result)) {
|
| 763 |
$files[$file->object_name] = $file;
|
| 764 |
}
|
| 765 |
|
| 766 |
foreach (api_scan_directories($branch->directory) as $path => $file_name) {
|
| 767 |
preg_match('!\.([a-z]*)$!', $file_name, $matches);
|
| 768 |
if (isset($parse_functions[$matches[1]])) {
|
| 769 |
if (isset($files[$file_name])) {
|
| 770 |
$parse = (filemtime($path) > $files[$file_name]->modified);
|
| 771 |
unset($files[$file_name]); // All remaining files will be removed.
|
| 772 |
}
|
| 773 |
else { // New file.
|
| 774 |
$parse = TRUE;
|
| 775 |
}
|
| 776 |
if ($parse) {
|
| 777 |
job_queue_add($parse_functions[$matches[1]], t('API parse %branch %file'), array($path, '%branch' => $branch->branch_name, '%file' => $file_name), drupal_get_path('module', 'api') .'/parser.inc', TRUE);
|
| 778 |
}
|
| 779 |
}
|
| 780 |
}
|
| 781 |
|
| 782 |
// Remove outdated files.
|
| 783 |
foreach (array_keys($files) as $file_name) {
|
| 784 |
watchdog('api', 'Removing %file.', array('%file' => $file_name));
|
| 785 |
$result = db_query("SELECT ad.did FROM {api_documentation} ad WHERE ad.file_name = '%s' AND ad.branch_name = '%s'", $file_name, $branch->branch_name);
|
| 786 |
while ($doc = db_fetch_object($result)) {
|
| 787 |
db_query("DELETE FROM {api_documentation} WHERE did = %d", $doc->did);
|
| 788 |
db_query("DELETE FROM {api_file} WHERE did = %d", $doc->did);
|
| 789 |
db_query("DELETE FROM {api_function} WHERE did = %d", $doc->did);
|
| 790 |
db_query("DELETE FROM {api_reference_storage} WHERE from_did = %d OR to_did = %d", $doc->did, $doc->did);
|
| 791 |
}
|
| 792 |
api_schedule_cache_clear();
|
| 793 |
}
|
| 794 |
}
|
| 795 |
|
| 796 |
/**
|
| 797 |
* Find all the files in the directories specified for a branch.
|
| 798 |
*/
|
| 799 |
function api_scan_directories($directories) {
|
| 800 |
$directory_array = explode("\n", $directories);
|
| 801 |
foreach ($directory_array as $key => $directory) {
|
| 802 |
$directory_array[$key] = trim($directory);
|
| 803 |
}
|
| 804 |
|
| 805 |
if (count($directory_array) > 1) {
|
| 806 |
$directories_components = array();
|
| 807 |
foreach ($directory_array as $directory) {
|
| 808 |
$directory_components = array();
|
| 809 |
$parts = explode('/', $directory);
|
| 810 |
foreach ($parts as $part) {
|
| 811 |
if (strlen($part)) {
|
| 812 |
array_unshift($directory_components, reset($directory_components) .'/'. $part);
|
| 813 |
}
|
| 814 |
}
|
| 815 |
$directories_components[] = $directory_components;
|
| 816 |
}
|
| 817 |
|
| 818 |
$common_ancestor_components = call_user_func_array('array_intersect', $directories_components);
|
| 819 |
$common_ancestor = reset($common_ancestor_components);
|
| 820 |
}
|
| 821 |
else {
|
| 822 |
$common_ancestor = $directories;
|
| 823 |
}
|
| 824 |
|
| 825 |
$source_files = array();
|
| 826 |
foreach ($directory_array as $directory) {
|
| 827 |
$files = file_scan_directory($directory, '.*');
|
| 828 |
foreach ($files as $path => $file) {
|
| 829 |
if (strpos($path, '/.') !== FALSE) {
|
| 830 |
continue;
|
| 831 |
}
|
| 832 |
$file_name = substr($path, strlen($common_ancestor) + 1);
|
| 833 |
|
| 834 |
$source_files[$path] = $file_name;
|
| 835 |
}
|
| 836 |
}
|
| 837 |
return $source_files;
|
| 838 |
}
|