[Zope3-checkins] CVS: Packages/ZConfig - datatypes.py:1.1.2.6 info.py:1.1.2.7 loader.py:1.1.2.9 matcher.py:1.1.2.12 schema.py:1.1.2.8

Fred L. Drake, Jr. fred@zope.com
Thu, 12 Dec 2002 13:11:21 -0500


Update of /cvs-repository/Packages/ZConfig
In directory cvs.zope.org:/tmp/cvs-serv15000

Modified Files:
      Tag: zconfig-schema-devel-branch
	datatypes.py info.py loader.py matcher.py schema.py 
Log Message:
Support the sectiontype element in schemas.
This requires separation of the SectionInfo and SectionType information;
the schema itself is only represented as type info.
(The tests pass, but new tests are definately needed.)


=== Packages/ZConfig/datatypes.py 1.1.2.5 => 1.1.2.6 ===
--- Packages/ZConfig/datatypes.py:1.1.2.5	Wed Dec 11 16:06:44 2002
+++ Packages/ZConfig/datatypes.py	Thu Dec 12 13:11:20 2002
@@ -22,6 +22,11 @@
     False = 0
 
 
+class NullConversion:
+    def convert(self, value):
+        return value
+
+
 class TrivialConversion:
     """Datatype that exposes a conversion implemented as a function."""
 


=== Packages/ZConfig/info.py 1.1.2.6 => 1.1.2.7 ===
--- Packages/ZConfig/info.py:1.1.2.6	Thu Dec 12 00:03:08 2002
+++ Packages/ZConfig/info.py	Thu Dec 12 13:11:20 2002
@@ -114,21 +114,59 @@
 
 
 class SectionInfo(KeyInfo):
-    def __init__(self, typename, datatype, minOccurs, maxOccurs, handler,
-                 attribute, keytype, names, nametype):
-        assert keytype is not None
-        self.typename = typename
+    def __init__(self, name, sectiontype, minOccurs, maxOccurs, handler,
+                 attribute):
+        # name        - name of the section; one of '*', '+', or name1
+        # sectiontype - SectionType instance
+        # minOccurs   - minimum number of occurances of the section
+        # maxOccurs   - maximum number of occurances; if > 1, name
+        #               must be '*' or '+'
+        # handler     - handler function called when value(s) must take
+        #               effect
+        # attribute   - name of the attribute on the SectionValue object
+        if maxOccurs > 1:
+            if name not in ('*', '+'):
+                raise ZConfig.ConfigurationError(
+                    "sections which can occur more than once must"
+                    " use a name of '*' or '+'")
+            if not attribute:
+                raise ZConfig.ConfigurationError(
+                    "sections which can occur more than once must"
+                    " specify a target attribute name")
+        KeyInfo.__init__(self, name, sectiontype.datatype,
+                         minOccurs, maxOccurs, handler, attribute)
+        self.sectiontype = sectiontype
+
+    def issection(self):
+        return True
+
+    def allowUnnamed(self):
+        return self.name == "*"
+
+    def isAllowedName(self, name):
+        if name == "*" or name == "+":
+            return False
+        elif self.name == "+":
+            return bool(name)
+        elif not name:
+            return self.name == "*"
+        else:
+            return name == self.name
+
+
+class SectionType:
+    def __init__(self, name, keytype, valuetype, datatype):
+        # name      -
+        # datatype  - type for the section itself
+        # keytype   - type for the keys themselves
+        # valuetype - default type for key values
+        self.name = name
+        self.datatype = datatype
         self.keytype = keytype
-        self.names = names     # '*', '+', or [name1, ...]
-        self.nametype = nametype
+        self.valuetype = valuetype
         self._children = []    # [info, ...]
         self._attrmap = {}     # {attribute: index, ...}
         self._keymap = {}      # {key: index, ...}
-        self._secttypemap = {} # {section type: info, ...}
-        if datatype is None:
-            datatype = _null_conversion
-        KeyInfo.__init__(self, None, datatype, minOccurs, maxOccurs,
-                         handler, attribute)
 
     def __len__(self):
         return len(self._children)
@@ -156,15 +194,7 @@
 
     def addsection(self, name, sectinfo):
         assert name not in ("*", "+")
-        # Check that this isn't a different definition of the section type,
-        # since once _add_child() returns we must succeed, or undo a whole
-        # bunch of stuff.  This is the easier alternative.  ;-)
-        oldinfo = self._secttypemap.get(sectinfo.typename)
-        if oldinfo not in (None, sectinfo):
-            raise ZConfig.ConfigurationError(
-                "duplicate definition of section type " + `name`)
         self._add_child(name, sectinfo)
-        self._secttypemap[sectinfo.typename] = sectinfo
 
     def getinfo(self, key):
         if not key:
