Main Page   Class Hierarchy   Alphabetical List   Compound List   File List   Compound Members   File Members  

panda/src/putil/globPattern.cxx

Go to the documentation of this file.
00001 // Filename: globPattern.cxx
00002 // Created by:  drose (30May00)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
00008 //
00009 // All use of this software is subject to the terms of the Panda 3d
00010 // Software license.  You should have received a copy of this license
00011 // along with this source code; you will also find a current copy of
00012 // the license at http://www.panda3d.org/license.txt .
00013 //
00014 // To contact the maintainers of this program write to
00015 // panda3d@yahoogroups.com .
00016 //
00017 ////////////////////////////////////////////////////////////////////
00018 
00019 #include "globPattern.h"
00020 
00021 ////////////////////////////////////////////////////////////////////
00022 //     Function: GlobPattern::has_glob_characters
00023 //       Access: Public
00024 //  Description: Returns true if the pattern includes any special
00025 //               globbing characters, or false if it is just a literal
00026 //               string.
00027 ////////////////////////////////////////////////////////////////////
00028 bool GlobPattern::
00029 has_glob_characters() const {
00030   string::const_iterator pi;
00031   pi = _pattern.begin();
00032   while (pi != _pattern.end()) {
00033     switch (*pi) {
00034     case '*':
00035     case '?':
00036     case '[':
00037       return true;
00038 
00039     case '\\':
00040       ++pi;
00041       if (pi == _pattern.end()) {
00042         return false;
00043       }
00044     }
00045     ++pi;
00046   }
00047   return false;
00048 }
00049 
00050 ////////////////////////////////////////////////////////////////////
00051 //     Function: GlobPattern::match_files
00052 //       Access: Public
00053 //  Description: Treats the GlobPattern as a filename pattern, and
00054 //               returns a list of any actual files that match the
00055 //               pattern.  This is the behavior of the standard Posix
00056 //               glob() function.  Any part of the filename may
00057 //               contain glob characters, including intermediate
00058 //               directory names.
00059 //
00060 //               If cwd is specified, it is the directory that
00061 //               relative filenames are taken to be relative to;
00062 //               otherwise, the actual current working directory is
00063 //               assumed.
00064 //
00065 //               The return value is the number of files matched,
00066 //               which are added to the results vector.
00067 ////////////////////////////////////////////////////////////////////
00068 int GlobPattern::
00069 match_files(vector_string &results, const Filename &cwd) {
00070   string prefix, pattern, suffix;
00071 
00072   string source = _pattern;
00073   if (!source.empty() && source[0] == '/') {
00074     // If the first character is a slash, that becomes the prefix.
00075     prefix = "/";
00076     source = source.substr(1);
00077   }
00078 
00079   size_t slash = source.find('/');
00080   if (slash == string::npos) {
00081     pattern = source;
00082   } else {
00083     pattern = source.substr(0, slash);
00084     suffix = source.substr(slash + 1);
00085   }
00086   
00087   GlobPattern glob(pattern);
00088   return glob.r_match_files(prefix, suffix, results, cwd);
00089 }
00090 
00091 ////////////////////////////////////////////////////////////////////
00092 //     Function: GlobPattern::r_match_files
00093 //       Access: Private
00094 //  Description: The recursive implementation of match_files().
00095 ////////////////////////////////////////////////////////////////////
00096 int GlobPattern::
00097 r_match_files(const Filename &prefix, const string &suffix,
00098               vector_string &results, const Filename &cwd) {
00099   string next_pattern, next_suffix;
00100 
00101   size_t slash = suffix.find('/');
00102   if (slash == string::npos) {
00103     next_pattern = suffix;
00104   } else {
00105     next_pattern = suffix.substr(0, slash);
00106     next_suffix = suffix.substr(slash + 1);
00107   }
00108 
00109   Filename parent_dir;
00110   if (prefix.is_local() && !cwd.empty()) {
00111     parent_dir = Filename(cwd, prefix);
00112   } else {
00113     parent_dir = prefix;
00114   }
00115 
00116   GlobPattern next_glob(next_pattern);
00117 
00118   if (!has_glob_characters()) {
00119     // If there are no special characters in the pattern, it's a
00120     // literal match.
00121     if (suffix.empty()) {
00122       // Time to stop.
00123       Filename single_filename(parent_dir, _pattern);
00124       if (single_filename.exists()) {
00125         results.push_back(Filename(prefix, _pattern));
00126         return 1;
00127       }
00128       return 0;
00129     }
00130 
00131     return next_glob.r_match_files(Filename(prefix, _pattern),
00132                                    next_suffix, results, cwd);
00133 
00134   } 
00135 
00136   // If there *are* special glob characters, we must attempt to
00137   // match the pattern against the files in this directory.
00138   
00139   vector_string dir_files;
00140   if (!parent_dir.scan_directory(dir_files)) {
00141     // Not a directory, or unable to read directory; stop here.
00142     return 0;
00143   }
00144   
00145   // Now go through each file in the directory looking for one that
00146   // matches the pattern.
00147   int num_matched = 0;
00148   
00149   vector_string::const_iterator fi;
00150   for (fi = dir_files.begin(); fi != dir_files.end(); ++fi) {
00151     const string &local_file = (*fi);
00152     if (_pattern[0] == '.' || (local_file.empty() || local_file[0] != '.')) {
00153       if (matches(local_file)) {
00154         // We have a match; continue.
00155         if (suffix.empty()) {
00156           results.push_back(Filename(prefix, local_file));
00157           num_matched++; 
00158         } else {
00159           num_matched += next_glob.r_match_files(Filename(prefix, local_file),
00160                                                  next_suffix, results, cwd);
00161         }
00162       }
00163     }
00164   }
00165   
00166   return num_matched;
00167 }
00168 
00169 ////////////////////////////////////////////////////////////////////
00170 //     Function: GlobPattern::matches_substr
00171 //       Access: Private
00172 //  Description: The recursive implementation of matches().  This
00173 //               returns true if the pattern substring [pi, pend)
00174 //               matches the candidate substring [ci, cend), false
00175 //               otherwise.
00176 ////////////////////////////////////////////////////////////////////
00177 bool GlobPattern::
00178 matches_substr(string::const_iterator pi, string::const_iterator pend,
00179                string::const_iterator ci, string::const_iterator cend) const {
00180   // If we run out of pattern or candidate string, it's a match only
00181   // if they both ran out at the same time.
00182   if (pi == pend || ci == cend) {
00183     // A special exception: we allow ci to reach the end before pi,
00184     // only if pi is one character before the end and that last
00185     // character is '*'.
00186     if ((ci == cend) && (pi + 1 == pend) && (*pi) == '*') {
00187       return true;
00188     }
00189     return (pi == pend && ci == cend);
00190   }
00191 
00192   switch (*pi) {
00193 
00194   case '*':
00195     // A '*' in the pattern string means to match any sequence of zero
00196     // or more characters in the candidate string.  This means we have
00197     // to recurse twice: either consume one character of the candidate
00198     // string and continue to try matching the *, or stop trying to
00199     // match the * here.
00200     return
00201       matches_substr(pi, pend, ci + 1, cend) ||
00202       matches_substr(pi + 1, pend, ci, cend);
00203 
00204   case '?':
00205     // A '?' in the pattern string means to match exactly one
00206     // character in the candidate string.  That's easy.
00207     return matches_substr(pi + 1, pend, ci + 1, cend);
00208 
00209   case '[':
00210     // An open square bracket begins a set.
00211     ++pi;
00212     if ((*pi) == '!') {
00213       ++pi;
00214       if (matches_set(pi, pend, *ci)) {
00215         return false;
00216       }
00217     } else {
00218       if (!matches_set(pi, pend, *ci)) {
00219         return false;
00220       }
00221     }
00222     if (pi == pend) {
00223       // Oops, there wasn't a closing square bracket.
00224       return false;
00225     }
00226     return matches_substr(pi + 1, pend, ci + 1, cend);
00227 
00228   case '\\':
00229     // A backslash escapes the next special character.
00230     ++pi;
00231     if (pi == pend) {
00232       return false;
00233     }
00234     // fall through.
00235 
00236   default:
00237     // Anything else means to match exactly that.
00238     if ((*pi) != (*ci)) {
00239       return false;
00240     }
00241     return matches_substr(pi + 1, pend, ci + 1, cend);
00242   }
00243 }
00244 
00245 
00246 ////////////////////////////////////////////////////////////////////
00247 //     Function: GlobPattern::matches_set
00248 //       Access: Private
00249 //  Description: Called when an unescaped open square bracked is
00250 //               scanned, this is called with pi positioned after the
00251 //               opening square bracket, scans the set sequence,
00252 //               leaving pi positioned on the closing square bracket,
00253 //               and returns true if the indicated character matches
00254 //               the set of characters indicated, false otherwise.
00255 ////////////////////////////////////////////////////////////////////
00256 bool GlobPattern::
00257 matches_set(string::const_iterator &pi, string::const_iterator pend,
00258             char ch) const {
00259   bool matched = false;
00260 
00261   while (pi != pend && (*pi) != ']') {
00262     if ((*pi) == '\\') {
00263       // Backslash escapes the next character.
00264       ++pi;
00265       if (pi == pend) {
00266         return false;
00267       }
00268     }
00269 
00270     if (ch == (*pi)) {
00271       matched = true;
00272     }
00273 
00274     // Maybe it's an a-z style range?
00275     char start = (*pi);
00276     ++pi;
00277     if (pi != pend && (*pi) == '-') {
00278       ++pi;
00279       if (pi != pend && (*pi) != ']') {
00280         // Yes, we have a range: start-end.
00281 
00282         if ((*pi) == '\\') {
00283           // Backslash escapes.
00284           ++pi;
00285           if (pi == pend) {
00286             return false;
00287           }
00288         }
00289 
00290         char end = (*pi);
00291         ++pi;
00292 
00293         if (ch >= start && ch <= end) {
00294           matched = true;
00295         }
00296       } else {
00297         // This was a - at the end of the string.
00298         if (ch == '-') {
00299           matched = true;
00300         }
00301       }
00302     }
00303   }
00304 
00305   return matched;
00306 }
00307 
00308 
00309 

Generated on Fri May 2 00:43:39 2003 for Panda by doxygen1.3