Edgewall Software

root/branches/0.9-stable/trac/web/chrome.py

Revision 2909, 9.8 kB (checked in by cmlenz, 2 years ago)

Ported [2908] to 0.9-stable.

  • Property svn:eol-style set to native
Line 
1# -*- coding: iso-8859-1 -*-
2#
3# Copyright (C) 2005 Edgewall Software
4# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
5# All rights reserved.
6#
7# This software is licensed as described in the file COPYING, which
8# you should have received as part of this distribution. The terms
9# are also available at http://trac.edgewall.com/license.html.
10#
11# This software consists of voluntary contributions made by many
12# individuals. For the exact contribution history, see the revision
13# history and logs, available at http://projects.edgewall.com/trac/.
14#
15# Author: Christopher Lenz <cmlenz@gmx.de>
16
17from __future__ import generators
18import os.path
19import re
20
21from trac import mimeview, util
22from trac.core import *
23from trac.env import IEnvironmentSetupParticipant
24from trac.web.api import IRequestHandler
25from trac.web.href import Href
26
27def add_link(req, rel, href, title=None, mimetype=None, classname=None):
28    """Add a link to the HDF data set that will be inserted as <link> element in
29    the <head> of the generated HTML
30    """
31    link = {'href': href}
32    if title:
33        link['title'] = title
34    if mimetype:
35        link['type'] = mimetype
36    if classname:
37        link['class'] = classname
38    idx = 0
39    while req.hdf.get('chrome.links.%s.%d.href' % (rel, idx)):
40        idx += 1
41    req.hdf['chrome.links.%s.%d' % (rel, idx)] = link
42
43def add_stylesheet(req, filename, mimetype='text/css'):
44    """Add a link to a style sheet to the HDF data set so that it gets included
45    in the generated HTML page.
46    """
47    if filename.startswith('common/') and 'htdocs_location' in req.hdf:
48        href = Href(req.hdf['htdocs_location'])
49        filename = filename[7:]
50    else:
51        href = Href(req.cgi_location).chrome
52    add_link(req, 'stylesheet', href(filename), mimetype=mimetype)
53
54
55class INavigationContributor(Interface):
56    """Extension point interface for components that contribute items to the
57    navigation.
58    """
59
60    def get_active_navigation_item(req):
61        """This method is only called for the `IRequestHandler` processing the
62        request.
63       
64        It should return the name of the navigation item that should be
65        highlighted as active/current.
66        """
67
68    def get_navigation_items(req):
69        """Should return an iterable object over the list of navigation items to
70        add, each being a tuple in the form (category, name, text).
71        """
72
73
74class ITemplateProvider(Interface):
75    """Extension point interface for components that provide their own
76    ClearSilver templates and accompanying static resources.
77    """
78
79    def get_htdocs_dirs():
80        """Return a list of directories with static resources (such as style
81        sheets, images, etc.)
82
83        Each item in the list must be a `(prefix, abspath)` tuple. The
84        `prefix` part defines the path in the URL that requests to these
85        resources are prefixed with.
86       
87        The `abspath` is the absolute path to the directory containing the
88        resources on the local file system.
89        """
90
91    def get_templates_dirs():
92        """Return a list of directories containing the provided ClearSilver
93        templates.
94        """
95
96
97class Chrome(Component):
98    """Responsible for assembling the web site chrome, i.e. everything that
99    is not actual page content.
100    """
101    implements(IEnvironmentSetupParticipant, IRequestHandler, ITemplateProvider)
102
103    navigation_contributors = ExtensionPoint(INavigationContributor)
104    template_providers = ExtensionPoint(ITemplateProvider)
105
106    # IEnvironmentSetupParticipant methods
107
108    def environment_created(self):
109        """Create the templates directory and some templates for
110        customization.
111        """
112        def _create_file(filename, data=None):
113            fd = open(filename, 'w')
114            if data:
115                fd.write(data)
116            fd.close()
117
118        if self.env.path:
119            templates_dir = os.path.join(self.env.path, 'templates')
120            if not os.path.exists(templates_dir):
121                os.mkdir(templates_dir)
122            _create_file(os.path.join(templates_dir, 'README'),
123                        'This directory contains project-specific custom '
124                        'templates and style sheet.\n')
125            _create_file(os.path.join(templates_dir, 'site_header.cs'),
126                         """<?cs
127####################################################################
128# Site header - Contents are automatically inserted above Trac HTML
129?>
130""")
131            _create_file(os.path.join(templates_dir, 'site_footer.cs'),
132                         """<?cs
133#########################################################################
134# Site footer - Contents are automatically inserted after main Trac HTML
135?>
136""")
137            _create_file(os.path.join(templates_dir, 'site_css.cs'),
138                         """<?cs
139##################################################################
140# Site CSS - Place custom CSS, including overriding styles here.
141?>
142""")
143
144    def environment_needs_upgrade(self, db):
145        return False
146
147    def upgrade_environment(self, db):
148        pass
149
150    # IRequestHandler methods
151
152    def match_request(self, req):
153        match = re.match(r'/chrome/(?P<prefix>[^/]+)/(?P<filename>[/\w\-\.]+)',
154                         req.path_info)
155        if match:
156            req.args['prefix'] = match.group('prefix')
157            req.args['filename'] = match.group('filename')
158            return True
159
160    def process_request(self, req):
161        prefix = req.args.get('prefix')
162        filename = req.args.get('filename')
163
164        dirs = []
165        for provider in self.template_providers:
166            for dir in [os.path.normpath(dir[1]) for dir
167                        in provider.get_htdocs_dirs() if dir[0] == prefix]:
168                dirs.append(dir)
169                path = os.path.normpath(os.path.join(dir, filename))
170                assert os.path.commonprefix([dir, path]) == dir
171                if os.path.isfile(path):
172                    req.send_file(path)
173
174        # FIXME: Should return a 404 error
175        self.log.warning('File %s not found in any of %s', filename, dirs)
176        raise TracError, 'File not found'
177
178    # ITemplateProvider methods
179
180    def get_htdocs_dirs(self):
181        from trac.config import default_dir
182        return [('common', default_dir('htdocs')),
183                ('site', self.env.get_htdocs_dir())]
184
185    def get_templates_dirs(self):
186        return [self.env.get_templates_dir(),
187                self.config.get('trac', 'templates_dir')]
188
189    # Public API methods
190
191    def get_all_templates_dirs(self):
192        """Return a list of the names of all known templates directories."""
193        dirs = []
194        for provider in self.template_providers:
195            dirs += provider.get_templates_dirs()
196        return dirs
197
198    def populate_hdf(self, req, handler):
199        """Add chrome-related data to the HDF."""
200
201        # Provided for template customization
202        req.hdf['HTTP.PathInfo'] = req.path_info
203
204        href = Href(req.cgi_location)
205        req.hdf['chrome.href'] = href.chrome()
206        htdocs_location = self.config.get('trac', 'htdocs_location') or \
207                          href.chrome('common')
208        req.hdf['htdocs_location'] = htdocs_location.rstrip('/') + '/'
209
210        # HTML <head> links
211        add_link(req, 'start', self.env.href.wiki())
212        add_link(req, 'search', self.env.href.search())
213        add_link(req, 'help', self.env.href.wiki('TracGuide'))
214        add_stylesheet(req, 'common/css/trac.css')
215        icon = self.config.get('project', 'icon')
216        if icon:
217            if not icon.startswith('/') and icon.find('://') == -1:
218                if '/' in icon:
219                    icon = href.chrome(icon)
220                else:
221                    icon = href.chrome('common', icon)
222            mimetype = mimeview.get_mimetype(icon)
223            add_link(req, 'icon', icon, mimetype=mimetype)
224            add_link(req, 'shortcut icon', icon, mimetype=mimetype)
225
226        # Logo image
227        logo_link = self.config.get('header_logo', 'link')
228        logo_src = self.config.get('header_logo', 'src')
229        if logo_src:
230            logo_src_abs = logo_src.startswith('http://') or \
231                           logo_src.startswith('https://')
232            if not logo_src.startswith('/') and not logo_src_abs:
233                if '/' in logo_src:
234                    logo_src = href.chrome(logo_src)
235                else:
236                    logo_src = href.chrome('common', logo_src)
237            req.hdf['chrome.logo'] = {
238                'link': logo_link, 'src': logo_src, 'src_abs': logo_src_abs,
239                'alt': self.config.get('header_logo', 'alt'),
240                'width': self.config.get('header_logo', 'width', ''),
241                'height': self.config.get('header_logo', 'height', '')
242            }
243        else:
244            req.hdf['chrome.logo.link'] = logo_link
245
246        # Navigation links
247        navigation = {}
248        active = None
249        for contributor in self.navigation_contributors:
250            for category, name, text in contributor.get_navigation_items(req):
251                navigation.setdefault(category, {})[name] = text
252            if contributor is handler:
253                active = contributor.get_active_navigation_item(req)
254
255        for category, items in [(k, v.items()) for k, v in navigation.items()]:
256            order = [x.strip() for x
257                     in self.config.get('trac', category).split(',')]
258            def navcmp(x, y):
259                if x[0] not in order:
260                    return int(y[0] in order)
261                if y[0] not in order:
262                    return -int(x[0] in order)
263                return cmp(order.index(x[0]), order.index(y[0]))
264            items.sort(navcmp)
265
266            for name, text in items:
267                req.hdf['chrome.nav.%s.%s' % (category, name)] = text
268                if name == active:
269                    req.hdf['chrome.nav.%s.%s.active' % (category, name)] = 1
Note: See TracBrowser for help on using the browser.