1adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward"""distutils.filelist
2adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
3adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg WardProvides the FileList class, used for poking about the filesystem
4adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Wardand building lists of files.
5adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward"""
6adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
7adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward__revision__ = "$Id$"
8adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
9e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadéimport os, re
10adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Wardimport fnmatch
11adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Wardfrom distutils.util import convert_path
127b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Wardfrom distutils.errors import DistutilsTemplateError, DistutilsInternalError
134f2f1335a8f889ac071b01becf49b49032beb163Jeremy Hyltonfrom distutils import log
14adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
15adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Wardclass FileList:
16c98927a059c458065ca3311a55f70b323f75c467Greg Ward    """A list of files built by on exploring the filesystem and filtered by
17c98927a059c458065ca3311a55f70b323f75c467Greg Ward    applying various patterns to what we find there.
18c98927a059c458065ca3311a55f70b323f75c467Greg Ward
19c98927a059c458065ca3311a55f70b323f75c467Greg Ward    Instance attributes:
20c98927a059c458065ca3311a55f70b323f75c467Greg Ward      dir
21c98927a059c458065ca3311a55f70b323f75c467Greg Ward        directory from which files will be taken -- only used if
22c98927a059c458065ca3311a55f70b323f75c467Greg Ward        'allfiles' not supplied to constructor
23c98927a059c458065ca3311a55f70b323f75c467Greg Ward      files
24c98927a059c458065ca3311a55f70b323f75c467Greg Ward        list of filenames currently being built/filtered/manipulated
25c98927a059c458065ca3311a55f70b323f75c467Greg Ward      allfiles
26c98927a059c458065ca3311a55f70b323f75c467Greg Ward        complete list of files under consideration (ie. without any
27c98927a059c458065ca3311a55f70b323f75c467Greg Ward        filtering applied)
28c98927a059c458065ca3311a55f70b323f75c467Greg Ward    """
29adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
30e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def __init__(self, warn=None, debug_print=None):
31cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton        # ignore argument to FileList, but keep them for backwards
32cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton        # compatibility
33979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        self.allfiles = None
34979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        self.files = []
35adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
36e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def set_allfiles(self, allfiles):
37979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        self.allfiles = allfiles
38979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
39e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def findall(self, dir=os.curdir):
40979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        self.allfiles = findall(dir)
41979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
42e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def debug_print(self, msg):
43adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        """Print 'msg' to stdout if the global DEBUG (taken from the
44adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        DISTUTILS_DEBUG environment variable) flag is true.
45adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        """
46fcd7353863c024bb87aabe7c4639ca8df692ac85Jeremy Hylton        from distutils.debug import DEBUG
47adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        if DEBUG:
48adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            print msg
49adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
50979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward    # -- List-like methods ---------------------------------------------
51979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
52e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def append(self, item):
53979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        self.files.append(item)
54979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
55e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def extend(self, items):
56979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        self.files.extend(items)
57979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
58e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def sort(self):
59979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        # Not a strict lexical sort!
60979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        sortable_files = map(os.path.split, self.files)
61979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        sortable_files.sort()
62979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        self.files = []
63979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        for sort_tuple in sortable_files:
64f638486cf09816668c662241c160dad863582067Tarek Ziadé            self.files.append(os.path.join(*sort_tuple))
65979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
66979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
67979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward    # -- Other miscellaneous utility methods ---------------------------
68979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
69e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def remove_duplicates(self):
70979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        # Assumes list has been sorted!
71cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton        for i in range(len(self.files) - 1, 0, -1):
72cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton            if self.files[i] == self.files[i - 1]:
73979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward                del self.files[i]
74979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
75979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
76979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward    # -- "File template" methods ---------------------------------------
77b94b849d65af71b4b432a74fdaef8ccd88209cc0Fred Drake
78e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def _parse_template_line(self, line):
79e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        words = line.split()
80c98927a059c458065ca3311a55f70b323f75c467Greg Ward        action = words[0]
81c98927a059c458065ca3311a55f70b323f75c467Greg Ward
827b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        patterns = dir = dir_pattern = None
837b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward
847b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        if action in ('include', 'exclude',
857b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward                      'global-include', 'global-exclude'):
86071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward            if len(words) < 2:
877b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward                raise DistutilsTemplateError, \
887b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward                      "'%s' expects <pattern1> <pattern2> ..." % action
89adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
907b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            patterns = map(convert_path, words[1:])
91adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
927b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        elif action in ('recursive-include', 'recursive-exclude'):
93071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward            if len(words) < 3:
947b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward                raise DistutilsTemplateError, \
957b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward                      "'%s' expects <dir> <pattern1> <pattern2> ..." % action
96adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
97c98927a059c458065ca3311a55f70b323f75c467Greg Ward            dir = convert_path(words[1])
987b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            patterns = map(convert_path, words[2:])
99adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
1007b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        elif action in ('graft', 'prune'):
101071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward            if len(words) != 2:
1027b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward                raise DistutilsTemplateError, \
1037b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward                     "'%s' expects a single <dir_pattern>" % action
104adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
1057b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            dir_pattern = convert_path(words[1])
106c98927a059c458065ca3311a55f70b323f75c467Greg Ward
107c98927a059c458065ca3311a55f70b323f75c467Greg Ward        else:
1087b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            raise DistutilsTemplateError, "unknown action '%s'" % action
1097b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward
110d5dcc174b0b8d3bef3e51b64f6fb1383e803cdd0Greg Ward        return (action, patterns, dir, dir_pattern)
1117b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward
112e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def process_template_line(self, line):
1137b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        # Parse the line: split it up, make sure the right number of words
1140f341855accddf217f1926d2f50e30f9da26babaGreg Ward        # is there, and return the relevant words.  'action' is always
1157b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        # defined: it's the first word of the line.  Which of the other
1167b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        # three are defined depends on the action; it'll be either
1177b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        # patterns, (dir and patterns), or (dir_pattern).
1181c8c9d12642ba04bb22f612f5d09eaf93c274a18Tarek Ziadé        action, patterns, dir, dir_pattern = self._parse_template_line(line)
119c98927a059c458065ca3311a55f70b323f75c467Greg Ward
120c98927a059c458065ca3311a55f70b323f75c467Greg Ward        # OK, now we know that the action is valid and we have the
121c98927a059c458065ca3311a55f70b323f75c467Greg Ward        # right number of words on the line for that action -- so we
1227b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward        # can proceed with minimal error-checking.
123c98927a059c458065ca3311a55f70b323f75c467Greg Ward        if action == 'include':
124e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé            self.debug_print("include " + ' '.join(patterns))
1257b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            for pattern in patterns:
126071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                if not self.include_pattern(pattern, anchor=1):
127cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                    log.warn("warning: no files found matching '%s'",
128cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                             pattern)
129c98927a059c458065ca3311a55f70b323f75c467Greg Ward
130c98927a059c458065ca3311a55f70b323f75c467Greg Ward        elif action == 'exclude':
131e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé            self.debug_print("exclude " + ' '.join(patterns))
1327b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            for pattern in patterns:
133071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                if not self.exclude_pattern(pattern, anchor=1):
134cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                    log.warn(("warning: no previously-included files "
135cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                              "found matching '%s'"), pattern)
136c98927a059c458065ca3311a55f70b323f75c467Greg Ward
137c98927a059c458065ca3311a55f70b323f75c467Greg Ward        elif action == 'global-include':
138e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé            self.debug_print("global-include " + ' '.join(patterns))
1397b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            for pattern in patterns:
140071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                if not self.include_pattern(pattern, anchor=0):
141cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                    log.warn(("warning: no files found matching '%s' " +
142cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                              "anywhere in distribution"), pattern)
143c98927a059c458065ca3311a55f70b323f75c467Greg Ward
144c98927a059c458065ca3311a55f70b323f75c467Greg Ward        elif action == 'global-exclude':
145e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé            self.debug_print("global-exclude " + ' '.join(patterns))
1467b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            for pattern in patterns:
147071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                if not self.exclude_pattern(pattern, anchor=0):
148cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                    log.warn(("warning: no previously-included files matching "
149cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                              "'%s' found anywhere in distribution"),
150cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                             pattern)
151c98927a059c458065ca3311a55f70b323f75c467Greg Ward
152c98927a059c458065ca3311a55f70b323f75c467Greg Ward        elif action == 'recursive-include':
153c98927a059c458065ca3311a55f70b323f75c467Greg Ward            self.debug_print("recursive-include %s %s" %
154e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé                             (dir, ' '.join(patterns)))
1557b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            for pattern in patterns:
156071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                if not self.include_pattern(pattern, prefix=dir):
157cbd0b365c1f27d390827e74a88a96f3a13034e0eWalter Dörwald                    log.warn(("warning: no files found matching '%s' " +
158182b5aca27d376b08a2904bed42b751496f932f3Tim Peters                                "under directory '%s'"),
159cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                             pattern, dir)
160c98927a059c458065ca3311a55f70b323f75c467Greg Ward
161c98927a059c458065ca3311a55f70b323f75c467Greg Ward        elif action == 'recursive-exclude':
162c98927a059c458065ca3311a55f70b323f75c467Greg Ward            self.debug_print("recursive-exclude %s %s" %
163e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé                             (dir, ' '.join(patterns)))
1647b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            for pattern in patterns:
165c98927a059c458065ca3311a55f70b323f75c467Greg Ward                if not self.exclude_pattern(pattern, prefix=dir):
166cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                    log.warn(("warning: no previously-included files matching "
167cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                              "'%s' found under directory '%s'"),
168cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                             pattern, dir)
169b94b849d65af71b4b432a74fdaef8ccd88209cc0Fred Drake
170c98927a059c458065ca3311a55f70b323f75c467Greg Ward        elif action == 'graft':
171c98927a059c458065ca3311a55f70b323f75c467Greg Ward            self.debug_print("graft " + dir_pattern)
1720f341855accddf217f1926d2f50e30f9da26babaGreg Ward            if not self.include_pattern(None, prefix=dir_pattern):
173cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                log.warn("warning: no directories found matching '%s'",
174cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                         dir_pattern)
175c98927a059c458065ca3311a55f70b323f75c467Greg Ward
176c98927a059c458065ca3311a55f70b323f75c467Greg Ward        elif action == 'prune':
177c98927a059c458065ca3311a55f70b323f75c467Greg Ward            self.debug_print("prune " + dir_pattern)
178c98927a059c458065ca3311a55f70b323f75c467Greg Ward            if not self.exclude_pattern(None, prefix=dir_pattern):
179cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                log.warn(("no previously-included directories found " +
180cd8a1148e19116db109f27d26c02e1de536dc76eJeremy Hylton                          "matching '%s'"), dir_pattern)
181c98927a059c458065ca3311a55f70b323f75c467Greg Ward        else:
1827b3d56c85cacf45cf27f87e13a95fe12d76984d1Greg Ward            raise DistutilsInternalError, \
183c98927a059c458065ca3311a55f70b323f75c467Greg Ward                  "this cannot happen: invalid action '%s'" % action
184adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
185979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward    # -- Filtering/selection methods -----------------------------------
186979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward
187e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def include_pattern(self, pattern, anchor=1, prefix=None, is_regex=0):
1880f341855accddf217f1926d2f50e30f9da26babaGreg Ward        """Select strings (presumably filenames) from 'self.files' that
189e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        match 'pattern', a Unix-style wildcard (glob) pattern.
190e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé
191e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        Patterns are not quite the same as implemented by the 'fnmatch'
192e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        module: '*' and '?'  match non-special characters, where "special"
193e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        is platform-dependent: slash on Unix; colon, slash, and backslash on
1940f341855accddf217f1926d2f50e30f9da26babaGreg Ward        DOS/Windows; and colon on Mac OS.
195adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
196adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        If 'anchor' is true (the default), then the pattern match is more
197adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        stringent: "*.py" will match "foo.py" but not "foo/bar.py".  If
198adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        'anchor' is false, both of these will match.
199adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
200adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        If 'prefix' is supplied, then only filenames starting with 'prefix'
201adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        (itself a pattern) and ending with 'pattern', with anything in between
202adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        them, will match.  'anchor' is ignored in this case.
203adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
204adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and
205adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        'pattern' is assumed to be either a string containing a regex or a
206adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        regex object -- no translation is done, the regex is just compiled
207adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        and used as-is.
208adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
209adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        Selected strings will be added to self.files.
210adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
211adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        Return 1 if files are found.
212adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        """
21331378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo        # XXX docstring lying about what the special chars are?
214adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        files_found = 0
215071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward        pattern_re = translate_pattern(pattern, anchor, prefix, is_regex)
2160f341855accddf217f1926d2f50e30f9da26babaGreg Ward        self.debug_print("include_pattern: applying regex r'%s'" %
217adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward                         pattern_re.pattern)
218adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
219adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        # delayed loading of allfiles list
220979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward        if self.allfiles is None:
221979db976a3a07e20df7664b567aba91a2d0b538cGreg Ward            self.findall()
222adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
223adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        for name in self.allfiles:
224071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward            if pattern_re.search(name):
225adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward                self.debug_print(" adding " + name)
226071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                self.files.append(name)
227adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward                files_found = 1
228b94b849d65af71b4b432a74fdaef8ccd88209cc0Fred Drake
229adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        return files_found
230adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
231adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
232e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    def exclude_pattern(self, pattern, anchor=1, prefix=None, is_regex=0):
233adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        """Remove strings (presumably filenames) from 'files' that match
234e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        'pattern'.
235e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé
236e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        Other parameters are the same as for 'include_pattern()', above.
237e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        The list 'self.files' is modified in place. Return 1 if files are
238e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        found.
239adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        """
240adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        files_found = 0
241071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward        pattern_re = translate_pattern(pattern, anchor, prefix, is_regex)
242adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        self.debug_print("exclude_pattern: applying regex r'%s'" %
243adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward                         pattern_re.pattern)
244071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward        for i in range(len(self.files)-1, -1, -1):
245071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward            if pattern_re.search(self.files[i]):
246adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward                self.debug_print(" removing " + self.files[i])
247adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward                del self.files[i]
248adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward                files_found = 1
249b94b849d65af71b4b432a74fdaef8ccd88209cc0Fred Drake
250adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        return files_found
251adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
252adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
253adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward# ----------------------------------------------------------------------
254adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward# Utility functions
255adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
256e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadédef findall(dir = os.curdir):
257adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    """Find all files under 'dir' and return the list of full filenames
258adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    (relative to 'dir').
259adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    """
260adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    from stat import ST_MODE, S_ISREG, S_ISDIR, S_ISLNK
261adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
262adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    list = []
263adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    stack = [dir]
264adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    pop = stack.pop
265adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    push = stack.append
266adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
267adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    while stack:
268adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        dir = pop()
269071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward        names = os.listdir(dir)
270adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
271adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        for name in names:
272adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            if dir != os.curdir:        # avoid the dreaded "./" syndrome
273071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                fullname = os.path.join(dir, name)
274adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            else:
275adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward                fullname = name
276adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
277adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            # Avoid excess stat calls -- just one will do, thank you!
278adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            stat = os.stat(fullname)
279adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            mode = stat[ST_MODE]
280adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            if S_ISREG(mode):
281071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                list.append(fullname)
282adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            elif S_ISDIR(mode) and not S_ISLNK(mode):
283071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward                push(fullname)
284adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
285adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    return list
286adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
287adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
288faa6b121fb34f1c4db36f20b625ece0291396174Tarek Ziadédef glob_to_re(pattern):
289e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    """Translate a shell-like glob pattern to a regular expression.
290e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé
291e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    Return a string containing the regex.  Differs from
292e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    'fnmatch.translate()' in that '*' does not match "special characters"
293e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    (which are platform-specific).
294adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    """
295071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward    pattern_re = fnmatch.translate(pattern)
296adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
297adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
298adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
299adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    # and by extension they shouldn't match such "special characters" under
300adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    # any OS.  So change all non-escaped dots in the RE to match any
30131378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo    # character except the special characters (currently: just os.sep).
30231378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo    sep = os.sep
30331378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo    if os.sep == '\\':
30431378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo        # we're using a regex to manipulate a regex, so we need
30531378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo        # to escape the backslash twice
30631378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo        sep = r'\\\\'
30731378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo    escaped = r'\1[^%s]' % sep
30831378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo    pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re)
309adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    return pattern_re
310adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
311adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
312e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadédef translate_pattern(pattern, anchor=1, prefix=None, is_regex=0):
313adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    """Translate a shell-like wildcard pattern to a compiled regular
314e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    expression.
315e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé
316e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé    Return the compiled regex.  If 'is_regex' true,
317adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    then 'pattern' is directly compiled to a regex (if it's a string)
318adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    or just returned as-is (assumes it's a regex object).
319adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    """
320adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    if is_regex:
321e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        if isinstance(pattern, str):
322adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            return re.compile(pattern)
323adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        else:
324adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            return pattern
325adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward
326adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    if pattern:
327071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward        pattern_re = glob_to_re(pattern)
328adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    else:
329adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        pattern_re = ''
330b94b849d65af71b4b432a74fdaef8ccd88209cc0Fred Drake
331adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    if prefix is not None:
33298026f1521b4c549168ecb0d03f0163070f3534eTarek Ziadé        # ditch end of pattern character
33398026f1521b4c549168ecb0d03f0163070f3534eTarek Ziadé        empty_pattern = glob_to_re('')
334e2f35c3588e6107e0166446c57da1da48399a005Tarek Ziadé        prefix_re = glob_to_re(prefix)[:-len(empty_pattern)]
33531378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo        sep = os.sep
33631378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo        if os.sep == '\\':
33731378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo            sep = r'\\'
33831378df83a5562df10b21f3f32d3b3a6cbfa054bÉric Araujo        pattern_re = "^" + sep.join((prefix_re, ".*" + pattern_re))
339adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward    else:                               # no prefix -- respect anchor flag
340adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward        if anchor:
341adc11720645a82c8115c8686b5bfdbc23cd78bb0Greg Ward            pattern_re = "^" + pattern_re
342b94b849d65af71b4b432a74fdaef8ccd88209cc0Fred Drake
343071ed76732e03f84bee67aef7a316aed82d2e79fGreg Ward    return re.compile(pattern_re)
344