$directory)), 'error'); return FALSE; } // Fetch files to process. $mask = '\.php$|\.module$|\.inc$|\.install|\.profile$'; $nomask = array('.', '..', 'CVS', '.svn'); $files = file_scan_directory($directory, $mask, $nomask, 0, true); foreach ($files as $file) { coder_format_file($file->filename, $undo); } } /** * Reads, backups, processes and writes the source code from and to a file. * * @param $filename * Path to a file to process or restore. Pass original filename to restore an * already processed file. * @param $undo * Whether to restore a processed file. Always restores the last backup. * * @return * TRUE on success. */ function coder_format_file($filename, $undo = FALSE) { // Restore a processed file. if ($undo) { // Do nothing if no backup file exists at all. if (!file_exists($filename .'.coder.orig')) { return; } // Save original filename. $original = $filename; // Retrieve the file's directory. $basename = file_check_path($filename); // Find all backups. $mask = '^'. preg_quote($basename) .'(\.coder\.orig)+$'; $nomask = array('.', '..', 'CVS', '.svn'); $backups = file_scan_directory($filename, $mask, $nomask, 0, FALSE); // Find the latest backup to restore. ksort($backups); $latest = array_pop($backups); // Restore latest backup. if (file_move($latest->filename, $original, FILE_EXISTS_REPLACE)) { drupal_set_message(t('%file restored.', array('%file' => $original))); return TRUE; } else { drupal_set_message(t('%file could not be restored.', array('%file' => $original)), 'error'); return FALSE; } } // Backup original file. // file_copy() replaces source filepath with target filepath. $sourcefile = $filename; if (!file_copy($filename, $filename .'.coder.orig', FILE_EXISTS_RENAME)) { drupal_set_message(t('%file could not be backup.', array('%file' => $filename)), 'error'); return FALSE; } // Read source code from source file. $fd = fopen($sourcefile, 'r'); $code = fread($fd, filesize($sourcefile)); fclose($fd); if ($code !== false) { $code = coder_format_string_all($code); if ($code !== false) { // Write formatted source code to target file. $fd = fopen($sourcefile, 'w'); $status = fwrite($fd, $code); fclose($fd); drupal_set_message(t('%file processed.', array('%file' => $sourcefile))); return $status; } else { drupal_set_message(t('An error occurred while processing %file.', array('%file' => $sourcefile)), 'error'); return FALSE; } } else { drupal_set_message(t('%file could not be opened.', array('%file' => $sourcefile)), 'error'); return FALSE; } } /** * Formats source code according to Drupal conventions, also using * post and pre-processors. * * @param * $code Code to process. */ function coder_format_string_all($code) { // Preprocess source code. $code = coder_exec_processors($code, 'coder_preprocessor'); // Process source code. $code = coder_format_string($code); // Postprocess source code. $code = coder_exec_processors($code, 'coder_postprocessor'); // Fix beginning and end of code. $code = coder_trim_php($code); return $code; } /** * Format the source code according to Drupal coding style guidelines. * * This function uses PHP's tokenizer functions. * @see * * To achieve the desired coding style, we have to take some special cases * into account. These are: * * Indent-related: * $_coder_indent int Indent level * The number of indents for the next line. This is * - increased after {, : (after case and default). * - decreased after }, break, case and default (after a previous case). * $in_case bool * Is true after case and default. Is false after break and return, if * $braces_in_case is not greater than 0. * $switches int Switch level * Nested switches need to have extra indents added to them. * $braces_in_case array Count of braces * The number of currently opened curly braces in a case. This is needed * to support arbitrary function exits inside of a switch control strucure. * This is an array to allow for nested switches. * $parenthesis int Parenthesis level * The number of currently opened parenthesis. This * - prevents line feeds in brackets (f.e. in arguments of for()). * - is the base for formatting of multiline arrays. Note: If the last * ');' is not formatted to the correct indent level then there is no * ',' (comma) behind the last array value. * $in_brace bool * Is true after left curly braces if they are in quotes, an object or * after a dollar sign. Prevents line breaks around such variable * statements. * $in_heredoc bool * Is true after heredoc output method and false after heredoc delimiter. * Prevents line breaks in heredocs. * $first_php_tag bool * Is false after the first PHP tag. Allows inserting a line break after * the first one. * $in_do_while bool * Is true after a do {} statement and set to false in the next while * statement. Prevents a line break in the do {...} while() construct. * * Whitespace-related: * $in_object bool * Prevents whitespace after ->. * Is true after ->. Is reset to false after the next string or variable. * $in_at bool * Prevents whitespace after @. * Is true after @. Is reset to false after the next string or variable. * $in_quote bool * Prevents * - removal of whitespace in double quotes. * - injection of new line feeds after brackets in double quotes. * $inline_if bool * Controls formatting of ? and : for inline ifs until a ; (semicolon) is * processed. * $in_function_declaration * Prevents whitespace after & for function declarations, e.g. * function &foo(). Is true after function token but before first * parenthesis. * $in_array * Array of parenthesis level to whether or not the structure * is for an array. * $in_multiline * Array of parenthesis level to whether or not the structure * is multiline. * * Context flags: * These variables give information about what tokens have just been * processed so that operations can change their behavior depending on * the preceding context without having to scan backwards on the fully * formed result. Most of these are ad hoc and have a very specific * purpose in the program. It would probably be a good idea to generalize * this facility. * * $after_semicolon * Is the token being processed on the same line as a semicolon? This * allows for the semicolon processor to unconditionally add a newline * while allowing things like inline comments on the same line to * be bubbled up. * $after_case * Is the token being processed on the same line as a case? This * is a specific override for comment movement behavior that places * inline comments after a case before the case declaration. * $after_comment * Is the line being processed preceded by an inline comment? * This is used to preserve newlines after comments. * $after_initial_comment * Is the line being processed preceded by the // $Id * (ending dollar sign omitted) comment? This is a workaround to * prevent the usual double-newline before docblocks for the very * first docblock. * $after_visibility_modifier * Is the token being processed immediately preceded by a * visibility modifier like public/protected/private? This prevents * extra newlines added by T_FUNCTION. * $after_return_in_case * Whether or not the token is after a return statement in a case. * This prevents the extra indent after case statements from being * terminated prematurely for multiline return lines. * * @param $code * The source code to format. * * @return * The formatted code or false if it fails. */ function coder_format_string($code = '') { global $_coder_indent; // Indent controls: $_coder_indent = 0; $in_case = false; $switches = 0; $parenthesis = 0; $braces_in_case = array(); $in_brace = false; $in_heredoc = false; $first_php_tag = true; $in_do_while = false; // Whitespace controls: $in_object = FALSE; $in_at = FALSE; $in_php = FALSE; $in_quote = FALSE; $inline_if = FALSE; $in_array = array(); $in_multiline = array(); // Context flags: $after_semicolon = FALSE; $after_case = FALSE; $after_comment = FALSE; $after_initial_comment = FALSE; $after_visibility_modifier = FALSE; $after_return_in_case = FALSE; $after_php = FALSE; // Whether or not a function token was encountered: $in_function_declaration = FALSE; // The position of the last character of the last non-whitespace // non-comment token, e.g. it would be: // function foo() { // bar // ^ this character $position_last_significant_token = 0; $result = ''; $lasttoken = array(0); $tokens = token_get_all($code); // Mask T_ML_COMMENT (PHP4) as T_COMMENT (PHP5). if (!defined('T_ML_COMMENT')) { define('T_ML_COMMENT', T_COMMENT); } // Mask T_DOC_COMMENT (PHP5) as T_ML_COMMENT (PHP4). else if (!defined('T_DOC_COMMENT')) { define('T_DOC_COMMENT', T_ML_COMMENT); } foreach ($tokens as $token) { if (is_string($token)) { // Simple 1-character token. $text = trim($token); switch ($text) { case '{': // Add a space before and behind a curly brace, if we are in inline // PHP, e.g. {$bar}) or in variables (${foo}). // (T_DOLLAR_OPEN_CURLY_BRACES exists but is never assigned.) $c = substr(rtrim($result), -1); if (!$after_php && !$in_quote && (!$in_variable && !$in_object && $c != '$' || $c == ')')) { if ($in_case) { ++$braces_in_case[$switches]; $_coder_indent += $switches - 1; } ++$_coder_indent; $result = rtrim($result) .' '. $text; coder_br($result); } else { $in_brace = true; $result .= $text; } break; case '}': if (!$in_quote && !$in_brace && !$in_heredoc) { if ($switches) { --$braces_in_case[$switches]; } --$_coder_indent; if ($braces_in_case[$switches] < 0 && $in_case) { // Decrease indent if last case in a switch is not terminated. --$_coder_indent; $in_case = FALSE; } if ($braces_in_case[$switches] < 0) { $braces_in_case[$switches] = 0; $switches--; } if ($switches > 0) { $in_case = TRUE; } if (!$after_php) { $result = rtrim($result); if (substr($result, -1) != '{') { // Avoid line break in empty curly braces. coder_br($result); } $result .= $text; coder_br($result); } else { // Add a space before a curly brace, if we are in inline PHP, e.g. // ': case '+': case '*': case '/': case '|': case '^': case '%': $result = rtrim($result) .' '. $text .' '; break; case '&': if (substr(rtrim($result), -1) == '=' || substr(rtrim($result), -1) == '(' || substr(rtrim($result), -1) == ',') { $result .= $text; } else { $result = rtrim($result) .' '. $text; // Ampersands used to declare reference return value for // functions should not have trailing space. if (!$in_function_declaration) { $result .= ' '; } } break; case '-': $result = rtrim($result); // Do not add a space before negative numbers or variables. $c = substr($result, -1); // Do not add a space between closing parenthesis and negative arithmetic operators. if ($c == '(') { $result .= ltrim($text); } // Add a space in front of the following chars, but not after them. elseif ($c == '>' || $c == '=' || $c == ',' || $c == ':' || $c == '?') { $result .= ' '. $text; } // Default arithmetic operator behavior. else { $result .= ' '. $text .' '; } break; case '"': // Toggle quote if the char is not escaped. if (rtrim($result) != "\\") { $in_quote = $in_quote ? false : true; } if (substr($result, -3) == ' . ') { // Write string concatenation character directly before strings. $result = rtrim($result); } $result .= $text; break; default: $result .= $text; break; } // All text possibilities are significant: $position_last_significant_token = strlen(rtrim($result)) - 1; // Because they are all significant, we cannot possibly be after // a comment now. $after_comment = FALSE; $after_initial_comment = FALSE; // TODO: Make resetting context flags easier to do. } else { // If we get here, then we have found not a single char, but a token. // See for a reference. // Fetch token array. list($id, $text) = $token; // Debugging: /* if ($lasttoken[0] == T_WHITESPACE) { $result .= token_name($id); } */ switch ($id) { case T_ARRAY: // Write array in lowercase. $result .= strtolower(trim($text)); // Mark the next parenthesis level (we haven't consumed that token // yet) as an array. $in_array[$parenthesis + 1] = TRUE; break; case T_OPEN_TAG: case T_OPEN_TAG_WITH_ECHO: $in_php = true; // Add a line break between two PHP tags. if (substr(rtrim($result), -2) == '?>' && !$after_php) { coder_br($result); } $after_php = true; $nl = substr_count($text, "\n"); $result .= trim($text); if ($first_php_tag) { coder_br($result); $first_php_tag = FALSE; } else { if ($nl) { coder_br($result, $parenthesis); } else { $result .= ' '; } } break; case T_CLOSE_TAG: $in_php = false; if ($after_php) { $result = rtrim($result, ' ') .' '; $text = ltrim($text, ' '); } // Do not alter a closing PHP tag ($text includes trailing white-space) // at all. Should allow to apply coder_format on phptemplate files. $result .= $text; break; case T_OBJECT_OPERATOR: $in_object = true; $result .= trim($text); break; case T_CONSTANT_ENCAPSED_STRING: // Handle special string concatenation case: 'bar' . 'baz' $c = substr($result, -3); if ($c == '". ' || $c == '\'. ') { $result = rtrim($result, ' .'); $result .= ' . '; } // Handle special string concatenation case: 'bar'. $foo elseif (substr($result, -2) == '. ') { $result = rtrim($result); } // Move on to T_STRING / T_VARIABLE. case T_STRING: case T_VARIABLE: // No space after object operator ($foo->bar) and error suppression (@function()). if ($in_object || $in_at) { $result = rtrim($result) . trim($text); $in_object = false; $in_at = false; } else { // Insert a space after right parenthesis, but not after type casts. 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))) { coder_add_space($result); } $result .= trim($text); } $in_variable = true; break; case T_ENCAPSED_AND_WHITESPACE: $result .= $text; break; case T_WHITESPACE: // Avoid duplicate line feeds outside arrays. $c = ($parenthesis || $after_comment) ? 0 : 1; for ($c, $cc = substr_count($text, "\n"); $c < $cc; ++$c) { // Newlines were added; not after semicolon anymore coder_br($result, $parenthesis); } // If there were newlines present inside a parenthesis, // turn on multiline mode. if ($cc && $parenthesis) { $in_multiline[$parenthesis] = TRUE; } // If there were newlines present, move inline comments above. if ($cc) { $after_semicolon = FALSE; $after_case = FALSE; $after_php = FALSE; } $in_variable = FALSE; break; case T_SWITCH: ++$switches; // Purposely fall through. case T_IF: case T_FOR: case T_FOREACH: case T_GLOBAL: case T_STATIC: case T_ECHO: case T_PRINT: case T_NEW: case T_REQUIRE: case T_REQUIRE_ONCE: case T_INCLUDE: case T_INCLUDE_ONCE: case T_VAR: coder_add_space($result); // Append a space. $result .= trim($text) .' '; break; case T_DO: $result .= trim($text); $in_do_while = true; break; case T_WHILE: if ($in_do_while && substr(rtrim($result), -1) === '}') { // Write while after right parenthesis for do {...} while(). $result = rtrim($result) .' '; $in_do_while = false; } // Append a space. $result .= trim($text) .' '; break; case T_ELSE: case T_ELSEIF: // Write else and else if to a new line. $result = rtrim($result); coder_br($result); $result .= trim($text) .' '; break; case T_CASE: case T_DEFAULT: $braces_in_case[$switches] = 0; $result = rtrim($result); $after_case = true; if (!$in_case) { $in_case = true; // Add a line break between cases. if (substr($result, -1) != '{') { coder_br($result); } } else { // Decrease current indent to align multiple cases. --$_coder_indent; } coder_br($result); $result .= trim($text) .' '; break; case T_BREAK: // Write break to a new line. $result = rtrim($result); coder_br($result); $result .= trim($text); if ($in_case && !$braces_in_case[$switches]) { --$_coder_indent; $in_case = FALSE; } break; case T_RETURN: if ($in_case && !$braces_in_case[$switches]) { // Defer reduction of indent for later. ++$_coder_indent; $after_return_in_case = true; } case T_CONTINUE: coder_add_space($result); $result .= trim($text) .' '; // Decrease indent only if we're not in a control structure inside a case. if ($in_case && !$braces_in_case[$switches]) { --$_coder_indent; $in_case = false; } break; case T_ABSTRACT: case T_PRIVATE: case T_PUBLIC: case T_PROTECTED: // Class member function properties must be treated similar to // T_FUNCTION, but without line-break after the token. Because more // than one of these tokens can appear in front of a function token, // we need another white-space control variable. $result .= trim($text) .' '; $after_visibility_modifier = TRUE; break; case T_FUNCTION: $in_function_declaration = TRUE; // Fall through. case T_CLASS: // Write function and class to new lines. $result = rtrim($result); if (substr($result, -1) == '}') { coder_br($result); } if (!$after_visibility_modifier) { coder_br($result); } else { // This code only applies to T_FUNCTION; do not add a newline // after public/protected/private/abstract. $after_visibility_modifier = FALSE; $result .= ' '; } $result .= trim($text) .' '; break; case T_EXTENDS: case T_INSTANCEOF: // Add space before and after 'extends' and 'instanceof'. $result = rtrim($result); $result .= ' '. trim($text) .' '; break; case T_AND_EQUAL: case T_AS: case T_BOOLEAN_AND: case T_BOOLEAN_OR: case T_CONCAT_EQUAL: case T_DIV_EQUAL: case T_DOUBLE_ARROW: case T_IS_EQUAL: case T_IS_NOT_EQUAL: case T_IS_IDENTICAL: case T_IS_NOT_IDENTICAL: case T_IS_GREATER_OR_EQUAL: case T_IS_SMALLER_OR_EQUAL: case T_LOGICAL_AND: case T_LOGICAL_OR: case T_LOGICAL_XOR: case T_MINUS_EQUAL: case T_MOD_EQUAL: case T_MUL_EQUAL: case T_OR_EQUAL: case T_PLUS_EQUAL: case T_SL: case T_SL_EQUAL: case T_SR: case T_SR_EQUAL: case T_XOR_EQUAL: // Surround operators with spaces. if (substr($result, -1) != ' ') { // $result must not be trimmed to allow multi-line if-clauses. $result .= ' '; } $result .= trim($text) .' '; break; case T_COMMENT: case T_ML_COMMENT: case T_DOC_COMMENT: if (substr($text, 0, 3) == '/**') { // Prepend a new line. $result = rtrim($result); if (!$after_initial_comment) { coder_br($result); } else { // This probably will get set below, but it's good to // explicitly turn it off after the initial comment has // influenced behavior and now is not necessary. $after_initial_comment = FALSE; } coder_br($result); // Remove carriage returns. $text = str_replace("\r", '', $text); $lines = explode("\n", $text); $params_fixed = false; for ($l = 0; $l < count($lines); ++$l) { $lines[$l] = trim($lines[$l]); // Add a new line between function description and first parameter description. if (!$params_fixed && substr($lines[$l], 0, 8) == '* @param' && $lines[$l - 1] != '*') { $result .= ' *'; coder_br($result); $params_fixed = true; } else if (!$params_fixed && substr($lines[$l], 0, 8) == '* @param') { // Do nothing if parameter description is properly formatted. $params_fixed = true; } // Add a new line between function params and return. if (substr($lines[$l], 0, 9) == '* @return' && $lines[$l - 1] != '*') { $result .= ' *'; coder_br($result); } // Add one space indent to get ' *[...]'. if ($l > 0) { $result .= ' '; } $result .= $lines[$l]; if ($l < count($lines)) { coder_br($result); } } } else { // Move the comment above if it's embedded. $statement = false; // Some PHP versions throw a warning about wrong parameter count for // substr_count(). $cc = substr_count(substr($result, $position_last_significant_token), "\n"); if ((!$cc || $after_semicolon) && !$after_case) { $nl_position = strrpos(rtrim($result, " \n"), "\n"); $statement = substr($result, $nl_position); $result = substr($result, 0, $nl_position); $after_semicolon = false; coder_br($result, $parenthesis); } $result .= trim($text); coder_br($result, $parenthesis); if ($statement) { // Newlines are automatically added, so remove these. $result = rtrim($result, "\n "); $result .= rtrim($statement, "\n "); coder_br($result, $parenthesis); // Need to update this, as our comment trickery has just // reshuffled the index. $position_last_significant_token = strlen(rtrim($result, " \n")) - 1; } else { if (strpos($text, '$' . 'Id$') === FALSE) { $after_comment = TRUE; } else { // Is the number two so that our bottom code doesn't override // our flag immediately. $after_initial_comment = 2; } } } break; case T_INLINE_HTML: $result .= $text; break; case T_START_HEREDOC: $result .= trim($text); coder_br($result, FALSE, FALSE); $in_heredoc = TRUE; break; case T_END_HEREDOC: $result .= trim($text); coder_br($result, FALSE, FALSE); $in_heredoc = FALSE; break; default: $result .= trim($text); break; } // Store last token. $lasttoken = $token; // Excluding comments and whitespace, set the position of the // last significant token's last character to the length of the // string minus one. switch ($id) { case T_WHITESPACE: case T_COMMENT: case T_ML_COMMENT: case T_DOC_COMMENT: break; default: $position_last_significant_token = strlen(rtrim($result, " \n")) - 1; break; } if ($id !== T_COMMENT && $id !== T_ML_COMMENT) { $after_comment = FALSE; } if ($after_initial_comment && $id !== T_WHITESPACE) $after_initial_comment--; } } return $result; } /** * Generate a line feed including current line indent. * * This function will also remove all line indentation from the * previous line if no text was added. * * @param &$result * Result variable to append break and indent to, passed by reference. * @param $parenthesis * Optional integer of parentheses level for extra indents. * @param $add_indent * Whether to add current line indent after line feed. */ function coder_br(&$result, $parenthesis = false, $add_indent = true) { global $_coder_indent; // Scan result backwards for whitespace. for ($i = strlen($result) - 1; $i >= 0; $i--) { if ($result[$i] == ' ') { continue; } if ($result[$i] == "\n") { $result = rtrim($result, ' '); break; } // Non-whitespace was encountered, no changes necessary. break; } if ($parenthesis) { // Add extra indent for each parenthesis in multiline definitions (f.e. arrays). $_coder_indent = $_coder_indent + $parenthesis; $result = rtrim($result); // This recursive call will only be done once, as $parenthesis is // set to false. coder_br($result, false, $add_indent); $_coder_indent = $_coder_indent - $parenthesis; } else { $output = "\n"; if ($add_indent && $_coder_indent >= 0) { $output .= str_repeat(' ', $_coder_indent); } $result .= $output; } } /** * Write a space in certain conditions. * * A conditional space is needed after a right parenthesis of an if statement * that is not followed by curly braces. * * @param $result * Current result string that will be checked. * * @return * Resulting string with or without an additional space. */ function coder_add_space(&$result) { if (substr($result, -1) == ')') { $result .= ' '; } } /** * Trim overall code. * * Strips whitespace at the beginning and end of code, * removes the closing PHP tag and appends two empty lines. */ function coder_trim_php($code) { // Remove surrounding whitespace. $code = trim($code); // Insert CVS keyword Id. // Search in the very first 1000 chars, insert only one instance. if (strpos(substr($code, 0, 1000), '$Id') === false) { $code = preg_replace('/<\?php\n/', "') { $code = rtrim($code, '?>'); } // Append two empty lines. $code .= str_repeat(chr(10), 2); return $code; } /** * Execute special tasks on source code. * * This function works similar to the Drupal hook and forms system. It searches * for all defined functions with the given prefix and performs a preg_replace * on the source code for each of these functions. * * Processor functions are defined with a associative array containing the * following keys with the corresponding values: * #title * A human readable text describing what the processor actually does. * #search * The regular expression to search for. * #replace * The replacement text for each match. * * Optional definitions: * #debug * Set this to true to directly output the results of preg_match_all and * exit script execution after this processor. * * @param string $code * The source code to process. * @param string $prefix * Prefix of the functions to execute. * * @return * The processed source code. */ function coder_exec_processors($code, $prefix = '') { if (empty($prefix)) { return; } $tasks = get_defined_functions(); $tasks = $tasks['user']; for ($c = 0, $cc = count($tasks); $c < $cc; ++$c) { if (strpos($tasks[$c], $prefix) === false) { unset($tasks[$c]); } else { $tasks[$tasks[$c]] = call_user_func($tasks[$c]); unset($tasks[$c]); } } uasort($tasks, 'coder_order_processors'); foreach ($tasks as $func => $task) { if (!isset($task['#search']) || (!isset($task['#replace']) && !isset($task['#replace_callback']))) { continue; } if (isset($task['#debug'])) { // Output regular expression results if debugging is enabled. preg_match_all($task['#search'], $code, $matches, PREG_SET_ORDER); echo "
";
      var_dump($matches);
      echo "
