[Zope-CVS] CVS: Packages/ZConfig - ApacheStyle.py:1.1 Common.py:1.1 Config.py:1.1 Context.py:1.1 __init__.py:1.1

Fred L. Drake, Jr. fdrake@acm.org
Tue, 8 Oct 2002 17:42:18 -0400


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

Added Files:
	ApacheStyle.py Common.py Config.py Context.py __init__.py 
Log Message:
Initial version of new configuration parser and support.
This is still in development.


=== Added File Packages/ZConfig/ApacheStyle.py ===
"""Configuration parser."""

import urlparse

from Common import *


def Parse(file, context, section, url):
    lineno = 0
    stack = []
    while 1:
        line = file.readline()
        if not line:
            break
        lineno += 1
        line = line.strip()
        if not line:
            # blank line
            continue
        if line[0] == "#":
            # comment
            continue
        if line[:2] == "</":
            # section end
            if line[-1] != ">":
                raise ConfigurationSyntaxError(
                    "malformed section end", url, lineno)
            if not stack:
                raise ConfigurationSyntaxError(
                    "unexpected section end", url, lineno)
            type = line[2:-1].rstrip()
            if type.lower() != section.type:
                raise ConfigurationSyntaxError(
                    "unbalanced section end", url, lineno)
            section = stack.pop()
            continue
        if line[0] == "<":
            # section start
            if line[-1] != ">":
                raise ConfigurationSyntaxError(
                    "malformed section start", url, lineno)
            isempty = line[-2] == "/"
            if isempty:
                text = line[1:-2].rstrip()
            else:
                text = line[1:-1].rstrip()
            # parse section start stuff here
            m = _section_start_rx.match(text)
            if not m:
                raise ConfigurationSyntaxError(
                    "malformed section header", url, lineno)
            type, name, delegatename = m.group('type', 'name', 'delegatename')
            try:
                newsect = context.nestSection(section, type, name,
                                              delegatename)
            except ConfigurationError, e:
                raise ConfigurationSyntaxError(e[0], url, lineno)
            if not isempty:
                stack.append(section)
                section = newsect
            continue
        # key-value
        m = _keyvalue_rx.match(line)
        if not m:
            raise ConfigurationSyntaxError(
                "malformed configuration data", url, lineno)
        key, value = m.group('key', 'value')
        if key == "import":
            if stack:
                raise ConfigurationSyntaxError(
                    "import only allowed at the outermost level of a resource",
                    url, lineno)
            newurl = urlparse.urljoin(url, value)
            context.importConfiguration(section, newurl)
        elif key == "include":
            newurl = urlparse.urljoin(section.url, value)
            context.includeConfiguration(section, newurl)
        else:
            try:
                section.addValue(key, value)
            except ConfigurationError, e:
                raise ConfigurationSyntaxError(e[0], url, lineno)
    if stack:
        raise ConfigurationSyntaxError(
            "unclosed sections no allowed", url, lineno + 1)


import re
# _name_re cannot allow "(" or ")" since we need to be able to tell if
# a section has a name or not: <section (name)> would be ambiguous if
# parentheses were allowed in names.
_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"(?:\s*[(](?P<delegatename>%s)[)])?"
                               r"$"
                               % (_name_re, _name_re, _name_re))
del re


=== Added File Packages/ZConfig/Common.py ===
"""Names used from all modules in the package.

Since some names are only defined if needed, this module should be
imported using the from-import-* syntax.
"""

try:
    True
except NameError:
    True = 1
    False = 0


class ConfigurationError(Exception):
    def __init__(self, msg):
        self.message = msg
        Exception.__init__(self, msg)

    def __str__(self):
        return self.message


class ConfigurationMissingSectionError(ConfigurationError):
    def __init__(self, type, name=None):
        self.type = type
        self.name = name
        details = 'Missing section, type: %s' % type
        if name is not None:
            details += ', name: %s' % name
        ConfigurationError.__init__(self, details + ')')


class ConfigurationConflictingSectionError(ConfigurationError):
    def __init__(self, type, name=None):
        self.type = type
        self.name = name
        details = 'Conflicting sections, (type: %s' % type
        if name is not None:
            details += ', name: %s' % name
        ConfigurationError.__init__(self, details + ')')


class ConfigurationSyntaxError(ConfigurationError):
    def __init__(self, msg, url, lineno):
        self.message = msg
        self.url = url
        self.lineno = lineno
        ConfigurationError.__init__(self, msg)

    def __str__(self):
        return "%s\n(line %s in %s)" % (self.message, self.lineno, self.url)


