From caf8b60b8319ddcfe7f0e776f97ba36cfd578515 Mon Sep 17 00:00:00 2001 From: helmutm Date: Tue, 9 Oct 2007 05:50:38 +0000 Subject: [PATCH] added util.config (experimental); clean-up of doctests git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@2103 fd906abe-77d9-0310-91a1-e0d9ade77398 --- browser/README.txt | 4 +- composer/schema/README.txt | 2 +- composer/schema/factory.py | 3 + composer/schema/interfaces.py | 1 + relation/ftests.py | 6 +- util/config.py | 143 ++++++++++++++++++++++++++++++++++ util/config.txt | 100 ++++++++++++++++++++++++ util/tests.py | 1 + view/web/template.py | 2 +- 9 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 util/config.py create mode 100644 util/config.txt diff --git a/browser/README.txt b/browser/README.txt index 20cbbb5..184056b 100644 --- a/browser/README.txt +++ b/browser/README.txt @@ -171,8 +171,8 @@ The default configurator uses attribute annotations for retrieving view properties; that means that there could be a form somewhere to edit those properties and store them in the content object's annotations. - >>> from zope.app.annotation.interfaces import IAttributeAnnotatable, IAnnotations - >>> from zope.app.annotation.attribute import AttributeAnnotations + >>> from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations + >>> from zope.annotation.attribute import AttributeAnnotations >>> component.provideAdapter(AttributeAnnotations, (SomeObject,), IAnnotations) The configurator is called automatically from the controller if there is diff --git a/composer/schema/README.txt b/composer/schema/README.txt index d921fb5..61a54ff 100644 --- a/composer/schema/README.txt +++ b/composer/schema/README.txt @@ -166,5 +166,5 @@ Macros / renderers >>> fieldRenderers = form.fieldRenderers >>> sorted(fieldRenderers.keys()) - [u'field', u'field_spacer', u'fields', u'form', u'input_dropdown', + [u'field', u'field_spacer', u'fields', u'form', u'input_date', u'input_dropdown', u'input_fileupload', u'input_password', u'input_textarea', u'input_textline'] diff --git a/composer/schema/factory.py b/composer/schema/factory.py index f7791a7..dd6d855 100644 --- a/composer/schema/factory.py +++ b/composer/schema/factory.py @@ -33,6 +33,9 @@ from cybertools.composer.schema.schema import Schema class SchemaFactory(object): + """ Creates a cybertools.composer schema from an + interface (a zope.schema schema). + """ implements(ISchemaFactory) adapts(Interface) diff --git a/composer/schema/interfaces.py b/composer/schema/interfaces.py index 80174a9..555a49b 100644 --- a/composer/schema/interfaces.py +++ b/composer/schema/interfaces.py @@ -95,6 +95,7 @@ fieldTypes = SimpleVocabulary(( fieldRenderer='field_spacer', storeData=False), )) +# TODO: move this to organize.service... standardFieldNames = SimpleVocabulary(( SimpleTerm('', '', 'Not selected'), SimpleTerm('lastName', 'lastName', 'Last name'), diff --git a/relation/ftests.py b/relation/ftests.py index d104597..d290eaa 100755 --- a/relation/ftests.py +++ b/relation/ftests.py @@ -3,7 +3,7 @@ import unittest, doctest from zope.app.testing.functional import FunctionalTestCase from zope.app.testing import setup -from zope.testbrowser import Browser +from zope.testbrowser.testing import Browser from zope.app import component, intid, zapi @@ -23,7 +23,7 @@ class BrowserTest(FunctionalTestCase): key = default.registrationManager.addRegistration(reg) default.registrationManager[key].status = component.interfaces.registration.ActiveStatus - def test(self): + def test(self): browser = Browser() browser.handleErrors = False browser.addHeader('Authorization', 'Basic mgr:mgrpw') @@ -38,7 +38,7 @@ class BrowserTest(FunctionalTestCase): button = browser.getControl('Apply') button.click() self.assert_(browser.isHtml) - + def test_suite(): flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS diff --git a/util/config.py b/util/config.py new file mode 100644 index 0000000..190e71f --- /dev/null +++ b/util/config.py @@ -0,0 +1,143 @@ +# +# Copyright (c) 2007 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 +# + +""" +Formulating configuration options. + +$Id$ +""" + + +import os +from zope.interface import implements + + +_not_found = object() + + +class Configurator(dict): + + filenamePrefix = 'cybertools' + + def __init__(self, *sections, **kw): + for s in sections: + setattr(self, s, ConfigSection(s)) + self.filename = kw.get('filename') + + def __getitem__(self, key): + item = getattr(self, key, _not_found) + if item is _not_found: + item = ConfigSection(key) + setattr(self, key, item) + return item + + def load(self, p=None, filename=None): + if p is None: + fn = self.getConfigFile(filename) + if fn is not None: + f = open(fn, 'r') + p = f.read() + f.close() + if p is None: + return + exec p in self + + def save(self, filename=None): + fn = self.getConfigFile(filename) + if fn is None: + fn = self.getDefaultConfigFile() + if fn is not None: + f = open(fn, 'w') + f.write(repr(self)) + f.close() + + def __repr__(self): + result = [] + for name, value in self.__dict__.items(): + if isinstance(value, ConfigSection): + value.collect(name, result) + return '\n'.join(sorted(result)) + + def getConfigFile(self, filename=None): + if filename is not None: + self.filename = filename + if self.filename is None: + fn = self.getDefaultConfigFile() + if os.path.isfile(fn): + self.filename = fn + return self.filename + + def getDefaultConfigFile(self): + return os.path.join(os.path.expanduser('~'), + '.%s.cfg' % self.filenamePrefix) + + +class ConfigSection(list): + + __name__ = '???' + + def __init__(self, name=None): + if name is not None: + self.__name__ = name + + def __getattr__(self, attr): + value = ConfigSection(attr) + setattr(self, attr, value) + return value + + def __getitem__(self, idx): + while idx >= len(self): + self.append(ConfigSection()) + return list.__getitem__(self, idx) + + def setdefault(self, attr, value): + if attr not in self.__dict__: + setattr(self, attr, value) + return value + return getattr(self, attr) + + def items(self): + for name, value in self.__dict__.items(): + if isinstance(value, (str, int)): + yield name, value + + def __call__(self, *args, **kw): + for s in args: + if isinstance(s, ConfigSection): + # should we update an existing entry? + #old = getattr(self, s.__name__, None) + #if old is not None: # this would have to be done recursively + # old.__dict__.update(s.__dict__) + # for elem in s: + # old.append(elem) + #else: + # or just keep the new one? + setattr(self, s.__name__, s) + for k, v in kw.items(): + setattr(self, k, v) + return self + + def collect(self, ident, result): + for idx, element in enumerate(self): + element.collect('%s[%i]' % (ident, idx), result) + for name, value in self.__dict__.items(): + if isinstance(value, ConfigSection): + value.collect('%s.%s' % (ident, name), result) + elif name != '__name__' and isinstance(value, (str, int)): + result.append('%s.%s = %s' % (ident, name, repr(value))) + diff --git a/util/config.txt b/util/config.txt new file mode 100644 index 0000000..8a56a74 --- /dev/null +++ b/util/config.txt @@ -0,0 +1,100 @@ +============================= +Setting Configuration Options +============================= + +$Id$ + + >>> from cybertools import util + >>> from cybertools.util.config import Configurator + >>> config = Configurator() + + >>> config.load('transport.serverURL = "http://demo.cy55.de"') + >>> config.transport.serverURL + 'http://demo.cy55.de' + +This setting may also contain indexed access; thus we can model +configuration parameters with multiple instances (like crawling +jobs). + + >>> config.load(''' + ... crawl[0].type = "filesystem" + ... crawl[0].directory = "documents/projects" + ... ''') + >>> config.crawl[0].type + 'filesystem' + >>> config.crawl[0].directory + 'documents/projects' + +Subsections are created automatically when they are first accessed. + + >>> config.load('ui.web.port = 8081') + >>> config.ui.web.port + 8081 + +The ``setdefault()`` method allows to retrieve a value and set +it with a default if not found, in one statement. + + >>> config.ui.web.setdefault('port', 8080) + 8081 + >>> config.transport.setdefault('userName', 'demo') + 'demo' + + >>> sorted(config.transport.items()) + [('__name__', 'transport'), ('serverURL', 'http://demo.cy55.de'), + ('userName', 'demo')] + +We can output a configuration in a form that is ready for loading +just by converting it to a string representation. + + >>> print config + crawl[0].directory = 'documents/projects' + crawl[0].type = 'filesystem' + transport.serverURL = 'http://demo.cy55.de' + transport.userName = 'demo' + ui.web.port = 8081 + +The configuration may also be saved to a file - +for testing purposes let's use the cybertools.util package directory +for storage; normally it would be stored in the users home directory. + + >>> import os + >>> os.environ['HOME'] = os.path.dirname(util.__file__) + + >>> config.save() + + >>> fn = config.getDefaultConfigFile() + >>> fn + '....cybertools.cfg' + + >>> print open(fn).read() + crawl[0].directory = 'documents/projects' + crawl[0].type = 'filesystem' + transport.serverURL = 'http://demo.cy55.de' + transport.userName = 'demo' + ui.web.port = 8081 + +The simplified syntax +--------------------- + + >>> config.load(''' + ... ui( + ... web( + ... port=11080, + ... )) + ... crawl[1]( + ... type='outlook', + ... folder='inbox', + ... ) + ... ''') + >>> config.ui.web.port + 11080 + >>> config.crawl[1].type + 'outlook' + + >>> #print config + +Cleaning up +----------- + + >>> os.unlink(fn) + diff --git a/util/tests.py b/util/tests.py index cf12bc1..449a190 100755 --- a/util/tests.py +++ b/util/tests.py @@ -20,6 +20,7 @@ def test_suite(): #doctest.DocTestSuite(cybertools.util.property, optionflags=flags), doctest.DocFileSuite('adapter.txt', optionflags=flags), doctest.DocFileSuite('aop.txt', optionflags=flags), + doctest.DocFileSuite('config.txt', optionflags=flags), doctest.DocFileSuite('defer.txt', optionflags=flags), doctest.DocFileSuite('format.txt', optionflags=flags), doctest.DocFileSuite('property.txt', optionflags=flags), diff --git a/view/web/template.py b/view/web/template.py index dfdb0a1..96c53fd 100644 --- a/view/web/template.py +++ b/view/web/template.py @@ -22,7 +22,7 @@ Generic template base class. $Id$ """ -from zope.app.traversing.adapters import DefaultTraversable +from zope.traversing.adapters import DefaultTraversable from zope import component