/[drupal]/contributions/modules/api/parser.inc
ViewVC logotype

Contents of /contributions/modules/api/parser.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.41 - (show annotations) (download) (as text)
Sat Dec 27 03:09:36 2008 UTC (11 months ago) by drumm
Branch: MAIN
CVS Tags: DRUPAL-6--1-0, HEAD
Branch point for: DRUPAL-6--1
Changes since 1.40: +19 -9 lines
File MIME type: text/x-php
#331784 undocumented functions should be parsed for function calls.
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 }

  ViewVC Help
Powered by ViewVC 1.1.2