diff --git a/ppremake/Makefile.am b/ppremake/Makefile.am index dd2e87208b..797722d4dc 100644 --- a/ppremake/Makefile.am +++ b/ppremake/Makefile.am @@ -5,6 +5,7 @@ ppremake_SOURCES = \ dSearchPath.I dSearchPath.cxx dSearchPath.h \ executionEnvironment.cxx executionEnvironment.h \ filename.I filename.cxx filename.h \ + globPattern.I globPattern.cxx globPattern.h \ gnu_getopt.c gnu_getopt.h gnu_regex.c gnu_regex.h \ ppCommandFile.cxx ppCommandFile.h ppDependableFile.cxx \ ppDependableFile.h ppDirectory.cxx \ diff --git a/ppremake/config_msvc.h b/ppremake/config_msvc.h index ed64a4e8a9..9b0ecfc121 100644 --- a/ppremake/config_msvc.h +++ b/ppremake/config_msvc.h @@ -40,9 +40,6 @@ /* Define if you have the header file. */ /* #undef HAVE_MINMAX_H */ -/* Define if you have the header file. */ -/* #undef HAVE_GLOB_H */ - /* Define if you have the header file. */ /* #undef HAVE_DIRENT_H */ diff --git a/ppremake/configure.in b/ppremake/configure.in index cc5a468306..b46a41f9e8 100644 --- a/ppremake/configure.in +++ b/ppremake/configure.in @@ -42,7 +42,7 @@ AC_SUBST(libm) dnl Checks for header files. AC_HEADER_STDC -AC_CHECK_HEADERS(malloc.h alloca.h unistd.h utime.h io.h minmax.h glob.h dirent.h sys/types.h sys/time.h sys/utime.h sys/wait.h string.h strstream.h regex.h) +AC_CHECK_HEADERS(malloc.h alloca.h unistd.h utime.h io.h minmax.h dirent.h sys/types.h sys/time.h sys/utime.h sys/wait.h string.h strstream.h regex.h) dnl Checks for typedefs, structures, and compiler characteristics. diff --git a/ppremake/filename.cxx b/ppremake/filename.cxx index fe297321cf..b345bb43bf 100644 --- a/ppremake/filename.cxx +++ b/ppremake/filename.cxx @@ -1065,7 +1065,13 @@ scan_directory(vector_string &contents) const { #if defined(HAVE_DIRENT_H) size_t orig_size = contents.size(); - DIR *root = opendir(_filename.c_str()); + string dirname; + if (empty()) { + dirname = "."; + } else { + dirname = _filename; + } + DIR *root = opendir(dirname.c_str()); if (root == (DIR *)NULL) { return false; } diff --git a/ppremake/globPattern.I b/ppremake/globPattern.I new file mode 100644 index 0000000000..2f4db8126c --- /dev/null +++ b/ppremake/globPattern.I @@ -0,0 +1,90 @@ +// Filename: globPattern.I +// Created by: drose (30May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE GlobPattern:: +GlobPattern(const string &pattern) : _pattern(pattern) { +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE GlobPattern:: +GlobPattern(const GlobPattern ©) : _pattern(copy._pattern) { +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::Copy Assignment Operator +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void GlobPattern:: +operator = (const GlobPattern ©) { + _pattern = copy._pattern; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::set_pattern +// Access: Public +// Description: Changes the pattern string that the GlobPattern +// object matches. +//////////////////////////////////////////////////////////////////// +INLINE void GlobPattern:: +set_pattern(const string &pattern) { + _pattern = pattern; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::get_pattern +// Access: Public +// Description: Returns the pattern string that the GlobPattern +// object matches. +//////////////////////////////////////////////////////////////////// +INLINE const string &GlobPattern:: +get_pattern() const { + return _pattern; +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::matches +// Access: Public +// Description: Returns true if the candidate string matches the +// pattern, false otherwise. +//////////////////////////////////////////////////////////////////// +INLINE bool GlobPattern:: +matches(const string &candidate) const { + return matches_substr(_pattern.begin(), _pattern.end(), + candidate.begin(), candidate.end()); +} + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::output +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE void GlobPattern:: +output(ostream &out) const { + out << _pattern; +} diff --git a/ppremake/globPattern.cxx b/ppremake/globPattern.cxx new file mode 100644 index 0000000000..a061cdf06a --- /dev/null +++ b/ppremake/globPattern.cxx @@ -0,0 +1,309 @@ +// Filename: globPattern.cxx +// Created by: drose (30May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#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 prefix, pattern, suffix; + + string source = _pattern; + if (!source.empty() && source[0] == '/') { + // If the first character is a slash, that becomes the prefix. + prefix = "/"; + source = source.substr(1); + } + + size_t slash = source.find('/'); + if (slash == string::npos) { + pattern = source; + } else { + pattern = source.substr(0, slash); + suffix = source.substr(slash + 1); + } + + GlobPattern glob(pattern); + return glob.r_match_files(prefix, 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 +// Description: The recursive implementation of matches(). This +// returns true if the pattern substring [pi, pend) +// matches the candidate substring [ci, cend), false +// otherwise. +//////////////////////////////////////////////////////////////////// +bool GlobPattern:: +matches_substr(string::const_iterator pi, string::const_iterator pend, + string::const_iterator ci, string::const_iterator cend) const { + // If we run out of pattern or candidate string, it's a match only + // if they both ran out at the same time. + if (pi == pend || ci == cend) { + // A special exception: we allow ci to reach the end before pi, + // only if pi is one character before the end and that last + // character is '*'. + if ((ci == cend) && (pi + 1 == pend) && (*pi) == '*') { + return true; + } + return (pi == pend && ci == cend); + } + + switch (*pi) { + + case '*': + // A '*' in the pattern string means to match any sequence of zero + // or more characters in the candidate string. This means we have + // to recurse twice: either consume one character of the candidate + // string and continue to try matching the *, or stop trying to + // match the * here. + return + matches_substr(pi, pend, ci + 1, cend) || + matches_substr(pi + 1, pend, ci, cend); + + case '?': + // A '?' in the pattern string means to match exactly one + // character in the candidate string. That's easy. + return matches_substr(pi + 1, pend, ci + 1, cend); + + case '[': + // An open square bracket begins a set. + ++pi; + if ((*pi) == '!') { + ++pi; + if (matches_set(pi, pend, *ci)) { + return false; + } + } else { + if (!matches_set(pi, pend, *ci)) { + return false; + } + } + if (pi == pend) { + // Oops, there wasn't a closing square bracket. + return false; + } + return matches_substr(pi + 1, pend, ci + 1, cend); + + case '\\': + // A backslash escapes the next special character. + ++pi; + if (pi == pend) { + return false; + } + // fall through. + + default: + // Anything else means to match exactly that. + if ((*pi) != (*ci)) { + return false; + } + return matches_substr(pi + 1, pend, ci + 1, cend); + } +} + + +//////////////////////////////////////////////////////////////////// +// Function: GlobPattern::matches_set +// Access: Private +// Description: Called when an unescaped open square bracked is +// scanned, this is called with pi positioned after the +// opening square bracket, scans the set sequence, +// leaving pi positioned on the closing square bracket, +// and returns true if the indicated character matches +// the set of characters indicated, false otherwise. +//////////////////////////////////////////////////////////////////// +bool GlobPattern:: +matches_set(string::const_iterator &pi, string::const_iterator pend, + char ch) const { + bool matched = false; + + while (pi != pend && (*pi) != ']') { + if ((*pi) == '\\') { + // Backslash escapes the next character. + ++pi; + if (pi == pend) { + return false; + } + } + + if (ch == (*pi)) { + matched = true; + } + + // Maybe it's an a-z style range? + char start = (*pi); + ++pi; + if (pi != pend && (*pi) == '-') { + ++pi; + if (pi != pend && (*pi) != ']') { + // Yes, we have a range: start-end. + + if ((*pi) == '\\') { + // Backslash escapes. + ++pi; + if (pi == pend) { + return false; + } + } + + char end = (*pi); + ++pi; + + if (ch >= start && ch <= end) { + matched = true; + } + } else { + // This was a - at the end of the string. + if (ch == '-') { + matched = true; + } + } + } + } + + return matched; +} + + + diff --git a/ppremake/globPattern.h b/ppremake/globPattern.h new file mode 100644 index 0000000000..d809a188f9 --- /dev/null +++ b/ppremake/globPattern.h @@ -0,0 +1,81 @@ +// Filename: globPattern.h +// Created by: drose (30May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef GLOBPATTERN_H +#define GLOBPATTERN_H + +#include "ppremake.h" +#include "filename.h" +#include "vector_string.h" + +//////////////////////////////////////////////////////////////////// +// Class : GlobPattern +// Description : This class can be used to test for string matches +// against standard Unix-shell filename globbing +// conventions. It serves as a portable standin for the +// Posix fnmatch() call. +// +// A GlobPattern is given a pattern string, which can +// contain operators like *, ?, and []. Then it can be +// tested against any number of candidate strings; for +// each candidate, it will indicate whether the string +// matches the pattern or not. It can be used, for +// example, to scan a directory for all files matching a +// particular pattern. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA GlobPattern { +public: + INLINE GlobPattern(const string &pattern = string()); + INLINE GlobPattern(const GlobPattern ©); + INLINE void operator = (const GlobPattern ©); + + INLINE void set_pattern(const string &pattern); + INLINE const string &get_pattern() const; + + INLINE bool matches(const string &candidate) const; + + 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, + string::const_iterator ci, + string::const_iterator cend) const; + + bool matches_set(string::const_iterator &pi, + 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; +}; + +INLINE ostream &operator << (ostream &out, const GlobPattern &glob) { + glob.output(out); + return out; +} + + +#include "globPattern.I" + +#endif diff --git a/ppremake/ppScope.cxx b/ppremake/ppScope.cxx index 0a1ba08da8..6babe96477 100644 --- a/ppremake/ppScope.cxx +++ b/ppremake/ppScope.cxx @@ -14,10 +14,7 @@ #include "tokenize.h" #include "filename.h" #include "dSearchPath.h" - -#ifdef HAVE_GLOB_H -#include -#endif +#include "globPattern.h" #ifdef HAVE_UNISTD_H #include @@ -3102,49 +3099,19 @@ p_find_map_variable(const string &varname) { //////////////////////////////////////////////////////////////////// void PPScope:: glob_string(const string &str, vector &results) { -#ifdef WIN32_VC - cerr << "glob temporarily unsupported in Win32 without Cygwin.\n"; - -#else // WIN32_VC - // We run glob_string() within the directory indicated by - // $[THISDIRPREFIX]. This way, local filenames will be expanded the - // way we expect. + // The globbing is relative to THISDIRPREFIX, not necessarily the + // current directory. string dirname = trim_blanks(expand_variable("THISDIRPREFIX")); - bool changed_dir = false; - if (!dirname.empty()) { - if (chdir(dirname.c_str()) < 0) { - perror("chdir"); - } else { - changed_dir = true; - } - } vector words; tokenize_whitespace(str, words); vector::const_iterator wi; - - glob_t pglob; - memset(&pglob, 0, sizeof(pglob)); - - int flags = 0; for (wi = words.begin(); wi != words.end(); ++wi) { - glob((*wi).c_str(), flags, NULL, &pglob); - flags |= GLOB_APPEND; + GlobPattern glob(*wi); + glob.match_files(results, dirname); } - for (int i = 0; i < (int)pglob.gl_pathc; i++) { - results.push_back(string(pglob.gl_pathv[i])); - } - - globfree(&pglob); - // Sort the results into alphabetical order. sort(results.begin(), results.end()); - - if (changed_dir) { - // Now restore the current directory back to where it should be. - PPMain::chdir_root(); - } -#endif // WIN32_VC } diff --git a/ppremake/ppremake.h b/ppremake/ppremake.h index d4fe31126f..45364d92c9 100644 --- a/ppremake/ppremake.h +++ b/ppremake/ppremake.h @@ -76,16 +76,17 @@ extern bool dry_run; extern bool verbose_dry_run; #endif -/* These are defined so that we may build Filename and DSearchPath, - which are copied from dtool. We have to copy these files from - dtool since ppremake must be built first, and stands outside of - Panda; but we want to minimize the changes we must make to these - files so that we can easily recopy them at need. +/* These are defined so that we may build Filename, DSearchPath, and + GlobPattern, which are copied from dtool and panda. We have to + copy these files in since ppremake must be built first, and stands + outside of Panda; but we want to minimize the changes we must make + to these files so that we can easily recopy them at need. These symbols just make the build environment a bit more Panda-like. */ #define PUBLISHED public #define INLINE inline #define EXPCL_DTOOL +#define EXPCL_PANDA #endif