class ConfigurationTypeError(ConfigurationError):
    def __init__(self, msg, found, expected):
        self.message = msg
        self.found = found
        self.expected = expected
        ConfigurationError.__init__(self, msg)


=== Added File Packages/ZConfig/Config.py ===
"""Configuration data structure."""

from Common import *

class Configuration:
    def __init__(self, type, name, url):
        self.type = type
        self.name = name or None
        self.delegate = None
        self.url = url
        self._sections_by_name = {}
        self._sections = []
        self._data = {}

    def __repr__(self):
        klass = self.__class__
        classname = "%s.%s" % (klass.__module__, klass.__name__)
        if self.name:
            return "<%s for %s (type %s) at %#x>" \
                   % (classname, repr(self.name),
                      repr(self.type), id(self))
        elif self.type:
            return "<%s (type %s) at 0x%x>" \
                   % (classname, repr(self.type), id(self))
        else:
            return "<%s at 0x%x>" % (classname, id(self))

    def setDelegate(self, section):
        if self.delegate is not None:
            raise ConfigurationError("cannot modify delegation")
        self.delegate = section

    def addChildSection(self, section):
        """Add a section that is a child of this one."""
        if section.name:
            self.addNamedSection(section)
        elif not section.type:
            raise ValeuError("'type' must be specified")
        self._sections.append(section)

    def addNamedSection(self, section):
        """Add a named section that may"""
        name = section.name
        type = section.type
        if not type:
            raise ValeuError("'type' must be specified")
        key = type, name
        child = self._sections_by_name.get(key)
        if child is None or child.url != self.url:
            self._sections_by_name[key] = section
        else:
            raise ConfigurationError(
                "cannot replace existing named section")

    def getSection(self, type, name=None):
        # get section by name, relative to this section
        type = type.lower()
        if name:
            return self._sections_by_name[(type, name.lower())]
        else:
            L = []
            for sect in self._sections:
                if sect.type == type:
                    L.append(sect)
            if len(L) > 1:
                raise ConfigurationConflictingSectionError(type, name)
            if L:
                return L[0]
            elif self.delegate:
                return self.delegate.getSection(type)
            else:
                return None

    def getChildSections(self):
        return self._sections[:]

    def addValue(self, key, value):
        key = key.lower()
        try:
            self._data[key]
        except KeyError:
            self._data[key] = value
        else:
            raise ConfigurationError("cannot add existing key")

    def setValue(self, key, value):
        key = key.lower()
        self._data[key] = value

    def items(self):
        """Returns a list of key-value pairs for this section.

        The returned list includes pairs retrieved from the delegation chain.
        """
        if self.delegate is None:
            return self._data.items()
        else:
            L = [self._data]
            while self.delegate is not None:
                self = self.delegate
                L.append(self._data)
            d = L.pop().copy()
            L.reverse()
            for m in L:
                d.update(m)
            return d.items()

    def keys(self):
        if self.delegate is None:
            return self._data.keys()
        else:
            L1 = self.delegate.keys()
            L2 = self._data.keys()
            for k in L1:
                if k not in L2:
                    L2.append(k)
            return L2

    def get(self, key, default=None):
        key = key.lower()
        try:
            return self._data[key]
        except KeyError:
            if self.delegate is None:
                return default
            else:
                return self.delegate.get(key, default)

    _boolean_values = {
         'true': True,  'yes': True,   'on': True,
        'false': False,  'no': False, 'off': False,
        } 

    def getbool(self, key, default=None):
        missing = []
        s = self.get(key, missing)
        if s is missing:
            return default
        try:
            return self._boolean_values[s.lower()]
        except KeyError:
            raise ValueError("%s is not a valid boolean value" % repr(s))

    def getfloat(self, key, default=None, min=None, max=None):
        missing = []
        s = self.get(key, missing)
        if s is missing:
            return default
        x = float(self.get(key))
        self._check_range(key, x, min, max)
        return x

    def getint(self, key, default=None, min=None, max=None):
        missing = []
        s = self.get(key, missing)
        if s is missing:
            return default
        x = int(s)
        self._check_range(key, x, min, max)
        return x

    def _check_range(self, key, x, min, max):
        if min is not None and x < min:
            raise ValueError("value for %s must be at least %s, found %s"
                             % (repr(key), min, x))
        if max is not None and x > max:
            raise ValueError("value for %s must be no more than %s, found %s"
                             % (repr(key), max, x))


