[Zope-CVS] SVN: zpkgtools/trunk/ Allow projects to avoid separate configuration and resource map files:

Fred L. Drake, Jr. fdrake at gmail.com
Sat Aug 6 00:53:36 EDT 2005


Log message for revision 37756:
  Allow projects to avoid separate configuration and resource map files:
  
  - refactor the resource map loading code so that the interpretation
    logic is handled by a helper class with a simple API
  
  - add a more specialized schema to the configuration loader which
    supports a <resources> section, allowing resource maps to be
    embedded within a zpkg configuration file
  

Changed:
  U   zpkgtools/trunk/doc/zpkg.txt
  U   zpkgtools/trunk/zpkgtools/config.py
  U   zpkgtools/trunk/zpkgtools/locationmap.py
  U   zpkgtools/trunk/zpkgtools/tests/test_config.py

-=-
Modified: zpkgtools/trunk/doc/zpkg.txt
===================================================================
--- zpkgtools/trunk/doc/zpkg.txt	2005-08-06 03:09:02 UTC (rev 37755)
+++ zpkgtools/trunk/doc/zpkg.txt	2005-08-06 04:53:36 UTC (rev 37756)
@@ -109,7 +109,8 @@
 
 Four keys are currently defined for the configuration file:
 ``build-application``, ``collect-dependencies``,
-``include-support-code``, and ``resource-map``.
+``include-support-code``, and ``resource-map``.  There is also one
+section type that can be used as well, ``<resources>``.
 
 If ``build-application`` is set to ``true``, then an application
 distribution is constructed instead of a conventional distutils
@@ -140,13 +141,25 @@
 contains::
 
   resource-map  local.map
-  resource-map  cvs://cvs.zope.org/cvs-repository:Packages2/packages.map
 
+  <resources>
+    somepkg  some/relative/path
+  </resources>
+
+  resource-map cvs://cvs.zope.org/cvs-repository:Packages2/packages.map
+
+  <resources>
+    another  some/where/else
+  </resources>
+
 **zpkg** will first load the maps in the order:
 
-1. ``/home/fdrake/local.map``
-2. ``cvs://cvs.zope.org/cvs-repository:Packages2/packages.map``
+1. embedded maps, in their order of appearance
+2. ``/home/fdrake/local.map``
+3. ``cvs://cvs.zope.org/cvs-repository:Packages2/packages.map``
 
+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/
 

Modified: zpkgtools/trunk/zpkgtools/config.py
===================================================================
--- zpkgtools/trunk/zpkgtools/config.py	2005-08-06 03:09:02 UTC (rev 37755)
+++ zpkgtools/trunk/zpkgtools/config.py	2005-08-06 04:53:36 UTC (rev 37756)
@@ -48,15 +48,44 @@
         raise ValueError("value cannot be empty")
     return string
 
-SCHEMA = cfgparser.Schema(
-    ({"resource-map": non_empty_string,
-      "include-support-code": boolean,
-      "collect-dependencies": boolean,
-      "build-application": boolean,
-      }, [], None),
-    )
 
+class Schema(cfgparser.Schema, object):
 
