From a9c8d39ed653a54ed99086aeef0fa716787dc73b Mon Sep 17 00:00:00 2001 From: Alexandre Feblot Date: Sat, 25 Apr 2015 17:43:10 +0200 Subject: Add an exclude parameter to Glob(), to allow excluding some elements matching the main pattern --- doc/user/less-simple.xml | 1 + src/engine/SCons/Environment.py | 4 +- src/engine/SCons/Environment.xml | 13 ++++++- src/engine/SCons/Node/FS.py | 36 ++++++++++-------- test/Glob/exclude.py | 82 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 test/Glob/exclude.py diff --git a/doc/user/less-simple.xml b/doc/user/less-simple.xml index cca6033..9f27738 100644 --- a/doc/user/less-simple.xml +++ b/doc/user/less-simple.xml @@ -257,6 +257,7 @@ Program('program', Glob('*.c')) (see , below) and repositories (see , below), + excluding some files and returning strings rather than Nodes. diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 5f2c9ff..6886e85 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -2080,8 +2080,8 @@ class Base(SubstitutionEnvironment): else: return result[0] - def Glob(self, pattern, ondisk=True, source=False, strings=False): - return self.fs.Glob(self.subst(pattern), ondisk, source, strings) + def Glob(self, pattern, ondisk=True, source=False, strings=False, exclude=None): + return self.fs.Glob(self.subst(pattern), ondisk, source, strings, exclude) def Ignore(self, target, dependency): """Ignore a dependency.""" diff --git a/src/engine/SCons/Environment.xml b/src/engine/SCons/Environment.xml index b3b132e..6de2669 100644 --- a/src/engine/SCons/Environment.xml +++ b/src/engine/SCons/Environment.xml @@ -1697,7 +1697,7 @@ Nodes or strings representing path names. -(pattern, [ondisk, source, strings]) +(pattern, [ondisk, source, strings, exclude]) @@ -1811,12 +1811,23 @@ directory.) +The +exclude +argument may be set to a list of patterns +(following the same Unix shell semantics) +which must be filtered out of returned elements. +Elements matching a least one pattern of +this list will be excluded. + + + Examples: Program('foo', Glob('*.c')) Zip('/tmp/everything', Glob('.??*') + Glob('*')) +sources = Glob('*.cpp', exlude=['os_*_specific_*.cpp']) + Glob('os_%s_specific_*.cpp'%currentOS) diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 4db1cb3..0156e27 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -1401,7 +1401,7 @@ class FS(LocalFS): message = fmt % ' '.join(map(str, targets)) return targets, message - def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None): + def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None): """ Globs @@ -1409,7 +1409,7 @@ class FS(LocalFS): """ if cwd is None: cwd = self.getcwd() - return cwd.glob(pathname, ondisk, source, strings) + return cwd.glob(pathname, ondisk, source, strings, exclude) class DirNodeInfo(SCons.Node.NodeInfoBase): # This should get reset by the FS initialization. @@ -2020,7 +2020,7 @@ class Dir(Base): for dirname in [n for n in names if isinstance(entries[n], Dir)]: entries[dirname].walk(func, arg) - def glob(self, pathname, ondisk=True, source=False, strings=False): + def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None): """ Returns a list of Nodes (or strings) matching a specified pathname pattern. @@ -2048,24 +2048,30 @@ class Dir(Base): The "strings" argument, when true, returns the matches as strings, not Nodes. The strings are path names relative to this directory. + The "exclude" argument, if not None, must be a list of patterns + following the same UNIX shell semantics. Elements matching a least + one pattern of this list will be excluded from the result. + The underlying algorithm is adapted from the glob.glob() function in the Python library (but heavily modified), and uses fnmatch() under the covers. """ dirname, basename = os.path.split(pathname) if not dirname: - return sorted(self._glob1(basename, ondisk, source, strings), - key=lambda t: str(t)) - if has_glob_magic(dirname): - list = self.glob(dirname, ondisk, source, strings=False) + result = self._glob1(basename, ondisk, source, strings) else: - list = [self.Dir(dirname, create=True)] - result = [] - for dir in list: - r = dir._glob1(basename, ondisk, source, strings) - if strings: - r = [os.path.join(str(dir), x) for x in r] - result.extend(r) + if has_glob_magic(dirname): + list = self.glob(dirname, ondisk, source, False, exclude) + else: + list = [self.Dir(dirname, create=True)] + result = [] + for dir in list: + r = dir._glob1(basename, ondisk, source, strings) + if strings: + r = [os.path.join(str(dir), x) for x in r] + result.extend(r) + if exclude: + result = filter(lambda x: not any(fnmatch.fnmatch(str(x), e) for e in exclude), result) return sorted(result, key=lambda a: str(a)) def _glob1(self, pattern, ondisk=True, source=False, strings=False): @@ -2128,14 +2134,12 @@ class Dir(Base): names = set(names) if pattern[0] != '.': - #names = [ n for n in names if n[0] != '.' ] names = [x for x in names if x[0] != '.'] names = fnmatch.filter(names, pattern) if strings: return names - #return [ self.entries[_my_normcase(n)] for n in names ] return [self.entries[_my_normcase(n)] for n in names] class RootDir(Dir): diff --git a/test/Glob/exclude.py b/test/Glob/exclude.py new file mode 100644 index 0000000..7ef06df --- /dev/null +++ b/test/Glob/exclude.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Verify the "exclude" parameter usage of the Glob() function. + - with or without subdir + - with or without returning strings + - a file in a subdir is not excluded by a pattern without this subdir +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """\ +env = Environment() + +def concatenate(target, source, env): + fp = open(str(target[0]), 'wb') + for s in source: + fp.write(open(str(s), 'rb').read()) + fp.close() + +env['BUILDERS']['Concatenate'] = Builder(action=concatenate) + +env.Concatenate('fa.out', sorted(Glob('f*.in' , exclude=['f2.in', 'f4.*'] , strings=False), key=lambda t: t.path)) +env.Concatenate('fb.out', sorted(Glob('f*.in' , exclude=['f2.in', 'f4.*'] , strings=True))) +env.Concatenate('fc.out', sorted(Glob('d?/f*.in', exclude=['d?/f1.*', 'f2.in'], strings=False), key=lambda t: t.path)) +env.Concatenate('fd.out', sorted(Glob('d?/f*.in', exclude=['d?/f1.*', 'f2.in'], strings=True))) +""") + +test.write('f1.in', "f1.in\n") +test.write('f2.in', "f2.in\n") +test.write('f3.in', "f3.in\n") +test.write('f4.in', "f4.in\n") +test.write('f5.in', "f5.in\n") + +test.subdir('d1', 'd2') +test.write(['d1', 'f1.in'], "d1/f1.in\n") +test.write(['d1', 'f2.in'], "d1/f2.in\n") +test.write(['d1', 'f3.in'], "d1/f3.in\n") +test.write(['d2', 'f1.in'], "d2/f1.in\n") +test.write(['d2', 'f2.in'], "d2/f2.in\n") +test.write(['d2', 'f3.in'], "d2/f3.in\n") + +test.run(arguments = '.') + +test.must_match('fa.out', "f1.in\nf3.in\nf5.in\n") +test.must_match('fb.out', "f1.in\nf3.in\nf5.in\n") +test.must_match('fc.out', "d1/f2.in\nd1/f3.in\nd2/f2.in\nd2/f3.in\n") +test.must_match('fd.out', "d1/f2.in\nd1/f3.in\nd2/f2.in\nd2/f3.in\n") + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: -- cgit v0.12