? 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 @@
+<?php
+// $Id$
+
+if (isset($_SERVER['argv']) && !empty($_SERVER['argv'])) {
+  array_shift($_SERVER['argv']);
+  $sourcefile = array_shift($_SERVER['argv']);
+  $targetfile = array_shift($_SERVER['argv']);
+  coder_format_file($sourcefile, $targetfile);
+}
+
+/**
+ * Reads, processes and writes the source code from and to a file.
+ */
+function coder_format_file($sourcefile, $targetfile) {
+  // Read source code from source file
+  $fd = fopen($sourcefile, 'r');
+  $code = fread($fd, filesize($sourcefile));
+  fclose($fd);
+  
+  if ($code !== false) {
+    // 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);
+    
+    if ($code !== false) {
+      // Write formatted source code to target file
+      $fd = fopen($targetfile, 'w');
+      $status = fwrite($fd, $code);
+      fclose($fd);
+      return $status;
+    }
+  }
+  
+  return false;
+}
+
+/**
+ * Format the source code according to Drupal coding style guidelines.
+ *
+ * This function uses PHP's tokenizer functions.
+ * @see <http://www.php.net/manual/en/ref.tokenizer.php>
+ *
+ * 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 <http://www.php.net/manual/en/tokens.php> 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/', "<?php\n// \$Id\$\n\n", $code, 1);
+  }
+  
+  // Remove closing PHP tag
+  if (substr($code, -2) == '?>') {
+    $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 "<pre>";
+      var_dump($matches);
+      echo "</pre>\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 &lt;?php ?&gt; to delimit PHP code, not the &lt;? ?&gt; shorthands.',
+    '#search' => '/<\?(\s)/',
+    '#replace' => "<?php$1",
+  );
+}
+
+/**
+ * @} 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' => '@^(//.*\$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".
+ */
+
