[Zope-CVS] SVN: zpkgtools/trunk/ Use ZConfig instead of a private configuration parser. ZConfig is now

Fred L. Drake, Jr. fdrake at gmail.com
Wed Aug 31 13:05:01 EDT 2005


Log message for revision 38200:
  Use ZConfig instead of a private configuration parser.  ZConfig is now
  used for the PACKAGE.cfg, SETUP.cfg, and zpkg.conf file formats.
  
  - Added ZConfig to a zpkg checkout.
  
  - Added ZConfig schemas for three different file formats.
  
  - Replace zpkgsetup.cfgparser with a smaller wrapper around ZConfig
    that provides case-sensitivity in all the needed places.
  
  - Re-build the handlers for three different file formats that zpkg
    uses so that ZConfig can be used instead of the bastardized
    configuration parser previously used.
  
  While the implementations of these configuration files are fairly
  unusual as ZConfig files, this still provides a substantial
  improvement in extensibility and readability.  There's also less
  code in zpkg itself.
  
  Merged from zpkgtools/branches/fdrake-zconfig-branch.
  

Changed:
  _U  zpkgtools/trunk/
  U   zpkgtools/trunk/DEPENDENCIES.cfg
  U   zpkgtools/trunk/PACKAGE.cfg
  U   zpkgtools/trunk/README.txt
  U   zpkgtools/trunk/doc/TODO.txt
  U   zpkgtools/trunk/doc/links.rst
  U   zpkgtools/trunk/doc/metadata.txt
  U   zpkgtools/trunk/doc/zpkg.txt
  U   zpkgtools/trunk/zpkg.conf
  U   zpkgtools/trunk/zpkgsetup/cfgparser.py
  U   zpkgtools/trunk/zpkgsetup/package.py
  A   zpkgtools/trunk/zpkgsetup/package.xml
  U   zpkgtools/trunk/zpkgsetup/tests/input/package2/SETUP.cfg
  D   zpkgtools/trunk/zpkgsetup/tests/test_cfgparser.py
  U   zpkgtools/trunk/zpkgsetup/tests/test_setup.py
  U   zpkgtools/trunk/zpkgsetup/urlutils.py
  U   zpkgtools/trunk/zpkgtools/app.py
  U   zpkgtools/trunk/zpkgtools/config.py
  A   zpkgtools/trunk/zpkgtools/config.xml
  U   zpkgtools/trunk/zpkgtools/include.py
  A   zpkgtools/trunk/zpkgtools/include.xml
  U   zpkgtools/trunk/zpkgtools/locationmap.py
  U   zpkgtools/trunk/zpkgtools/tests/test_include.py

-=-

Property changes on: zpkgtools/trunk
___________________________________________________________________
Name: svn:externals
   + ZConfig  svn://svn.zope.org/repos/main/ZConfig/tags/ZConfig-2.3.1


Modified: zpkgtools/trunk/DEPENDENCIES.cfg
===================================================================
--- zpkgtools/trunk/DEPENDENCIES.cfg	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/DEPENDENCIES.cfg	2005-08-31 17:05:00 UTC (rev 38200)
@@ -1,3 +1,5 @@
 # The "zpkg" package depends on these Python packages:
+# ZConfig 2.3.1 or newer
+ZConfig
 zpkgsetup
 zpkgtools

Modified: zpkgtools/trunk/PACKAGE.cfg
===================================================================
--- zpkgtools/trunk/PACKAGE.cfg	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/PACKAGE.cfg	2005-08-31 17:05:00 UTC (rev 38200)
@@ -1,4 +1,5 @@
 <collection>
+  ZConfig    -
   zpkgsetup  -
   zpkgtools  -
 </collection>

Modified: zpkgtools/trunk/README.txt
===================================================================
--- zpkgtools/trunk/README.txt	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/README.txt	2005-08-31 17:05:00 UTC (rev 38200)
@@ -38,9 +38,9 @@
   $ python2.3 tests.py
   ......................................................................
   ......................................................................
-  ........................................
+  ............................................
   ----------------------------------------------------------------------
-  Ran 180 tests in 0.798s
+  Ran 184 tests in 0.798s
 
   OK
 

Modified: zpkgtools/trunk/doc/TODO.txt
===================================================================
--- zpkgtools/trunk/doc/TODO.txt	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/doc/TODO.txt	2005-08-31 17:05:00 UTC (rev 38200)
@@ -11,15 +11,10 @@
 
 - Package assembler script and distribution runtime
 
-  - It should be possible to simply embed a resoure map within a
-    configuration file, so a project can avoid needing two files to
-    configure **zpkg**.  This might look something like this::
+  - It should be possible to specify a separate resource map for
+    support code.  This would allow the packaging and the distributed
+    code to remain completely independent.
 
