/[drupal]/contributions/modules/coder/scripts/coder_format/coder_format.inc
ViewVC logotype

Contents of /contributions/modules/coder/scripts/coder_format/coder_format.inc

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


Revision 1.6 - (show annotations) (download) (as text)
Tue Jan 20 14:54:19 2009 UTC (10 months ago) by sun
Branch: MAIN
CVS Tags: DRUPAL-7--1-0-BETA1, HEAD
Changes since 1.5: +14 -3 lines
File MIME type: text/x-php
by sun: Fixed boolean constants must be written uppercase.
1 <?php
2 // $Id: coder_format.inc,v 1.5 2009/01/19 03:28:08 sun Exp $
3
4
5 /**
6 * Recursively process .module and .inc files in directory with coder_format_file().
7 *
8 * @param $directory
9 * Path to a directory to process recursively.
10 * @param $undo
11 * Boolean whether or not to undo batch replacements.
12 */
13 function coder_format_recursive($directory, $undo = false) {
14 // Convert Windows paths (only cosmetical).
15 $directory = str_replace('\\', '/', $directory);
16
17 // Check if directory exists.
18 if (!file_check_directory($directory)) {
19 drupal_set_message(t('%directory not found.', array('%directory' => $directory)), 'error');
20 return FALSE;
21 }
22
23 // Fetch files to process.
24 $mask = '\.php$|\.module$|\.inc$|\.install|\.profile$';
25 $nomask = array('.', '..', 'CVS', '.svn');
26 $files = file_scan_directory($directory, $mask, $nomask, 0, true);
27 foreach ($files as $file) {
28 coder_format_file($file->filename, $undo);
29 }
30 }
31
32 /**
33 * Reads, backups, processes and writes the source code from and to a file.
34 *
35 * @param $filename
36 * Path to a file to process or restore. Pass original filename to restore an
37 * already processed file.
38 * @param $undo
39 * Whether to restore a processed file. Always restores the last backup.
40 *
41 * @return
42 * TRUE on success.
43 */
44 function coder_format_file($filename, $undo = FALSE) {
45 // Restore a processed file.
46 if ($undo) {
47 // Do nothing if no backup file exists at all.
48 if (!file_exists($filename .'.coder.orig')) {
49 return;
50 }
51 // Save original filename.
52 $original = $filename;
53 // Retrieve the file's directory.
54 $basename = file_check_path($filename);
55 // Find all backups.
56 $mask = '^'. preg_quote($basename) .'(\.coder\.orig)+$';
57 $nomask = array('.', '..', 'CVS', '.svn');
58 $backups = file_scan_directory($filename, $mask, $nomask, 0, FALSE);
59 // Find the latest backup to restore.
60 ksort($backups);
61 $latest = array_pop($backups);
62 // Restore latest backup.
63 if (file_move($latest->filename, $original, FILE_EXISTS_REPLACE)) {
64 drupal_set_message(t('%file restored.', array('%file' => $original)));
65 return TRUE;
66 }
67 else {
68 drupal_set_message(t('%file could not be restored.', array('%file' => $original)), 'error');
69 return FALSE;
70 }
71 }
72
73 // Backup original file.
74 // file_copy() replaces source filepath with target filepath.
75 $sourcefile = $filename;
76 if (!file_copy($filename, $filename .'.coder.orig', FILE_EXISTS_RENAME)) {
77 drupal_set_message(t('%file could not be backup.', array('%file' => $filename)), 'error');
78 return FALSE;
79 }
80
81 // Read source code from source file.
82 $fd = fopen($sourcefile, 'r');
83 $code = fread($fd, filesize($sourcefile));
84 fclose($fd);
85
86 if ($code !== false) {
87 $code = coder_format_string_all($code);
88
89 if ($code !== false) {
90 // Write formatted source code to target file.
91 $fd = fopen($sourcefile, 'w');
92 $status = fwrite($fd, $code);
93 fclose($fd);
94
95 drupal_set_message(t('%file processed.', array('%file' => $sourcefile)));
96 return $status;
97 }
98 else {
99 drupal_set_message(t('An error occurred while processing %file.', array('%file' => $sourcefile)), 'error');
100 return FALSE;
101 }
102 }
103 else {
104 drupal_set_message(t('%file could not be opened.', array('%file' => $sourcefile)), 'error');
105 return FALSE;
106 }
107 }
108
109 /**
110 * Formats source code according to Drupal conventions, also using
111 * post and pre-processors.
112 *
113 * @param
114 * $code Code to process.
115 */
116 function coder_format_string_all($code) {
117 // Preprocess source code.
118 $code = coder_exec_processors($code, 'coder_preprocessor');
119
120 // Process source code.
121 $code = coder_format_string($code);
122
123 // Postprocess source code.
124 $code = coder_exec_processors($code, 'coder_postprocessor');
125
126 // Fix beginning and end of code.
127 $code = coder_trim_php($code);
128
129 return $code;
130 }
131
132 /**
133 * Format the source code according to Drupal coding style guidelines.
134 *
135 * This function uses PHP's tokenizer functions.
136 * @see <http://www.php.net/manual/en/ref.tokenizer.php>
137 *
138 * To achieve the desired coding style, we have to take some special cases
139 * into account. These are:
140 *
141 * Indent-related:
142 * $_coder_indent int Indent level
143 * The number of indents for the next line. This is
144 * - increased after {, : (after case and default).
145 * - decreased after }, break, case and default (after a previous case).
146 * $in_case bool
147 * Is true after case and default. Is false after break and return, if
148 * $braces_in_case is not greater than 0.
149 * $switches int Switch level
150 * Nested switches need to have extra indents added to them.
151 * $braces_in_case array Count of braces
152 * The number of currently opened curly braces in a case. This is needed
153 * to support arbitrary function exits inside of a switch control strucure.
154 * This is an array to allow for nested switches.
155 * $parenthesis int Parenthesis level
156 * The number of currently opened parenthesis. This
157 * - prevents line feeds in brackets (f.e. in arguments of for()).
158 * - is the base for formatting of multiline arrays. Note: If the last
159 * ');' is not formatted to the correct indent level then there is no
160 * ',' (comma) behind the last array value.
161 * $in_brace bool
162 * Is true after left curly braces if they are in quotes, an object or
163 * after a dollar sign. Prevents line breaks around such variable
164 * statements.
165 * $in_heredoc bool
166 * Is true after heredoc output method and false after heredoc delimiter.
167 * Prevents line breaks in heredocs.
168 * $first_php_tag bool
169 * Is false after the first PHP tag. Allows inserting a line break after
170 * the first one.
171 * $in_do_while bool
172 * Is true after a do {} statement and set to false in the next while
173 * statement. Prevents a line break in the do {...} while() construct.
174 *
175 * Whitespace-related:
176 * $in_object bool
177 * Prevents whitespace after ->.
178 * Is true after ->. Is reset to false after the next string or variable.
179 * $in_at bool
180 * Prevents whitespace after @.
181 * Is true after @. Is reset to false after the next string or variable.
182 * $in_quote bool
183 * Prevents
184 * - removal of whitespace in double quotes.
185 * - injection of new line feeds after brackets in double quotes.
186 * $inline_if bool
187 * Controls formatting of ? and : for inline ifs until a ; (semicolon) is
188 * processed.
189 * $in_function_declaration
190 * Prevents whitespace after & for function declarations, e.g.
191 * function &foo(). Is true after function token but before first
192 * parenthesis.
193 * $in_array
194 * Array of parenthesis level to whether or not the structure
195 * is for an array.
196 * $in_multiline
197 * Array of parenthesis level to whether or not the structure
198 * is multiline.
199 *
200 * Context flags:
201 * These variables give information about what tokens have just been
202 * processed so that operations can change their behavior depending on
203 * the preceding context without having to scan backwards on the fully
204 * formed result. Most of these are ad hoc and have a very specific
205 * purpose in the program. It would probably be a good idea to generalize
206 * this facility.
207 *
208 * $after_semicolon
209 * Is the token being processed on the same line as a semicolon? This
210 * allows for the semicolon processor to unconditionally add a newline
211 * while allowing things like inline comments on the same line to
212 * be bubbled up.
213 * $after_case
214 * Is the token being processed on the same line as a case? This
215 * is a specific override for comment movement behavior that places
216 * inline comments after a case before the case declaration.
217 * $after_comment
218 * Is the line being processed preceded by an inline comment?
219 * This is used to preserve newlines after comments.
220 * $after_initial_comment
221 * Is the line being processed preceded by the // $Id
222 * (ending dollar sign omitted) comment? This is a workaround to
223 * prevent the usual double-newline before docblocks for the very
224 * first docblock.
225 * $after_visibility_modifier
226 * Is the token being processed immediately preceded by a
227 * visibility modifier like public/protected/private? This prevents
228 * extra newlines added by T_FUNCTION.
229 * $after_return_in_case
230 * Whether or not the token is after a return statement in a case.
231 * This prevents the extra indent after case statements from being
232 * terminated prematurely for multiline return lines.
233 *
234 * @param $code
235 * The source code to format.
236 *
237 * @return
238 * The formatted code or false if it fails.
239 */
240 function coder_format_string($code = '') {
241 global $_coder_indent;
242
243 // Indent controls:
244 $_coder_indent = 0;
245 $in_case = false;
246 $switches = 0;
247 $parenthesis = 0;
248 $braces_in_case = array();
249 $in_brace = false;
250 $in_heredoc = false;
251 $first_php_tag = true;
252 $in_do_while = false;
253
254 // Whitespace controls:
255 $in_object = FALSE;
256 $in_at = FALSE;
257 $in_php = FALSE;
258 $in_quote = FALSE;
259 $inline_if = FALSE;
260 $in_array = array();
261 $in_multiline = array();
262
263 // Context flags:
264 $after_semicolon = FALSE;
265 $after_case = FALSE;
266 $after_comment = FALSE;
267 $after_initial_comment = FALSE;
268 $after_visibility_modifier = FALSE;
269 $after_return_in_case = FALSE;
270 $after_php = FALSE;
271
272 // Whether or not a function token was encountered:
273 $in_function_declaration = FALSE;
274
275 // The position of the last character of the last non-whitespace
276 // non-comment token, e.g. it would be:
277 // function foo() { // bar
278 // ^ this character
279 $position_last_significant_token = 0;
280
281 $result = '';
282 $lasttoken = array(0);
283 $tokens = token_get_all($code);
284
285 // Mask T_ML_COMMENT (PHP4) as T_COMMENT (PHP5).
286 if (!defined('T_ML_COMMENT')) {
287 define('T_ML_COMMENT', T_COMMENT);
288 }
289 // Mask T_DOC_COMMENT (PHP5) as T_ML_COMMENT (PHP4).
290 else if (!defined('T_DOC_COMMENT')) {
291 define('T_DOC_COMMENT', T_ML_COMMENT);
292 }
293
294 foreach ($tokens as $token) {
295 if (is_string($token)) {
296 // Simple 1-character token.
297 $text = trim($token);
298 switch ($text) {
299 case '{':
300 // Add a space before and behind a curly brace, if we are in inline
301 // PHP, e.g. <?php if ($foo) { print $foo }
302 if ($after_php) {
303 $text = " $text ";
304 }
305 // Write curly braces at the end of lines followed by a line break if
306 // not in quotes (""), object ($foo->{$bar}) or in variables (${foo}).
307 // (T_DOLLAR_OPEN_CURLY_BRACES exists but is never assigned.)
308 $c = substr(rtrim($result), -1);
309 if (!$after_php && !$in_quote && (!$in_variable && !$in_object && $c != '$' || $c == ')')) {
310 if ($in_case) {
311 ++$braces_in_case[$switches];
312 $_coder_indent += $switches - 1;
313 }
314 ++$_coder_indent;
315 $result = rtrim($result) .' '. $text;
316 coder_br($result);
317 }
318 else {
319 $in_brace = true;
320 $result .= $text;
321 }
322 break;
323
324 case '}':
325 if (!$in_quote && !$in_brace && !$in_heredoc) {
326 if ($switches) {
327 --$braces_in_case[$switches];
328 }
329 --$_coder_indent;
330 if ($braces_in_case[$switches] < 0 && $in_case) {
331 // Decrease indent if last case in a switch is not terminated.
332 --$_coder_indent;
333 $in_case = FALSE;
334 }
335 if ($braces_in_case[$switches] < 0) {
336 $braces_in_case[$switches] = 0;
337 $switches--;
338 }
339 if ($switches > 0) {
340 $in_case = TRUE;
341 }
342
343 if (!$after_php) {
344 $result = rtrim($result);
345 if (substr($result, -1) != '{') {
346 // Avoid line break in empty curly braces.
347 coder_br($result);
348 }
349 $result .= $text;
350 coder_br($result);
351 }
352 else {
353 // Add a space before a curly brace, if we are in inline PHP, e.g.
354 // <?php if ($foo) { print $foo }
355 $result = rtrim($result, ' ');
356 if (substr($result, -1) !== "\n") {
357 $result .= ' ';
358 }
359 $result .= $text;
360 }
361 }
362 else {
363 $in_brace = false;
364 $result .= $text;
365 }
366 break;
367
368 case ';':
369 $result = rtrim($result) . $text;
370 // Check if we had deferred reduction of indent because we were in
371 // a case statement. Now we can decrease the indent.
372 if ($after_return_in_case) {
373 --$_coder_indent;
374 $after_return_in_case = FALSE;
375 }
376 if (!$parenthesis && !$in_heredoc && !$after_php) {
377 coder_br($result);
378 $after_semicolon = TRUE;
379 }
380 else {
381 $result .= ' ';
382 }
383 if ($inline_if) {
384 $inline_if = false;
385 }
386 break;
387
388 case '?':
389 $inline_if = true;
390 $result .= ' '. $text .' ';
391 break;
392
393 case ':':
394 if ($inline_if) {
395 $result .= ' '. $text .' ';
396 }
397 elseif ($after_php) {
398 $result .= $text;
399 }
400 else {
401 if ($in_case) {
402 ++$_coder_indent;
403 }
404 $result = rtrim($result) . $text;
405 coder_br($result);
406 }
407 break;
408
409 case '(':
410 $result .= $text;
411 ++$parenthesis;
412 // Not multiline until proven so by whitespace.
413 $in_multiline[$parenthesis] = FALSE;
414 // If the $in_array flag for this parenthesis level was not
415 // set previously, set it to FALSE.
416 if (!isset($in_array[$parenthesis])) {
417 $in_array[$parenthesis] = FALSE;
418 }
419 // Terminate function declaration, as a parenthesis indicates
420 // the beginning of the arguments. This will catch all other
421 // instances of parentheses, but in this case it's not a problem.
422 $in_function_declaration = FALSE;
423 break;
424
425 case ')':
426 if ($in_array[$parenthesis] && $in_multiline[$parenthesis]) {
427 // Check if a comma insertion is necessary:
428 $c = $position_last_significant_token;
429 if ($result[$c] !== ',') {
430 // We need to add a comma at $c:
431 $result = substr($result, 0, $c + 1) .','. substr($result, $c + 1);
432 }
433 }
434 if (!$in_quote && !$in_heredoc && (substr(rtrim($result), -1) == ',' || $in_multiline[$parenthesis])) {
435 // Fix indent of right parenthesis in multiline structures by
436 // increasing indent for each parenthesis and decreasing one level.
437 $result = rtrim($result);
438 coder_br($result, $parenthesis - 1);
439 $result .= $text;
440 }
441 else {
442 $result .= $text;
443 }
444 if ($parenthesis) {
445 // Current parenthesis level is not an array anymore.
446 $in_array[$parenthesis] = FALSE;
447 --$parenthesis;
448 }
449 break;
450
451 case '@':
452 $in_at = true;
453 $result .= $text;
454 break;
455
456 case ',':
457 $result .= $text .' ';
458 break;
459
460 case '.':
461 // Starting from 7.x, string concatenations follow PEAR's standard.
462 $result = rtrim($result) . ' ' . $text . ' ';
463 break;
464
465 case '=':
466 case '<':
467 case '>':
468 case '+':
469 case '*':
470 case '/':
471 case '|':
472 case '^':
473 case '%':
474 $result = rtrim($result) .' '. $text .' ';
475 break;
476
477 case '&':
478 if (substr(rtrim($result), -1) == '=' || substr(rtrim($result), -1) == '(' || substr(rtrim($result), -1) == ',') {
479 $result .= $text;
480 }
481 else {
482 $result = rtrim($result) .' '. $text;
483 // Ampersands used to declare reference return value for
484 // functions should not have trailing space.
485 if (!$in_function_declaration) {
486 $result .= ' ';
487 }
488 }
489 break;
490
491 case '-':
492 $result = rtrim($result);
493 // Do not add a space before negative numbers or variables.
494 $c = substr($result, -1);
495 // Do not add a space between closing parenthesis and negative arithmetic operators.
496 if ($c == '(') {
497 $result .= ltrim($text);
498 }
499 // Add a space in front of the following chars, but not after them.
500 elseif ($c == '>' || $c == '=' || $c == ',' || $c == ':' || $c == '?') {
501 $result .= ' '. $text;
502 }
503 // Default arithmetic operator behavior.
504 else {
505 $result .= ' '. $text .' ';
506 }
507 break;
508
509 case '"':
510 // Toggle quote if the char is not escaped.
511 if (rtrim($result) != "\\") {
512 $in_quote = $in_quote ? false : true;
513 }
514 $result .= $text;
515 break;
516
517 default:
518 $result .= $text;
519 break;
520 }
521
522 // All text possibilities are significant:
523 $position_last_significant_token = strlen(rtrim($result)) - 1;
524
525 // Because they are all significant, we cannot possibly be after
526 // a comment now.
527 $after_comment = FALSE;
528 $after_initial_comment = FALSE;
529
530 // TODO: Make resetting context flags easier to do.
531 }
532 else {
533 // If we get here, then we have found not a single char, but a token.
534 // See <http://www.php.net/manual/en/tokens.php> for a reference.
535
536 // Fetch token array.
537 list($id, $text) = $token;
538
539 // Debugging:
540 /*
541 if ($lasttoken[0] == T_WHITESPACE) {
542 $result .= token_name($id);
543 }
544 */
545
546
547 switch ($id) {
548 case T_ARRAY:
549 // Write array in lowercase.
550 $result .= strtolower(trim($text));
551 // Mark the next parenthesis level (we haven't consumed that token
552 // yet) as an array.
553 $in_array[$parenthesis + 1] = TRUE;
554 break;
555
556 case T_OPEN_TAG:
557 case T_OPEN_TAG_WITH_ECHO:
558 $in_php = true;
559 // Add a line break between two PHP tags.
560 if (substr(rtrim($result), -2) == '?>' && !$after_php) {
561 coder_br($result);
562 }
563 $after_php = true;
564 $nl = substr_count($text, "\n");
565 $result .= trim($text);
566 if ($first_php_tag) {
567 coder_br($result);
568 $first_php_tag = FALSE;
569 }
570 else {
571 if ($nl) {
572 coder_br($result, $parenthesis);
573 }
574 else {
575 $result .= ' ';
576 }
577 }
578 break;
579
580 case T_CLOSE_TAG:
581 $in_php = false;
582 if ($after_php) {
583 $result = rtrim($result, ' ') .' ';
584 $text = ltrim($text, ' ');
585 }
586 // Do not alter a closing PHP tag ($text includes trailing white-space)
587 // at all. Should allow to apply coder_format on phptemplate files.
588 $result .= $text;
589 break;
590
591 case T_OBJECT_OPERATOR:
592 $in_object = true;
593 $result .= trim($text);
594 break;
595
596 case T_CONSTANT_ENCAPSED_STRING:
597 case T_STRING:
598 case T_VARIABLE:
599 // Boolean constants (TRUE, FALSE, NULL) are T_STRINGs, but must be
600 // written uppercase.
601 $text = trim($text);
602 if ($text == 'true' || $text == 'false' || $text == 'null') {
603 $text = strtoupper($text);
604 }
605 // No space after object operator ($foo->bar) and error suppression (@function()).
606 if ($in_object || $in_at) {
607 $result = rtrim($result) . $text;
608 $in_object = false;
609 $in_at = false;
610 }
611 else {
612 // Insert a space after right parenthesis, but not after type casts.
613 if (!in_array($lasttoken[0], array(T_ARRAY_CAST, T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, T_OBJECT_CAST, T_STRING_CAST, T_UNSET_CAST))) {
614 coder_add_space($result);
615 }
616 $result .= $text;
617 }
618 $in_variable = true;
619 break;
620
621 case T_CONST:
622 // Constants are written uppercase.
623 $result = rtrim($result) . strtoupper(trim($text));
624 break;
625
626 case T_ENCAPSED_AND_WHITESPACE:
627 $result .= $text;
628 break;
629
630 case T_WHITESPACE:
631 // Avoid duplicate line feeds outside arrays.
632 $c = ($parenthesis || $after_comment) ? 0 : 1;
633
634 for ($c, $cc = substr_count($text, "\n"); $c < $cc; ++$c) {
635 // Newlines were added; not after semicolon anymore
636 coder_br($result, $parenthesis);
637 }
638
639 // If there were newlines present inside a parenthesis,
640 // turn on multiline mode.
641 if ($cc && $parenthesis) {
642 $in_multiline[$parenthesis] = TRUE;
643 }
644
645 // If there were newlines present, move inline comments above.
646 if ($cc) {
647 $after_semicolon = FALSE;
648 $after_case = FALSE;
649 $after_php = FALSE;
650 }
651
652 $in_variable = FALSE;
653 break;
654
655 case T_SWITCH:
656 ++$switches;
657 // Purposely fall through.
658 case T_IF:
659 case T_FOR:
660 case T_FOREACH:
661 case T_GLOBAL:
662 case T_STATIC:
663 case T_ECHO:
664 case T_PRINT:
665 case T_NEW:
666 case T_REQUIRE:
667 case T_REQUIRE_ONCE:
668 case T_INCLUDE:
669 case T_INCLUDE_ONCE:
670 case T_VAR:
671 coder_add_space($result);
672 // Append a space.
673 $result .= trim($text) .' ';
674 break;
675
676 case T_DO:
677 $result .= trim($text);
678 $in_do_while = true;
679 break;
680
681 case T_WHILE:
682 if ($in_do_while && substr(rtrim($result), -1) === '}') {
683 // Write while after right parenthesis for do {...} while().
684 $result = rtrim($result) .' ';
685 $in_do_while = false;
686 }
687 // Append a space.
688 $result .= trim($text) .' ';
689 break;
690
691 case T_ELSE:
692 case T_ELSEIF:
693 // Write else and else if to a new line.
694 $result = rtrim($result);
695 coder_br($result);
696 $result .= trim($text) .' ';
697 break;
698
699 case T_CASE:
700 case T_DEFAULT:
701 $braces_in_case[$switches] = 0;
702 $result = rtrim($result);
703 $after_case = true;
704 if (!$in_case) {
705 $in_case = true;
706 // Add a line break between cases.
707 if (substr($result, -1) != '{') {
708 coder_br($result);
709 }
710 }
711 else {
712 // Decrease current indent to align multiple cases.
713 --$_coder_indent;
714 }
715 coder_br($result);
716 $result .= trim($text) .' ';
717 break;
718
719 case T_BREAK:
720 // Write break to a new line.
721 $result = rtrim($result);
722 coder_br($result);
723 // Trailing space needed for 'break 3;'.
724 $result .= trim($text) . ' ';
725 if ($in_case && !$braces_in_case[$switches]) {
726 --$_coder_indent;
727 $in_case = FALSE;
728 }
729 break;
730
731 case T_RETURN:
732 if ($in_case && !$braces_in_case[$switches]) {
733 // Defer reduction of indent for later.
734 ++$_coder_indent;
735 $after_return_in_case = true;
736 }
737 case T_CONTINUE:
738 coder_add_space($result);
739 $result .= trim($text) .' ';
740 // Decrease indent only if we're not in a control structure inside a case.
741 if ($in_case && !$braces_in_case[$switches]) {
742 --$_coder_indent;
743 $in_case = false;
744 }
745 break;
746
747 case T_ABSTRACT:
748 case T_PRIVATE:
749 case T_PUBLIC:
750 case T_PROTECTED:
751 // Class member function properties must be treated similar to
752 // T_FUNCTION, but without line-break after the token. Because more
753 // than one of these tokens can appear in front of a function token,
754 // we need another white-space control variable.
755 $result .= trim($text) .' ';
756 $after_visibility_modifier = TRUE;
757 break;
758
759 case T_FUNCTION:
760 $in_function_declaration = TRUE;
761 // Fall through.
762 case T_CLASS:
763 // Write function and class to new lines.
764 $result = rtrim($result);
765 if (substr($result, -1) == '}') {
766 coder_br($result);
767 }
768 if (!$after_visibility_modifier) {
769 coder_br($result);
770 }
771 else {
772 // This code only applies to T_FUNCTION; do not add a newline
773 // after public/protected/private/abstract.
774 $after_visibility_modifier = FALSE;
775 $result .= ' ';
776 }
777 $result .= trim($text) .' ';
778 break;
779
780 case T_EXTENDS:
781 case T_INSTANCEOF:
782 // Add space before and after 'extends' and 'instanceof'.
783 $result = rtrim($result);
784 $result .= ' '. trim($text) .' ';
785 break;
786
787 case T_AND_EQUAL:
788 case T_AS:
789 case T_BOOLEAN_AND:
790 case T_BOOLEAN_OR:
791 case T_CONCAT_EQUAL:
792 case T_DIV_EQUAL:
793 case T_DOUBLE_ARROW:
794 case T_IS_EQUAL:
795 case T_IS_NOT_EQUAL:
796 case T_IS_IDENTICAL:
797 case T_IS_NOT_IDENTICAL:
798 case T_IS_GREATER_OR_EQUAL:
799 case T_IS_SMALLER_OR_EQUAL:
800 case T_LOGICAL_AND:
801 case T_LOGICAL_OR:
802 case T_LOGICAL_XOR:
803 case T_MINUS_EQUAL:
804 case T_MOD_EQUAL:
805 case T_MUL_EQUAL:
806 case T_OR_EQUAL:
807 case T_PLUS_EQUAL:
808 case T_SL:
809 case T_SL_EQUAL:
810 case T_SR:
811 case T_SR_EQUAL:
812 case T_XOR_EQUAL:
813 // Surround operators with spaces.
814 if (substr($result, -1) != ' ') {
815 // $result must not be trimmed to allow multi-line if-clauses.
816 $result .= ' ';
817 }
818 $result .= trim($text) .' ';
819 break;
820
821 case T_COMMENT:
822 case T_ML_COMMENT:
823 case T_DOC_COMMENT:
824 if (substr($text, 0, 3) == '/**') {
825 // Prepend a new line.
826 $result = rtrim($result);
827 if (!$after_initial_comment) {
828 coder_br($result);
829 }
830 else {
831 // This probably will get set below, but it's good to
832 // explicitly turn it off after the initial comment has
833 // influenced behavior and now is not necessary.
834 $after_initial_comment = FALSE;
835 }
836 coder_br($result);
837
838 // Remove carriage returns.
839 $text = str_replace("\r", '', $text);
840
841 $lines = explode("\n", $text);
842 $params_fixed = false;
843 for ($l = 0; $l < count($lines); ++$l) {
844 $lines[$l] = trim($lines[$l]);
845
846 // Add a new line between function description and first parameter description.
847 if (!$params_fixed && substr($lines[$l], 0, 8) == '* @param' && $lines[$l - 1] != '*') {
848 $result .= ' *';
849 coder_br($result);
850 $params_fixed = true;
851 }
852 else if (!$params_fixed && substr($lines[$l], 0, 8) == '* @param') {
853 // Do nothing if parameter description is properly formatted.
854 $params_fixed = true;
855 }
856
857 // Add a new line between function params and return.
858 if (substr($lines[$l], 0, 9) == '* @return' && $lines[$l - 1] != '*') {
859 $result .= ' *';
860 coder_br($result);
861 }
862
863 // Add one space indent to get ' *[...]'.
864 if ($l > 0) {
865 $result .= ' ';
866 }
867 $result .= $lines[$l];
868 if ($l < count($lines)) {
869 coder_br($result);
870 }
871 }
872 }
873 else {
874 // Move the comment above if it's embedded.
875 $statement = false;
876 // Some PHP versions throw a warning about wrong parameter count for
877 // substr_count().
878 $cc = substr_count(substr($result, $position_last_significant_token), "\n");
879 if ((!$cc || $after_semicolon) && !$after_case) {
880 $nl_position = strrpos(rtrim($result, " \n"), "\n");
881 $statement = substr($result, $nl_position);
882 $result = substr($result, 0, $nl_position);
883 $after_semicolon = false;
884 coder_br($result, $parenthesis);
885 }
886 $result .= trim($text);
887 coder_br($result, $parenthesis);
888 if ($statement) {
889 // Newlines are automatically added, so remove these.
890 $result = rtrim($result, "\n ");
891 $result .= rtrim($statement, "\n ");
892 coder_br($result, $parenthesis);
893 // Need to update this, as our comment trickery has just
894 // reshuffled the index.
895 $position_last_significant_token = strlen(rtrim($result, " \n")) - 1;
896 }
897 else {
898 if (strpos($text, '$' . 'Id$') === FALSE) {
899 $after_comment = TRUE;
900 }
901 else {
902 // Is the number two so that our bottom code doesn't override
903 // our flag immediately.
904 $after_initial_comment = 2;
905 }
906 }
907 }
908 break;
909
910 case T_INLINE_HTML:
911 $result .= $text;
912 break;
913
914 case T_START_HEREDOC:
915 $result .= trim($text);
916 coder_br($result, FALSE, FALSE);
917 $in_heredoc = TRUE;
918 break;
919
920 case T_END_HEREDOC:
921 $result .= trim($text);
922 coder_br($result, FALSE, FALSE);
923 $in_heredoc = FALSE;
924 break;
925
926 default:
927 $result .= trim($text);
928 break;
929 }
930
931 // Store last token.
932 $lasttoken = $token;
933
934 // Excluding comments and whitespace, set the position of the
935 // last significant token's last character to the length of the
936 // string minus one.
937 switch ($id) {
938 case T_WHITESPACE:
939 case T_COMMENT:
940 case T_ML_COMMENT:
941 case T_DOC_COMMENT:
942 break;
943
944 default:
945 $position_last_significant_token = strlen(rtrim($result, " \n")) - 1;
946 break;
947 }
948
949 if ($id !== T_COMMENT && $id !== T_ML_COMMENT) {
950 $after_comment = FALSE;
951 }
952 if ($after_initial_comment && $id !== T_WHITESPACE) $after_initial_comment--;
953 }
954 }
955 return $result;
956 }
957
958 /**
959 * Generate a line feed including current line indent.
960 *
961 * This function will also remove all line indentation from the
962 * previous line if no text was added.
963 *
964 * @param &$result
965 * Result variable to append break and indent to, passed by reference.
966 * @param $parenthesis
967 * Optional integer of parentheses level for extra indents.
968 * @param $add_indent
969 * Whether to add current line indent after line feed.
970 */
971 function coder_br(&$result, $parenthesis = false, $add_indent = true) {
972 global $_coder_indent;
973
974 // Scan result backwards for whitespace.
975 for ($i = strlen($result) - 1; $i >= 0; $i--) {
976 if ($result[$i] == ' ') {
977 continue;
978 }
979 if ($result[$i] == "\n") {
980 $result = rtrim($result, ' ');
981 break;
982 }
983 // Non-whitespace was encountered, no changes necessary.
984 break;
985 }
986
987 if ($parenthesis) {
988 // Add extra indent for each parenthesis in multiline definitions (f.e. arrays).
989 $_coder_indent = $_coder_indent + $parenthesis;
990 $result = rtrim($result);
991 // This recursive call will only be done once, as $parenthesis is
992 // set to false.
993 coder_br($result, false, $add_indent);
994 $_coder_indent = $_coder_indent - $parenthesis;
995 }
996 else {
997 $output = "\n";
998 if ($add_indent && $_coder_indent >= 0) {
999 $output .= str_repeat(' ', $_coder_indent);
1000 }
1001 $result .= $output;
1002 }
1003 }
1004
1005 /**
1006 * Write a space in certain conditions.
1007 *
1008 * A conditional space is needed after a right parenthesis of an if statement
1009 * that is not followed by curly braces.
1010 *
1011 * @param $result
1012 * Current result string that will be checked.
1013 *
1014 * @return
1015 * Resulting string with or without an additional space.
1016 */
1017 function coder_add_space(&$result) {
1018 if (substr($result, -1) == ')') {
1019 $result .= ' ';
1020 }
1021 }
1022
1023 /**
1024 * Trim overall code.
1025 *
1026 * Strips whitespace at the beginning and end of code,
1027 * removes the closing PHP tag and appends two empty lines.
1028 */
1029 function coder_trim_php($code) {
1030 // Remove surrounding whitespace.
1031 $code = trim($code);
1032
1033 // Insert CVS keyword Id.
1034 // Search in the very first 1000 chars, insert only one instance.
1035 if (strpos(substr($code, 0, 1000), '$Id') === false) {
1036 $code = preg_replace('/<\?php\n/', "<?php\n// \$Id\$\n\n", $code, 1);
1037 }
1038
1039 // Remove closing PHP tag.
1040 if (substr($code, -2) == '?>') {
1041 $code = rtrim($code, '?>');
1042 }
1043
1044 // Append two empty lines.
1045 $code .= str_repeat(chr(10), 2);
1046
1047 return $code;
1048 }
1049
1050 /**
1051 * Execute special tasks on source code.
1052 *
1053 * This function works similar to the Drupal hook and forms system. It searches
1054 * for all defined functions with the given prefix and performs a preg_replace
1055 * on the source code for each of these functions.
1056 *
1057 * Processor functions are defined with a associative array containing the
1058 * following keys with the corresponding values:
1059 * #title
1060 * A human readable text describing what the processor actually does.
1061 * #search
1062 * The regular expression to search for.
1063 * #replace
1064 * The replacement text for each match.
1065 *
1066 * Optional definitions:
1067 * #debug
1068 * Set this to true to directly output the results of preg_match_all and
1069 * exit script execution after this processor.
1070 *
1071 * @param string $code
1072 * The source code to process.
1073 * @param string $prefix
1074 * Prefix of the functions to execute.
1075 *
1076 * @return
1077 * The processed source code.
1078 */
1079 function coder_exec_processors($code, $prefix = '') {
1080 if (empty($prefix)) {
1081 return;
1082 }
1083 $tasks = get_defined_functions();
1084 $tasks = $tasks['user'];
1085 for ($c = 0, $cc = count($tasks); $c < $cc; ++$c) {
1086 if (strpos($tasks[$c], $prefix) === false) {
1087 unset($tasks[$c]);
1088 }
1089 else {
1090 $tasks[$tasks[$c]] = call_user_func($tasks[$c]);
1091 unset($tasks[$c]);
1092 }
1093 }
1094 uasort($tasks, 'coder_order_processors');
1095 foreach ($tasks as $func => $task) {
1096 if (!isset($task['#search']) || (!isset($task['#replace']) && !isset($task['#replace_callback']))) {
1097 continue;
1098 }
1099 if (isset($task['#debug'])) {
1100 // Output regular expression results if debugging is enabled.
1101 preg_match_all($task['#search'], $code, $matches, PREG_SET_ORDER);
1102 echo "<pre>";
1103 var_dump($matches);
1104 echo "</pre>\n";
1105 // Exit immediately in debugging mode.
1106 exit;
1107 }
1108 if (isset($task['#replace_callback'])) {
1109 $code = preg_replace_callback($task['#search'], $task['#replace_callback'], $code);
1110 }
1111 else {
1112 $code = preg_replace($task['#search'], $task['#replace'], $code);
1113 }
1114 }
1115
1116 return $code;
1117 }
1118
1119 /**
1120 * Orders preprocessors by weight.
1121 *
1122 * @see coder_exec_processors()
1123 */
1124 function coder_order_processors($a, $b) {
1125 if (isset($a['#weight']) && isset($b['#weight'])) {
1126 return $a['#weight'] - $b['#weight'];
1127 }
1128 else {
1129 return isset($a['#weight']) ? false : true;
1130 }
1131 }
1132
1133 /**
1134 * @defgroup coder_preprocessor Preprocessors.
1135 * @{
1136 */
1137 function coder_preprocessor_line_breaks_win() {
1138 return array(
1139 '#title' => 'Convert Windows line breaks to Unix format.',
1140 '#weight' => 1,
1141 '#search' => "@\r\n@",
1142 '#replace' => "\n",
1143 );
1144 }
1145
1146 function coder_preprocessor_line_breaks_mac() {
1147 return array(
1148 '#title' => 'Convert Macintosh line breaks to Unix format.',
1149 '#weight' => 2,
1150 '#search' => "@\r@",
1151 '#replace' => "\n",
1152 );
1153 }
1154
1155 function coder_preprocessor_php() {
1156 return array(
1157 '#title' => 'Always use &lt;?php ?&gt; to delimit PHP code, not the &lt;? ?&gt; shorthands.',
1158 '#search' => '@<\?(\s)@',
1159 '#replace' => "<?php$1",
1160 );
1161 }
1162
1163 function coder_preprocessor_switch_duplicate_exit() {
1164 return array(
1165 '#title' => 'Either exit a switch case with return *or* break.',
1166 '#search' => '@
1167 (return # match a return
1168 \s+ # - followed by some white-space
1169 .+ # - followed by any characters
1170 ; # - followed by a semicolon
1171 )
1172 \s+ # match white-space (required)
1173 break; # match a directly following "break;"
1174 @mx',
1175 '#replace' => '$1',
1176 );
1177 }
1178
1179 function coder_preprocessor_inline_comment() {
1180 return array(
1181 '#title' => 'Move inline comments above remarked line.',
1182 '#weight' => 2,
1183 '#search' => '@
1184 ^([\040\t]*) # match spaces or tabs only.
1185 (?!case) # do not match case statements.
1186 (\S.+? # do not match lines containing only a comment.
1187 [;,{] # match the TRICKY lines only.
1188 )
1189 [\040\t]* # match spaces or tabs only.
1190 (?!:) # do not match URL protocols.
1191 //\s* # match inline comment token.
1192 ([^;\$]+?)$ # fetch comment, but do not match CVS keyword Id, nested comments, and comment tokens in quotes (f.e. "W3C//DTD").
1193 @mx',
1194 '#replace' => "$1// $3\n$1$2",
1195 );
1196 }
1197
1198 /**
1199 * @} End of "defgroup coder_preprocessor".
1200 */
1201
1202 /**
1203 * @defgroup coder_postprocessor Postprocessors.
1204 * @{
1205 */
1206 function coder_postprocessor_cvs_id() {
1207 return array(
1208 '#title' => 'If the CVS keyword Id already exists, append a new line after it.',
1209 '#search' => '@
1210 ^( # match start of a line
1211 //.* # match an inline comment followed by any characters
1212 \$Id.*\$ # match a CVS Id tag
1213 )$ # match end of a line
1214 @mx',
1215 '#replace' => "$1\n",
1216 );
1217 }
1218
1219 function coder_postprocessor_multiple_vars() {
1220 return array(
1221 '#title' => 'Align equal signs of multiple variable assignments in the same column.',
1222 '#search' => '@
1223 ^( # match start of a line
1224 \n?\ * # match white-space, but only one new line
1225 \$.+? # match a variable name
1226 \ =\ # match a variable assignment
1227 .+?$ # match a variable value
1228 ){3,} # require the pattern to match at least 3 times
1229 @mx',
1230 '#replace_callback' => 'coder_replace_multiple_vars',
1231 );
1232 }
1233
1234 function coder_replace_multiple_vars($matches) {
1235 // Retrieve all variable name = variable value pairs.
1236 $regex = '@
1237 ^ # match start of a line
1238 (\s*) # match a single optional white-space char
1239 (\$.+?) # match a variable name
1240 \ (.?)=\ # match a variable assignment
1241 (.+?$) # match a variable value including end of line
1242 @mx';
1243 preg_match_all($regex, $matches[0], $vars, PREG_SET_ORDER);
1244
1245 // Determine the longest variable name.
1246 $maxlength = 0;
1247 foreach ($vars as $var) {
1248 if (strlen($var[2]) > $maxlength) {
1249 $maxlength = strlen($var[2] . $var[3]);
1250 }
1251 }
1252
1253 // Realign variable values at the longest variable names.
1254 $return = '';
1255 $extra_spaces = 0;
1256 for ($c = 0, $cc = count($vars); $c < $cc; ++$c) {
1257 if ($maxlength <= 20) {
1258 $extra_spaces = $maxlength - strlen($vars[$c][2] . $vars[$c][3]);
1259 }
1260 $return .= $vars[$c][1] . $vars[$c][2];
1261 $return .= str_repeat(' ', $extra_spaces) .' '. $vars[$c][3] .'= ';
1262 $return .= $vars[$c][4];
1263 if ($c < $cc - 1) {
1264 // Append a line break, but not to the last variable assignment.
1265 $return .= "\n";
1266 }
1267 }
1268
1269 return $return;
1270 }
1271
1272 function coder_postprocessor_indent_multiline_array() {
1273 // Still buggy, disabled for now.
1274 return array(
1275 '#title' => 'Align equal signs of multiline array assignments in the same column.',
1276 '#search' => '@
1277 ^ # match start of a line
1278 (?:\s* # require initial white-space
1279 (?:
1280 (?:
1281 ([\'"]).+?\1 # capture a string key
1282 |.+? # or any other key without white-space
1283