[Zope-Checkins] CVS: Zope/lib/python/ZConfig - ApacheStyle.py:1.1.4.1 BRANCHES.txt:1.1.4.1 Common.py:1.1.4.1 Config.py:1.2.4.1 Context.py:1.1.4.1 Interpolation.py:1.5.4.1 __init__.py:1.1.4.1
Chris McDonough
chrism@zope.com
Thu, 10 Oct 2002 14:29:13 -0400
Update of /cvs-repository/Zope/lib/python/ZConfig
In directory cvs.zope.org:/tmp/cvs-serv2630
Added Files:
Tag: chrism-install-branch
ApacheStyle.py BRANCHES.txt Common.py Config.py Context.py
Interpolation.py __init__.py
Log Message:
Using "repolinked" ZConfig package rather than one checked directly in
to this branch.
=== Added File Zope/lib/python/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 Zope/lib/python/ZConfig/BRANCHES.txt ===
Branches defined specifically for the ZConfig package are listed
below. The canonical version of this list is on the HEAD of the
ZConfig package.
zconfig-brace-syntax
An example of an alternate syntax for ZConfig. This syntax was
developed while trying the package initially, but was rejected.
It is saved on a branch to avoid losing historical information.
=== Added File Zope/lib/python/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 Zope/lib/python/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 ValueError("'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 ValueError("'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 Zope/lib/python/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 Zope/lib/python/ZConfig/Interpolation.py ===
"""Interpolation support for ZConfig values.
XXX document syntax here
"""
class InterpolationError(Exception):
"""Base exception for string interpolation errors."""
def __init__(self, msg, context):
self.message = msg
self.context = context
def __str__(self):
return self.message
class InterpolationSyntaxError(InterpolationError):
"""Raised when interpolation source text contains syntactical errors."""
def __init__(self, msg, context):
if context is not None:
context = context[:]
InterpolationError.__init__(self, msg, context)
class InterpolationRecursionError(InterpolationError):
"""Raised when a nested interpolation is recursive."""
def __init__(self, name, context):
self.name = name
msg = ("recursion on %s; current context:\n%s"
% (repr(name), ", ".join(context)))
InterpolationError.__init__(self, msg, context[:])
def get(section, name, default=None):
# XXX should this interpolate from default if that's what's used?
missing = []
s = section.get(name, missing)
if s is missing:
return default
if "$" in s:
accum = []
_interp(accum, s, section, [name])
s = ''.join(accum)
return s
def interpolate(s, section):
"""Interpolate variables from `section` into `s`."""
if '$' in s:
accum = []
_interp(accum, s, section, None)
s = ''.join(accum)
return s
def _interp(accum, rest, section, context):
while 1:
i = rest.find("$")
if i < 0:
accum.append(rest)
break
accum.append(rest[:i])
rest = rest[i+1:]
if not rest:
accum.append("$")
break
if rest[0] == "$":
accum.append("$")
rest = rest[1:]
elif rest[0] == "{":
rest = rest[1:]
m = _name_match(rest[:])
if not m:
raise InterpolationSyntaxError("'${' not followed by name",
context)
name = m.group(0)
length = len(name)
if rest[length:length+1] != "}":
raise InterpolationSyntaxError(
"'${%s' not followed by '}'" % name, context)
v = section.get(name, "")
if "$" in v and context:
if name in context:
raise InterpolationRecursionError(name, context)
_interp(accum, v, section, context + [name])
else:
accum.append(v)
rest = rest[length+1:]
else:
m = _name_match(rest)
if not m:
accum.append("$")
continue
name = m.group(0)
v = section.get(name, "")
if "$" in v and context:
if name in context:
raise InterpolationRecursionError(name, context)
_interp(accum, v, section, context + [name])
else:
accum.append(v)
rest = rest[len(name):]
import re
_name_match = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*").match
del re
=== Added File Zope/lib/python/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.4.1 2002/10/10 18:29:12 chrism Exp $
"""
def load(url):
import Context
return Context.Context().load(url)