root/relatorio/reporting.py @ 60:deb52535750d

Revision 60:deb52535750d, 4.9 kB (checked in by Nicolas ?vrard <nicoe@…>, 5 years ago)

Use the same signature for generate and for report.call

Using coverage.py to complete the tests

Line 
1###############################################################################
2#
3# Copyright (c) 2007, 2008 OpenHex SPRL. (http://openhex.com) All Rights
4# Reserved.
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; either version 2 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14# details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program.  If not, see <http://www.gnu.org/licenses/>.
18#
19###############################################################################
20
21__metaclass__ = type
22
23import os, sys
24import warnings
25from cStringIO import StringIO
26
27import pkg_resources
28from genshi.template import TemplateLoader
29
30def _absolute(path):
31    "Compute the absolute path of path relative to the caller file"
32    if os.path.isabs(path):
33        return path
34    caller_fname = sys._getframe(2).f_globals['__file__']
35    dir = os.path.dirname(caller_fname)
36    return os.path.abspath(os.path.join(dir, path))
37
38def _guess_type(mime):
39    mime = mime.lower()
40    type, stype = mime.split('/', 1)
41    if type == 'application':
42        if 'opendocument' in stype:
43            return 'oo.org'
44        else:
45            return stype
46    elif type == 'text':
47        if stype in ('xml', 'html', 'xhtml'):
48            return 'markup'
49        else:
50            return 'text'
51
52
53class MIMETemplateLoader(TemplateLoader):
54    """This subclass of TemplateLoader use mimetypes to search and find
55    templates to load.
56    """
57
58    factories = {}
59
60    mime_func = [_guess_type]
61
62    def get_type(self, mime):
63        for func in reversed(self.mime_func):
64            t = func(mime)
65            if t is not None:
66                return t
67
68    def load(self, path, mime):
69        rtype = self.get_type(mime)
70        return super(MIMETemplateLoader, self).load(path,
71                                                    cls=self.factories[rtype])
72
73    @classmethod
74    def add_factory(cls, abbr_mimetype, template_factory, id_function=None):
75        """adds a template factory to the already known factories"""
76        if abbr_mimetype in cls.factories:
77            warnings.warn('You are overriding an already defined link.')
78        cls.factories[abbr_mimetype] = template_factory
79        if id_function is not None:
80            cls.mime_func.append(id_function)
81
82    @classmethod
83    def load_template_engines(cls):
84        """loads template engines found via PEAK's pkg_resources"""
85        for entrypoint in pkg_resources.iter_entry_points(
86                                        'relatorio.templates.engines'):
87            try:
88                engine = entrypoint.load()
89                if hasattr(engine, 'id_function'):
90                    cls.add_factory(entrypoint.name, engine, engine.id_function)
91                else:
92                    cls.add_factory(entrypoint.name, engine)
93            except ImportError:
94                warnings.warn('We were not able to load %s. You will not '
95                              'be able to use its functonlities' %
96                              entrypoint.module_name)
97
98
99class Report:
100    """Report is a simple interface on top of a rendering template.
101    """
102
103    def __init__(self, path, mimetype, factory, loader):
104        self.fpath = path
105        self.mimetype = mimetype
106        self.data_factory = factory
107        self.tmpl_loader = loader
108        self.filters = []
109
110    def __call__(self, **kwargs):
111        template = self.tmpl_loader.load(self.fpath, self.mimetype)
112        data = self.data_factory(**kwargs)
113        return template.generate(**data).filter(*self.filters)
114
115    def __repr__(self):
116        return '<relatorio report on %s>' % self.fpath
117
118
119class DefaultFactory:
120
121    def __call__(self, **kwargs):
122        data = kwargs.copy()
123        return data
124
125
126class ReportRepository:
127    """ReportRepository stores the report definition associated to objects.
128
129    The report are indexed in this object by the object class they are working
130    on and the name given to it by the user.
131    """
132
133    def __init__(self, datafactory=DefaultFactory):
134        self.reports = {}
135        self.default_factory = datafactory
136        self.loader = MIMETemplateLoader(auto_reload=True)
137
138    def add_report(self, klass, mimetype, template_path, data_factory=None,
139                   report_name='default'):
140        if data_factory is None:
141            data_factory = self.default_factory
142        reports = self.reports.setdefault(klass, {})
143        report = Report(_absolute(template_path), mimetype, data_factory(),
144                        self.loader)
145        reports[report_name] = report, mimetype
146        reports.setdefault(mimetype, []).append((report_name, report))
Note: See TracBrowser for help on using the browser.