@@ -188,10 +218,10 @@
                     if not info.issection():
                         raise ZConfig.ConfigurationError(
                             "section name %s already in use for key" % key)
-                    if not info.typename == type:
+                    if not info.sectiontype.name == type:
                         raise ZConfig.ConfigurationError(
                             "name %s must be used for a %s section"
-                            % (`name`, `info.typename`))
+                            % (`name`, `info.sectiontype.name`))
                     return index
             elif info.typename == type:
                 if not (name or info.allowUnnamed()):
@@ -204,27 +234,24 @@
         i = self.getsectionindex(type, name)
         return self._children[i][1]
 
-    def issection(self):
-        return True
 
-    def allowUnnamed(self):
-        return self.names == "*"
-
-    def isAllowedName(self, name):
-        if name == "*" or name == "+":
-            return False
-        elif self.names == "+":
-            return bool(name)
-        elif not name:
-            return self.names == "*"
-        else:
-            return name in self.names
-
-
-class SchemaInfo(SectionInfo):
-    def __init__(self, datatype, handler, keytype):
-        SectionInfo.__init__(self, None, datatype, 1, 1, handler, None,
-                             keytype, (), None)
+class SchemaType(SectionType):
+    def __init__(self, keytype, valuetype, datatype, handler):
+        SectionType.__init__(self, None, keytype, valuetype, datatype)
+        self.handler = handler
+        self._types = {}
+
+    def addtype(self, typeinfo):
+        if self._types.has_key(typeinfo.name):
+            raise ZConfig.ConfigurationError("type name cannot be redefined: "
+                                             + `typeinfo.name`)
+        self._types[typeinfo.name] = typeinfo
+
+    def gettype(self, name):
+        try:
+            return self._types[name]
+        except KeyError:
+            raise ZConfig.ConfigurationError("unknown type name: " + `name`)
 
     def allowUnnamed(self):
         return True
@@ -232,9 +259,5 @@
     def isAllowedName(self, name):
         return False
 
-
-class NullConversion:
-    def convert(self, value):
-        return value
-
-_null_conversion = NullConversion()
+    def issection(self):
+        return True


=== Packages/ZConfig/loader.py 1.1.2.8 => 1.1.2.9 ===
--- Packages/ZConfig/loader.py:1.1.2.8	Wed Dec 11 15:18:15 2002
+++ Packages/ZConfig/loader.py	Thu Dec 12 13:11:20 2002
@@ -98,7 +98,13 @@
     def startSection(self, parent, type, name, delegatename):
         if delegatename:
             raise NotImpementedError("section delegation is not yet supported")
-        ci = parent.info.getsectioninfo(type, name)
+        info = parent.info
+        info = getattr(info, "sectiontype", info)
+        ci = info.getsectioninfo(type, name)
+        if not ci.isAllowedName(name):
+            raise ZConfig.ConfigurationError(
+                "%s is not an allowed name for %s sections"
+                % (`name`, `ci.sectiontype.name`))
         return matcher.SectionMatcher(ci, name)
 
     def endSection(self, parent, type, name, delegatename, matcher):


=== Packages/ZConfig/matcher.py 1.1.2.11 => 1.1.2.12 ===
--- Packages/ZConfig/matcher.py:1.1.2.11	Thu Dec 12 00:03:08 2002
+++ Packages/ZConfig/matcher.py	Thu Dec 12 13:11:20 2002
@@ -19,24 +19,21 @@
 
 
 class SectionMatcher:
-    def __init__(self, info, name=None):
+    def __init__(self, info, name):
         if name is not None:
-            self.name = info.nametype.convert(name)
-            if not info.isAllowedName(self.name):
-                raise ZConfig.ConfigurationError(
-                    "%s is not an allowed name for %s sections"
-                    % (`name`, `info.name`))
+            self.name = name
         elif info.allowUnnamed():
             self.name = None
         else:
             raise ZConfig.ConfigurationError(
                 `info.name` + " sections may not be unnamed")
         self.info = info
-        self._values = [None] * len(info)
+        self.type = info.sectiontype
+        self._values = [None] * len(self.type)
 
     def addSection(self, type, name, sectvalue):
-        i = self.info.getsectionindex(type, name)
-        ci = self.info.getsectioninfo(type, name)
+        i = self.type.getsectionindex(type, name)
+        ci = self.type.getsectioninfo(type, name)
         v = self._values[i]
         if v is None and ci.ismulti():
             v = [sectvalue]
@@ -47,9 +44,9 @@
             self._values[i] = sectvalue
 
     def addValue(self, key, value):
-        length = len(self.info)
+        length = len(self.type)
         for i in range(length):
