diff --git a/meta/README.txt b/meta/README.txt index 2822ef8..ef07814 100644 --- a/meta/README.txt +++ b/meta/README.txt @@ -10,3 +10,36 @@ Configuration Options, Settings, Preferences >>> from cybertools.meta.config import Options + >>> config = Options() + +The Options object allows us to access arbitrary attributes that will +be created as elements within the Options object. + + >>> config.storage + + + >>> config.i18n.languages = ['de', 'en', 'it'] + + >>> config.i18n.languages + ['de', 'en', 'it'] + +Loading options as Python code +------------------------------ + + >>> from cybertools.meta.namespace import Executor + >>> config = Options() + >>> ex = Executor(config) + + >>> code = """ + ... controller(names=['cmdline', 'telnet']) + ... controller.telnet.port = 5001 + ... scheduler(name='core') + ... logger(name='default', standard=30) + ... """ + + >>> result = ex.execute(code) + + >>> config.scheduler.name + 'core' + >>> config.logger.standard + 30 diff --git a/meta/config.py b/meta/config.py index 071e543..4cbbc8f 100644 --- a/meta/config.py +++ b/meta/config.py @@ -22,17 +22,16 @@ Basic implementations for configuration options $Id$ """ -from zope.component import adapts -from zope.interface import implements, Interface +from zope.interface import implements from cybertools.meta.interfaces import IOptions +from cybertools.meta.namespace import AutoNamespace -class Options(object): +class Options(AutoNamespace): implements(IOptions) - adapts(Interface) - def __init__(self, context): + def __init__(self, context=None): self.context = context diff --git a/meta/element.py b/meta/element.py new file mode 100644 index 0000000..a09cabc --- /dev/null +++ b/meta/element.py @@ -0,0 +1,120 @@ +# +# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Building flexible hierarchical object structures with XML-like +elements. + +$Id$ +""" + +from cybertools.util.jeep import Jeep + +_not_found = object() + + +class Element(dict): + + typeName = 'Element' + posArgs = ('name',) + + def __init__(self, namespace, name, collection=None, parent=None): + self.namespace = namespace + self.name = name + self.collection = collection + self.parent = parent + self.children = Jeep() + + def __call__(self, *args, **kw): + elem = self.__class__(self.namespace, '') + for idx, v in enumerate(args): + if idx < len(self.posArgs): + elem[self.posArgs[idx]] = v + for k, v in kw.items(): + elem[k] = v + elem.name = elem.get('name') + if not elem.name: + elem.name = self.name + if self.collection is not None: + self.collection.append(elem) + return elem + + def __getitem__(self, key): + if isinstance(key, (list, tuple)): + return tuple(self[k] for k in key) + elif isinstance(key, Element): + self.children.append(key) + return key + elif isinstance(key, (int, long, basestring)): + return self.children[key] + else: + raise KeyError(key) + + def __getattr__(self, key): + result = self.get(key, _not_found) + if result is _not_found: + raise AttributeError(key) + return result + + def __iter__(self): + return iter(self.children) + + def __str__(self): + return self.name + + def __repr__(self): + return "<%s '%s'>" % (self.typeName, self.name) + + +class AutoElement(Element): + + typeName = 'AutoElement' + + def __call__(self, *args, **kw): + if self.collection is None: + elem = self + else: + elem = self.__class__(self.namespace, '') + self.collection.append(elem) + for idx, v in enumerate(args): + if idx < len(self.posArgs): + elem[self.posArgs[idx]] = v + for k, v in kw.items(): + elem[k] = v + elem.name = elem.get('name') + if not elem.name: + elem.name = self.name + return elem + + def __getattr__(self, key): + result = self.get(key, _not_found) + if result is _not_found: + result = self.__class__(self.namespace, key, parent=self) + self[key] = result + return result + + def __getitem__(self, key): + try: + return super(AutoElement, self).__getitem__(key) + except KeyError: + if isinstance(key, basestring): + result = self.__class__(self.namespace, key, parent=self) + self.children[key] = result + return result + else: + raise KeyError(key) diff --git a/meta/namespace.py b/meta/namespace.py index c203a29..4b7ed11 100644 --- a/meta/namespace.py +++ b/meta/namespace.py @@ -26,6 +26,7 @@ $Id$ import traceback +from cybertools.meta.element import Element, AutoElement from cybertools.util.jeep import Jeep _not_found = object() @@ -62,60 +63,21 @@ class BaseNamespace(dict): return result -class Element(object): - - posArgs = ('name',) - - def __init__(self, namespace, name, collection=None, parent=None): - self.namespace = namespace - self.name = name - self.collection = collection - self.parent = parent - self.subElements = Jeep() - - def __call__(self, *args, **kw): - elem = self.__class__(self.namespace, '', parent=self) - for idx, v in enumerate(args): - if idx < len(self.posArgs): - setattr(elem, self.posArgs[idx], v) - for k, v in kw.items(): - setattr(elem, k, v) - if not elem.name: - elem.name = self.name - if self.collection is not None: - self.collection.append(elem) - return elem - - def __getitem__(self, key): - if isinstance(key, (list, tuple)): - return tuple(self[k] for k in key) - elif isinstance(key, Element): - self.subElements.append(key) - return key - elif isinstance(key, (int, long, basestring)): - return self.subElements[key] - else: - print '*** Error', key - - def __str__(self): - return self.name - - def __repr__(self): - return "" % self.name - - class AutoNamespace(BaseNamespace): - elementFactory = Element + elementFactory = AutoElement def __getitem__(self, key): result = self.get(key, _not_found) if result is _not_found: - result = getattr(self, key, _not_found) - if result is _not_found: - elem = Element(self, key) - self[key] = elem - return elem + result = getattr(self, key) + return result + + def __getattr__(self, key): + result = self.get(key, _not_found) + if result is _not_found: + result = self.elementFactory(self, key) + self[key] = result return result diff --git a/meta/namespace.txt b/meta/namespace.txt index 10f599b..053d72a 100644 --- a/meta/namespace.txt +++ b/meta/namespace.txt @@ -50,8 +50,8 @@ namespace provides a secure restricted execution environment. ... NameError: open -Elements and sub-elements -------------------------- +Elements and sub-elements (children) +------------------------------------ >>> code = """ ... topic('zope3', title='Zope 3')[ @@ -62,18 +62,19 @@ Elements and sub-elements ... """ >>> from cybertools.util.jeep import Jeep + >>> from cybertools.meta.element import Element >>> topics = Jeep() >>> symbols = namespace.BaseNamespace() - >>> symbols['topic'] = namespace.Element(symbols, 'topic', topics) - >>> symbols['annotation'] = namespace.Element(symbols, 'annotation') - >>> symbols['child'] = namespace.Element(symbols, 'child') + >>> symbols['topic'] = Element(symbols, 'topic', topics) + >>> symbols['annotation'] = Element(symbols, 'annotation') + >>> symbols['child'] = Element(symbols, 'child') >>> exec code in symbols >>> dict(topics) {'python': , 'zope3': } - >>> dict(topics['python'].subElements) + >>> dict(topics['python'].children) {'zope3': , 'annotation': } @@ -86,7 +87,7 @@ A Namespace Automatically Generating Elements something >>> auto.something - + Execution of Python Code in a Namespace diff --git a/meta/tests.py b/meta/tests.py index a152241..94b5bfb 100755 --- a/meta/tests.py +++ b/meta/tests.py @@ -11,7 +11,7 @@ from zope.testing.doctestunit import DocFileSuite class Test(unittest.TestCase): - "Basic tests for the storage package." + "Basic tests for the meta package." def testBasicStuff(self): pass diff --git a/util/jeep.py b/util/jeep.py index e369a0a..f545561 100644 --- a/util/jeep.py +++ b/util/jeep.py @@ -111,7 +111,7 @@ class Jeep(object): if key is _notfound: raise AttributeError("No name attribute present") if key in self.keys(): - raise ValueError("Object already present") + raise ValueError("Object '%s' already present" % key) self._sequence.insert(idx, key) object.__setattr__(self, key, obj)