Index: api.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/api/api.install,v
retrieving revision 1.5.2.5
diff -u -w -B -F^f -r1.5.2.5 api.install
--- api.install	20 Oct 2007 01:39:16 -0000	1.5.2.5
+++ api.install	3 Feb 2008 22:00:36 -0000
@@ -177,6 +177,24 @@ function api_update_7() {
   return $return;
 }
 
+/**
+ * Add exclude_pattern column.
+ */
+function api_update_8() {
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+     $ret[] = update_sql("ALTER TABLE {api_branch} ADD exclude_pattern text");
+     break;
+    case 'pgsql':
+      // untested
+      db_add_column($ret, 'vocabulary', 'exclude_pattern', 'text');
+      break;
+  }
+  
+  return $ret;
+}
+
 function api_uninstall() {
   db_query('DROP TABLE IF EXISTS {api_branch}');
   db_query('DROP TABLE IF EXISTS {api_documentation}');
Index: api.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/api/api.module,v
retrieving revision 1.48.2.21
diff -u -w -B -F^f -r1.48.2.21 api.module
--- api.module	26 Jan 2008 03:34:44 -0000	1.48.2.21
+++ api.module	3 Feb 2008 22:51:40 -0000
@@ -20,9 +20,12 @@
  */
 
 /**
- * Regular expression for matching file names.
+ * Regular expressions for matching file names.
  */
 define('API_RE_FILENAME', '([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)+)');
+define('API_EXTENSION_PHP', 'php|inc|module|theme|engine|install|profile');
+define('API_EXTENSION_TXT', 'txt');
+define('API_EXTENSION_HTML', 'html?');
 
 /**
  * Implementation of hook_help()
@@ -1039,7 +1042,7 @@ function api_page_admin() {
 function api_page_admin_form() {
   $form = array();
 
-  $result = db_query('SELECT branch_name, title, directory FROM {api_branch}');
+  $result = db_query('SELECT branch_name, title, directory, exclude_pattern FROM {api_branch}');
   $form['branches'] = array(
     '#tree' => TRUE,
   );
@@ -1064,7 +1067,13 @@ function api_page_admin_form() {
       '#title' => t('Directory name'),
       '#type' => 'textfield',
       '#default_value' => $branch->directory,
-      '#description' => t('The absolute path of the directory to index. Multiple paths may be given, separated by colons, e.g.: "/mysite/drupal:/mysite/developer". <em>Note that the module will index recursively from the path given.</em>'),
+      '#description' => t('The absolute path of the directory to index. Multiple paths may be given, separated by "'. PATH_SEPARATOR .'", e.g.: "/mysite/drupal'. PATH_SEPARATOR .'/mysite/developer". <em>Note that the module will index recursively from the path given.</em>'),
+    );
+    $form['branches'][$branch->branch_name]['exclude_pattern'] = array(
+      '#title' => t('Exclude pattern'),
+      '#type' => 'textfield',
+      '#default_value' => $branch->exclude_pattern,
+      '#description' => t('<a href="http://php.net/pcre">PCRE</a> pattern of files to exclude from index, matched against absolute file paths. Example: <code>drupal/(sites/all|files|_)</code>.'),
     );
 
     $radios[$branch->branch_name] = $branch->title;
@@ -1082,7 +1091,13 @@ function api_page_admin_form() {
   $form['branches']['new']['directory'] = array(
     '#title' => t('Directory name'),
     '#type' => 'textfield',
-    '#description' => t('The absolute path of the directory to index. Multiple paths may be given, separated by colons, e.g.: "/mysite/drupal:/mysite/developer".'),
+    '#description' => t('The absolute path of the directory to index. Multiple paths may be given, separated by "'. PATH_SEPARATOR .'", e.g.: "/mysite/drupal'. PATH_SEPARATOR .'/mysite/developer". <em>Note that the module will index recursively from the path given.</em>'),
+  );
+  $form['branches']['new']['exclude_pattern'] = array(
+    '#title' => t('Exclude pattern'),
+    '#type' => 'textfield',
+    '#default_value' => $branch->exclude_pattern,
+    '#description' => t('<a href="http://php.net/pcre">PCRE</a> pattern of files to exclude from index, matched against absolute file paths. Example: <code>drupal/(sites/all|files|_)</code>.'),
   );
 
   global $base_url;
@@ -1130,7 +1145,7 @@ function api_page_admin_form_submit($for
   foreach ($form_values['branches'] as $branch_name => $branch) {
     if ($branch_name == 'new') {
       if ($branch['branch_name'] != '') {
-        db_query("INSERT INTO {api_branch} (branch_name, title, directory) VALUES ('%s', '%s', '%s')", $branch['branch_name'], $branch['title'], $branch['directory']);
+        db_query("INSERT INTO {api_branch} (branch_name, title, directory, exclude_pattern) VALUES ('%s', '%s', '%s', '%s')", $branch['branch_name'], $branch['title'], $branch['directory'], $branch['exclude_pattern']);
       }
     }
     else {
@@ -1145,7 +1160,7 @@ function api_page_admin_form_submit($for
         }
       }
       else {
-        db_query("UPDATE {api_branch} SET branch_name = '%s', title = '%s', directory = '%s' WHERE branch_name = '%s'", $branch['branch_name'], $branch['title'], $branch['directory'], $branch_name);
+        db_query("UPDATE {api_branch} SET branch_name = '%s', title = '%s', directory = '%s', exclude_pattern = '%s' WHERE branch_name = '%s'", $branch['branch_name'], $branch['title'], $branch['directory'], $branch['exclude_pattern'], $branch_name);
         if ($branch['branch_name'] != $branch_name) {
           db_query("UPDATE {api_documentation} SET branch_name = '%s' WHERE branch_name = '%s'", $branch['branch_name'], $branch_name);
           $result = db_query("SELECT did FROM {api_documentation} WHERE branch_name = '%s'", $branch['branch_name']);
@@ -1226,9 +1241,9 @@ function api_cron() {
 
   db_query("UPDATE {api_file} SET found = 0");
 
-  $branches = db_query('SELECT branch_name, directory FROM {api_branch}');
+  $branches = db_query('SELECT branch_name, directory, exclude_pattern FROM {api_branch}');
   while ($branch = db_fetch_object($branches)) {
-    $files = api_scan_directories($branch->directory);
+    $files = api_scan_directories($branch->directory, $branch->exclude_pattern);
     foreach ($files as $path => $file_name) {
       if ($files_scanned >= $max_files) {
         break;
@@ -1435,42 +1450,59 @@ function api_link_name($name, $branch_na
 /**
  * Find all the files in the directories specified for a branch.
  */
-function api_scan_directories($directories) {
-  $directory_array = explode(':', $directories);
+function api_scan_directories($directories, $exclude_pattern) {
+  $directory_array = explode(PATH_SEPARATOR, $directories);
 
   if (count($directory_array) > 1) {
-    $directories_components = array();
-    foreach ($directory_array as $directory) {
-      $directory_components = array();
-      $parts = explode('/', $directory);
-      foreach ($parts as $part) {
-        if (strlen($part)) {
-          array_unshift($directory_components, reset($directory_components) .'/'. $part);
-        }
-      }
-      $directories_components[] = $directory_components;
-    }
-
-    $common_ancestor_components = call_user_func_array('array_intersect', $directories_components);
-    $common_ancestor = reset($common_ancestor_components);
+    // split directory strings into 1 char arrays and extract matching key-value pairs
+    $matching_chars = call_user_func_array('array_intersect_assoc', array_map('str_split', $directory_array));
+    // remove non-prefix values (non-sequential keys)
+    $common_ancestor_chars = array_intersect_key($matching_chars, range(0, count($matching_chars) - 1));
+    // join common anchestor chars and strip trailing '/'s
+    $common_ancestor = rtrim(implode('', $common_ancestor_chars), '/');
   }
   else {
     $common_ancestor = $directories;
   }
 
   $source_files = array();
+  
   foreach ($directory_array as $directory) {
-    $files = file_scan_directory($directory, '.*');
-    foreach ($files as $path => $file) {
-      if (strpos($path, '/.') !== FALSE) {
-        continue;
+    $files = api_scan_directory($directory, $exclude_pattern, $common_ancestor);
+    $source_files += $files;
       }
-      $file_name = substr($path, strlen($common_ancestor) + 1);
+  return $source_files;
+}
+
+/**
+ * Find all files in a directory. Use a modified version of Drupals file_scan_directory(), as latter
+ * <ol>
+ * <li>doesn't allow preg include patterns
+ * <li>doesn't support preg Exclude pattern against the full path, and
+ * <li>we have to loop through the results again.
+ * </ol>
+ */
+function api_scan_directory($dir, $exclude_pattern, $common_ancestor) {
+  $files = array();
 
-      $source_files[$path] = $file_name;
+  if (is_dir($dir) && $handle = opendir($dir)) {
+    while ($file = readdir($handle)) {
+      $absolute_filename = "$dir/$file";
+      if (!in_array($file, array('.', '..', 'CVS'))
+          && $file[0] != '.'
+          && !preg_match("!$exclude_pattern!", $absolute_filename)) {
+        if (is_dir($absolute_filename)) {
+          $files = array_merge($files, api_scan_directory($absolute_filename, $exclude_pattern, $common_ancestor));
     }
+        elseif (preg_match('!\.('. API_EXTENSION_PHP .'|'. API_EXTENSION_TXT .'|'. API_EXTENSION_HTML .')$!', $file)) {
+          $files[$absolute_filename] = substr($absolute_filename, strlen($common_ancestor) + 1);
   }
-  return $source_files;
+      }
+    }
+    closedir($handle);
+  }
+  
+  return $files;
 }
 
 function api_simpletest() {
Index: parser.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/api/parser.inc,v
retrieving revision 1.24.2.15
diff -u -w -B -F^f -r1.24.2.15 parser.inc
--- parser.inc	23 Jan 2008 02:49:56 -0000	1.24.2.15
+++ parser.inc	3 Feb 2008 22:07:51 -0000
@@ -34,17 +34,17 @@ function api_parse_php_manual($location)
  * for parsing.
  */
 function api_parse_file($file_path, $branch_name, $file_name) {
-  if (preg_match('!\.(php|module|inc|install|engine|theme)$!', $file_name)) {
+  if (preg_match('!\.('. API_EXTENSION_PHP .')$!', $file_name)) {
     watchdog('api', t('Parsing %path...', array('%path' => $file_path)));
     api_parse_php_file($file_path, $branch_name, $file_name);
     return TRUE;
   }
-  if (preg_match('!\.(txt)$!', $file_name)) {
+  if (preg_match('!\.('. API_EXTENSION_TXT .')$!', $file_name)) {
     watchdog('api', t('Parsing %path...', array('%path' => $file_path)));
     api_parse_text_file($file_path, $branch_name, $file_name);
     return TRUE;
   }
-  if (preg_match('!\.(htm|html)$!', $file_name)) {
+  if (preg_match('!\.('. API_EXTENSION_HTML .')$!', $file_name)) {
     watchdog('api', t('Parsing %path...', array('%path' => $file_path)));
     api_parse_html_file($file_path, $branch_name, $file_name);
     return TRUE;