-            k, ci = self.info[i]
+            k, ci = self.type[i]
             if k == key:
                 break
         else:
@@ -57,7 +54,7 @@
                 `key` + " is not a known key name")
         if ci.issection():
             if ci.name:
-                extra = " in %s sections" % `self.info.name`
+                extra = " in %s sections" % `self.type.name`
             else:
                 extra = ""
             raise ZConfig.ConfigurationError(
@@ -83,11 +80,11 @@
     def finish(self):
         """Check the constraints of the section and convert to an application
         object."""
-        length = len(self.info)
+        length = len(self.type)
         values = self._values
         attrnames = [None] * length
         for i in range(length):
-            key, ci = self.info[i]
+            key, ci = self.type[i]
             attrnames[i] = ci.attribute or key
             v = values[i]
             if v is None and ci.minOccurs:
@@ -119,7 +116,7 @@
     def constuct(self, attrnames):
         values = self._values
         for i in range(len(values)):
-            name, ci = self.info[i]
+            name, ci = self.type[i]
             dt = ci.datatype
             if ci.ismulti():
                 values[i] = [dt.convert(s) for s in values[i]]
@@ -129,20 +126,22 @@
         # XXX  Really should delay this until after all the
         # XXX  sibling SectionValue instances have been created and
         # XXX  we're ready to construct the parent.
-        if self.info.handler:
+        if self.info.handler is not None:
             v = self.info.handler(v)
         return v
 
 
 class SchemaMatcher(SectionMatcher):
     def __init__(self, info):
-        SectionMatcher.__init__(self, info, None)
+        self.info = info
+        self.type = info
+        self._values = [None] * len(info)
 
     def finish(self):
         # Since there's no outer container to call datatype.convert()
         # for the schema, we convert on the way out.
         v = SectionMatcher.finish(self)
-        return self.info.datatype.convert(v)
+        return self.type.datatype.convert(v)
 
 
 class SectionValue:


=== Packages/ZConfig/schema.py 1.1.2.7 => 1.1.2.8 ===
--- Packages/ZConfig/schema.py:1.1.2.7	Thu Dec 12 00:03:08 2002
+++ Packages/ZConfig/schema.py	Thu Dec 12 13:11:20 2002
@@ -34,6 +34,7 @@
 default_value_type = datatypes.get("str")
 default_key_type = datatypes.get("basic-key")
 default_name_type = default_key_type
+default_section_type = datatypes.NullConversion()
 
 _identifier = datatypes.get("identifier").convert
 
@@ -41,7 +42,7 @@
 class SchemaParser(xml.sax.ContentHandler):
 
     _cdata_tags = "description", "metadefault", "example", "default"
-    _handled_tags = "schema", "key", "section", "sectiongroup"
+    _handled_tags = "schema", "key", "section", "sectiongroup", "sectiontype"
 
     def __init__(self):
         self._cdata = None
@@ -49,7 +50,6 @@
         self._prefixes = []
         self._schema = None
         self._stack = []
-        self._sections = []
 
     def parseStream(self, stream):
         xml.sax.parse(stream, self)
@@ -111,67 +111,69 @@
             return name
 
     def push_prefix(self, attrs):
-        name = attrs.get("prefix")
-        self._prefixes.append(self.get_classname(name or ""))
+        name = attrs.get("prefix", "")
+        prefix = self.get_classname(name)
+        if prefix[:1] == ".":
+            raise ZConfig.ConfigurationError("prefix may not begin with '.'")
+        self._prefixes.append(prefix)
+
+    def get_datatype(self, attrs, key, default):
+        if attrs.has_key(key):
+            dtname = self.get_classname(attrs[key])
+            return datatypes.get(dtname)
+        else:
+            return default
+
+    def get_sect_typeinfo(self, attrs):
+        keytype = self.get_datatype(attrs, "keytype", default_key_type)
+        valuetype = self.get_datatype(attrs, "valuetype", default_value_type)
+        datatype = self.get_datatype(attrs, "type", default_section_type)
+        return keytype, valuetype, datatype
 
     def start_schema(self, attrs):
-        self._prefixes.append(attrs.get("prefix", ""))
-        handler = attrs.get("handler")
-        if handler:
-            handler = self.get_classname(handler)
-        if attrs.has_key("keytype"):
-            keytype = datatypes.get(attrs["keytype"])
-        else:
-            keytype = default_key_type
-        datatype = attrs.get("type")
-        if datatype:
-            datatype = datatypes.get(self.get_classname(datatype))
-        self._schema = info.SchemaInfo(datatype, handler, keytype)
-        self._sections = [self._schema]
+        self.push_prefix(attrs)
+        handler = self.get_classname(attrs.get("handler", "")) or None
+        keytype, valuetype, datatype = self.get_sect_typeinfo(attrs)
+        self._schema = info.SchemaType(keytype, valuetype, datatype, handler)
         self._stack = [self._schema]
 
     def end_schema(self):
         del self._prefixes[-1]
         assert not self._prefixes
-        del self._sections[-1]
-        assert not self._sections
+
+    def start_sectiontype(self, attrs):
+        name = attrs.get("name")
+        if not name:
+            raise ZConfig.ConfigurationError(
+                "sectiontype name must not be omitted or empty")
+        name = _identifier(name)
+        keytype, valuetype, datatype = self.get_sect_typeinfo(attrs)
+        sectinfo = info.SectionType(name, keytype, valuetype, datatype)
+        self._schema.addtype(sectinfo)
+        self._stack.append(sectinfo)
+
+    def end_sectiontype(self):
+        self._stack.pop()
 
     def start_section(self, attrs):
         self.push_prefix(attrs)
-        type = attrs.get("sectiontype")
+        type = attrs.get("type")
         if not type:
-            self.doSchemaError("section must specify sectiontype")
-        if attrs.has_key("keytype"):
-            keytype = datatypes.get(attrs["keytype"])
-        else:
-            keytype = default_key_type
-        if attrs.has_key("nametype"):
-            nametype = datatypes.get(attrs["nametype"])
-        else:
-            nametype = default_name_type
-        datatype = attrs.get("type")
-        if datatype:
-            datatype = datatypes.get(self.get_classname(datatype))
+            self.doSchemaError("section must specify type")
+        sectiontype = self._schema.gettype(type)
         maxOccurs, minOccurs, handler = self.get_common_info(attrs)
-        any, names, attribute = self.get_names_info(attrs, nametype)
+        any, name, attribute = self.get_name_info(attrs)
         if self._stack:
             parent = self._stack[-1]
         else:
             parent = self._schema
-        section = info.SectionInfo(type, datatype, minOccurs, maxOccurs,
-                                   handler,
-                                   attribute, keytype, names, nametype)
-        if any:
-            parent.addsection(None, section)
-        else:
-            for n in names:
-                parent.addsection(n, section)
+        section = info.SectionInfo(name, sectiontype, minOccurs, maxOccurs,
+                                   handler, attribute)
+        parent.addsection(name, section)
         self._stack.append(section)
-        self._sections.append(section)
 
     def end_section(self):
         del self._prefixes[-1]
-        del self._sections[-1]
         self._stack.pop().finish()
 
     def start_sectiongroup(self, attrs):
@@ -199,7 +201,7 @@
             attribute = _identifier(name.replace("-", "_"))
         key = info.KeyInfo(name, datatype, minOccurs, maxOccurs, handler,
                            attribute)
-        self._sections[-1].addkey(key)
+        self._stack[-1].addkey(key)
         self._stack.append(key)
 
     def end_key(self):
@@ -226,44 +228,21 @@
             handler = self.get_classname(handler)
         return maxOccurs, minOccurs, handler
 
-    def get_names_info(self, attrs, nametype):
-        if not attrs.get("names"):
-            self.doSchemaError("allowed section names must be specified")
-        anyname = None
-        names = []
-        s = attrs["names"]
-        if s in ("*", "+"):
-            aname = attrs.get("attribute")
+    def get_name_info(self, attrs):
+        name = attrs.get("name")
+        if not name:
+            self.doSchemaError("section name must be specified and non-empty")
+        aname = attrs.get("attribute")
+        if aname:
+            aname = _identifier(aname)
+        if name in ("*", "+"):
             if not aname:
                 self.doSchemaError(
-                    "container attribute must be specified when using"
-                    " '*' or '+' for section names")
-            return s, (), _identifier(aname)
-
-        for s in s.split("|"):
-            s = s.strip()
-            if not s:
-                self.doSchemaError("empty section name not allowed")
-            if s in ("*", "+"):
-                self.doSchemaError(
-                    "'+' and '*' cannot be combined with other names")
-            else:
-                s = nametype.convert(s)
-                if s in ("*", "+"):
-                    self.doSchemaError("nametypes may not convert to"
-                                       " '*' or '+'")
-                names.append(s)
-
-        if attrs.has_key("attribute"):
-            if len(names) > 1:
-                self.doSchemaError(
-                    "cannot give target attribute name with"
-                    " multiple named sections")
-            aname = _identifier(attrs["attribute"])
+                    "container attribute must be specified and non-empty"
+                    " when using '*' or '+' for a section name")
+            return name, None, aname
         else:
-            aname = None
-
-        return None, tuple(names), aname
+            return None, _identifier(name), aname
 
     def doSchemaError(self, message):
         raise ZConfig.ConfigurationError(message)