# -*- coding: iso8859-1 -*- # # Copyright (C) 2005 Edgewall Software # Copyright (C) 2005 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Christopher Lenz from __future__ import generators import os.path import re from trac import mimeview, util from trac.core import * from trac.env import IEnvironmentSetupParticipant from trac.web.api import IRequestHandler from trac.web.href import Href def add_link(req, rel, href, title=None, mimetype=None, classname=None): """Add a link to the HDF data set that will be inserted as element in the of the generated HTML """ link = {'href': util.escape(href)} if title: link['title'] = util.escape(title) if mimetype: link['type'] = mimetype if classname: link['class'] = classname idx = 0 while req.hdf.get('chrome.links.%s.%d.href' % (rel, idx)): idx += 1 req.hdf['chrome.links.%s.%d' % (rel, idx)] = link def add_stylesheet(req, filename, mimetype='text/css'): """Add a link to a style sheet to the HDF data set so that it gets included in the generated HTML page. """ href = Href(req.cgi_location) add_link(req, 'stylesheet', href.chrome(filename), mimetype=mimetype) class INavigationContributor(Interface): """Extension point interface for components that contribute items to the navigation. """ def get_active_navigation_item(req): """This method is only called for the `IRequestHandler` processing the request. It should return the name of the navigation item that should be highlighted as active/current. """ def get_navigation_items(req): """Should return an iterable object over the list of navigation items to add, each being a tuple in the form (category, name, text). """ class ITemplateProvider(Interface): """Extension point interface for components that provide their own ClearSilver templates and accompanying static resources. """ def get_htdocs_dirs(): """Return a list of directories with static resources (such as style sheets, images, etc.) Each item in the list must be a `(prefix, abspath)` tuple. The `prefix` part defines the path in the URL that requests to these resources are prefixed with. The `abspath` is the absolute path to the directory containing the resources on the local file system. """ def get_templates_dirs(): """Return a list of directories containing the provided ClearSilver templates. """ class Chrome(Component): """Responsible for assembling the web site chrome, i.e. everything that is not actual page content. """ implements(IEnvironmentSetupParticipant, IRequestHandler, ITemplateProvider) navigation_contributors = ExtensionPoint(INavigationContributor) template_providers = ExtensionPoint(ITemplateProvider) # IEnvironmentSetupParticipant methods def environment_created(self): """Create the templates directory and some templates for customization. """ def _create_file(filename, data=None): fd = open(filename, 'w') if data: fd.write(data) fd.close() if self.env.path: templates_dir = os.path.join(self.env.path, 'templates') os.mkdir(templates_dir) _create_file(os.path.join(templates_dir, 'README'), 'This directory contains project-specific custom ' 'templates and style sheet.\n') _create_file(os.path.join(templates_dir, 'site_header.cs'), """ """) _create_file(os.path.join(templates_dir, 'site_footer.cs'), """ """) _create_file(os.path.join(templates_dir, 'site_css.cs'), """ """) def environment_needs_upgrade(self, db): return False def upgrade_environment(self, db): pass # IRequestHandler methods def match_request(self, req): match = re.match(r'/chrome/(?P[^/]+)/(?P[/\w\-\.]+)', req.path_info) if match: req.args['prefix'] = match.group('prefix') req.args['filename'] = match.group('filename') return True def process_request(self, req): prefix = req.args.get('prefix') filename = req.args.get('filename') dirs = {} for provider in self.template_providers: for dir in [os.path.normpath(dir[1]) for dir in provider.get_htdocs_dirs() if dir[0] == prefix]: path = os.path.normpath(os.path.join(dir, filename)) assert os.path.commonprefix([dir, path]) == dir if os.path.isfile(path): req.send_file(path) # FIXME: Should return a 404 error self.log.warning('File %s not found in any of %s', filename, dirs) raise TracError, 'File not found' # ITemplateProvider methods def get_htdocs_dirs(self): from trac.config import default_dir yield ['common', default_dir('htdocs')] def get_templates_dirs(self): return [self.env.get_templates_dir(), self.config.get('trac', 'templates_dir')] # Public API methods def get_all_templates_dirs(self): """Return a list of the names of all known templates directories.""" dirs = [] for provider in self.template_providers: dirs += provider.get_templates_dirs() return dirs def populate_hdf(self, req, handler): """Add chrome-related data to the HDF.""" # Provided for template customization req.hdf['HTTP.PathInfo'] = req.path_info href = Href(req.cgi_location) req.hdf['chrome.href'] = href.chrome() req.hdf['htdocs_location'] = href.chrome('common', '/') # HTML links add_link(req, 'start', self.env.href.wiki()) add_link(req, 'search', self.env.href.search()) add_link(req, 'help', self.env.href.wiki('TracGuide')) add_stylesheet(req, 'common/css/trac.css') icon = self.config.get('project', 'icon') if icon: if icon[0] != '/' and icon.find('://') == -1: icon = href.chrome('common', icon) mimetype = mimeview.get_mimetype(icon) add_link(req, 'icon', icon, mimetype=mimetype) add_link(req, 'shortcut icon', icon, mimetype=mimetype) # Logo image logo_link = self.config.get('header_logo', 'link') logo_src = self.config.get('header_logo', 'src') if logo_src: logo_src_abs = logo_src.startswith('http://') or \ logo_src.startswith('https://') if not logo_src.startswith('/') and not logo_src_abs: logo_src = href.chrome('common', logo_src) req.hdf['chrome.logo'] = { 'link': util.escape(logo_link), 'src': util.escape(logo_src), 'src_abs': util.escape(logo_src_abs), 'alt': util.escape(self.config.get('header_logo', 'alt')), 'width': self.config.get('header_logo', 'width', ''), 'height': self.config.get('header_logo', 'height', '') } else: req.hdf['chrome.logo.link'] = util.escape(logo_link) # Navigation links navigation = {} active = None for contributor in self.navigation_contributors: for category, name, text in contributor.get_navigation_items(req): navigation.setdefault(category, {})[name] = text if contributor is handler: active = contributor.get_active_navigation_item(req) for category, items in [(k, v.items()) for k, v in navigation.items()]: order = [x.strip() for x in self.config.get('trac', category).split(',')] def navcmp(x, y): if x[0] not in order: return int(y[0] in order) if y[0] not in order: return -int(x[0] in order) return cmp(order.index(x[0]), order.index(y[0])) items.sort(navcmp) for name, text in items: req.hdf['chrome.nav.%s.%s' % (category, name)] = text if name == active: req.hdf['chrome.nav.%s.%s.active' % (category, name)] = 1