diff --git a/integrator/README.txt b/integrator/README.txt new file mode 100644 index 0000000..ff4e43d --- /dev/null +++ b/integrator/README.txt @@ -0,0 +1,58 @@ +========================================= +Integrating objects from external systems +========================================= + +Integration of external sources. + + ($Id$) + + +Getting started +=============== + +Let's do some basic set up + + >>> from zope import component, interface + + >>> from cybertools.integrator.tests import testDir + >>> from cybertools.integrator.filesystem import ContainerFactory, FileFactory + >>> from cybertools.integrator.interfaces import IContainerFactory + >>> component.provideUtility(ContainerFactory(), name='filesystem') + >>> component.provideUtility(FileFactory(), name='filesystem') + + +Accessing Objects in the Filesystem +======================================= + + >>> top = component.getUtility(IContainerFactory, name='filesystem')(testDir) + >>> sorted(top) + ['index.html', 'sub'] + >>> len(top) + 2 + + >>> sub = top['sub'] + >>> sorted(sub) + ['demo.tgz', 'index.html', 'loops_logo.png'] + + >>> file = sub['demo.tgz'] + >>> file.contentType + 'application/x-tar' + >>> file.getSize() + 432L + + >>> logo = sub['loops_logo.png'] + >>> logo.contentType + 'image/png' + >>> logo.getImageSize() + (145, 42) + + >>> html = top['index.html'] + >>> html.contentType + 'text/html' + >>> print html.data + ... + + Subdirectory + Demo... + ... + diff --git a/integrator/__init__.py b/integrator/__init__.py new file mode 100644 index 0000000..38314f3 --- /dev/null +++ b/integrator/__init__.py @@ -0,0 +1,3 @@ +""" +$Id$ +""" diff --git a/integrator/base.py b/integrator/base.py new file mode 100644 index 0000000..59a99bd --- /dev/null +++ b/integrator/base.py @@ -0,0 +1,134 @@ +# +# 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 +# + +""" +Base implementation for accessing external content objects. + +$Id$ +""" + +from zope.app.container.contained import Contained +from zope.cachedescriptors.property import Lazy +from zope import component +from zope.interface import implements + +from cybertools.integrator.interfaces import IContainerFactory, IFileFactory +from cybertools.integrator.interfaces import IReadContainer, IFile, IImage + + +# proxy base (sample) classes + +class ReadContainer(Contained): + + implements(IReadContainer) + + factoryName = 'sample' + + def __init__(self, address, **kw): + self.address = address + + @Lazy + def fileFactory(self): + return component.getUtility(IFileFactory, name=self.factoryName) + + @Lazy + def containerFactory(self): + return component.getUtility(IContainerFactory, name=self.factoryName) + + def keys(self): + return [k for k, v in self.items()] + + def __iter__(self): + return iter(self.keys()) + + def __getitem__(self, key): + if key in self: + return self.get(key) + raise KeyError(key) + + def get(self, key, default=None): + return default + + def values(self): + return [v for k, v in self.items()] + + def __len__(self): + return len(self.keys()) + + def items(self): + return [] + + def __contains__(self, key): + return key in self.keys() + + has_key = __contains__ + + +class File(object): + + implements(IFile) + + contentType = None + data = None + + def __init__(self, address, contentType, **kw): + self.address = address + self.contentType = contentType + for k, v in kw.items(): + setattr(self, k, v) + + def getData(self, num=None): + return '' + + data = property(getData) + + def getSize(self): + return len(self.data) + + +def Image(File): + + implements(IImage) + + def getImageSize(self): + return 0, 0 + + +# factory base (sample) classes + +class Factory(object): + + proxyClass = ReadContainer + + def __call__(self, address, **kw): + return self.proxyClass(address, **kw) + + +class ContainerFactory(Factory): + + implements(IContainerFactory) + + proxyClass = ReadContainer + + +class FileFactory(Factory): + + implements(IFileFactory) + + proxyClass = File # real implementations should also care about images + diff --git a/integrator/filesystem.py b/integrator/filesystem.py new file mode 100644 index 0000000..48934f6 --- /dev/null +++ b/integrator/filesystem.py @@ -0,0 +1,130 @@ +# +# 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 +# + +""" +Access to objects in the file system. + +$Id$ +""" + +import os, stat + +from zope import component +from zope.app.file.image import getImageInfo +from zope.cachedescriptors.property import Lazy +from zope.contenttype import guess_content_type +from zope.interface import implements, Attribute + +from cybertools.integrator.base import ContainerFactory, FileFactory +from cybertools.integrator.base import ReadContainer, File, Image +from cybertools.text import mimetypes + + +# proxy classes + +class ReadContainer(ReadContainer): + + factoryName = 'filesystem' + + @Lazy + def filenames(self): + return os.listdir(self.address) + + def keys(self): + return self.filenames + + def __iter__(self): + return iter(self.keys()) + + def __getitem__(self, key): + if key in self: + return self.get(key) + raise KeyError(key) + + def get(self, key, default=None): + if key not in self.keys(): + return default + path = os.path.join(self.address, key) + if os.path.isdir(path): + return self.containerFactory(path) + else: + return self.fileFactory(path) + + def values(self): + return [self.get(k) for k in self] + + def __len__(self): + return len(self.keys()) + + def items(self): + return [(k, self.get(k)) for k in self] + + def __contains__(self, key): + return key in self.keys() + + +class File(File): + + contentType = None + data = None + + def getData(self, num=-1): + f = open(self.address, 'r') + data = f.read(num) + f.close() + return data + + data = property(getData) + + def getSize(self): + return os.stat(self.address)[stat.ST_SIZE] + + +class Image(File): + + width = height = 0 + + def getImageSize(self): + return self.width, self.height + + +# factory classes + +class ContainerFactory(ContainerFactory): + + proxyClass = ReadContainer + + +class FileFactory(FileFactory): + + def __call__(self, address, **kw): + contentType = kw.pop('contentType', None) + width = height = 0 + obj = File(address, contentType, **kw) + if not contentType: + data = obj.getData(50) + contentType, width, height = getImageInfo(data) + if not contentType: + name = os.path.basename(address) + contentType, encoding = guess_content_type(name, data, '') + if contentType.startswith('image/'): + return Image(address, contentType=contentType, + width=width, height=height, **kw) + else: + obj.contentType = contentType + return obj diff --git a/integrator/interfaces.py b/integrator/interfaces.py new file mode 100644 index 0000000..1916eca --- /dev/null +++ b/integrator/interfaces.py @@ -0,0 +1,59 @@ +# +# 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 +# + +""" +External content integration interfaces. + +$Id$ +""" + +from zope.app.container.interfaces import IReadContainer +from zope.app.file.interfaces import IFile, IImage +from zope.app.publication.interfaces import IFileContent +from zope.interface import Interface, Attribute + + +class IFile(IFile, IFileContent): + + def getData(num): + """ Return num bytes from the file`s data. + """ + + +class IProxyFactory(Interface): + """ Creates proxy objects for external objects. + """ + + def __call__(address, **kw): + """ Return a proxy object based on an external object that + can be accessed using the address (and optional + keyword arguments) given. + """ + + +class IContainerFactory(IProxyFactory): + """ Creates container proxy objects for the external specification + given. + """ + + +class IFileFactory(IProxyFactory): + """ Creates file proxy objects for the external specification + given. + """ + diff --git a/integrator/testdata/index.html b/integrator/testdata/index.html new file mode 100644 index 0000000..8527580 --- /dev/null +++ b/integrator/testdata/index.html @@ -0,0 +1,9 @@ + + + + + + Subdirectory + Demo + + diff --git a/integrator/testdata/sub/demo.tgz b/integrator/testdata/sub/demo.tgz new file mode 100644 index 0000000..3d53de0 Binary files /dev/null and b/integrator/testdata/sub/demo.tgz differ diff --git a/integrator/testdata/sub/index.html b/integrator/testdata/sub/index.html new file mode 100644 index 0000000..c45bbe4 --- /dev/null +++ b/integrator/testdata/sub/index.html @@ -0,0 +1,9 @@ + + + + + + Demo + Up + + diff --git a/integrator/testdata/sub/loops_logo.png b/integrator/testdata/sub/loops_logo.png new file mode 100644 index 0000000..15123ab Binary files /dev/null and b/integrator/testdata/sub/loops_logo.png differ diff --git a/integrator/tests.py b/integrator/tests.py new file mode 100755 index 0000000..5b04189 --- /dev/null +++ b/integrator/tests.py @@ -0,0 +1,28 @@ +# $Id$ + +import os +import unittest, doctest +from zope.testing.doctestunit import DocFileSuite +from zope.interface.verify import verifyClass + + +baseDir = os.path.dirname(__file__) +testDir = os.path.join(baseDir, 'testdata') + + +class Test(unittest.TestCase): + "Basic tests for the cybertools.integrator package." + + def testSomething(self): + pass + + +def test_suite(): + flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + return unittest.TestSuite(( + unittest.makeSuite(Test), + DocFileSuite('README.txt', optionflags=flags), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite')