Viewing file: DirectoryScanner.php (25.63 KB) -rw-rw-rw- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php /* * $Id: DirectoryScanner.php 1089 2011-05-10 12:54:37Z mrook $ * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * and is licensed under the LGPL. For more information please see * <http://phing.info>. */
require_once 'phing/types/selectors/SelectorScanner.php'; include_once 'phing/util/StringHelper.php'; include_once 'phing/types/selectors/SelectorUtils.php';
/** * Class for scanning a directory for files/directories that match a certain * criteria. * * These criteria consist of a set of include and exclude patterns. With these * patterns, you can select which files you want to have included, and which * files you want to have excluded. * * The idea is simple. A given directory is recursively scanned for all files * and directories. Each file/directory is matched against a set of include * and exclude patterns. Only files/directories that match at least one * pattern of the include pattern list, and don't match a pattern of the * exclude pattern list will be placed in the list of files/directories found. * * When no list of include patterns is supplied, "**" will be used, which * means that everything will be matched. When no list of exclude patterns is * supplied, an empty list is used, such that nothing will be excluded. * * The pattern matching is done as follows: * The name to be matched is split up in path segments. A path segment is the * name of a directory or file, which is bounded by DIRECTORY_SEPARATOR * ('/' under UNIX, '\' under Windows). * E.g. "abc/def/ghi/xyz.php" is split up in the segments "abc", "def", "ghi" * and "xyz.php". * The same is done for the pattern against which should be matched. * * Then the segments of the name and the pattern will be matched against each * other. When '**' is used for a path segment in the pattern, then it matches * zero or more path segments of the name. * * There are special case regarding the use of DIRECTORY_SEPARATOR at * the beginning of the pattern and the string to match: * When a pattern starts with a DIRECTORY_SEPARATOR, the string * to match must also start with a DIRECTORY_SEPARATOR. * When a pattern does not start with a DIRECTORY_SEPARATOR, the * string to match may not start with a DIRECTORY_SEPARATOR. * When one of these rules is not obeyed, the string will not * match. * * When a name path segment is matched against a pattern path segment, the * following special characters can be used: * '*' matches zero or more characters, * '?' matches one character. * * Examples: * * "**\*.php" matches all .php files/dirs in a directory tree. * * "test\a??.php" matches all files/dirs which start with an 'a', then two * more characters and then ".php", in a directory called test. * * "**" matches everything in a directory tree. * * "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123"). * * Case sensitivity may be turned off if necessary. By default, it is * turned on. * * Example of usage: * $ds = new DirectroyScanner(); * $includes = array("**\*.php"); * $excludes = array("modules\*\**"); * $ds->SetIncludes($includes); * $ds->SetExcludes($excludes); * $ds->SetBasedir("test"); * $ds->SetCaseSensitive(true); * $ds->Scan(); * * print("FILES:"); * $files = ds->GetIncludedFiles(); * for ($i = 0; $i < count($files);$i++) { * println("$files[$i]\n"); * } * * This will scan a directory called test for .php files, but excludes all * .php files in all directories under a directory called "modules" * * This class is complete preg/ereg free port of the Java class * org.apache.tools.ant.DirectoryScanner. Even functions that use preg/ereg * internally (like split()) are not used. Only the _fast_ string functions * and comparison operators (=== !=== etc) are used for matching and tokenizing. * * @author Arnout J. Kuiper, ajkuiper@wxs.nl * @author Magesh Umasankar, umagesh@rediffmail.com * @author Andreas Aderhold, andi@binarycloud.com * * @version $Revision: 1089 $ * @package phing.util */ class DirectoryScanner implements SelectorScanner {
/** default set of excludes */ protected $DEFAULTEXCLUDES = array( "**/*~", "**/#*#", "**/.#*", "**/%*%", "**/CVS", "**/CVS/**", "**/.cvsignore", "**/SCCS", "**/SCCS/**", "**/vssver.scc", "**/.svn", "**/.svn/**", "**/._*", "**/.DS_Store", "**/.darcs", "**/.darcs/**" );
/** The base directory which should be scanned. */ protected $basedir;
/** The patterns for the files that should be included. */ protected $includes = null;
/** The patterns for the files that should be excluded. */ protected $excludes = null; /** Whether to expand/dereference symbolic links, default is false */ protected $expandSymbolicLinks = false;
/** * The files that where found and matched at least one includes, and matched * no excludes. */ protected $filesIncluded;
/** The files that where found and did not match any includes. Trie */ protected $filesNotIncluded;
/** * The files that where found and matched at least one includes, and also * matched at least one excludes. Trie object. */ protected $filesExcluded;
/** * The directories that where found and matched at least one includes, and * matched no excludes. */ protected $dirsIncluded;
/** The directories that where found and did not match any includes. */ protected $dirsNotIncluded;
/** * The files that where found and matched at least one includes, and also * matched at least one excludes. */ protected $dirsExcluded;
/** Have the vars holding our results been built by a slow scan? */ protected $haveSlowResults = false;
/** Should the file system be treated as a case sensitive one? */ protected $isCaseSensitive = true;
/** Selectors */ protected $selectors = null; protected $filesDeselected; protected $dirsDeselected; /** if there are no deselected files */ protected $everythingIncluded = true;
/** * Does the path match the start of this pattern up to the first "**". * This is a static mehtod and should always be called static * * This is not a general purpose test and should only be used if you * can live with false positives. * * pattern=**\a and str=b will yield true. * * @param pattern the (non-null) pattern to match against * @param str the (non-null) string (path) to match * @param isCaseSensitive must matches be case sensitive? * @return boolean true if matches, otherwise false */ function matchPatternStart($pattern, $str, $isCaseSensitive = true) { return SelectorUtils::matchPatternStart($pattern, $str, $isCaseSensitive); }
/** * Matches a path against a pattern. Static * * @param pattern the (non-null) pattern to match against * @param str the (non-null) string (path) to match * @param isCaseSensitive must a case sensitive match be done? * * @return true when the pattern matches against the string. * false otherwise. */ function matchPath($pattern, $str, $isCaseSensitive = true) { return SelectorUtils::matchPath($pattern, $str, $isCaseSensitive); }
/** * Matches a string against a pattern. The pattern contains two special * characters: * '*' which means zero or more characters, * '?' which means one and only one character. * * @param pattern the (non-null) pattern to match against * @param str the (non-null) string that must be matched against the * pattern * * @return boolean true when the string matches against the pattern, * false otherwise. * @access public */ function match($pattern, $str, $isCaseSensitive = true) { return SelectorUtils::match($pattern, $str, $isCaseSensitive); }
/** * Sets the basedir for scanning. This is the directory that is scanned * recursively. All '/' and '\' characters are replaced by * DIRECTORY_SEPARATOR * * @param basedir the (non-null) basedir for scanning */ function setBasedir($_basedir) { $_basedir = str_replace('\\', DIRECTORY_SEPARATOR, $_basedir); $_basedir = str_replace('/', DIRECTORY_SEPARATOR, $_basedir); $this->basedir = $_basedir; }
/** * Gets the basedir that is used for scanning. This is the directory that * is scanned recursively. * * @return the basedir that is used for scanning */ function getBasedir() { return $this->basedir; }
/** * Sets the case sensitivity of the file system * * @param specifies if the filesystem is case sensitive */ function setCaseSensitive($_isCaseSensitive) { $this->isCaseSensitive = ($_isCaseSensitive) ? true : false; }
/** * Sets the set of include patterns to use. All '/' and '\' characters are * replaced by DIRECTORY_SEPARATOR. So the separator used need * not match DIRECTORY_SEPARATOR. * * When a pattern ends with a '/' or '\', "**" is appended. * * @param includes list of include patterns */ function setIncludes($_includes = array()) { if (empty($_includes) || is_null($_includes)) { $this->includes = null; } else { for ($i = 0; $i < count($_includes); $i++) { $pattern = null; $pattern = str_replace('\\', DIRECTORY_SEPARATOR, $_includes[$i]); $pattern = str_replace('/', DIRECTORY_SEPARATOR, $pattern); if (StringHelper::endsWith(DIRECTORY_SEPARATOR, $pattern)) { $pattern .= "**"; } $this->includes[] = $pattern; } } }
/** * Sets the set of exclude patterns to use. All '/' and '\' characters are * replaced by <code>File.separatorChar</code>. So the separator used need * not match <code>File.separatorChar</code>. * * When a pattern ends with a '/' or '\', "**" is appended. * * @param excludes list of exclude patterns */
function setExcludes($_excludes = array()) { if (empty($_excludes) || is_null($_excludes)) { $this->excludes = null; } else { for ($i = 0; $i < count($_excludes); $i++) { $pattern = null; $pattern = str_replace('\\', DIRECTORY_SEPARATOR, $_excludes[$i]); $pattern = str_replace('/', DIRECTORY_SEPARATOR, $pattern); if (StringHelper::endsWith(DIRECTORY_SEPARATOR, $pattern)) { $pattern .= "**"; } $this->excludes[] = $pattern; } } } /** * Sets whether to expand/dereference symbolic links * * @param expandSymbolicLinks boolean value */ function setExpandSymbolicLinks($expandSymbolicLinks) { $this->expandSymbolicLinks = $expandSymbolicLinks; }
/** * Scans the base directory for files that match at least one include * pattern, and don't match any exclude patterns. * */ function scan() { if ((empty($this->basedir)) || (!@is_dir($this->basedir))) { return false; }
if ($this->includes === null) { // No includes supplied, so set it to 'matches all' $this->includes = array("**"); } if (is_null($this->excludes)) { $this->excludes = array(); }
$this->filesIncluded = array(); $this->filesNotIncluded = array(); $this->filesExcluded = array(); $this->dirsIncluded = array(); $this->dirsNotIncluded = array(); $this->dirsExcluded = array(); $this->dirsDeselected = array(); $this->filesDeselected = array(); if ($this->isIncluded("")) { if (!$this->isExcluded("")) { if ($this->isSelected("", $this->basedir)) { $this->dirsIncluded[] = ""; } else { $this->dirsDeselected[] = ""; } } else { $this->dirsExcluded[] = ""; } } else { $this->dirsNotIncluded[] = ""; }
$this->scandir($this->basedir, "", true); return true; }
/** * Toplevel invocation for the scan. * * Returns immediately if a slow scan has already been requested. */ protected function slowScan() {
if ($this->haveSlowResults) { return; }
// copy trie object add CopyInto() method $excl = $this->dirsExcluded; $notIncl = $this->dirsNotIncluded;
for ($i=0, $_i=count($excl); $i < $_i; $i++) { if (!$this->couldHoldIncluded($excl[$i])) { $this->scandir($this->basedir.$excl[$i], $excl[$i].DIRECTORY_SEPARATOR, false); } }
for ($i=0, $_i=count($notIncl); $i < $_i; $i++) { if (!$this->couldHoldIncluded($notIncl[$i])) { $this->scandir($this->basedir.$notIncl[$i], $notIncl[$i].DIRECTORY_SEPARATOR, false); } }
$this->haveSlowResults = true; }
/** * Lists contens of a given directory and returns array with entries * * @param src String. Source path and name file to copy. * * @access public * @return array directory entries * @author Albert Lash, alash@plateauinnovation.com */
function listDir($_dir) { $d = dir($_dir); $list = array(); while(($entry = $d->read()) !== false) { if ($entry != "." && $entry != "..") { $list[] = $entry; } } $d->close(); return $list; }
/** * Scans the passed dir for files and directories. Found files and * directories are placed in their respective collections, based on the * matching of includes and excludes. When a directory is found, it is * scanned recursively. * * @param dir the directory to scan * @param vpath the path relative to the basedir (needed to prevent * problems with an absolute path when using dir) * * @access private * @see #filesIncluded * @see #filesNotIncluded * @see #filesExcluded * @see #dirsIncluded * @see #dirsNotIncluded * @see #dirsExcluded */ private function scandir($_rootdir, $_vpath, $_fast) { if (!is_readable($_rootdir)) { return; } $newfiles = self::listDir($_rootdir); for ($i=0,$_i=count($newfiles); $i < $_i; $i++) { $file = $_rootdir . DIRECTORY_SEPARATOR . $newfiles[$i]; $name = $_vpath . $newfiles[$i];
if (@is_link($file) && !$this->expandSymbolicLinks) { if ($this->isIncluded($name)) { if (!$this->isExcluded($name)) { if ($this->isSelected($name, $file)) { $this->filesIncluded[] = $name; } else { $this->everythingIncluded = false; $this->filesDeselected[] = $name; } } else { $this->everythingIncluded = false; $this->filesExcluded[] = $name; } } else { $this->everythingIncluded = false; $this->filesNotIncluded[] = $name; } } else if (@is_dir($file)) { if ($this->isIncluded($name)) { if (!$this->isExcluded($name)) { if ($this->isSelected($name, $file)) { $this->dirsIncluded[] = $name; if ($_fast) { $this->scandir($file, $name.DIRECTORY_SEPARATOR, $_fast); } } else { $this->everythingIncluded = false; $this->dirsDeselected[] = $name; if ($_fast && $this->couldHoldIncluded($name)) { $this->scandir($file, $name.DIRECTORY_SEPARATOR, $_fast); } } } else { $this->everythingIncluded = false; $this->dirsExcluded[] = $name; if ($_fast && $this->couldHoldIncluded($name)) { $this->scandir($file, $name.DIRECTORY_SEPARATOR, $_fast); } } } else { $this->everythingIncluded = false; $this->dirsNotIncluded[] = $name; if ($_fast && $this->couldHoldIncluded($name)) { $this->scandir($file, $name.DIRECTORY_SEPARATOR, $_fast); } } if (!$_fast) { $this->scandir($file, $name.DIRECTORY_SEPARATOR, $_fast); } } elseif (@is_file($file)) { if ($this->isIncluded($name)) { if (!$this->isExcluded($name)) { if ($this->isSelected($name, $file)) { $this->filesIncluded[] = $name; } else { $this->everythingIncluded = false; $this->filesDeselected[] = $name; } } else { $this->everythingIncluded = false; $this->filesExcluded[] = $name; } } else { $this->everythingIncluded = false; $this->filesNotIncluded[] = $name; } } } }
/** * Tests whether a name matches against at least one include pattern. * * @param name the name to match * @return <code>true</code> when the name matches against at least one * include pattern, <code>false</code> otherwise. */ protected function isIncluded($_name) { for ($i=0, $_i=count($this->includes); $i < $_i; $i++) { if (DirectoryScanner::matchPath($this->includes[$i], $_name, $this->isCaseSensitive)) { return true; } } return false; }
/** * Tests whether a name matches the start of at least one include pattern. * * @param name the name to match * @return <code>true</code> when the name matches against at least one * include pattern, <code>false</code> otherwise. */ protected function couldHoldIncluded($_name) { for ($i = 0; $i < count($this->includes); $i++) { if (DirectoryScanner::matchPatternStart($this->includes[$i], $_name, $this->isCaseSensitive)) { return true; } } return false; }
/** * Tests whether a name matches against at least one exclude pattern. * * @param name the name to match * @return <code>true</code> when the name matches against at least one * exclude pattern, <code>false</code> otherwise. */ protected function isExcluded($_name) { for ($i = 0; $i < count($this->excludes); $i++) { if (DirectoryScanner::matchPath($this->excludes[$i], $_name, $this->isCaseSensitive)) { return true; } } return false; }
/** * Get the names of the files that matched at least one of the include * patterns, and matched none of the exclude patterns. * The names are relative to the basedir. * * @return the names of the files */ function getIncludedFiles() { return $this->filesIncluded; }
/** * Get the names of the files that matched at none of the include patterns. * The names are relative to the basedir. * * @return the names of the files */ function getNotIncludedFiles() { $this->slowScan(); return $this->filesNotIncluded; }
/** * Get the names of the files that matched at least one of the include * patterns, an matched also at least one of the exclude patterns. * The names are relative to the basedir. * * @return the names of the files */
function getExcludedFiles() { $this->slowScan(); return $this->filesExcluded; }
/** * <p>Returns the names of the files which were selected out and * therefore not ultimately included.</p> * * <p>The names are relative to the base directory. This involves * performing a slow scan if one has not already been completed.</p> * * @return the names of the files which were deselected. * * @see #slowScan */ public function getDeselectedFiles() { $this->slowScan(); return $this->filesDeselected; }
/** * Get the names of the directories that matched at least one of the include * patterns, an matched none of the exclude patterns. * The names are relative to the basedir. * * @return the names of the directories */
function getIncludedDirectories() { return $this->dirsIncluded; }
/** * Get the names of the directories that matched at none of the include * patterns. * The names are relative to the basedir. * * @return the names of the directories */ function getNotIncludedDirectories() { $this->slowScan(); return $this->dirsNotIncluded; }
/** * <p>Returns the names of the directories which were selected out and * therefore not ultimately included.</p> * * <p>The names are relative to the base directory. This involves * performing a slow scan if one has not already been completed.</p> * * @return the names of the directories which were deselected. * * @see #slowScan */ public function getDeselectedDirectories() { $this->slowScan(); return $this->dirsDeselected; } /** * Get the names of the directories that matched at least one of the include * patterns, an matched also at least one of the exclude patterns. * The names are relative to the basedir. * * @return the names of the directories */ function getExcludedDirectories() { $this->slowScan(); return $this->dirsExcluded; }
/** * Adds the array with default exclusions to the current exclusions set. * */ function addDefaultExcludes() { //$excludesLength = ($this->excludes == null) ? 0 : count($this->excludes); foreach($this->DEFAULTEXCLUDES as $pattern) { $pattern = str_replace('\\', DIRECTORY_SEPARATOR, $pattern); $pattern = str_replace('/', DIRECTORY_SEPARATOR, $pattern); $this->excludes[] = $pattern; } } /** * Sets the selectors that will select the filelist. * * @param selectors specifies the selectors to be invoked on a scan */ public function setSelectors($selectors) { $this->selectors = $selectors; }
/** * Returns whether or not the scanner has included all the files or * directories it has come across so far. * * @return <code>true</code> if all files and directories which have * been found so far have been included. */ public function isEverythingIncluded() { return $this->everythingIncluded; } /** * Tests whether a name should be selected. * * @param string $name The filename to check for selecting. * @param string $file The full file path. * @return boolean False when the selectors says that the file * should not be selected, True otherwise. */ protected function isSelected($name, $file) { if ($this->selectors !== null) { $basedir = new PhingFile($this->basedir); $file = new PhingFile($file); if (!$file->canRead()) return false; foreach($this->selectors as $selector) { if (!$selector->isSelected($basedir, $name, $file)) { return false; } } } return true; }
}
|