diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eee466f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+.installed.cfg
+bin/
+develop-eggs/
+eggs/
+
+*.py[co]
+__pycache__/
+build/
+dist/
+*.egg-info/
+.tox/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..c0929a4
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+language: python
+python:
+ - 2.6
+ - 2.7
+install:
+ - python bootstrap.py
+ - bin/buildout
+script:
+ - bin/test -v1
+notifications:
+ email: false
diff --git a/CHANGES.txt b/CHANGES.txt
index 6439fb6..7aa4b27 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,14 @@
Changes
=======
+2.5.1 (unreleased)
+------------------
+
+- Add ability to exclude more than one module or package using
+ ```` and allow to use unix shell-style
+ wildcards within. (Backport of this feature from version 2.7)
+
+
2.5 (2012-05-01)
----------------
diff --git a/README.txt b/README.txt
index 38d1a87..772c5f1 100644
--- a/README.txt
+++ b/README.txt
@@ -49,6 +49,10 @@ To sum up, your ``site.zcml`` file should look like something like this::
+There is an optional ``exclude`` on the `grok` directive. It allows to specify
+names of packages or modules that if encountered won't be grokked. These
+names might contain unix shell-style wildcards.
+
Examples
========
diff --git a/bootstrap.py b/bootstrap.py
index a3d5b92..1f59b21 100644
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -18,73 +18,17 @@
use the -c option to specify an alternate configuration file.
"""
-import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess
+import os
+import shutil
+import sys
+import tempfile
+
from optparse import OptionParser
-if sys.platform == 'win32':
- def quote(c):
- if ' ' in c:
- return '"%s"' % c # work around spawn lamosity on windows
- else:
- return c
-else:
- quote = str
-
-# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
-stdout, stderr = subprocess.Popen(
- [sys.executable, '-Sc',
- 'try:\n'
- ' import ConfigParser\n'
- 'except ImportError:\n'
- ' print 1\n'
- 'else:\n'
- ' print 0\n'],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
-has_broken_dash_S = bool(int(stdout.strip()))
-
-# In order to be more robust in the face of system Pythons, we want to
-# run without site-packages loaded. This is somewhat tricky, in
-# particular because Python 2.6's distutils imports site, so starting
-# with the -S flag is not sufficient. However, we'll start with that:
-if not has_broken_dash_S and 'site' in sys.modules:
- # We will restart with python -S.
- args = sys.argv[:]
- args[0:0] = [sys.executable, '-S']
- args = map(quote, args)
- os.execv(sys.executable, args)
-# Now we are running with -S. We'll get the clean sys.path, import site
-# because distutils will do it later, and then reset the path and clean
-# out any namespace packages from site-packages that might have been
-# loaded by .pth files.
-clean_path = sys.path[:]
-import site
-sys.path[:] = clean_path
-for k, v in sys.modules.items():
- if (hasattr(v, '__path__') and
- len(v.__path__)==1 and
- not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))):
- # This is a namespace package. Remove it.
- sys.modules.pop(k)
-
-is_jython = sys.platform.startswith('java')
-
-setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
-distribute_source = 'http://python-distribute.org/distribute_setup.py'
-
-# parsing arguments
-def normalize_to_url(option, opt_str, value, parser):
- if value:
- if '://' not in value: # It doesn't smell like a URL.
- value = 'file://%s' % (
- urllib.pathname2url(
- os.path.abspath(os.path.expanduser(value))),)
- if opt_str == '--download-base' and not value.endswith('/'):
- # Download base needs a trailing slash to make the world happy.
- value += '/'
- else:
- value = None
- name = opt_str[2:].replace('-', '_')
- setattr(parser.values, name, value)
+__version__ = '2015-07-01'
+# See zc.buildout's changelog if this version is up to date.
+
+tmpeggs = tempfile.mkdtemp(prefix='bootstrap-')
usage = '''\
[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
@@ -94,128 +38,134 @@ def normalize_to_url(option, opt_str, value, parser):
Simply run this script in a directory containing a buildout.cfg, using the
Python that you want bin/buildout to use.
-Note that by using --setup-source and --download-base to point to
-local resources, you can keep this script from going over the network.
+Note that by using --find-links to point to local resources, you can keep
+this script from going over the network.
'''
parser = OptionParser(usage=usage)
-parser.add_option("-v", "--version", dest="version",
- help="use a specific zc.buildout version")
-parser.add_option("-d", "--distribute",
- action="store_true", dest="use_distribute", default=False,
- help="Use Distribute rather than Setuptools.")
-parser.add_option("--setup-source", action="callback", dest="setup_source",
- callback=normalize_to_url, nargs=1, type="string",
- help=("Specify a URL or file location for the setup file. "
- "If you use Setuptools, this will default to " +
- setuptools_source + "; if you use Distribute, this "
- "will default to " + distribute_source +"."))
-parser.add_option("--download-base", action="callback", dest="download_base",
- callback=normalize_to_url, nargs=1, type="string",
- help=("Specify a URL or directory for downloading "
- "zc.buildout and either Setuptools or Distribute. "
- "Defaults to PyPI."))
-parser.add_option("--eggs",
- help=("Specify a directory for storing eggs. Defaults to "
- "a temporary directory that is deleted when the "
- "bootstrap script completes."))
+parser.add_option("--version",
+ action="store_true", default=False,
+ help=("Return bootstrap.py version."))
parser.add_option("-t", "--accept-buildout-test-releases",
dest='accept_buildout_test_releases',
action="store_true", default=False,
- help=("Normally, if you do not specify a --version, the "
- "bootstrap script and buildout gets the newest "
+ help=("Normally, if you do not specify a --buildout-version, "
+ "the bootstrap script and buildout gets the newest "
"*final* versions of zc.buildout and its recipes and "
"extensions for you. If you use this flag, "
"bootstrap and buildout will get the newest releases "
"even if they are alphas or betas."))
-parser.add_option("-c", None, action="store", dest="config_file",
- help=("Specify the path to the buildout configuration "
- "file to be used."))
+parser.add_option("-c", "--config-file",
+ help=("Specify the path to the buildout configuration "
+ "file to be used."))
+parser.add_option("-f", "--find-links",
+ help=("Specify a URL to search for buildout releases"))
+parser.add_option("--allow-site-packages",
+ action="store_true", default=False,
+ help=("Let bootstrap.py use existing site packages"))
+parser.add_option("--buildout-version",
+ help="Use a specific zc.buildout version")
+parser.add_option("--setuptools-version",
+ help="Use a specific setuptools version")
+parser.add_option("--setuptools-to-dir",
+ help=("Allow for re-use of existing directory of "
+ "setuptools versions"))
options, args = parser.parse_args()
+if options.version:
+ print("bootstrap.py version %s" % __version__)
+ sys.exit(0)
-# if -c was provided, we push it back into args for buildout's main function
-if options.config_file is not None:
- args += ['-c', options.config_file]
-if options.eggs:
- eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
-else:
- eggs_dir = tempfile.mkdtemp()
-
-if options.setup_source is None:
- if options.use_distribute:
- options.setup_source = distribute_source
- else:
- options.setup_source = setuptools_source
-
-if options.accept_buildout_test_releases:
- args.append('buildout:accept-buildout-test-releases=true')
-args.append('bootstrap')
+######################################################################
+# load/install setuptools
try:
- import pkg_resources
- import setuptools # A flag. Sometimes pkg_resources is installed alone.
- if not hasattr(pkg_resources, '_distribute'):
- raise ImportError
+ from urllib.request import urlopen
except ImportError:
- ez_code = urllib2.urlopen(
- options.setup_source).read().replace('\r\n', '\n')
- ez = {}
- exec ez_code in ez
- setup_args = dict(to_dir=eggs_dir, download_delay=0)
- if options.download_base:
- setup_args['download_base'] = options.download_base
- if options.use_distribute:
- setup_args['no_fake'] = True
- ez['use_setuptools'](**setup_args)
- reload(sys.modules['pkg_resources'])
- import pkg_resources
- # This does not (always?) update the default working set. We will
- # do it.
- for path in sys.path:
- if path not in pkg_resources.working_set.entries:
- pkg_resources.working_set.add_entry(path)
-
-cmd = [quote(sys.executable),
- '-c',
- quote('from setuptools.command.easy_install import main; main()'),
- '-mqNxd',
- quote(eggs_dir)]
-
-if not has_broken_dash_S:
- cmd.insert(1, '-S')
-
-find_links = options.download_base
-if not find_links:
- find_links = os.environ.get('bootstrap-testing-find-links')
-if find_links:
- cmd.extend(['-f', quote(find_links)])
+ from urllib2 import urlopen
-if options.use_distribute:
- setup_requirement = 'distribute'
+ez = {}
+if os.path.exists('ez_setup.py'):
+ exec(open('ez_setup.py').read(), ez)
else:
- setup_requirement = 'setuptools'
+ exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
+
+if not options.allow_site_packages:
+ # ez_setup imports site, which adds site packages
+ # this will remove them from the path to ensure that incompatible versions
+ # of setuptools are not in the path
+ import site
+ # inside a virtualenv, there is no 'getsitepackages'.
+ # We can't remove these reliably
+ if hasattr(site, 'getsitepackages'):
+ for sitepackage_path in site.getsitepackages():
+ # Strip all site-packages directories from sys.path that
+ # are not sys.prefix; this is because on Windows
+ # sys.prefix is a site-package directory.
+ if sitepackage_path != sys.prefix:
+ sys.path[:] = [x for x in sys.path
+ if sitepackage_path not in x]
+
+setup_args = dict(to_dir=tmpeggs, download_delay=0)
+
+if options.setuptools_version is not None:
+ setup_args['version'] = options.setuptools_version
+if options.setuptools_to_dir is not None:
+ setup_args['to_dir'] = options.setuptools_to_dir
+
+ez['use_setuptools'](**setup_args)
+import setuptools
+import pkg_resources
+
+# This does not (always?) update the default working set. We will
+# do it.
+for path in sys.path:
+ if path not in pkg_resources.working_set.entries:
+ pkg_resources.working_set.add_entry(path)
+
+######################################################################
+# Install buildout
+
ws = pkg_resources.working_set
-setup_requirement_path = ws.find(
- pkg_resources.Requirement.parse(setup_requirement)).location
-env = dict(
- os.environ,
- PYTHONPATH=setup_requirement_path)
+
+setuptools_path = ws.find(
+ pkg_resources.Requirement.parse('setuptools')).location
+
+# Fix sys.path here as easy_install.pth added before PYTHONPATH
+cmd = [sys.executable, '-c',
+ 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path +
+ 'from setuptools.command.easy_install import main; main()',
+ '-mZqNxd', tmpeggs]
+
+find_links = os.environ.get(
+ 'bootstrap-testing-find-links',
+ options.find_links or
+ ('http://downloads.buildout.org/'
+ if options.accept_buildout_test_releases else None)
+ )
+if find_links:
+ cmd.extend(['-f', find_links])
requirement = 'zc.buildout'
-version = options.version
+version = options.buildout_version
if version is None and not options.accept_buildout_test_releases:
# Figure out the most recent final version of zc.buildout.
import setuptools.package_index
_final_parts = '*final-', '*final'
+
def _final_version(parsed_version):
- for part in parsed_version:
- if (part[:1] == '*') and (part not in _final_parts):
- return False
- return True
+ try:
+ return not parsed_version.is_prerelease
+ except AttributeError:
+ # Older setuptools
+ for part in parsed_version:
+ if (part[:1] == '*') and (part not in _final_parts):
+ return False
+ return True
+
index = setuptools.package_index.PackageIndex(
- search_path=[setup_requirement_path])
+ search_path=[setuptools_path])
if find_links:
index.add_find_links((find_links,))
req = pkg_resources.Requirement.parse(requirement)
@@ -237,22 +187,24 @@ def _final_version(parsed_version):
requirement = '=='.join((requirement, version))
cmd.append(requirement)
-if is_jython:
- import subprocess
- exitcode = subprocess.Popen(cmd, env=env).wait()
-else: # Windows prefers this, apparently; otherwise we would prefer subprocess
- exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
-if exitcode != 0:
- sys.stdout.flush()
- sys.stderr.flush()
- print ("An error occurred when trying to install zc.buildout. "
- "Look above this message for any errors that "
- "were output by easy_install.")
- sys.exit(exitcode)
-
-ws.add_entry(eggs_dir)
+import subprocess
+if subprocess.call(cmd) != 0:
+ raise Exception(
+ "Failed to execute command:\n%s" % repr(cmd)[1:-1])
+
+######################################################################
+# Import and run buildout
+
+ws.add_entry(tmpeggs)
ws.require(requirement)
import zc.buildout.buildout
+
+if not [a for a in args if '=' not in a]:
+ args.append('bootstrap')
+
+# if -c was provided, we push it back into args for buildout' main function
+if options.config_file is not None:
+ args[0:0] = ['-c', options.config_file]
+
zc.buildout.buildout.main(args)
-if not options.eggs: # clean up temporary egg directory
- shutil.rmtree(eggs_dir)
+shutil.rmtree(tmpeggs)
diff --git a/buildout.cfg b/buildout.cfg
index b264c74..b7c5fe3 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -1,9 +1,8 @@
[buildout]
develop = .
parts = interpreter test
-extends = http://svn.zope.org/repos/main/groktoolkit/trunk/grok.cfg
+extends = https://raw.githubusercontent.com/zopefoundation/groktoolkit/master/grok.cfg
versions = versions
-extensions = buildout.dumppickedversions
[versions]
grokcore.component =
diff --git a/setup.py b/setup.py
index 469b25e..c8b1cbb 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@ def read(*rnames):
setup(
name='grokcore.component',
- version='2.5',
+ version='2.5.1.dev0',
author='Grok Team',
author_email='grok-dev@zope.org',
url='http://grok.zope.org',
diff --git a/src/grokcore/component/tests/test_grok.py b/src/grokcore/component/tests/test_grok.py
index 4d7d681..9942a52 100644
--- a/src/grokcore/component/tests/test_grok.py
+++ b/src/grokcore/component/tests/test_grok.py
@@ -48,7 +48,7 @@ def suiteFromPackage(name):
def test_suite():
suite = unittest.TestSuite()
for name in ['adapter', 'directive', 'grokker', 'utility', 'view',
- 'event', 'inherit', 'order', 'subscriptions']:
+ 'event', 'inherit', 'order', 'subscriptions', 'zcml']:
suite.addTest(suiteFromPackage(name))
api = doctest.DocFileSuite('api.txt')
diff --git a/src/grokcore/component/tests/zcml/__init__.py b/src/grokcore/component/tests/zcml/__init__.py
new file mode 100644
index 0000000..a003759
--- /dev/null
+++ b/src/grokcore/component/tests/zcml/__init__.py
@@ -0,0 +1 @@
+# this is a package
diff --git a/src/grokcore/component/tests/zcml/exclude.py b/src/grokcore/component/tests/zcml/exclude.py
new file mode 100644
index 0000000..8e5effd
--- /dev/null
+++ b/src/grokcore/component/tests/zcml/exclude.py
@@ -0,0 +1,34 @@
+"""
+It allows to exclude a single package or module from beeing grokked.
+
+There is a NameError in `.excludepkg.sample` which is raised when this
+module is not excluded:
+
+>>> xmlconfig.string('''
+...
+...
+...
+... ''', context)
+Traceback (most recent call last):
+ZopeXMLConfigurationError: File "", line 4.6-4.31
+ NameError: name 'asdf' is not defined
+
+Excluding `.excludepkg.sample` via ZCML allows to successfully grok the
+module:
+
+>>> xmlconfig.string('''
+...
+...
+...
+... ''', context)
+
+
+"""
+
+from zope.configuration import config, xmlconfig
+from grokcore.component.tests.zcml import excludepkg
+
+context = config.ConfigurationMachine()
+xmlconfig.registerCommonDirectives(context)
+context.package = excludepkg
diff --git a/src/grokcore/component/tests/zcml/excludemany.py b/src/grokcore/component/tests/zcml/excludemany.py
new file mode 100644
index 0000000..209808f
--- /dev/null
+++ b/src/grokcore/component/tests/zcml/excludemany.py
@@ -0,0 +1,50 @@
+"""
+It allows to exclude a many packages or modules from beeing grokked.
+
+These packages or modules can be specified using unix shell-style wildcards
+
+There is a NameError in `.excludemanypkg.file_1` which is raised when this
+module is not excluded:
+
+>>> xmlconfig.string('''
+...
+...
+...
+... ''', context)
+Traceback (most recent call last):
+ZopeXMLConfigurationError: File "", line 4.6-4.31
+ NameError: name 'asdf' is not defined
+
+There is a NameError in `.excludemanypkg.test_asdf`, too which is raised when
+this module is not excluded:
+
+>>> xmlconfig.string('''
+...
+...
+...
+... ''', context)
+Traceback (most recent call last):
+ZopeXMLConfigurationError: File "", line 4.6-5.36
+ NameError: name 'qwe' is not defined
+
+
+Excluding both 'file_1` and `test_asdf`allows to successfully grok the module:
+
+>>> xmlconfig.string('''
+...
+...
+...
+... ''', context)
+
+
+"""
+
+from zope.configuration import config, xmlconfig
+from grokcore.component.tests.zcml import excludemanypkg
+
+context = config.ConfigurationMachine()
+xmlconfig.registerCommonDirectives(context)
+context.package = excludemanypkg
diff --git a/src/grokcore/component/tests/zcml/excludemanypkg/__init__.py b/src/grokcore/component/tests/zcml/excludemanypkg/__init__.py
new file mode 100644
index 0000000..a003759
--- /dev/null
+++ b/src/grokcore/component/tests/zcml/excludemanypkg/__init__.py
@@ -0,0 +1 @@
+# this is a package
diff --git a/src/grokcore/component/tests/zcml/excludemanypkg/file_1.py b/src/grokcore/component/tests/zcml/excludemanypkg/file_1.py
new file mode 100644
index 0000000..0becf80
--- /dev/null
+++ b/src/grokcore/component/tests/zcml/excludemanypkg/file_1.py
@@ -0,0 +1 @@
+asdf # This leads to a NameError if exclude does not work correctly.
diff --git a/src/grokcore/component/tests/zcml/excludemanypkg/test_asdf.py b/src/grokcore/component/tests/zcml/excludemanypkg/test_asdf.py
new file mode 100644
index 0000000..3e15663
--- /dev/null
+++ b/src/grokcore/component/tests/zcml/excludemanypkg/test_asdf.py
@@ -0,0 +1 @@
+qwe # This leads to a NameError if exclude does not work correctly.
diff --git a/src/grokcore/component/tests/zcml/excludepkg/__init__.py b/src/grokcore/component/tests/zcml/excludepkg/__init__.py
new file mode 100644
index 0000000..a003759
--- /dev/null
+++ b/src/grokcore/component/tests/zcml/excludepkg/__init__.py
@@ -0,0 +1 @@
+# this is a package
diff --git a/src/grokcore/component/tests/zcml/excludepkg/sample.py b/src/grokcore/component/tests/zcml/excludepkg/sample.py
new file mode 100644
index 0000000..0becf80
--- /dev/null
+++ b/src/grokcore/component/tests/zcml/excludepkg/sample.py
@@ -0,0 +1 @@
+asdf # This leads to a NameError if exclude does not work correctly.
diff --git a/src/grokcore/component/zcml.py b/src/grokcore/component/zcml.py
index c8c6fb7..9f7d1d2 100644
--- a/src/grokcore/component/zcml.py
+++ b/src/grokcore/component/zcml.py
@@ -14,10 +14,11 @@
"""Grok ZCML directives."""
from zope.interface import Interface
-from zope.configuration.fields import GlobalObject
+from zope.configuration.fields import GlobalObject, Tokens
from zope.schema import TextLine
import martian
+import fnmatch
class IGrokDirective(Interface):
@@ -28,10 +29,12 @@ class IGrokDirective(Interface):
description=u"The package or module to be analyzed by grok.",
required=False)
- exclude = TextLine(
+ exclude = Tokens(
title=u"Exclude",
- description=u"Name to exclude in the grokking process.",
- required=False)
+ description=u"Names (which might contain unix shell-style wildcards) "
+ u"to be excluded in the grokking process.",
+ required=False,
+ value_type=TextLine())
# add a cleanup hook so that grok will bootstrap itself again whenever
@@ -61,8 +64,12 @@ def do_grok(dotted_name, config, extra_exclude=None):
if extra_exclude is not None:
def exclude_filter(name):
- return skip_tests(name) or extra_exclude == name
-
+ if skip_tests(name):
+ return True
+ for exclude in extra_exclude:
+ if fnmatch.fnmatch(name, exclude):
+ return True
+ return False
else:
exclude_filter = skip_tests