diff --git a/panda/src/putil/Sources.pp b/panda/src/putil/Sources.pp index 9573d6f115..00e6343aaa 100644 --- a/panda/src/putil/Sources.pp +++ b/panda/src/putil/Sources.pp @@ -159,6 +159,7 @@ test_glob.cxx #define LOCAL_LIBS $[LOCAL_LIBS] putil + #define OTHER_LIBS $[OTHER_LIBS] pystub #end test_bin_target diff --git a/panda/src/putil/globPattern.cxx b/panda/src/putil/globPattern.cxx index 27bf2c8c13..219005ff82 100644 --- a/panda/src/putil/globPattern.cxx +++ b/panda/src/putil/globPattern.cxx @@ -18,6 +18,147 @@ #include "globPattern.h" +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::has_glob_characters +// Access: Public +// Description: Returns true if the pattern includes any special +// globbing characters, or false if it is just a literal +// string. +//////////////////////////////////////////////////////////////////// +bool GlobPattern:: +has_glob_characters() const { + string::const_iterator pi; + pi = _pattern.begin(); + while (pi != _pattern.end()) { + switch (*pi) { + case '*': + case '?': + case '[': + return true; + + case '\\': + ++pi; + if (pi == _pattern.end()) { + return false; + } + } + ++pi; + } + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::match_files +// Access: Public +// Description: Treats the GlobPattern as a filename pattern, and +// returns a list of any actual files that match the +// pattern. This is the behavior of the standard Posix +// glob() function. Any part of the filename may +// contain glob characters, including intermediate +// directory names. +// +// If cwd is specified, it is the directory that +// relative filenames are taken to be relative to; +// otherwise, the actual current working directory is +// assumed. +// +// The return value is the number of files matched, +// which are added to the results vector. +//////////////////////////////////////////////////////////////////// +int GlobPattern:: +match_files(vector_string &results, const Filename &cwd) { + string next_pattern, next_suffix; + + size_t slash = _pattern.find('/'); + if (slash == string::npos) { + next_pattern = _pattern; + } else { + next_pattern = _pattern.substr(0, slash); + next_suffix = _pattern.substr(slash + 1); + } + + GlobPattern next_glob(next_pattern); + return next_glob.r_match_files(Filename(), next_suffix, results, cwd); +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::r_match_files +// Access: Private +// Description: The recursive implementation of match_files(). +//////////////////////////////////////////////////////////////////// +int GlobPattern:: +r_match_files(const Filename &prefix, const string &suffix, + vector_string &results, const Filename &cwd) { + string next_pattern, next_suffix; + + size_t slash = suffix.find('/'); + if (slash == string::npos) { + next_pattern = suffix; + } else { + next_pattern = suffix.substr(0, slash); + next_suffix = suffix.substr(slash + 1); + } + + Filename parent_dir; + if (prefix.is_local() && !cwd.empty()) { + parent_dir = Filename(cwd, prefix); + } else { + parent_dir = prefix; + } + + GlobPattern next_glob(next_pattern); + + if (!has_glob_characters()) { + // If there are no special characters in the pattern, it's a + // literal match. + if (suffix.empty()) { + // Time to stop. + Filename single_filename(parent_dir, _pattern); + if (single_filename.exists()) { + results.push_back(Filename(prefix, _pattern)); + return 1; + } + return 0; + } + + return next_glob.r_match_files(Filename(prefix, _pattern), + next_suffix, results, cwd); + + } + + // If there *are* special glob characters, we must attempt to + // match the pattern against the files in this directory. + + vector_string dir_files; + if (!parent_dir.scan_directory(dir_files)) { + // Not a directory, or unable to read directory; stop here. + return 0; + } + + // Now go through each file in the directory looking for one that + // matches the pattern. + int num_matched = 0; + + vector_string::const_iterator fi; + for (fi = dir_files.begin(); fi != dir_files.end(); ++fi) { + const string &local_file = (*fi); + if (_pattern[0] == '.' || (local_file.empty() || local_file[0] != '.')) { + if (matches(local_file)) { + // We have a match; continue. + if (suffix.empty()) { + results.push_back(Filename(prefix, local_file)); + num_matched++; + } else { + num_matched += next_glob.r_match_files(Filename(prefix, local_file), + next_suffix, results, cwd); + } + } + } + } + + return num_matched; +} + //////////////////////////////////////////////////////////////////// // Function: GlobPattern::matches_substr // Access: Private diff --git a/panda/src/putil/globPattern.h b/panda/src/putil/globPattern.h index 44ef452303..f3b3162fd3 100644 --- a/panda/src/putil/globPattern.h +++ b/panda/src/putil/globPattern.h @@ -19,7 +19,9 @@ #ifndef GLOBPATTERN_H #define GLOBPATTERN_H -#include +#include "pandabase.h" +#include "filename.h" +#include "vector_string.h" //////////////////////////////////////////////////////////////////// // Class : GlobPattern @@ -49,6 +51,9 @@ public: INLINE void output(ostream &out) const; + bool has_glob_characters() const; + int match_files(vector_string &results, const Filename &cwd = Filename()); + private: bool matches_substr(string::const_iterator pi, string::const_iterator pend, @@ -59,6 +64,9 @@ private: string::const_iterator pend, char ch) const; + int r_match_files(const Filename &prefix, const string &suffix, + vector_string &results, const Filename &cwd); + string _pattern; }; diff --git a/panda/src/putil/test_glob.cxx b/panda/src/putil/test_glob.cxx index 621b6f05b6..01f29fef96 100644 --- a/panda/src/putil/test_glob.cxx +++ b/panda/src/putil/test_glob.cxx @@ -18,20 +18,14 @@ #include "globPattern.h" -#ifdef WIN32 -#include -#else -#include -#include -#endif - int main(int argc, char *argv[]) { - if (argc != 2) { + if (argc != 2 && argc != 3) { cerr - << "test_glob \"pattern\"\n\n" + << "test_glob \"pattern\" [from-directory]\n\n" << "Attempts to match the pattern against each of the files in the\n" - << "current directory. Reports all of the matching files. This is,\n" + << "indicated directory if specified, or the current directory\n" + << "otherwise. Reports all of the matching files. This is,\n" << "of course, exactly the normal behavior of the shell; this test\n" << "program merely exercises the Panda GlobPattern object, which\n" << "duplicates the shell functionality.\n\n"; @@ -39,42 +33,20 @@ main(int argc, char *argv[]) { } GlobPattern pattern(argv[1]); - -#ifdef WIN32 - struct _finddata_t fd; - long dp = _findfirst("*.*", &fd); - - if (dp != 0) { - do { - if (pattern.matches(fd.name)) { - cout << "Matches: " << fd.name << "\n"; - } else { - cout << "Fails: " << fd.name << "\n"; - } - } while (_findnext(dp, &fd) == 0); + Filename from_directory; + if (argc == 3) { + from_directory = argv[2]; } -#else // WIN32 - DIR *dp = opendir("."); - if (dp == (DIR *)NULL) { - cerr << "Unable to scan directory '.'\n"; - exit(1); - } + vector_string results; + int num_matched = pattern.match_files(results, from_directory); - struct dirent *ent; - ent = readdir(dp); - while (ent != (struct dirent *)NULL) { - if (pattern.matches(ent->d_name)) { - cout << "Matches: " << ent->d_name << "\n"; - } else { - cout << "Fails: " << ent->d_name << "\n"; - } - ent = readdir(dp); + cerr << num_matched << " results:\n"; + vector_string::const_iterator si; + for (si = results.begin(); si != results.end(); ++si) { + cerr << " " << *si << "\n"; } - closedir(dp); -#endif // WIN32 - return (0); }