diff --git a/configure.zcml b/configure.zcml index d482317..7a60e6a 100644 --- a/configure.zcml +++ b/configure.zcml @@ -417,8 +417,11 @@ + name="varsubdir" /> + + + diff --git a/integrator/README.txt b/integrator/README.txt index 2c931f1..662dc50 100644 --- a/integrator/README.txt +++ b/integrator/README.txt @@ -29,6 +29,63 @@ configuration): >>> len(concepts) + len(resources) 18 + +External Collections +==================== + +The basis of our work will be ExternalCollection objects, i.e. concepts +of the 'extcollection' type. We use an adapter for providing the attributes +and methods of the external collect object. + + >>> from loops.concept import Concept + >>> from loops.setup import addObject + >>> from loops.integrator.collection import ExternalCollectionAdapter + >>> tExternalCollection = concepts['extcollection'] + >>> coll01 = addObject(concepts, Concept, 'coll01', + ... title=u'Collection One', type=tExternalCollection) + >>> aColl01 = ExternalCollectionAdapter(coll01) + +An external collection carries a set of attributes that control the access +to the external system: + + >>> aColl01.providerName, aColl01.baseAddress, aColl01.address, aColl01.pattern + (None, None, None, None) + >>> from loops.integrator.testsetup import dataDir + >>> aColl01.baseAddress = dataDir + >>> aColl01.address = 'topics' + + +Directory Collection Provider +----------------------------- + +The DirectoryCollectionProvider collects files from a directory in the +file system. The parameters (directory paths) are provided by the calling +object, the external collection itself. + + >>> from loops.integrator.collection import DirectoryCollectionProvider + >>> dcp = DirectoryCollectionProvider() + + >>> sorted(dcp.collect(aColl01)) + ['programming/BeautifulProgram.pdf', 'programming/zope/zope3.txt'] + +If we provide a selective pattern we get only part of the files: + + >>> aColl01.pattern = r'.*\.txt' + >>> sorted(dcp.collect(aColl01)) + ['programming/zope/zope3.txt'] + +Let's now create the corresponding resource objects. + + >>> aColl01.pattern = '' + >>> addresses = dcp.collect(aColl01) + >>> res = list(dcp.createExtFileObjects(aColl01, addresses)) + >>> len(sorted(r.__name__ for r in res)) + 2 + >>> xf1 = res[0] + >>> xf1.__name__ + u'programming/BeautifulProgram.pdf' + + Fin de partie ============= diff --git a/integrator/collection.py b/integrator/collection.py new file mode 100644 index 0000000..b978706 --- /dev/null +++ b/integrator/collection.py @@ -0,0 +1,99 @@ +# +# 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 +# + +""" +Concept adapter(s) for external collections, e.g. a directory in the +file system. + +$Id$ +""" + +import os, re +from zope.component import adapts +from zope.interface import implements, Attribute +from zope.cachedescriptors.property import Lazy +from zope.schema.interfaces import IField +from zope.traversing.api import getName, getParent + +from cybertools.typology.interfaces import IType +from loops.common import AdapterBase +from loops.interfaces import IResource, IConcept +from loops.integrator.interfaces import IExternalCollection +from loops.integrator.interfaces import IExternalCollectionProvider +from loops.resource import Resource +from loops.setup import addObject +from loops.type import TypeInterfaceSourceList + + +TypeInterfaceSourceList.typeInterfaces += (IExternalCollection,) + + +class ExternalCollectionAdapter(AdapterBase): + """ A concept adapter for accessing an external collection. + May delegate access to a named utility. + """ + + implements(IExternalCollection) + adapts(IConcept) + + _adapterAttributes = ('context', '__parent__',) + _contextAttributes = list(IExternalCollection) + list(IConcept) + + def create(self): + pass + + def update(self): + pass + + +class DirectoryCollectionProvider(object): + """ A utility that provides access to files in a directory. + """ + + implements(IExternalCollectionProvider) + + def collect(self, client): + directory = self.getDirectory(client) + pattern = re.compile(client.pattern or '.*') + result = [] + for path, dirs, files in os.walk(directory): + if files: + result.extend(os.path.join(path[len(directory)+1:], f) + for f in files + if pattern.match(f)) + return result + + def createExtFileObjects(self, client, addresses, extFileType=None): + if extFileType is None: + extFileType = client.context.getLoopsRoot().getConceptManager()['extfile'] + rm = client.context.getLoopsRoot().getResourceManager() + directory = self.getDirectory(client) + for addr in addresses: + name = addr + obj = addObject(rm, Resource, name, + title=addr.decode('UTF-8'), type=extFileType, + externalAddress=addr, + storage='fullpath', + storageParams=dict(subdirectory=directory)) + yield obj + + def getDirectory(self, client): + baseAddress = client.baseAddress or '' + address = client.address or '' + return os.path.join(baseAddress, address) + diff --git a/integrator/configure.zcml b/integrator/configure.zcml index cc0be03..ad7799a 100644 --- a/integrator/configure.zcml +++ b/integrator/configure.zcml @@ -6,4 +6,17 @@ i18n_domain="zope" > + + + + + + + + + diff --git a/integrator/interfaces.py b/integrator/interfaces.py index 99e4816..a80e159 100644 --- a/integrator/interfaces.py +++ b/integrator/interfaces.py @@ -25,8 +25,91 @@ $Id$ from zope.interface import Interface, Attribute from zope import interface, component, schema +from loops.util import _ + class IExternalCollection(Interface): - """ A collection of resources. + """ A collection of resources, to be used for a concept adapter. """ + providerName = schema.TextLine( + title=_(u'Provider name'), + description=_(u'The name of a utility that provides the ' + 'external objects; default is a directory ' + 'collection provider'), + required=False) + baseAddress = schema.TextLine( + title=_(u'Base address'), + description=_(u'A base path or URL for accessing this collection ' + 'on the external system'), + required=False) + address = schema.TextLine( + title=_(u'Relative address'), + description=_(u'Optional second (local) part of the ' + 'collection\'s address'), + required=False) + pattern = schema.TextLine( + title=_(u'Selection pattern'), + description=_(u'A regular expression for selecting external objects ' + 'that should belong to this collection'), + required=False) + + def create(): + """ Select external objects that should belong to a collection + using all the informations in the attributes, + create a resource of type 'extfile' for each of them, + and associate them with this collection. + Fire appropriate events. + """ + + def update(): + """ Check for new, changed, or deleted external objects. + Create an 'extfile' resource for new ones, fire appropriate + events for new, changed, or deleted ones. + Resources for deleted objects are not removed but should + be empty; they also should receive some state change. + """ + + +class IExternalCollectionProvider(Interface): + """ A utility that provides access to an external collection of objects. + """ + + def collect(clientCollection): + """ Select objects that should belong to a collection, + return an iterable of local address parts of the selected external + objects. The object specified by the 'clientCollection' argument + is usually the caller of the method and should provide the + IExternalCollection interface. + """ + + def createExtFileObjects(clientCollection, addresses, extFileType=None): + """ Create a resource of type 'extFileType' (default is the + type with the name 'extfile') for each of the addresses + provided. Return the list of objects created. + """ + + +class IAutoClassifier(Interface): + """ An adapter that more or less automagically assigns concepts to a + resource using some sort of selection criteria for the concepts + that should be considered. + """ + + +class IOntologyExporter(Interface): + """ An adapter for creating an XML file with all appropriate informations + from the context and its children, selecting children via a + pattern or a set of selection criteria. + + This may then be used by an external tool for classifying + a set of external objects. + """ + + +class IClassificationImporter(Interface): + """ An Adapter for importing an XML file with classification + information for a collection of external objects." + """ + + diff --git a/integrator/testdata/topics/programming/BeautifulProgram.pdf b/integrator/testdata/topics/programming/BeautifulProgram.pdf new file mode 100644 index 0000000..4ec732b Binary files /dev/null and b/integrator/testdata/topics/programming/BeautifulProgram.pdf differ diff --git a/integrator/testdata/topics/programming/zope/zope3.txt b/integrator/testdata/topics/programming/zope/zope3.txt new file mode 100644 index 0000000..7f90f93 --- /dev/null +++ b/integrator/testdata/topics/programming/zope/zope3.txt @@ -0,0 +1,5 @@ +This file carries some information about Zope 3, the open-source +web application server. + +It is used as an example document for integrating external resources +into loops. diff --git a/integrator/testsetup.py b/integrator/testsetup.py index 1043316..4677b04 100644 --- a/integrator/testsetup.py +++ b/integrator/testsetup.py @@ -4,15 +4,20 @@ Set up a loops site for testing. $Id$ """ +import os from zope import component from loops import util +from loops.interfaces import IExternalFile from loops.concept import Concept from loops.resource import Resource from loops.integrator.interfaces import IExternalCollection from loops.knowledge.setup import SetupManager as KnowledgeSetupManager +from loops.setup import SetupManager, addObject from loops.tests.setup import TestSite as BaseTestSite +dataDir = os.path.join(os.path.dirname(__file__), 'testdata') + class TestSite(BaseTestSite): @@ -24,9 +29,12 @@ class TestSite(BaseTestSite): concepts, resources, views = self.baseSetup() tType = concepts.getTypeConcept() - - tExtFile = concepts['extfile'] = Concept(u'External File') - tExtCollection = concepts['extcollection'] = Concept(u'External Collection') + tExtFile = addObject(concepts, Concept, 'extfile', + title=u'External File', type=tType, + typeInterface=IExternalFile) + tExtCollection = addObject(concepts, Concept, 'extcollection', + title=u'External Collection', type=tType, + typeInterface=IExternalCollection) self.indexAll(concepts, resources) return concepts, resources, views diff --git a/organize/interfaces.py b/organize/interfaces.py index 02e9994..e7cd780 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -27,7 +27,6 @@ from zope import interface, component, schema from zope.app import zapi from zope.app.principalannotation import annotations from zope.app.security.interfaces import IAuthentication, PrincipalLookupError -from zope.i18nmessageid import MessageFactory from zope.security.proxy import removeSecurityProxy from cybertools.organize.interfaces import IPerson as IBasePerson diff --git a/resource.py b/resource.py index a9b4857..dbda046 100644 --- a/resource.py +++ b/resource.py @@ -315,12 +315,20 @@ class ExternalFileAdapter(FileAdapter): self.context._storageParams = value storageParams = property(getStorageParams, setStorageParams) - @Lazy - def externalAddress(self): + def getExternalAddress(self): + return getattr(self.context, '_externalAddress', self.context.__name__) + def setExternalAddress(self, addr): + # TODO (?) - use intId as default? + self.context._externalAddress = addr + externalAddress = property(getExternalAddress, setExternalAddress) + + #@Lazy + #def externalAddress(self): # or is this an editable attribute? # or some sort of subpath set during import? # anyway: an attribute of the context object. - return self.context.__name__ + # TODO: use intId and store in special attribute for later reuse + #return self.context.__name__ def setData(self, data): storageParams = self.storageParams