? coder.patch Index: coder_format/coder_format.info =================================================================== RCS file: coder_format/coder_format.info diff -N coder_format/coder_format.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ coder_format/coder_format.info 23 Mar 2007 15:19:23 -0000 @@ -0,0 +1,5 @@ +; $Id: coder.info,v 1.5 2007/02/08 22:35:14 douggreen Exp $ +name = Coder Format +description = Developer Module that reformats source code to adhere to Drupal coding standards +version = "$Name: $" +package = Development Index: coder_format/coder_format.module =================================================================== RCS file: coder_format/coder_format.module diff -N coder_format/coder_format.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ coder_format/coder_format.module 22 Mar 2007 20:05:41 -0000 @@ -0,0 +1,723 @@ + + * + * 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. + * $braces_in_case int 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. + * $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. + * $first_php_tag bool + * Is false after the first PHP tag. Allows inserting a line break after + * the first one. + * + * 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. + * + * @param string $code + * The source code to format. + * + * @return mixed $result + * Returns the formatted code or false if it fails. + */ +function coder_format_string($code = '') { + global $_coder_indent; + + // indent controls + $_coder_indent = 0; + $in_case = false; + $parenthesis = 0; + $braces_in_case = 0; + $in_brace = false; + $first_php_tag = true; + + // whitespace controls + $in_object = false; + $in_at = false; + $in_php = false; + $in_quote = false; + $inline_if = false; + + $result = ''; + $lasttoken = array(0); + $tokens = token_get_all($code); + + // Mask T_ML_COMMENT (PHP4) as T_COMMENT (PHP5). + // Mask T_DOC_COMMENT (PHP5) as T_ML_COMMENT (PHP4). + if (!defined('T_ML_COMMENT')) { + define('T_ML_COMMENT', T_COMMENT); + } + else { + 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 '{': + // Write curly braces at the end of lines followed by a line break if + // not in quotes (""), object ($foo->{$bar}) or in variables (${foo}). + // [T_DOLLAR_OPEN_CURLY_BRACES exists but is never assigned.] + if (!$in_quote && !$in_object && substr(rtrim($result), -1) != '$') { + if ($in_case) { + ++$braces_in_case; + } + ++$_coder_indent; + $result = rtrim($result) .' '. $text . coder_br(); + } + else { + $in_brace = true; + $result .= $text; + } + break; + + case '}': + if (!$in_quote && !$in_brace) { + if ($in_case) { + --$braces_in_case; + } + --$_coder_indent; + if ($braces_in_case < 0) { + // Decrease indent if last case in a switch is not terminated + --$_coder_indent; + $in_case = false; + $braces_in_case = 0; + } + $result = rtrim($result) . coder_br() . $text . coder_br(); + } + else { + $in_brace = false; + $result .= $text; + } + break; + + case ';': + $result = rtrim($result) . $text; + if (!$parenthesis) { + $result .= coder_br(); + } + else { + $result .= ' '; + } + if ($inline_if) { + $inline_if = false; + } + break; + + case '?': + $inline_if = true; + $result .= ' '. $text .' '; + break; + + case ':': + if ($inline_if) { + $result .= ' '. $text .' '; + } + else { + if ($in_case) { + ++$_coder_indent; + } + $result = rtrim($result) . $text . coder_br(); + } + break; + + case '(': + $result .= $text; + ++$parenthesis; + break; + + case ')': + if (!$in_quote && substr(rtrim($result), -1) == ',') { + // Fix indent of right parenthesis in multiline arrays by + // increasing indent for each parenthesis and decreasing one level. + $_coder_indent = $_coder_indent + $parenthesis - 1; + $result = rtrim($result) . coder_br() . $text; + $_coder_indent = $_coder_indent - $parenthesis + 1; + } + else { + $result .= $text; + } + if ($parenthesis) { + --$parenthesis; + } + break; + + case '@': + $in_at = true; + $result .= $text; + break; + + case ',': + $result .= $text .' '; + break; + + case '.': + // Write string concatenation character directly after strings + if (substr(rtrim($result), -1) == "'" || substr(rtrim($result), -1) == '"') { + $result = rtrim($result) . $text .' '; + } + else { + $result = rtrim($result) .' '. $text .' '; + } + break; + + case '=': + case '<': + case '>': + case '+': + case '*': + case '/': + $result = rtrim($result) .' '. $text .' '; + break; + + case '-': + $result = rtrim($result); + // Do not add a space before negative numbers or variables + if (substr($result, -1) == '>' || substr($result, -1) == '=' || substr($result, -1) == ',') { + $result .= ' '. $text; + } + else { + $result .= ' '. $text .' '; + } + break; + + case '"': + // toggle quote if the char is not escaped + if (rtrim($result) != '\\') { + $in_quote = $in_quote ? false : true; + } + $result .= $text; + break; + + default: + $result .= $text; + break; + } + } + else { + // If we get here, then we have found not a single char, but a token. + // See for a reference. + + // token array + list($id, $text) = $token; + + // Debugging + /* + if ($lasttoken[0] == T_WHITESPACE) { + $result .= token_name($id); + } + $lasttoken = $token; + */ + + switch ($id) { + case T_ARRAY: + // Write array in lowercase + $result .= strtolower(trim($text)); + 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) == '?>') { + $result .= coder_br(); + } + $result .= trim($text); + if ($first_php_tag) { + $result .= coder_br(); + $first_php_tag = false; + } + else { + $result .= ' '; + } + break; + + case T_CLOSE_TAG: + $in_php = false; + // Remove preceding line break for inline PHP output in HTML + if (substr(rtrim($result), -1) == ';' || substr(rtrim($result), -1) == ':') { + $result = rtrim($result) .' '; + } + $result .= trim($text); + break; + + case T_OBJECT_OPERATOR: + $in_object = true; + $result .= trim($text); + break; + + case T_CONSTANT_ENCAPSED_STRING: + if (substr(rtrim($result), -1) == '.') { + $result = rtrim($result); + } + // Move on to T_STRING / T_VARIABLE + case T_STRING: + case T_VARIABLE: + if ($in_object || $in_at) { + // No space after object operator ($foo->bar) and error suppression (@function()) + $result = rtrim($result) . trim($text); + $in_object = false; + $in_at = false; + } + else { + $result .= trim($text); + } + break; + + case T_ENCAPSED_AND_WHITESPACE: + $result .= $text; + break; + + case T_WHITESPACE: + // Avoid duplicate line feeds outside arrays + $c = $parenthesis ? 0 : 1; + + for ($c, $cc = substr_count($text, chr(10)); $c < $cc; ++$c) { + if ($parenthesis) { + // Add extra indent for each parenthesis in multiline definitions (f.e. arrays) + $_coder_indent = $_coder_indent + $parenthesis; + $result = rtrim($result) . coder_br(); + $_coder_indent = $_coder_indent - $parenthesis; + } + else { + // Discard any whitespace, just insert a line break + $result .= coder_br(); + } + } + break; + + case T_IF: + case T_FOR: + case T_FOREACH: + case T_SWITCH: + case T_GLOBAL: + case T_STATIC: + case T_ECHO: + case T_PRINT: + // Append a space + $result .= trim($text) .' '; + break; + + case T_WHILE: + if (substr(rtrim($result), -1) == '}') { + // Write while after right parenthesis for do {...} while() + $result = rtrim($result) .' '; + } + // 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() . trim($text) .' '; + break; + + case T_CASE: + case T_DEFAULT: + $braces_in_case = 0; + $result = rtrim($result); + if (!$in_case) { + $in_case = true; + // Add a line break between cases + if (substr($result, -1) != '{') { + $result .= coder_br(); + } + } + else { + // Decrease current indent to align multiple cases + --$_coder_indent; + } + $result .= coder_br() . trim($text) .' '; + break; + + case T_BREAK: + // Write break to a new line + $result = rtrim($result) . coder_br() . trim($text); + if ($in_case && !$braces_in_case) { + --$_coder_indent; + $in_case = false; + } + break; + + case T_RETURN: + case T_CONTINUE: + $result .= trim($text) .' '; + // Decrease indent only if we're not in a control structure inside a case + if ($in_case && !$braces_in_case) { + --$_coder_indent; + $in_case = false; + } + break; + + case T_FUNCTION: + case T_CLASS: + // Write function and class to new lines + $result = rtrim($result); + if (substr($result, -1) == '}') { + $result .= coder_br(); + } + $result .= coder_br() . 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 + $result = rtrim($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) . coder_br() . coder_br(); + + // 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(); + $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(); + } + + // Add one space indent to get ' *[...]' + if ($l > 0) { + $result .= ' '; + } + $result .= $lines[$l]; + if ($l < count($lines)) { + $result .= coder_br(); + } + } + } + else { + $result .= trim($text) . coder_br(); + } + break; + + case T_INLINE_HTML: + $result .= $text; + break; + + case T_START_HEREDOC: + $result .= trim($text) . coder_br(false); + break; + + default: + $result .= trim($text); + break; + } + } + } + return $result; +} + +/** + * Generate a line feed including current line indent. + * + * @param bool $add_indent + * Whether to add current line indent after line feed. + * + * @return string + * The resulting string. + */ +function coder_br($add_indent = true) { + global $_coder_indent; + + $output = "\n"; + if ($add_indent && $_coder_indent >= 0) { + $output .= str_repeat(' ', $_coder_indent); + } + return $output; +} + +/** + * 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 string + * 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'])) { + 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; + } + $code = preg_replace($task['#search'], $task['#replace'], $code); + } + + return $code; +} + +/** + * Orders preprocessors by weight. + */ +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_cr() { + return array( + '#title' => 'Remove carriage returns.', + '#weight' => 1, + '#search' => '/\r/', + '#replace' => '', + ); +} + +function coder_preprocessor_ml_array_add_comma() { + return array( + '#title' => 'Append a comma to the last value of multiline arrays.', + '#search' => '/\sarray\((.+?[^,]),?(\n\s*)\);/is', + '#replace' => ' array($1,$2);', + ); +} + +function coder_preprocessor_inline_comment() { + return array( + '#title' => 'Move inline comments above remarked line.', + '#search' => '@^(\s*)(.+;)\s?[^:](//.+)$@m', + '#replace' => "$1$3\n$1$2", + ); +} + +function coder_preprocessor_php() { + return array( + '#title' => 'Always use <?php ?> to delimit PHP code, not the <? ?> shorthands.', + '#search' => '/<\?(\s)/', + '#replace' => " 'If the CVS keyword $Id$ already exists, append a new line after it.', + '#search' => '@^(//.*\$Id.*\$)$@m', + '#replace' => "$1\n", + ); +} + +function coder_postprocessor_multiple_vars() { + // Not yet functional 15/03/2007 sun + /* return array( + '#title' => 'Align equal signs of multiple variable assignments in a column.', + '#search' => '/^(\s*\$.+? = .+?$){2,20}/me', + '#replace' => '', + '#debug' => true, + ); + */ +} + +/** + * @} End of "defgroup coder_postprocessor". + */ +