globPattern.cxx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // Filename: globPattern.cxx
  2. // Created by: drose (30May00)
  3. //
  4. ////////////////////////////////////////////////////////////////////
  5. //
  6. // PANDA 3D SOFTWARE
  7. // Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
  8. //
  9. // All use of this software is subject to the terms of the Panda 3d
  10. // Software license. You should have received a copy of this license
  11. // along with this source code; you will also find a current copy of
  12. // the license at http://www.panda3d.org/license.txt .
  13. //
  14. // To contact the maintainers of this program write to
  15. // [email protected] .
  16. //
  17. ////////////////////////////////////////////////////////////////////
  18. #include "globPattern.h"
  19. ////////////////////////////////////////////////////////////////////
  20. // Function: GlobPattern::has_glob_characters
  21. // Access: Public
  22. // Description: Returns true if the pattern includes any special
  23. // globbing characters, or false if it is just a literal
  24. // string.
  25. ////////////////////////////////////////////////////////////////////
  26. bool GlobPattern::
  27. has_glob_characters() const {
  28. string::const_iterator pi;
  29. pi = _pattern.begin();
  30. while (pi != _pattern.end()) {
  31. switch (*pi) {
  32. case '*':
  33. case '?':
  34. case '[':
  35. return true;
  36. case '\\':
  37. ++pi;
  38. if (pi == _pattern.end()) {
  39. return false;
  40. }
  41. }
  42. ++pi;
  43. }
  44. return false;
  45. }
  46. ////////////////////////////////////////////////////////////////////
  47. // Function: GlobPattern::match_files
  48. // Access: Public
  49. // Description: Treats the GlobPattern as a filename pattern, and
  50. // returns a list of any actual files that match the
  51. // pattern. This is the behavior of the standard Posix
  52. // glob() function. Any part of the filename may
  53. // contain glob characters, including intermediate
  54. // directory names.
  55. //
  56. // If cwd is specified, it is the directory that
  57. // relative filenames are taken to be relative to;
  58. // otherwise, the actual current working directory is
  59. // assumed.
  60. //
  61. // The return value is the number of files matched,
  62. // which are added to the results vector.
  63. ////////////////////////////////////////////////////////////////////
  64. int GlobPattern::
  65. match_files(vector_string &results, const Filename &cwd) {
  66. string prefix, pattern, suffix;
  67. string source = _pattern;
  68. if (!source.empty() && source[0] == '/') {
  69. // If the first character is a slash, that becomes the prefix.
  70. prefix = "/";
  71. source = source.substr(1);
  72. }
  73. size_t slash = source.find('/');
  74. if (slash == string::npos) {
  75. pattern = source;
  76. } else {
  77. pattern = source.substr(0, slash);
  78. suffix = source.substr(slash + 1);
  79. }
  80. GlobPattern glob(pattern);
  81. return glob.r_match_files(prefix, suffix, results, cwd);
  82. }
  83. ////////////////////////////////////////////////////////////////////
  84. // Function: GlobPattern::r_match_files
  85. // Access: Private
  86. // Description: The recursive implementation of match_files().
  87. ////////////////////////////////////////////////////////////////////
  88. int GlobPattern::
  89. r_match_files(const Filename &prefix, const string &suffix,
  90. vector_string &results, const Filename &cwd) {
  91. string next_pattern, next_suffix;
  92. size_t slash = suffix.find('/');
  93. if (slash == string::npos) {
  94. next_pattern = suffix;
  95. } else {
  96. next_pattern = suffix.substr(0, slash);
  97. next_suffix = suffix.substr(slash + 1);
  98. }
  99. Filename parent_dir;
  100. if (prefix.is_local() && !cwd.empty()) {
  101. parent_dir = Filename(cwd, prefix);
  102. } else {
  103. parent_dir = prefix;
  104. }
  105. GlobPattern next_glob(next_pattern);
  106. if (!has_glob_characters()) {
  107. // If there are no special characters in the pattern, it's a
  108. // literal match.
  109. if (suffix.empty()) {
  110. // Time to stop.
  111. Filename single_filename(parent_dir, _pattern);
  112. if (single_filename.exists()) {
  113. results.push_back(Filename(prefix, _pattern));
  114. return 1;
  115. }
  116. return 0;
  117. }
  118. return next_glob.r_match_files(Filename(prefix, _pattern),
  119. next_suffix, results, cwd);
  120. }
  121. // If there *are* special glob characters, we must attempt to
  122. // match the pattern against the files in this directory.
  123. vector_string dir_files;
  124. if (!parent_dir.scan_directory(dir_files)) {
  125. // Not a directory, or unable to read directory; stop here.
  126. return 0;
  127. }
  128. // Now go through each file in the directory looking for one that
  129. // matches the pattern.
  130. int num_matched = 0;
  131. vector_string::const_iterator fi;
  132. for (fi = dir_files.begin(); fi != dir_files.end(); ++fi) {
  133. const string &local_file = (*fi);
  134. if (_pattern[0] == '.' || (local_file.empty() || local_file[0] != '.')) {
  135. if (matches(local_file)) {
  136. // We have a match; continue.
  137. if (suffix.empty()) {
  138. results.push_back(Filename(prefix, local_file));
  139. num_matched++;
  140. } else {
  141. num_matched += next_glob.r_match_files(Filename(prefix, local_file),
  142. next_suffix, results, cwd);
  143. }
  144. }
  145. }
  146. }
  147. return num_matched;
  148. }
  149. ////////////////////////////////////////////////////////////////////
  150. // Function: GlobPattern::matches_substr
  151. // Access: Private
  152. // Description: The recursive implementation of matches(). This
  153. // returns true if the pattern substring [pi, pend)
  154. // matches the candidate substring [ci, cend), false
  155. // otherwise.
  156. ////////////////////////////////////////////////////////////////////
  157. bool GlobPattern::
  158. matches_substr(string::const_iterator pi, string::const_iterator pend,
  159. string::const_iterator ci, string::const_iterator cend) const {
  160. // If we run out of pattern or candidate string, it's a match only
  161. // if they both ran out at the same time.
  162. if (pi == pend || ci == cend) {
  163. // A special exception: we allow ci to reach the end before pi,
  164. // only if pi is one character before the end and that last
  165. // character is '*'.
  166. if ((ci == cend) && (pi + 1 == pend) && (*pi) == '*') {
  167. return true;
  168. }
  169. return (pi == pend && ci == cend);
  170. }
  171. switch (*pi) {
  172. case '*':
  173. // A '*' in the pattern string means to match any sequence of zero
  174. // or more characters in the candidate string. This means we have
  175. // to recurse twice: either consume one character of the candidate
  176. // string and continue to try matching the *, or stop trying to
  177. // match the * here.
  178. return
  179. matches_substr(pi, pend, ci + 1, cend) ||
  180. matches_substr(pi + 1, pend, ci, cend);
  181. case '?':
  182. // A '?' in the pattern string means to match exactly one
  183. // character in the candidate string. That's easy.
  184. return matches_substr(pi + 1, pend, ci + 1, cend);
  185. case '[':
  186. // An open square bracket begins a set.
  187. ++pi;
  188. if ((*pi) == '!') {
  189. ++pi;
  190. if (matches_set(pi, pend, *ci)) {
  191. return false;
  192. }
  193. } else {
  194. if (!matches_set(pi, pend, *ci)) {
  195. return false;
  196. }
  197. }
  198. if (pi == pend) {
  199. // Oops, there wasn't a closing square bracket.
  200. return false;
  201. }
  202. return matches_substr(pi + 1, pend, ci + 1, cend);
  203. case '\\':
  204. // A backslash escapes the next special character.
  205. ++pi;
  206. if (pi == pend) {
  207. return false;
  208. }
  209. // fall through.
  210. default:
  211. // Anything else means to match exactly that.
  212. if ((*pi) != (*ci)) {
  213. return false;
  214. }
  215. return matches_substr(pi + 1, pend, ci + 1, cend);
  216. }
  217. }
  218. ////////////////////////////////////////////////////////////////////
  219. // Function: GlobPattern::matches_set
  220. // Access: Private
  221. // Description: Called when an unescaped open square bracked is
  222. // scanned, this is called with pi positioned after the
  223. // opening square bracket, scans the set sequence,
  224. // leaving pi positioned on the closing square bracket,
  225. // and returns true if the indicated character matches
  226. // the set of characters indicated, false otherwise.
  227. ////////////////////////////////////////////////////////////////////
  228. bool GlobPattern::
  229. matches_set(string::const_iterator &pi, string::const_iterator pend,
  230. char ch) const {
  231. bool matched = false;
  232. while (pi != pend && (*pi) != ']') {
  233. if ((*pi) == '\\') {
  234. // Backslash escapes the next character.
  235. ++pi;
  236. if (pi == pend) {
  237. return false;
  238. }
  239. }
  240. if (ch == (*pi)) {
  241. matched = true;
  242. }
  243. // Maybe it's an a-z style range?
  244. char start = (*pi);
  245. ++pi;
  246. if (pi != pend && (*pi) == '-') {
  247. ++pi;
  248. if (pi != pend && (*pi) != ']') {
  249. // Yes, we have a range: start-end.
  250. if ((*pi) == '\\') {
  251. // Backslash escapes.
  252. ++pi;
  253. if (pi == pend) {
  254. return false;
  255. }
  256. }
  257. char end = (*pi);
  258. ++pi;
  259. if (ch >= start && ch <= end) {
  260. matched = true;
  261. }
  262. } else {
  263. // This was a - at the end of the string.
  264. if (ch == '-') {
  265. matched = true;
  266. }
  267. }
  268. }
  269. }
  270. return matched;
  271. }