\n"; // Exit immediately in debugging mode. exit; } if (isset($task['#replace_callback'])) { $code = preg_replace_callback($task['#search'], $task['#replace_callback'], $code); } else { $code = preg_replace($task['#search'], $task['#replace'], $code); } } return $code; } /** * Orders preprocessors by weight. * * @see coder_exec_processors() */ function coder_order_processors($a, $b) { if (isset($a['#weight']) && isset($b['#weight'])) { return $a['#weight'] - $b['#weight']; } else { return isset($a['#weight']) ? false : true; } } /** * @defgroup coder_preprocessor Preprocessors. * @{ */ function coder_preprocessor_line_breaks_win() { return array( '#title' => 'Convert Windows line breaks to Unix format.', '#weight' => 1, '#search' => "@\r\n@", '#replace' => "\n", ); } function coder_preprocessor_line_breaks_mac() { return array( '#title' => 'Convert Macintosh line breaks to Unix format.', '#weight' => 2, '#search' => "@\r@", '#replace' => "\n", ); } function coder_preprocessor_php() { return array( '#title' => 'Always use <?php ?> to delimit PHP code, not the <? ?> shorthands.', '#search' => '@<\?(\s)@', '#replace' => " 'Either exit a switch case with return *or* break.', '#search' => '@ (return # match a return \s+ # - followed by some white-space .+ # - followed by any characters ; # - followed by a semicolon ) \s+ # match white-space (required) break; # match a directly following "break;" @mx', '#replace' => '$1', ); } function coder_preprocessor_inline_comment() { return array( '#title' => 'Move inline comments above remarked line.', '#weight' => 2, '#search' => '@ ^([\040\t]*) # match spaces or tabs only. (?!case) # do not match case statements. (\S.+? # do not match lines containing only a comment. [;,{] # match the TRICKY lines only. ) [\040\t]* # match spaces or tabs only. (?!:) # do not match URL protocols. //\s* # match inline comment token. ([^;\$]+?)$ # fetch comment, but do not match CVS keyword Id, nested comments, and comment tokens in quotes (f.e. "W3C//DTD"). @mx', '#replace' => "$1// $3\n$1$2", ); } /** * @} End of "defgroup coder_preprocessor". */ /** * @defgroup coder_postprocessor Postprocessors. * @{ */ function coder_postprocessor_cvs_id() { return array( '#title' => 'If the CVS keyword Id already exists, append a new line after it.', '#search' => '@ ^( # match start of a line //.* # match an inline comment followed by any characters \$Id.*\$ # match a CVS Id tag )$ # match end of a line @mx', '#replace' => "$1\n", ); } function coder_postprocessor_multiple_vars() { return array( '#title' => 'Align equal signs of multiple variable assignments in the same column.', '#search' => '@ ^( # match start of a line \n?\ * # match white-space, but only one new line \$.+? # match a variable name \ =\ # match a variable assignment .+?$ # match a variable value ){3,} # require the pattern to match at least 3 times @mx', '#replace_callback' => 'coder_replace_multiple_vars', ); } function coder_replace_multiple_vars($matches) { // Retrieve all variable name = variable value pairs. $regex = '@ ^ # match start of a line (\s*) # match a single optional white-space char (\$.+?) # match a variable name \ (.?)=\ # match a variable assignment (.+?$) # match a variable value including end of line @mx'; preg_match_all($regex, $matches[0], $vars, PREG_SET_ORDER); // Determine the longest variable name. $maxlength = 0; foreach ($vars as $var) { if (strlen($var[2]) > $maxlength) { $maxlength = strlen($var[2] . $var[3]); } } // Realign variable values at the longest variable names. $return = ''; $extra_spaces = 0; for ($c = 0, $cc = count($vars); $c < $cc; ++$c) { if ($maxlength <= 20) { $extra_spaces = $maxlength - strlen($vars[$c][2] . $vars[$c][3]); } $return .= $vars[$c][1] . $vars[$c][2]; $return .= str_repeat(' ', $extra_spaces) .' '. $vars[$c][3] .'= '; $return .= $vars[$c][4]; if ($c < $cc - 1) { // Append a line break, but not to the last variable assignment. $return .= "\n"; } } return $return; } function coder_postprocessor_indent_multiline_array() { // Still buggy, disabled for now. return array( '#title' => 'Align equal signs of multiline array assignments in the same column.', '#search' => '@ ^ # match start of a line (?:\s* # require initial white-space (?: (?: ([\'"]).+?\1 # capture a string key |.+? # or any other key without white-space ) \s*=>\s* # require associative array arrow syntax .+? # match an array value |\),\s? # or a closing brace followed by a comma and a single optional white-space char )$ # require end of a line ){3,} # require the pattern to match at least 3 times @mix', //'#replace_callback' => 'coder_replace_indent_multiline_array', ); } function coder_replace_indent_multiline_array($matches) { // Separate out important components of the multiline array: // (\s*) matches existing indent as \1 // (([\'"]).+?\2|\$.+?|[+\-]?(?:0x)?[0-9A-F]+) matches key as \2 // ([\'"]).+?\3 matches a quoted key, quote used is \3 // \.+? matches anything else // \),\s*? matches a closing parenthesis in a nested array // \s*=>\s* matches existing indentation and arrow to be discarded // (.+?) matches value as \4 // {3,} requires three or more of these lines // mi enables multiline and caseless mode preg_match_all('/^(\s*)(?:(([\'"]).+?\3|\.+?)\s*=>\s*(.+?),?|\),)\s*?$/mi', $matches[0], $vars, PREG_SET_ORDER); // Determine max key length for varying indentations. $maxlengths = array(); foreach ($vars as $var) { list(, $indent, $key) = $var; if (!isset($maxlengths[$indent])) { $maxlengths[$indent] = 0; } if (($t = strlen($key)) > $maxlengths[$indent]) { $maxlengths[$indent] = $t; } } // Reconstruct variable array declaration. $return = ''; foreach ($vars as $var) { list(, $indent, $key, , $value) = $var; if ($key === null) { $return .= "$indent),\n"; continue; } $spaces = str_repeat(' ', $maxlengths[$indent] - strlen($key)); if ($value !== 'array(') { $comma = ','; } else { $comma = ''; } $return .= "$indent$key$spaces => $value$comma\n"; } $return = rtrim($return, "\n"); return $return; } function coder_postprocessor_array_rearrange() { // @bug common.inc, comment.module: // Not yet working properly 25/03/2007 sun. return array( '#title' => 'Break array elements into separate lines, indented one level.', // ([\040\t]*) matches blanks and tabs. // (.*?array\() matches anything and 'array('. // ((.+ => .+, ){3,}) matches all array items, except the last one. // (.+ => ([^\(\)]+)) matches the last array item, excluding. // arrays or functions (starting with a left parenthesis) (not supported yet). //'#search' => '/^([\040\t]*)(.*?array\()((.+ => .+, ){3,})(.+ => ([^\(\)]+))\)/m', '#replace_callback' => 'coder_replace_array_rearrange', ); } function coder_replace_array_rearrange($matches) { // Retrieve all array items, except the last one. preg_match_all('/(.+? => .+?,) /', $matches[3], $items); // The original line including array(. $return = $matches[1] . $matches[2] ."\n"; foreach ($items[1] as $item) { // All array items, except the last one, with extra indent. $return .= $matches[1] .' '. $item ."\n"; } // Last array item, with extra indent and comma. $return .= $matches[1] .' '. $matches[5] .",\n"; // Closing parenthesis (on a new line). $return .= $matches[1] .')'; return $return; } function coder_postprocessor_if_curly_braces() { // This post-processor relies on the fact that coder_format already // re-formatted if statements without curly braces to be on one line. return array( '#title' => 'Use curly braces even in situations where they are technically optional.', '#search' => '@ (\s*) # match leading white-space, including newline (if\ \(.+\)\ ) # match if statement ([^\{].+;) # match conditional executed code not starting with a curly brace, delimited by a semicolon. @x', '#replace' => '$1$2{$1 $3$1}', ); } /** * @} End of "defgroup coder_postprocessor". */