+    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,
+              }, ["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.LocationMap):
+            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**.
 
@@ -111,7 +140,7 @@
             paths found in the configuration file.
 
         """
-        p = cfgparser.Parser(f, path, SCHEMA)
+        p = cfgparser.Parser(f, path, Schema(path, self.locations))
         cf = p.load()
         base = urlutils.file_url(os.path.abspath(basedir)) + "/"
         for value in cf.resource_map:

Modified: zpkgtools/trunk/zpkgtools/locationmap.py
===================================================================
--- zpkgtools/trunk/zpkgtools/locationmap.py	2005-08-06 03:09:02 UTC (rev 37755)
+++ zpkgtools/trunk/zpkgtools/locationmap.py	2005-08-06 04:53:36 UTC (rev 37756)
@@ -33,7 +33,7 @@
 
 
 class MapLoadingError(ValueError):
-    def __init__(self, message, filename, lineno):
+    def __init__(self, message, filename, lineno=None):
         self.filename = filename
         self.lineno = lineno
         ValueError.__init__(self, message)
@@ -142,58 +142,44 @@
         return key in self._wildcards
 
 
-def load(f, base, mapping):
-    """Parse a location map from an open file.
+class MapLoader:
 
-    :param f:  The open file to read from.
+    def __init__(self, base, filename, mapping):
+        assert base
+        self.base = base
+        self.filename = filename
+        self.mapping = mapping
+        self.local_entries = {}
+        try:
+            self.cvsbase = loader.parse(base)
+        except ValueError:
+            self.cvsbase = None
 
-    :param base:  URL corresponding to the open file.
-
-    :param mapping:  Mapping to update.
-
-    This routine is not part of the API for this module; it is
-    separated out from the `fromPathOrUrl()` function to make it
-    easier to test the map parsing aspect of this module.
-
-    """
-    assert base
-    try:
-        cvsbase = loader.parse(base)
-    except ValueError:
-        cvsbase = None
-    local_entries = {}
-    lineno = 0
-    for line in f:
-        lineno += 1
-        line = line.strip()
-        if line[:1] in ("", "#"):
-            continue
-
-        parts = line.split()
-        if len(parts) != 2:
+    def add(self, resource, url):
+        urlparts = url.split()
+        if len(urlparts) != 1:
             raise MapLoadingError("malformed package specification",
-                                  getattr(f, "name", "<unknown>"), lineno)
-        resource, url = parts
+                                  self.filename)
         try:
             cvsurl = loader.parse(url)
         except ValueError:
             # conventional URL
-            if cvsbase is None:
-                url = urlparse.urljoin(base, url)
+            if self.cvsbase is None:
+                url = urlparse.urljoin(self.base, url)
         else:
             if isinstance(cvsurl, cvsloader.RepositoryUrl):
-                if cvsbase is None:
+                if self.cvsbase is None:
                     raise MapLoadingError(
                         "repository: URLs are not supported"
                         " without a cvs: or Subversion base URL",
-                        getattr(f, "name", "<unknown>"), lineno)
-                cvsurl = cvsbase.join(cvsurl)
+                        self.filename)
+                cvsurl = self.cvsbase.join(cvsurl)
                 url = get_template_url(cvsurl)
 
-        if resource in local_entries:
+        if resource in self.local_entries:
             _logger.warn(
                 "found duplicate entry for resource %r in %s at line %d",
-                resource, getattr(f, "name", "<unknown>"), lineno)
+                resource, self.filename)
         elif resource.endswith(".*"):
             # Deal with wildcarded resources;
             # need to check if it's already there
@@ -202,20 +188,54 @@
                 raise MapLoadingError("wildcard package name specified, but"
                                       " prefix is not a legal package name: %r"
                                       % wildcard,
-                                      getattr(f, "name", "<unknown>"), lineno)
-            if not mapping._have_wildcard(wildcard):
-                mapping._add_wildcard(wildcard, url)
+                                      self.filename)
+            if not self.mapping._have_wildcard(wildcard):
+                self.mapping._add_wildcard(wildcard, url)
         elif "*" in resource:
             raise MapLoadingError("invalid wildcard specification: %r"
                                   % resource,
-                                  getattr(f, "name", "<unknown>"), lineno)
-        elif resource not in mapping:
+                                  self.filename)
+        elif resource not in self.mapping:
             # We only want to add it once, so that loading several
             # mappings causes the first defining a resource to "win":
-            mapping[resource] = url
-        local_entries[resource] = resource
+            self.mapping[resource] = url
+        self.local_entries[resource] = resource
 
 
+def load(f, base, mapping):
+    """Parse a location map from an open file.
+
+    :param f:  The open file to read from.
+
+    :param base:  URL corresponding to the open file.
+
+    :param mapping:  Mapping to update.
+
+    This routine is not part of the API for this module; it is
+    separated out from the `fromPathOrUrl()` function to make it
+    easier to test the map parsing aspect of this module.
+
+    """
+    lineno = 0
+    filename = getattr(f, "name", "<unknown>")
+    maploader = MapLoader(base, filename, mapping)
+    for line in f:
+        lineno += 1
+        line = line.strip()
+        if line[:1] in ("", "#"):
+            continue
+
+        parts = line.split()
+        if len(parts) != 2:
+            raise MapLoadingError("malformed package specification",
+                                  filename, lineno)
+        try:
+            maploader.add(*parts)
+        except MapLoadingError, e:
+            e.lineno = lineno
+            raise
+
+
 def get_template_url(parsed):
     #
     # XXX We need to distinguish between the tag being unspecified at

Modified: zpkgtools/trunk/zpkgtools/tests/test_config.py
===================================================================
--- zpkgtools/trunk/zpkgtools/tests/test_config.py	2005-08-06 03:09:02 UTC (rev 37755)
+++ zpkgtools/trunk/zpkgtools/tests/test_config.py	2005-08-06 04:53:36 UTC (rev 37756)
@@ -98,6 +98,30 @@
         self.assert_(cf.collect_dependencies)
         self.assert_(cf.include_support_code)
 
+    def test_embedded_resource_map(self):
+        cf = config.Configuration()
+        fd, path = tempfile.mkstemp(".conf", text=True)
+        f = os.fdopen(fd, "w+")
+        f.write(
+            "<resources>\n"
+            "  PKG1  svn://svn.example.net/repos/pkg1/trunk\n"
+            "  pkg2  svn://svn.example.net/repos/proj/trunk/src/pkg2\n"
+            "  rel  some/dir\n"
+            "</resources>\n")
+        f.close()
+        where = os.path.dirname(path)
+        whereurl = urlutils.file_url(where)
+        try:
+            cf.loadPath(path)
+            self.assertEqual(cf.locations["PKG1"],
+                             "svn://svn.example.net/repos/pkg1/trunk")
+            self.assertEqual(cf.locations["pkg2"],
+                             "svn://svn.example.net/repos/proj/trunk/src/pkg2")
+            self.assertEqual(cf.locations["rel"],
+                             whereurl + "/some/dir")
+        finally:
+            os.unlink(path)
+
     def load_text(self, text, path=None, basedir=None):
         if path is None:
             if basedir is None:



More information about the Zope-CVS mailing list