class ImportingConfiguration(Configuration):
    def __init__(self, *args):
        self._imports = []
        Configuration.__init__(self, *args)

    def addImport(self, section):
        self._imports.append(section)

    def get(self, key, default=None):
        s = Configuration.get(self, key, default)
        if s is default:
            for config in self._imports:
                s = config.get(key, default)
                if s is not default:
                    break
        return s


=== Added File Packages/ZConfig/Context.py ===
"""Top-level configuration handle."""

import urllib2

from Common import *
from Config import Configuration, ImportingConfiguration


class Context:

    def __init__(self):
        #Configuration.__init__(self, None, None, url)
        self._imports = []         # URL  -> Configuration
        self._named_sections = {}  # name -> Configuration
        self._needed_names = {}    # name -> [needy Configuration, ...]
        self._current_imports = []

    # subclass-support API

    def createImportedSection(self, section, url):
        return ImportingConfiguration(None, None, url)

    def createNestedSection(self, section, type, name, delegatename):
        if name:
            name = name.lower()
        return Configuration(type.lower(), name, section.url)

    def createToplevelSection(self, url):
        return ImportingConfiguration(None, None, url)

    def getDelegateType(self, type):
        # Applications must provide delegation typing information by
        # overriding the Context.getDelegateType() method.
        return type.lower()

    def parse(self, file, section, url):
        from ApacheStyle import Parse
        Parse(file, self, section, url)

    # public API

    def load(self, url):
        top = self.createToplevelSection(url)
        self._imports = [top]
        self._parse_url(url, top)
        self._finish()
        return top

    # interface for parser

    def importConfiguration(self, section, url):
        for config in self._imports:
            if config.url == url:
                return config
        newsect = self.createImportedSection(section, url)
        self._imports.append(newsect)
        section.addImport(newsect)
        self._parse_url(url, newsect)

    def includeConfiguration(self, section, url):
        # XXX we always re-parse, unlike import
        file = urllib2.urlopen(url)
        try:
            self.parse(file, section, url)
        finally:
            file.close()

    def nestSection(self, section, type, name, delegatename):
        if name:
            name = name.lower()
        type = type.lower()
        if name and self._named_sections.has_key(name):
            # Make sure sections of the same name are not defined
            # twice in the same resource, and that once a name has
            # been defined, its type is not changed by a section from
            # another resource.
            oldsect = self._named_sections[name]
            if oldsect.url == section.url:
                raise ConfigurationError(
                    "named section cannot be defined twice in same resource")
            if oldsect.type != type:
                raise ConfigurationError(
                    "named section cannot change type")
        newsect = self.createNestedSection(section, type, name, delegatename)
        if delegatename:
            # The knitting together of the delegation graph needs this.
            try:
                L = self._needed_names[delegatename]
            except KeyError:
                L = []
                self._needed_names[delegatename] = L
            L.append(newsect)
        section.addChildSection(newsect)
        if name:
            self._named_sections[name] = newsect
            current = self._current_imports[-1]
            if section is not current:
                current.addNamedSection(newsect)
            for config in self._current_imports[:-1]:
                # XXX seems very painful
                if not config._sections_by_name.has_key((type, name)):
                    config.addNamedSection(newsect)
        return newsect

    # internal helpers

    def _parse_url(self, url, section):
        file = urllib2.urlopen(url)
        self._current_imports.append(section)
        try:
            self.parse(file, section, url)
        finally:
            del self._current_imports[-1]
            file.close()

    def _finish(self):
        # Resolve section delegations
        for name, L in self._needed_names.items():
            section = self._named_sections[name]
            for referrer in L:
                type = self.getDelegateType(referrer.type)
                if type is None:
                    raise ConfigurationTypeError(
                        "%s sections are not allowed to specify delegation\n"
                        "(in %s)"
                        % (repr(referrer.type), referrer.url),
                        referrer.type, None)
                type = type.lower()
                if type != section.type:
                    raise ConfigurationTypeError(
                        "%s sections can only inherit from %s sections\n"
                        "(in %s)"
                        % (repr(referrer.type), repr(type), referrer.url),
                        referrer.type, type)
                referrer.setDelegate(section)
        self._needed_names = None


=== Added File Packages/ZConfig/__init__.py ===
##############################################################################
#
# Copyright (c) 2002 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.
#
##############################################################################
"""Configuration data structures and loader for the ZRS.

$Id: __init__.py,v 1.1 2002/10/08 21:42:17 fdrake Exp $
"""

def load(url):
    import Context
    return Context.Context().load(url)