diff --git a/meta/README.txt b/meta/README.txt index ef07814..602d828 100644 --- a/meta/README.txt +++ b/meta/README.txt @@ -31,8 +31,8 @@ Loading options as Python code >>> ex = Executor(config) >>> code = """ - ... controller(names=['cmdline', 'telnet']) - ... controller.telnet.port = 5001 + ... controller(names=('cmdline', 'telnet')) + ... controller.telnet(port= 5001) ... scheduler(name='core') ... logger(name='default', standard=30) ... """ @@ -43,3 +43,14 @@ Loading options as Python code 'core' >>> config.logger.standard 30 + >>> config.controller.names + ('cmdline', 'telnet') + >>> config.controller.telnet.port + 5001 + + >>> print config + controller.telnet(port=5001) + controller(names=('cmdline', 'telnet')) + scheduler(name='core') + logger(name='default', standard=30) + diff --git a/meta/config.py b/meta/config.py index 4cbbc8f..93ea4c0 100644 --- a/meta/config.py +++ b/meta/config.py @@ -24,14 +24,42 @@ $Id$ from zope.interface import implements -from cybertools.meta.interfaces import IOptions -from cybertools.meta.namespace import AutoNamespace +from cybertools.meta.interfaces import IOptions, IConfigurator +from cybertools.meta.namespace import AutoNamespace, Executor, ExecutionError class Options(AutoNamespace): implements(IOptions) - def __init__(self, context=None): + +class Configurator(object): + + implements(IConfigurator) + + def __init__(self, context): self.context = context + def load(self, text=None, file=None): + if file is not None: + if hasattr(file, 'read'): + text = file.read() + else: # must be a file name + f = open(file, 'r') + text = f.read() + f.close() + result = Executor(self.context).execute(text) + if result: + raise ExecutionError('\n' + result) + + def dump(self, file=None): + text = str(options) + if file is not None: + if hasattr(file, 'write'): + file.write(text) + else: # must be a file name + f = open(file, 'w') + f.write(text) + f.close() + return text + diff --git a/meta/element.py b/meta/element.py index a09cabc..a9b046d 100644 --- a/meta/element.py +++ b/meta/element.py @@ -23,6 +23,8 @@ elements. $Id$ """ +from cStringIO import StringIO + from cybertools.util.jeep import Jeep _not_found = object() @@ -31,28 +33,25 @@ _not_found = object() class Element(dict): typeName = 'Element' - posArgs = ('name',) + posArgs = ('__name__',) + realAttributes = ('namespace', '__name__', 'factory', 'parent', 'children') - def __init__(self, namespace, name, collection=None, parent=None): + def __init__(self, namespace, name, factory=None, parent=None): self.namespace = namespace - self.name = name - self.collection = collection + self.__name__ = name self.parent = parent + self.factory = factory 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 + if isinstance(v, Element): + self[v.__name__] = v + elif idx < len(self.posArgs): + self[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 + self[k] = v + return self def __getitem__(self, key): if isinstance(key, (list, tuple)): @@ -68,44 +67,61 @@ class Element(dict): def __getattr__(self, key): result = self.get(key, _not_found) if result is _not_found: - raise AttributeError(key) + result = self.children.get(key, _not_found) + if result is _not_found: + raise AttributeError(key) return result + def __setattr__(self, key, value): + if key in self.realAttributes: + super(Element, self).__setattr__(key, value) + else: + self[key] = value + def __iter__(self): return iter(self.children) def __str__(self): - return self.name + return self.__name__ def __repr__(self): - return "<%s '%s'>" % (self.typeName, self.name) + return "<%s '%s'>" % (self.typeName, self.__name__) + + +class ElementFactory(object): + + elementClass = Element + + def __init__(self, namespace, name): + self.namespace = namespace + self.__name__ = name + self.instances = [] + + def __call__(self, *args, **kw): + elem = self.elementClass(self.namespace, '', factory=self) + for idx, v in enumerate(args): + if idx < len(elem.posArgs): + elem[elem.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__ + self.instances.append(elem) + return elem 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 + result = self.children.get(key, _not_found) + if result is _not_found: + result = self.__class__(self.namespace, key, parent=self) + self.children[key] = result return result def __getitem__(self, key): @@ -118,3 +134,16 @@ class AutoElement(Element): return result else: raise KeyError(key) + + def __str__(self): + out = StringIO() + for v in self.children: + out.write('%s.%s\n' % (self.__name__, v)) + if self or not self.children: + out.write(self.__name__) + if self: + out.write('(') + out.write(', '.join('%s=%r' % (k, v) for k, v in self.items())) + out.write(')') + return out.getvalue() + diff --git a/meta/interfaces.py b/meta/interfaces.py index 039aa48..78f5dad 100644 --- a/meta/interfaces.py +++ b/meta/interfaces.py @@ -27,5 +27,52 @@ from zope.interface import Interface, Attribute class IOptions(Interface): """ Provide a set of options (settings, configuration options, - preferences) based on a given context object. + preferences) based on a given context object or loaded from + a file. """ + + def __contains__(key): + """ Return True if this object provides the option identified + by the key given. + """ + + def __iter__(): + """ Return an iterator over the option keys provided by this + object. + """ + + def values(): + """ Return an iterator over all settings. + """ + + def __getitem__(key): + """ Return the value belonging to the key given. + """ + + def __getattr__(key): + """ Return the value belonging to the key given + (same as ``__getitem__()``). + """ + + def __str__(): + """ Return a string representation that shows all settings. + """ + + +class IConfigurator(Interface): + """ Adapter for an IOptions object that allows loading and saving + of configuration settings. + """ + + def load(text=None, file=None): + """ Load settings from the string or the file given. + + The ``file`` argument may be a string - that will be interpreted + as a file name or path - or a file object + """ + + def dump(file=None): + """ Return a string representation of the context's configuration + settings; if ``file`` is given write the representation to + the corresponding file object. + """ diff --git a/meta/namespace.py b/meta/namespace.py index 4b7ed11..3db565b 100644 --- a/meta/namespace.py +++ b/meta/namespace.py @@ -34,11 +34,11 @@ _not_found = object() class BaseNamespace(dict): - builtins = 'dir', 'output' + builtins = '__builtins__', 'dir', 'output' def __init__(self, *args, **kw): + self.__builtins__ = {} super(BaseNamespace, self).__init__(*args, **kw) - self['__builtins__'] = {} for key in self.builtins: self[key] = getattr(self, key) @@ -62,24 +62,33 @@ class BaseNamespace(dict): raise AttributeError(key) return result + def __str__(self): + def result(): + for k, v in self.items(): + if k not in self.builtins: + if isinstance(v, Element): + yield str(v) + else: + yield '%s=%r' % (k, v) + return '\n'.join(list(result())) + + __repr__ = object.__repr__ + class AutoNamespace(BaseNamespace): elementFactory = AutoElement def __getitem__(self, key): - result = self.get(key, _not_found) - if result is _not_found: - 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 + def __getattr__(self, key): + return self[key] + class Executor(object): @@ -129,3 +138,8 @@ class Evaluator(Executor): except: error = traceback.format_exc() return result, error + + +class ExecutionError(ValueError): + + pass diff --git a/meta/namespace.txt b/meta/namespace.txt index 053d72a..63e219a 100644 --- a/meta/namespace.txt +++ b/meta/namespace.txt @@ -62,20 +62,21 @@ Elements and sub-elements (children) ... """ >>> from cybertools.util.jeep import Jeep - >>> from cybertools.meta.element import Element - >>> topics = Jeep() + >>> from cybertools.meta.element import ElementFactory >>> symbols = namespace.BaseNamespace() - >>> symbols['topic'] = Element(symbols, 'topic', topics) - >>> symbols['annotation'] = Element(symbols, 'annotation') - >>> symbols['child'] = Element(symbols, 'child') + >>> symbols['topic'] = ElementFactory(symbols, 'topic') + >>> symbols['annotation'] = ElementFactory(symbols, 'annotation') + >>> symbols['child'] = ElementFactory(symbols, 'child') >>> exec code in symbols - >>> dict(topics) - {'python': , 'zope3': } - - >>> dict(topics['python'].children) - {'zope3': , 'annotation': } + >>> symbols.topic.instances + [, ] + >>> zope3 = symbols.topic.instances[0] + >>> dict(zope3.annotation) + {'author': 'jim'} + >>> zope3.title + 'Zope 3' A Namespace Automatically Generating Elements