[Zope-CVS] CVS: Packages/zpkgtools/zpkgtools - cfgparser.py:1.1

Fred L. Drake, Jr. fred at zope.com
Thu Mar 11 09:54:34 EST 2004


Update of /cvs-repository/Packages/zpkgtools/zpkgtools
In directory cvs.zope.org:/tmp/cvs-serv27275

Added Files:
	cfgparser.py 
Log Message:
ZConfig-lite configuration parser:  A very thin configuration parser
that accepts a subset of the ZConfig syntax and raises errors for what
isn't supported.

Schemas are *much* more limited, and are defined by simple Python data
structures.


=== Added File Packages/zpkgtools/zpkgtools/cfgparser.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Extra-lite parser for a ZConfig-like configuration syntax.

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.
"""

import re


class ConfigurationError(Exception):
    """Exception raised for errors in a configuration file."""

    def __init__(self, message, url=None, lineno=None):
        Exception.__init__(self, message)
        self.url = url
        self.lineno = lineno


class Schema:
    """Schema definition that can be used by the Parser class to
    construct a configuration.

    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 __init__(self, toplevel, typedefs=None):
        """Initialize a schema definition based on type definitions.

        'toplevel' is a type definition that represents the otherwise
        anonymous top-level section of a configuration.

        'typedefs' is a 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.

        """
        self._toplevel = toplevel
        if typedefs is None:
            typedefs = {}
        self._typedefs = typedefs

    def getConfiguration(self):
        return self.createSection(None, None, self._toplevel)

    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 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)

    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)


# 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))

_nulljoin = "".join


class Parser:

    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()
        else:
            return True, None

    def load(self):
        section = self.schema.getConfiguration()
        self.parse(section)
        return self.schema.finishSection(section)

    def parse(self, section):
        done, line = self.nextline()
        while not done:
            if line[:1] in ("", "#"):
                # blank line or comment
                pass

            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()
        if name:
            # XXX Argh!  This was a mistake in ZConfig, but can't
            # change it here.
            name = name.lower()
        newsect = self.schema.startSection(section, type, name)
        if isempty:
            self.schema.endSection(section, type, name, newsect)
            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")
        self.schema.endSection(prevsection, type, name, section)
        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 = ''
        self.schema.addValue(section, key, value)

    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 self._name

    def getSectionType(self):
        return self._typename

    def getSectionDefinition(self):
        return self._typedef




More information about the Zope-CVS mailing list