-      <resources>
-        First  svn://svn.example.org/repos/main/First/tags/*/
-        Second svn://svn.example.org/repos/main/Second/tags/*/
-      </resources>
-
   - We should have a way to express dependence on particular versions
     of Python, and a way to say which is preferred.  (This can be used
     when building an "application" distribution since that has some
@@ -97,16 +92,4 @@
 - Fix up finddeps.py to stop searching at defined boundaries.  How to
   define these boundaries is in flux at the moment.
 
-- Write a new setup.py for Zope 3 and ZODB to use in a checkout that
-  uses the package metadata; this can happen after the ZopeX3 release.
-
-  A preliminary setup.py replacement for the ZODB project is much
-  shorter than the conventional setup.py (28 lines, not including the
-  license blurb).
-
-  Depending on <load> sections or renamings in <collection> sections
-  makes this much more difficult since the sources needed to
-  build/install a package may not be available since the distribution
-  construction has not been performed.
-
 - Documentation, documentation, documentation!

Modified: zpkgtools/trunk/doc/links.rst
===================================================================
--- zpkgtools/trunk/doc/links.rst	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/doc/links.rst	2005-08-31 17:05:00 UTC (rev 38200)
@@ -6,3 +6,5 @@
 
 .. _zpkg: zpkg.html
 .. |zpkg| replace:: **zpkg**
+
+.. _ZConfig: http://www.zope.org/Members/fdrake/zconfig/

Modified: zpkgtools/trunk/doc/metadata.txt
===================================================================
--- zpkgtools/trunk/doc/metadata.txt	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/doc/metadata.txt	2005-08-31 17:05:00 UTC (rev 38200)
@@ -162,19 +162,14 @@
     scripts
   </collection>
 
-What isn't obvious is that one of the files in the ``ZConfig``
-package under revision control isn't included in either the
-distribution root or the component-specific directory (the
-`BRANCHES.txt` file in particular).
 
-
 Embedded Package Definitions
 ----------------------------
 
 Distribution components can include a `SETUP.cfg` that contains
 information about special files in the component (such as
 documentation and scripts), and what extensions need to be built.
-This file is a ZConfig-like configuration file that can contain the
+This file uses the `ZConfig`_ syntax, and contains the
 following settings, repeated as necessary:
 
 **documentation**

Modified: zpkgtools/trunk/doc/zpkg.txt
===================================================================
--- zpkgtools/trunk/doc/zpkg.txt	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/doc/zpkg.txt	2005-08-31 17:05:00 UTC (rev 38200)
@@ -108,7 +108,7 @@
 Configuration File Format
 -------------------------
 
-The configuration files used by **zpkg** use a subset of the
+The configuration files used by **zpkg** use the
 `ZConfig`_ configuration language.
 
 Blank lines and comments (lines that start with ``#`` as the first
@@ -136,10 +136,10 @@
 bundles the support code along with the resulting distribution.  The
 value is a boolean, where the strings ``true`` and ``false`` can be
 used in the configuration file.  If true (the default), copies of the
-``zpkgsetup`` package and any additionally configured support packages
-will be included in the distribution (less the test code).  If false,
-these packages will be assumed to be available for import on target
-systems.
+``zpkgsetup`` and ``ZConfig`` packages and any additionally configured
+support packages will be included in the distribution (less the test
+code).  If false, the right versions of these packages will be assumed
+to be available for import on target systems.
 
 The value for ``resource-map`` is a path or URL (including
 ``cvs:`` URLs) for a file that defines the map.  Relative paths are
@@ -172,9 +172,7 @@
 Note that all ``<resources>`` sections will be processed before any
 external maps are loaded, regardless of ordering.
 
-.. _ZConfig: http://www.zope.org/Members/fdrake/zconfig/
 
-
 Resource Maps
 -------------
 
@@ -253,13 +251,14 @@
 ----------------
 
 **zpkg** creates distributions which require some additional support
-packages to operate properly.  These packages are located in much the
-same way that other packages are located: the resource map is
-consulted to determine their location.  One difference is that if the
-resource map does not define the appropriate resources, a built-in
-fallback location will be used.
+packages to operate properly.  These packages are located in a similar
+way to packages that go into the distribution.  An internal map is
+used first, followed by the resource map based on the configuration
+files.
 
-The fallback location for the ``zpkgsetup`` package is the copy
-provided as part of the running **zpkg**; a location only needs to be
-specified if a different version should be used.  (This seems highly
-unlikely.)
+The sources for the ``zpkgsetup`` and ``ZConfig`` packages are the
+copies provided as part of the running **zpkg**; a location only needs
+to be specified for additional packages that should appear in the
+support directory.
+
+.. include:: links.rst

Modified: zpkgtools/trunk/zpkg.conf
===================================================================
--- zpkgtools/trunk/zpkg.conf	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkg.conf	2005-08-31 17:05:00 UTC (rev 38200)
@@ -1,7 +1,9 @@
 build-application     no
 collect-dependencies  yes
+default-collection    zpkg
 
 <resources>
+  ZConfig    ZConfig
   zpkg       .
   zpkgsetup  zpkgsetup
   zpkgtools  zpkgtools

Modified: zpkgtools/trunk/zpkgsetup/cfgparser.py
===================================================================
--- zpkgtools/trunk/zpkgsetup/cfgparser.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgsetup/cfgparser.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -11,350 +11,138 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""Extra-lite parser for a `ZConfig`_-like configuration syntax.
+"""Configuration parsing based on ZConfig instead of the bastard parser.
 
-There is no support for external schemas; schemas are simpler and must
-be specified using Python data structures.
-
-There is no support for any %-directives, but dollar signs in values
-must be doubled to ensure compatibility with `ZConfig`_.
-
-.. _ZConfig:  http://www.zope.org/Members/fdrake/zconfig/
-
 """
+__docformat__ = "reStructuredText"
 
-import re
+import os
+import sys
 
+import ZConfig.cfgparser
+import ZConfig.cmdline
+import ZConfig.datatypes
+import ZConfig.loader
 
-class ConfigurationError(Exception):
-    """Exception raised for errors in a configuration file.
+from ZConfig import ConfigurationError
 
-    :ivar url: URL of the resource being read when the error was
-      detected.
 
-    :ivar lineno: Line number within the resource at which the
-      error was detected.
+# This is new:
 
-    """
+def cachedSchemaLoader(filename="schema.xml", package=None,
+                       loader_factory=None):
+    if package is None:
+        frame = sys._getframe(1)
+        __path__ = _get_path_from_frame(frame)
+    elif package == "":
+        __path__ = sys.path
+    else:
+        __import__(package)
+        __path__ = sys.modules[package].__path__
 
-    def __init__(self, message, url=None, lineno=None):
-        """Initialize the ConfigurationError instance.
+    if loader_factory is None:
+        loader_factory = SchemaLoader
+    cache = []
+    def loadSchemaCache():
+        if cache:
+            return cache[0]
+        for p in __path__:
+            path = os.path.join(p, filename)
+            if os.path.isfile(path):
+                schema = loader_factory().loadURL(path)
+                cache.append(schema)
+                return schema
+        raise ValueError("could not locate schema %r for package %r (path=%r)"
+                         % (filename, package, __path__))
 
-        :param message: Text of the error message.
+    return loadSchemaCache
 
-        :param url: URL of the resource being read when the error was
-          detected.
+def _get_path_from_frame(frame):
+    globs = frame.f_globals
+    if "__path__" in globs:
+        return globs["__path__"]
+    path = globs.get("__file__")
+    module = globs.get("__name__")
+    if (path and module):
+        dir, fn = os.path.split(path)
+        fnbase, ext = os.path.splitext(fn)
+        if "." in module and fnbase == "__init__":
+            package = module[:module.rindex(".")]
+            return sys.modules[package].__path__
+    if "." in module:
+        # the module is likely still being imported for the first
+        # time; just drop the module name and check the package
+        package = module[:module.rindex(".")]
+        return sys.modules[package].__path__
+    return sys.path
 
-        :param lineno: Line number within the resource at which the
-          error was detected.
 
-        """
-        Exception.__init__(self, message)
-        self.url = url
-        self.lineno = lineno
 
-    def __str__(self):
-        s = Exception.__str__(self)
-        if self.url:
-            s = "%s\n(%s" % (s, self.url)
-            if self.lineno is not None:
-                s = "%s, line %s" % (s, self.lineno)
-            s += ")"
-        return s
 
+def loadConfig(schema, url, overrides=()):
+    return _get_config_loader(schema, overrides).loadURL(url)
 
-class Schema:
-    """Schema definition that can be used by the Parser class to
-    construct a configuration.
+def loadConfigFile(schema, file, url=None, overrides=()):
+    return _get_config_loader(schema, overrides).loadFile(file, url)
 
-    The definition is defined as a set of *type definitions*.  Each
-    type definition is a triple containing a dictionary, a list, and a
-    function (or `None`).  The dictionary maps the names of the keys
-    allowed in the section to conversion functions for the values (or
-    None if no conversion is required).  The list names the section
-    types which can occur in the section.  The function is used to
-    convert a `SectionValue` representing the collected values to the
-    actual value of the section itself; if `None` is used, no
-    conversion is performed.
 
-    """
+def _get_config_loader(schema, overrides):
+    if overrides:
+        loader = ExtendedConfigLoader(schema)
+        for opt in overrides:
+            loader.addOption(opt)
+    else:
+        loader = ConfigLoader(schema)
+    return loader
 
-    def __init__(self, toplevel, typedefs=None):
-        """Initialize a schema definition based on type definitions.
 
-        :param toplevel: Type definition that represents the otherwise
-          anonymous top-level section of a configuration.
+# These classes override enough to get case-sensitive behavior by default; 
 
-        :param typedefs: Mapping from typenames (which must be given
-          as lower-case strings) to type definitions.  Only section
-          types specified in `typedefs` can be used anywhere in
-          configurations described by the schema.
+class Parser(ZConfig.cfgparser.ZConfigParser):
+    """ZConfig-parser that doesn't lower-case section types and names."""
 
-        """
-        self._toplevel = toplevel
-        if typedefs is None:
-            typedefs = {}
-        self._typedefs = typedefs
+    def _normalize_case(self, string):
+        return string
 
-    def getConfiguration(self):
-        """Return a configuration object for the top-level section.
 
-        :return: New configuration object.
+class BasicKeyConversion(ZConfig.datatypes.BasicKeyConversion):
+    """Alternate basic-key type that does no case-normalizing."""
 
-        The attributes of the configuration object represent values
-        that have not been specified in a configuration file; these
-        will be filled in during parsing.
-        """
-        return self.createSection(None, None, self._toplevel)
+    def __call__(self, value):
+        value = str(value)
+        return ZConfig.datatypes.RegularExpressionConversion.__call__(
+            self, value)
 
-    def startSection(self, parent, typename, name):
-        # make sure typename is defined:
-        typedef = self._typedefs.get(typename)
-        if typedef is None:
-            raise ConfigurationError("unknown section type: %s" % typename)
-        # make sure typename is allowed:
-        x, sects, x = parent.getSectionDefinition()
-        if typename not in sects:
-            parent_type = parent.getSectionType()
-            if parent_type:
-                msg = ("%r sections not allowed in %r sections"
-                       % (typename, parent_type))
-            else:
-                msg = "%r sections not allowed" % typename
-            raise ConfigurationError(msg)
-        return self.createSection(name, typename, typedef)
 
-    def createSection(self, name, typename, typedef):
-        child = SectionValue(name, typename, typedef)
-        keys, sects, x = typedef
-        # initialize the defaults:
-        for name in keys:
-            name = name.lower().replace("-", "_")
-            setattr(child, name, [])
-        for name in sects:
-            name = name.lower().replace("-", "_")
-            setattr(child, name, [])
-        return child
+def SchemaLoader(registry=None):
+    if registry is None:
+        registry = ZConfig.datatypes.Registry()
+        registry._stock["basic-key"] = BasicKeyConversion()
+    return ZConfig.loader.SchemaLoader(registry)
 
-    def finishSection(self, section):
-        x, x, datatype = section.getSectionDefinition()
-        if datatype is not None:
-            typename = section.getSectionType()
-            try:
-                section = datatype(section)
-            except ValueError, e:
-                raise ConfigurationError(
-                    "could not convert %r section value: %s"
-                    % (typename, e))
-        return section
 
-    def endSection(self, parent, typename, name, child):
-        value = self.finishSection(child)
-        getattr(parent, typename).append(value)
+class ConfigLoaderMixin:
 
-    def addValue(self, section, key, value):
-        keys, x, x = section.getSectionDefinition()
-        keyname = key.lower()
-        if keyname not in keys:
-            typename = section.getSectionType()
-            if typename:
-                msg = "key %r not defined in %r sections" % (key, typename)
-            else:
-                msg = "key %r not defined" % key
-            raise ConfigurationError(msg)
-        datatype = keys[keyname]
-        if datatype is not None:
-            try:
-                value = datatype(value)
-            except ValueError, e:
-                raise ConfigurationError("could not convert value: %s" % e)
-        attrname = keyname.replace("-", "_")
-        getattr(section, attrname).append(value)
+    def _parse_resource(self, matcher, resource, defines=None):
+        parser = Parser(resource, self, defines)
+        parser.parse(matcher)
 
 
-# These regular expressions should match the corresponding definitions
-# in ZConfig.cfgparser since this needs to be a format that could be
-# read by ZConfig with an appropriate schema definition.
-#
-_name_re = r"[^\s()]+"
-_keyvalue_rx = re.compile(r"(?P<key>%s)\s*(?P<value>[^\s].*)?$"
-                          % _name_re)
-_section_start_rx = re.compile(r"(?P<type>%s)"
-                               r"(?:\s+(?P<name>%s))?"
-                               r"$"
-                               % (_name_re, _name_re))
+class ConfigLoader(ConfigLoaderMixin, ZConfig.loader.ConfigLoader):
+    pass
 
-_nulljoin = "".join
 
+class ExtendedConfigLoader(ConfigLoaderMixin,
+                           ZConfig.cmdline.ExtendedConfigLoader):
 
-class Parser:
-    """Parser for ZConfig-like configuration files."""
-
-    def __init__(self, file, url, schema):
-        self.schema = schema
-        self.file = file
-        self.url = url
-        self.lineno = 0
-        self.stack = []   # [(type, name, prevmatcher), ...]
-
-    def nextline(self):
-        line = self.file.readline()
-        if line:
-            self.lineno += 1
-            return False, line.strip()
+    def cook(self):
+        if self.clopts:
+            return OptionBag(self.schema, self.schema, self.clopts)
         else:
-            return True, None
+            return None
 
-    def load(self):
-        section = self.schema.getConfiguration()
-        self.parse(section)
-        try:
-            return self.schema.finishSection(section)
-        except ConfigurationError, e:
-            e.lineno = self.lineno
-            e.url = self.url
-            raise
 
-    def parse(self, section):
-        done, line = self.nextline()
-        while not done:
-            if line[:1] in ("", "#"):
-                # blank line or comment
-                pass
+class OptionBag(ZConfig.cmdline.OptionBag):
 
-            elif line[:2] == "</":
-                # section end
-                if line[-1] != ">":
-                    self.error("malformed section end")
-                section = self.end_section(section, line[2:-1])
-
-            elif line[0] == "<":
-                # section start
-                if line[-1] != ">":
-                    self.error("malformed section start")
-                section = self.start_section(section, line[1:-1])
-
-            elif line[0] == "%":
-                self.error("ZConfig-style directives are not supported")
-
-            else:
-                self.handle_key_value(section, line)
-
-            done, line = self.nextline()
-
-        if self.stack:
-            self.error("unclosed sections not allowed")
-
-    def start_section(self, section, rest):
-        isempty = rest[-1:] == "/"
-        if isempty:
-            text = rest[:-1].rstrip()
-        else:
-            text = rest.rstrip()
-        # parse section start stuff here
-        m = _section_start_rx.match(text)
-        if not m:
-            self.error("malformed section header")
-        type, name = m.group('type', 'name')
-        type = type.lower()
-        #
-        # XXX Argh!  Converting section names to lower-case was a
-        # mistake in ZConfig, but we have to honor case here for
-        # <extension> sections.  We need to add some way to control
-        # the "nametype" of sections in ZConfig anyway.
-        #
-        # if name:
-        #    name = name.lower()
-        #
-        try:
-            newsect = self.schema.startSection(section, type, name)
-        except ConfigurationError, e:
-            e.lineno = self.lineno
-            e.url = self.url
-            raise
-        if isempty:
-            try:
-                self.schema.endSection(section, type, name, newsect)
-            except ConfigurationError, e:
-                e.lineno = self.lineno
-                e.url = self.url
-                raise
-            return section
-        else:
-            self.stack.append((type, name, section))
-            return newsect
-
-    def end_section(self, section, rest):
-        if not self.stack:
-            self.error("unexpected section end")
-        type = rest.rstrip().lower()
-        opentype, name, prevsection = self.stack.pop()
-        if type != opentype:
-            self.error("unbalanced section end")
-        try:
-            self.schema.endSection(prevsection, type, name, section)
-        except ConfigurationError, e:
-            e.lineno = self.lineno
-            e.url = self.url
-            raise
-        return prevsection
-
-    def handle_key_value(self, section, rest):
-        m = _keyvalue_rx.match(rest)
-        if not m:
-            self.error("malformed configuration data")
-        key, value = m.group('key', 'value')
-        if value:
-            value = self.replace(value)
-        else:
-            value = ''
-        try:
-            self.schema.addValue(section, key, value)
-        except ConfigurationError, e:
-            e.lineno = self.lineno
-            e.url = self.url
-            raise
-
-    def replace(self, text):
-        parts = []
-        rest = text
-        while "$" in rest:
-            i = rest.index("$")
-            if i:
-                parts.append(rest[:i])
-            rest = rest[i+1:]
-            if not rest:
-                self.error("text cannot end with a bare '$'")
-            if rest[0] == "$":
-                parts.append("$")
-                rest = rest[1:]
-            else:
-                self.error("unsupported substitution syntax")
-        parts.append(rest)
-        return _nulljoin(parts)
-
-    def error(self, message):
-        raise ConfigurationError(message, self.url, self.lineno)
-
-
-class SectionValue:
-    """Generic bag-of-values object for a section."""
-
-    def __init__(self, name, typename, typedef):
-        self._name = name
-        self._typename = typename
-        self._typedef = typedef
-
-    def getSectionName(self):
-        """Return the name of the section, or `None`."""
-        return self._name
-
-    def getSectionType(self):
-        """Return the name of the section type."""
-        return self._typename
-
-    def getSectionDefinition(self):
-        """Return the data structure that represents the type of this
-        section value.
-        """
-        return self._typedef
+    def _normalize_case(self, string):
+        return string

Modified: zpkgtools/trunk/zpkgsetup/package.py
===================================================================
--- zpkgtools/trunk/zpkgsetup/package.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgsetup/package.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -19,12 +19,6 @@
 described for any component, with the exception that extensions only
 make sense for package components.
 
-Package configuration files use a syntax very like the `ZConfig`_
-package, but fewer features are available.  The specific syntax is
-implemented by the `zpkgsetup.cfgparser` module.
-
-.. _ZConfig:  http://www.zope.org/Members/fdrake/zconfig/
-
 There are only a few types of information which can appear in a
 package configuration file; the file is intended to describe what a
 package provides that is not a module or data file.  The three types
@@ -55,7 +49,7 @@
 :Groups:
   - `Public interface`: loadCollectionInfo loadPackageInfo
   - `Helper functions`: create_extension expand_globs read_package_info
-  - `Datatype functions`: cpp_definition cpp_names path_ref extension
+  - `Datatype functions`: cpp_definition cpp_names path_ref
 
 """
 
@@ -74,7 +68,9 @@
 
 PACKAGE_CONF = "SETUP.cfg"
 
+get_schema = cfgparser.cachedSchemaLoader("package.xml")
 
+
 class Header(object):
     """Information about a header file and the package that provides it."""
 
@@ -103,7 +99,7 @@
     """
     pkginfo = read_package_info(directory, reldir)
     pkginfo.extensions = [create_extension(ext, pkgname, reldir)
-                          for ext in pkginfo.extension]
+                          for ext in pkginfo.extensions]
     pkginfo.package_headers = [Header(pkgname, path)
                                for path in pkginfo.header]
     return pkginfo
@@ -123,7 +119,7 @@
     """
     pkginfo = read_package_info(directory, reldir)
     pkginfo.extensions = [create_extension(ext, None, reldir)
-                          for ext in pkginfo.extension]
+                          for ext in pkginfo.extensions]
     pkginfo.package_headers = [Header(None, path)
                                for path in pkginfo.header]
     return pkginfo
@@ -154,10 +150,11 @@
         url = "<no file>"
         f = StringIO("")
     try:
-        p = cfgparser.Parser(f, url, PackageSchema(directory, reldir))
-        pkginfo = p.load()
+        pkginfo, _ = cfgparser.loadConfigFile(get_schema(), f, url)
     finally:
         f.close()
+    for name, data_files in pkginfo.data_files:
+        data_files[:] = expand_globs(directory, reldir, data_files)
     pkginfo.documentation = expand_globs(directory, reldir,
                                          pkginfo.documentation)
     pkginfo.header = expand_globs(directory, reldir, pkginfo.header)
@@ -243,9 +240,9 @@
     """
     kwargs = {}
     if pkgname:
-        kwargs["name"] = "%s.%s" % (pkgname, section.name)
+        kwargs["name"] = "%s.%s" % (pkgname, section.getSectionName())
     else:
-        kwargs["name"] = section.name
+        kwargs["name"] = section.getSectionName()
     kwargs["sources"] = [posixpath.join(reldir, fn)
                          for fn in section.source]
     if section.define:
@@ -385,22 +382,12 @@
 _cpp_ident_match = re.compile("[A-Za-z_][A-Za-z_0-9]*$").match
 
 
-def extension(section):
-    """Transform `section`, checking several fields for valid values.
+def empty_string(s):
+    if s:
+        raise ValueError("data-file specifications may not have values")
+    return s
 
-    :param section: Configuration section.
-    :return: Modified section.
-    """
-    section.name = section.getSectionName()
-    if not section.name:
-        raise ValueError("extensions must be named")
-    if not section.source:
-        raise ValueError("at least one extension source file must be listed")
-    if len(section.language) > 1:
-        raise ValueError("language can only be specified once")
-    return section
 
-
 def path_ref(s):
     """Datatype for a local path reference.
 
@@ -456,80 +443,6 @@
     return p
 
 
-class PackageSchema(cfgparser.Schema):
-    """Schema implementation with a <data-files> section type.
-
-    The <data-files> sections have keys that are glob-expanded (based
-    on information passed to the constructor) and combined into a
-    single ``data_files`` member on the resulting package information
-    object.  The value of the ``data_files`` attribute is suitable for
-    passing to the `distutils.core.setup()` function.
-
-    """
-
-    def __init__(self, directory, reldir):
-        cfgparser.Schema.__init__(
-            self,
-            ({"script": path_ref,
-              "documentation": path_ref,
-              "header": path_ref},
-             ["extension"], None),
-            {"extension": ({"source": path_ref, "depends-on": path_ref,
-                            "define" : cpp_definition, "undefine": cpp_names,
-                            "language": str,
-                            },
-                           (), extension),
-             }
-            )
-        self.__cf = None
-        self.__datafiles = None
-        self.__directory = directory
-        self.__reldir = reldir
-
-    def getConfiguration(self):
-        assert self.__cf is None
-        self.__cf = cfgparser.Schema.getConfiguration(self)
-        self.__cf.data_files = []
-        return self.__cf
-
-    def startSection(self, parent, typename, name):
-        if self.__datafiles is not None:
-            raise cfgparser.ConfigurationError(
-                "can't nest another section inside <data-files> section")
-        if typename == "data-files":
-            if not name:
-                raise cfgparser.ConfigurationError(
-                    "<data-files> section must have a name")
-            normname = posixpath.normpath(name)
-            for target, files in self.__cf.data_files:
-                if target == normname:
-                    raise cfgparser.ConfigurationError(
-                        "can't have two sections of the same name:"
-                        " <data-files %s>" % name)
-            self.__datafiles = []
-            self.__cf.data_files.append((normname, self.__datafiles))
-            # The return value is passed around, but that's it
-            return ()
-        else:
-            return cfgparser.Schema.startSection(self, parent, typename, name)
-
-    def endSection(self, parent, typename, name, child):
-        if self.__datafiles is None:
-            cfgparser.Schema.endSection(self, parent, typename, name, child)
-        else:
-            # mutate self.__datafiles since the reference from
-            # self.__cf.data_files is what's actually used
-            self.__datafiles[:] = expand_globs(self.__directory,
-                                               self.__reldir,
-                                               self.__datafiles)
-            self.__datafiles = None
-
-    def addValue(self, section, key, value):
-        if self.__datafiles is not None:
-            if value:
-                raise cfgparser.ConfigurationError(
-                    "each entry in a <data-files> section must be"
-                    " a single glob pattern")
-            self.__datafiles.append(key)
-        else:
-            cfgparser.Schema.addValue(self, section, key, value)
+def data_file_section(section):
+    name = posixpath.normpath(section.getSectionName())
+    return name, section.files.keys()

Copied: zpkgtools/trunk/zpkgsetup/package.xml (from rev 38197, zpkgtools/branches/fdrake-zconfig-branch/zpkgsetup/package.xml)


Property changes on: zpkgtools/trunk/zpkgsetup/package.xml
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: zpkgtools/trunk/zpkgsetup/tests/input/package2/SETUP.cfg
===================================================================
--- zpkgtools/trunk/zpkgsetup/tests/input/package2/SETUP.cfg	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgsetup/tests/input/package2/SETUP.cfg	2005-08-31 17:05:00 UTC (rev 38200)
@@ -1,6 +1,6 @@
 header  public.h
 
-<extension sample>
+<extension Sample>
   source      sample.c
   depends-on  internal.h
   # Don't depend on public.h since we want to make sure things

Deleted: zpkgtools/trunk/zpkgsetup/tests/test_cfgparser.py
===================================================================
--- zpkgtools/trunk/zpkgsetup/tests/test_cfgparser.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgsetup/tests/test_cfgparser.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -1,162 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Tests for zpkgtools.cfgparser."""
-
-import unittest
-
-from StringIO import StringIO
-
-from zpkgsetup import cfgparser
-
-
-class SimpleSection:
-
-    finished = False
-    ending_parent = None
-    ending_typename = None
-    ending_name = None
-
-    def __init__(self, parent=None, typename=None, name=None):
-        self.parent = parent
-        self.typename = typename
-        self.name = name
-
-
-class AnythingGoesSchema:
-
-    def getConfiguration(self):
-        return SimpleSection()
-
-    def startSection(self, parent, typename, name):
-        return SimpleSection(parent, typename, name)
-
-    def finishSection(self, section):
-        section.finished = True
-        return section
-
-    def endSection(self, parent, typename, name, child):
-        child.ending_parent = parent
-        child.ending_typename = typename
-        child.ending_name = name
-        if not hasattr(parent, typename):
-            setattr(parent, typename, [])
-        getattr(parent, typename).append(child)
-        self.finishSection(child)
-
-    def addValue(self, section, key, value):
-        key = key.lower().replace("-", "_")
-        if not hasattr(section, key):
-            setattr(section, key, [])
-        getattr(section, key).append(value)
-
-
-class ParserTestCase(unittest.TestCase):
-
-    schema = AnythingGoesSchema()
-
-    def createParser(self, text=""):
-        sio = StringIO(text)
-        self.parser = cfgparser.Parser(sio, "<some-url>", self.schema)
-        return self.parser
-
-    def test_replace(self):
-        # "legal" values are those that are legal in ZConfig
-        eq = self.assertEqual
-        raises = self.assertRaises
-        replace = self.createParser().replace
-
-        # some legal values that don't have '$':
-        eq(replace(""), "")
-        eq(replace(" foo bar "), " foo bar ")
-        eq(replace("x"), "x")
-
-        # legal, supported values with '$':
-        eq(replace("$$"), "$")
-        eq(replace("$$$$"), "$$")
-        eq(replace("$$xyz$$"), "$xyz$")
-
-        # legal, unsupported values (all have '$'):
-        raises(cfgparser.ConfigurationError, replace, "$foo")
-        raises(cfgparser.ConfigurationError, replace, "${foo-bar}")
-
-        # illegal values:
-        raises(cfgparser.ConfigurationError, replace, "$")
-        raises(cfgparser.ConfigurationError, replace, "foo$")
-
-    def test_schema_use(self):
-        eq = self.assertEqual
-        p = self.createParser("""
-            # This is a comment.
-
-            key value 1
-            key value 2
-            <section/>
-            <section foo/>
-            <section>
-              key  value 3
-            </section>
-            <section splat>
-              <inner>
-                key value 5
-              </inner>
-              key value 4
-            </section>
-            """)
-        cf = p.load()
-        self.check_section(cf, None, None, None, key=["value 1", "value 2"])
-        s1, s2, s3, s4 = cf.section
-        self.check_section(s1, cf, None, "section")
-        self.check_section(s2, cf, "foo", "section")
-        self.check_section(s3, cf, None, "section", key=["value 3"])
-        self.check_section(s4, cf, "splat", "section", key=["value 4"])
-        inner, = s4.inner
-        self.check_section(inner, s4, None, "inner", key=["value 5"])
-
-    def check_section(self, section, parent, name, typename, **attrs):
-        self.assert_(section.finished)
-        self.assert_(section.parent is parent)
-        self.assert_(section.parent is section.ending_parent)
-        self.assertEqual(section.name, name)
-        self.assertEqual(section.name, section.ending_name)
-        self.assertEqual(section.typename, typename)
-        self.assertEqual(section.typename, section.ending_typename)
-        for name, value in attrs.iteritems():
-            v = getattr(section, name)
-            self.assertEqual(v, value)
-
-
-class SchemaTestCase(unittest.TestCase):
-
-    top_level_converted = False
-
-    def setUp(self):
-        self.schema = cfgparser.Schema(
-            ({}, [], self.top_level_conversion))
-
-    def top_level_conversion(self, section):
-        self.top_level_converted = True
-        return section
-
-    def test_getConfiguration(self):
-        cf = self.schema.getConfiguration()
-        self.failIf(self.top_level_converted)
-
-
-def test_suite():
-    suite = unittest.makeSuite(ParserTestCase)
-    suite.addTest(unittest.makeSuite(SchemaTestCase))
-    return suite
-
-if __name__ == "__main__":
-    unittest.main(defaultTest="test_suite")

Modified: zpkgtools/trunk/zpkgsetup/tests/test_setup.py
===================================================================
--- zpkgtools/trunk/zpkgsetup/tests/test_setup.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgsetup/tests/test_setup.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -60,7 +60,7 @@
         context.walk_packages("zpkgsetup/tests/input")
         exts = [ext.name for ext in context.ext_modules]
         exts.sort()
-        self.assertEqual(exts, ["foo", "package2.sample"])
+        self.assertEqual(exts, ["foo", "package2.Sample"])
         #
         # See the comments in the walk_packages() function for an
         # explanation of the limitations of the method.  The following

Modified: zpkgtools/trunk/zpkgsetup/urlutils.py
===================================================================
--- zpkgtools/trunk/zpkgsetup/urlutils.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgsetup/urlutils.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -17,8 +17,15 @@
 
 import posixpath
 import urllib
+import urlparse
 
+# svn: and svn+ssh: weren't handled properly by urlparse before Python
+# 2.4.2 and 2.5; this makes all versions handled them correctly:
+#
+if "svn" not in urlparse.uses_netloc:
+    urlparse.uses_netloc.extend(["svn", "svn+ssh"])
 
+
 def file_url(path):
     return "file://" + pathname2url(path)
 

Modified: zpkgtools/trunk/zpkgtools/app.py
===================================================================
--- zpkgtools/trunk/zpkgtools/app.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgtools/app.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -21,6 +21,8 @@
 import sys
 import tempfile
 
+import ZConfig
+import zpkgsetup
 import zpkgtools
 
 from zpkgsetup import cfgparser
@@ -38,12 +40,13 @@
 from zpkgtools import runlog
 
 
-zpkgsetup_dir = os.path.join(
-    os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
-    "zpkgsetup")
+def package_resource(package):
+    dir = os.path.dirname(os.path.abspath(package.__path__[0]))
+    return urlutils.file_url(dir)
 
 DEFAULT_SUPPORT_PACKAGES = [
-    ("zpkgsetup", urlutils.file_url(zpkgsetup_dir)),
+    ("ZConfig",   package_resource(ZConfig)),
+    ("zpkgsetup", package_resource(zpkgsetup)),
     ]
 
 
@@ -289,16 +292,11 @@
             # have the package as a side effect of something else
             return
         source = None
-        if name in self.locations:
-            url = self.locations[name]
-        else:
-            url = fallback
-            if not url:
-                self.logger.warning("resource %s not configured;"
-                                    " no fallback URL" % name)
-                return
-            self.logger.info("resource %s not configured;"
-                             " using fallback URL" % name)
+        url = fallback or self.locations.get(name)
+        if not url:
+            self.logger.warning("resource %s not configured;"
+                                " no location known" % name)
+            return
         if source is None:
             self.logger.debug("loading resource '%s' from %s",
                               name, url)

Modified: zpkgtools/trunk/zpkgtools/config.py
===================================================================
--- zpkgtools/trunk/zpkgtools/config.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgtools/config.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -13,14 +13,7 @@
 ##############################################################################
 """Configuration support for **zpkg**.
 
-The syntax of the configuration files is incredibly simple, but is
-intended to be a strict subset of the `ZConfig`_.  This allows us to
-switch to `ZConfig`_ in the future if we decide the dependency is
-worth it.
-
-.. _ZConfig:  http://www.zope.org/Members/fdrake/zconfig/
-
-:undocumented: boolean non_empty_string \*_STRINGS
+:undocumented: non_empty_string resource_map
 """
 
 import os
@@ -32,16 +25,8 @@
 from zpkgtools import locationmap
 
 
-TRUE_STRINGS =("yes", "true", "on")
-FALSE_STRINGS = ("no", "false", "off")
+get_schema = cfgparser.cachedSchemaLoader("config.xml")
 
-def boolean(string):
-    s = string.lower()
-    if s in FALSE_STRINGS:
-        return False
-    if s in TRUE_STRINGS:
-        return True
-    raise ValueError("unknown boolean value: %r" % string)
 
 def non_empty_string(string):
     if not string:
@@ -49,44 +34,10 @@
     return string
 
 
-class Schema(cfgparser.Schema, object):
+def resource_map(value):
+    return value.map
 
-    def __init__(self, filename, locations):
-        # We can use the base schema for the top-level definitions,
-        # except for the <resources> section.
-        super(Schema, self).__init__(
-            ({"resource-map": non_empty_string,
-              "include-support-code": boolean,
-              "collect-dependencies": boolean,
-              "build-application": boolean,
-              "default-collection": non_empty_string,
-              }, ["resources"], None))
-        self.base = urlutils.file_url(filename)
-        self.filename = filename
-        self.locations = locations
 
-    def startSection(self, parent, typename, name):
-        if typename != "resources":
-            raise cfgparser.ConfigurationError(
-                "only <resources> sections are allowed")
-        if isinstance(parent, locationmap.MapLoader):
-            raise cfgparser.ConfigurationError(
-                "<resources> sections may not be nested")
-        return locationmap.MapLoader(self.base, self.filename, self.locations)
-
-    def addValue(self, section, key, value):
-        if isinstance(section, locationmap.MapLoader):
-            section.add(key, value)
-        else:
-            super(Schema, self).addValue(section, key, value)
-
-    def finishSection(self, section):
-        if isinstance(section, locationmap.MapLoader):
-            return section.mapping
-        return super(Schema, self).finishSection(section)
-
-
-
 class Configuration:
     """Configuration settings for **zpkg**.
 
@@ -146,37 +97,27 @@
             basedir = os.path.abspath(basedir)
         else:
             basedir = os.getcwd()
-        p = cfgparser.Parser(f, path, Schema(os.path.abspath(path),
-                                             self.locations))
-        cf = p.load()
-        base = urlutils.file_url(basedir) + "/"
-        for value in cf.resource_map:
-            value = urlparse.urljoin(base, value)
+        schema = get_schema()
+        url = urlutils.file_url(os.path.abspath(path))
+        cf, _ = cfgparser.loadConfigFile(schema, f, url)
+        # deal with embedded resource maps:
+        for map in cf.resource_maps:
+            for key, value in map.iteritems():
+                value = urlparse.urljoin(url, value)
+                if key.endswith(".*"):
+                    wildcard = key[:-2]
+                    if not self.locations._have_wildcard(wildcard):
+                        self.locations._add_wildcard(wildcard, value)
+                elif key not in self.locations:
+                    self.locations[key] = value
+        self.application = cf.build_application
+        self.collect_dependencies = cf.collect_dependencies
+        self.default_collection = cf.default_collection
+        self.include_support_code = cf.include_support_code
+        self.resource_maps = cf.resource_maps
+        for value in cf.location_maps:
+            value = urlparse.urljoin(url, value)
             self.location_maps.append(value)
-        # include-support-code
-        if len(cf.include_support_code) > 1:
-            raise cfgparser.ConfigurationError(
-                "include-support-code can be specified at most once")
-        if cf.include_support_code:
-            self.include_support_code = cf.include_support_code[0]
-        # collect-dependencies
-        if len(cf.collect_dependencies) > 1:
-            raise cfgparser.ConfigurationError(
-                "collect-dependencies can be specified at most once")
-        if cf.collect_dependencies:
-            self.collect_dependencies = cf.collect_dependencies[0]
-        # build-application
-        if len(cf.build_application) > 1:
-            raise cfgparser.ConfigurationError(
-                "build-application can be specified at most once")
-        if cf.build_application:
-            self.application = cf.build_application[0]
-        # default-collection
-        if len(cf.default_collection) > 1:
-            raise cfgparser.ConfigurationError(
-                "default-collection can be specified at most once")
-        if cf.default_collection:
-            self.default_collection = cf.default_collection[0]
 
 
 def defaultConfigurationPath():

Copied: zpkgtools/trunk/zpkgtools/config.xml (from rev 38197, zpkgtools/branches/fdrake-zconfig-branch/zpkgtools/config.xml)


Property changes on: zpkgtools/trunk/zpkgtools/config.xml
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: zpkgtools/trunk/zpkgtools/include.py
===================================================================
--- zpkgtools/trunk/zpkgtools/include.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgtools/include.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -27,6 +27,7 @@
 
 from zpkgsetup import cfgparser
 from zpkgsetup import loggingapi as logging
+from zpkgsetup import package
 from zpkgsetup import publication
 from zpkgsetup import setup
 from zpkgsetup import urlutils
@@ -41,6 +42,84 @@
 PACKAGE_CONF = "PACKAGE.cfg"
 
 
+get_schema = cfgparser.cachedSchemaLoader("include.xml")
+
+
+def collection_path_ref(value):
+    if value == "-":
+        return value
+    if value:
+        return normalize_path(value, "destination")
+    else:
+        return None
+
+def distribution_path_ref(value):
+    if value == "-":
+        raise ValueError("exclusion not allowed in <distribution>")
+    if value:
+        return normalize_path(value, "destination")
+    else:
+        return None
+
+def load_path_ref(value):
+    if value == "-":
+        raise ValueError("exclusion not allowed in <load>")
+    if not value:
+        raise ValueError("source must be specified in <load>")
+    return normalize_path_or_url(value, "source")
+
+def workspace_path_ref(value):
+    return normalize_path(value, "workspace file")
+
+
+def collection_section(section):
+    excludes = []
+    includes = {}
+    for (k, vs) in section.mapping.iteritems():
+        for v in vs:
+            if v == "-":
+                if len(vs) != 1:
+                    raise ValueError("too many exclusions specified for %s"
+                                     % k)
+                excludes.append(k)
+                continue
+            if v:
+                includes[v] = k
+            else:
+                L = includes.setdefault(None, [])
+                L.append(k)
+    if excludes and includes:
+        raise ValueError(
+            "includes and excludes cannot be mixed in <collection>")
+    section.excludes = excludes
+    section.includes = includes
+    return section
+
+def distribution_section(section):
+    includes = {}
+    for (k, v) in section.mapping.iteritems():
+        if v:
+            includes[v] = k
+        else:
+            L = includes.setdefault(None, [])
+            L.append(k)
+    section.excludes = {}
+    section.includes = includes
+    return section
+
+def load_section(section):
+    includes = {}
+    for (k, v) in section.mapping.iteritems():
+        if v:
+            includes[v] = k
+        else:
+            L = includes.setdefault(None, [])
+            L.append(k)
+    section.excludes = {}
+    section.includes = includes
+    return section
+
+
 class InclusionError(Error):
     """Raised to indicate errors processing inclusions."""
 
@@ -74,14 +153,22 @@
     If there is not specification file, return empty specifications.
     """
     package_conf = os.path.join(sourcedir, PACKAGE_CONF)
-    schema = SpecificationSchema(sourcedir, package_conf)
+    url = urlutils.file_url(os.path.abspath(package_conf))
     if os.path.isfile(package_conf):
-        f = open(package_conf, "rU")
-        try:
-            parser = cfgparser.Parser(f, package_conf, schema)
-            config = parser.load()
-        finally:
-            f.close()
+        cf, _ = cfgparser.loadConfig(get_schema(), package_conf)
+        config = PackageConstruction(sourcedir, package_conf)
+        if cf.collection is not None:
+            config.collection.excludes = cf.collection.excludes
+            config.collection.includes = cf.collection.includes
+        if cf.distribution is not None:
+            config.distribution.includes = cf.distribution.includes
+        if cf.loads is not None:
+            config.loads.includes = cf.loads.includes
+
+        # Now that the configuration object is populated and we know
+        # that the PACKAGE_CONF file exists, make sure it isn't copied
+        # if there's nothing specified in the <collection> section.
+        #
         if config.collection.excludes:
             # XXX should make sure PACKAGE_CONF isn't already excluded
             config.collection.excludes.append(PACKAGE_CONF)
@@ -89,11 +176,19 @@
             # Nothing included or excluded; simply exclude PACKAGE_CONF:
             config.collection.excludes.append(PACKAGE_CONF)
     else:
-        config = schema.getConfiguration()
+        config = PackageConstruction(sourcedir, None)
     return config
 
 
-def normalize_path(path, type, group):
+class PackageConstruction(object):
+
+    def __init__(self, source, filename):
+        self.loads = Specification(source, filename, "load")
+        self.collection = Specification(source, filename, "collection")
+        self.distribution = Specification(source, filename, "distribution")
+
+
+def normalize_path(path, type):
     if ":" in path:
         scheme, rest = urllib.splittype(path)
         if len(scheme) == 1:
@@ -116,101 +211,15 @@
         return np.replace("/", os.sep)
 
 
-def normalize_path_or_url(path, type, group):
+def normalize_path_or_url(path, type):
     if ":" in path:
         scheme, rest = urllib.splittype(path)
         if len(scheme) != 1:
             # should normalize the URL, but skip that for now
             return path
-    return normalize_path(path, type, group)
+    return normalize_path(path, type)
 
 
-class SpecificationSchema(cfgparser.Schema):
-    """Specialized schema that handles populating a set of Specifications.
-    """
-
-    def __init__(self, source, filename):
-        self.filename = filename
-        self.source = source
-
-    def getConfiguration(self):
-        conf = cfgparser.SectionValue(None, None, None)
-        conf.loads = Specification(
-            self.source, self.filename, "load")
-        conf.collection = Specification(
-            self.source, self.filename, "collection")
-        conf.distribution = Specification(
-            self.source, self.filename, "distribution")
-        return conf
-
-    def startSection(self, parent, typename, name):
-        if not isinstance(parent, cfgparser.SectionValue):
-            raise cfgparser.ConfigurationError("unexpected section")
-        if typename == "collection":
-            return parent.collection
-        elif typename == "distribution":
-            return parent.distribution
-        elif typename == "load":
-            return parent.loads
-        raise cfgparser.ConfigurationError("unknown section type: %s"
-                                           % typename)
-
-    def endSection(self, parent, typename, name, child):
-        if child.includes and child.excludes:
-            # XXX not sure what the exact semantics should be of
-            # allowing both inclusions and exclusions at the same
-            # time; which takes precedence?  what about precedence
-            # when wildcards are involved?
-            raise cfgparser.ConfigurationError(
-                "exclusions and inclusions cannot coexist in a single section")
-
-    def createSection(self, name, typename, typedef):
-        raise NotImplementedError(
-            "createSection() should not be called for SpecificationSchema")
-
-    def finishSection(self, section):
-        return section
-
-    def addValue(self, section, workfile, other):
-        if not isinstance(section, Specification):
-            raise cfgparser.ConfigurationError(
-                "all inclusion lines must be in a section")
-
-        if other == "-":
-            # This is an exclusion.
-            if section.group != "collection":
-                raise cfgparser.ConfigurationError(
-                    "exclusions are only permitted in <collection>")
-            workfile = normalize_path(workfile, "exclusion", section.group)
-            section.excludes.append(workfile)
-            return
-
-        if section.group == "load":
-            if not other:
-                raise cfgparser.ConfigurationError(
-                    "referenced file must be named explicitly"
-                    " in <load> section")
-            # perhaps should make sure workfile and other don't refer
-            # to the same file
-            other = normalize_path_or_url(other, "source", section.group)
-        elif other:
-            # workfile and other have a backward relationship for this:
-            # <destination>
-            #   target workfile
-            # </destination>
-            other = normalize_path(other, "destination", section.group)
-
-        if workfile:
-            workfile = normalize_path(workfile, "workspace file",
-                                      section.group)
-
-        if other:
-            section.includes[other] = workfile
-        else:
-            L = section.includes.setdefault(None, [])
-            L.append(workfile)
-
-
 class Specification:
     """Specification for files to include.
 

Copied: zpkgtools/trunk/zpkgtools/include.xml (from rev 38197, zpkgtools/branches/fdrake-zconfig-branch/zpkgtools/include.xml)


Property changes on: zpkgtools/trunk/zpkgtools/include.xml
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: zpkgtools/trunk/zpkgtools/locationmap.py
===================================================================
--- zpkgtools/trunk/zpkgtools/locationmap.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgtools/locationmap.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -150,6 +150,17 @@
         self.path = path
 
 
+def resource_name(value):
+    if value.endswith(".*"):
+        if not is_module_name(value[:-2]):
+            raise ValueError("wildcard package name specified, but"
+                             " prefix is not a legal package name: %r"
+                             % value)
+    elif "*" in value:
+        raise ValueError("invalid wildcard specification: %r" % resource)
+    return value
+
+
 class MapLoader:
 
     def __init__(self, base, filename, mapping):

Modified: zpkgtools/trunk/zpkgtools/tests/test_include.py
===================================================================
--- zpkgtools/trunk/zpkgtools/tests/test_include.py	2005-08-31 16:55:23 UTC (rev 38199)
+++ zpkgtools/trunk/zpkgtools/tests/test_include.py	2005-08-31 17:05:00 UTC (rev 38200)
@@ -194,7 +194,7 @@
             </collection>
             """)
         specs = include.load(self.source)
-        self.assertRaises(include.InclusionSpecificationError,
+        self.assertRaises(cfgparser.ConfigurationError,
                           specs.collection.cook)
 
     def test_omitted_destination_keeps_name(self):
@@ -234,7 +234,7 @@
             </%s>
             """ % (sectionname, sectionname)
         self.write_file(include.PACKAGE_CONF, text)
-        self.assertRaises(include.InclusionSpecificationError,
+        self.assertRaises(cfgparser.ConfigurationError,
                           include.load, self.source)
 
     # These two tests are really checking internal helpers, but
@@ -251,29 +251,29 @@
         self.check_normalize_urls(normalize)
 
     def check_normalize_paths(self, normalize):
-        self.assertEqual(normalize("README.txt", "t", "group"),
+        self.assertEqual(normalize("README.txt", "t"),
                          "README.txt")
-        self.assertEqual(normalize("doc/README.txt", "t", "group"),
+        self.assertEqual(normalize("doc/README.txt", "t"),
                          join("doc", "README.txt"))
-        self.assertEqual(normalize(".", "t", "group"),
+        self.assertEqual(normalize(".", "t"),
                          os.curdir)
         # Ignore this because it looks like a Windows drive letter:
         self.assertRaises(include.InclusionSpecificationError,
-                          normalize, "c:foo/bar", "t", "group")
+                          normalize, "c:foo/bar", "t")
         # Absolute paths are an error as well:
         self.assertRaises(include.InclusionSpecificationError,
-                          normalize, "/absolute/path", "t", "group")
+                          normalize, "/absolute/path", "t")
         # Relative paths that point up the hierarchy are also disallowed:
         self.assertRaises(include.InclusionSpecificationError,
-                          normalize, "abc/../../def.txt", "t", "group")
+                          normalize, "abc/../../def.txt", "t")
         self.assertRaises(include.InclusionSpecificationError,
-                          normalize, "../def.txt", "t", "group")
+                          normalize, "../def.txt", "t")
 
     def check_normalize_urls(self, normalize):
         for url in ("http://www.example.com/index.html",
                     "repository:/Zope3/doc",
                     "cvs://cvs.zope.com/cvs-repository:/Zope3/doc:HEAD"):
-            self.assertEqual(normalize(url, "t", "group"), url)
+            self.assertEqual(normalize(url, "t"), url)
 
     def test_createDistributionTree_creates_destination(self):
         os.rmdir(self.destination)



More information about the Zope-CVS mailing list