Mercurial > genshi > mirror
diff examples/trac/trac/web/chrome.py @ 39:93b4dcbafd7b trunk
Copy Trac to main branch.
author | cmlenz |
---|---|
date | Mon, 03 Jul 2006 18:53:27 +0000 |
parents | |
children | f8a5a6ee2097 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/examples/trac/trac/web/chrome.py @@ -0,0 +1,337 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005 Edgewall Software +# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de> +# 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 <cmlenz@gmx.de> + +import os +import re + +from trac import mimeview +from trac.config import * +from trac.core import * +from trac.env import IEnvironmentSetupParticipant +from trac.util.markup import html +from trac.web.api import IRequestHandler, HTTPNotFound +from trac.web.href import Href +from trac.wiki import IWikiSyntaxProvider + +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 <link> element in + the <head> of the generated HTML + """ + link = {'href': href} + if title: + link['title'] = 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. + """ + if filename.startswith('common/') and 'htdocs_location' in req.hdf: + href = Href(req.hdf['htdocs_location']) + filename = filename[7:] + else: + href = Href(req.base_path).chrome + add_link(req, 'stylesheet', href(filename), mimetype=mimetype) + +def add_script(req, filename, mimetype='text/javascript'): + """Add a reference to an external javascript file to the template.""" + if filename.startswith('common/') and 'htdocs_location' in req.hdf: + href = Href(req.hdf['htdocs_location']) + filename = filename[7:] + else: + href = Href(req.base_path).chrome + href = href(filename) + idx = 0 + while True: + js = req.hdf.get('chrome.scripts.%i.href' % idx) + if not js: + break + if js == href: # already added + return + idx += 1 + req.hdf['chrome.scripts.%i' % idx] = {'href': href, 'type': mimetype} + +def add_javascript(req, filename): + """Deprecated: use `add_script()` instead.""" + add_script(req, filename, mimetype='text/javascript') + + +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, + IWikiSyntaxProvider) + + navigation_contributors = ExtensionPoint(INavigationContributor) + template_providers = ExtensionPoint(ITemplateProvider) + + templates_dir = Option('trac', 'templates_dir', default_dir('templates'), + """Path to the !ClearSilver templates.""") + + htdocs_location = Option('trac', 'htdocs_location', '', + """Base URL of the core static resources.""") + + metanav_order = ListOption('trac', 'metanav', + 'login,logout,settings,help,about', doc= + """List of items IDs to display in the navigation bar `metanav`.""") + + mainnav_order = ListOption('trac', 'mainnav', + 'wiki,timeline,roadmap,browser,tickets,' + 'newticket,search', doc= + """List of item IDs to display in the navigation bar `mainnav`.""") + + logo_link = Option('header_logo', 'link', 'http://example.org/', + """URL to link to from header logo.""") + + logo_src = Option('header_logo', 'src', 'common/trac_banner.png', + """URL of the image to use as header logo.""") + + logo_alt = Option('header_logo', 'alt', '', + """Alternative text for the header logo.""") + + logo_width = IntOption('header_logo', 'width', -1, + """Width of the header logo image in pixels.""") + + logo_height = IntOption('header_logo', 'height', -1, + """Height of the header logo image in pixels.""") + + # 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') + if not os.path.exists(templates_dir): + 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'), + """<?cs +#################################################################### +# Site header - Contents are automatically inserted above Trac HTML +?> +""") + _create_file(os.path.join(templates_dir, 'site_footer.cs'), + """<?cs +######################################################################### +# Site footer - Contents are automatically inserted after main Trac HTML +?> +""") + _create_file(os.path.join(templates_dir, 'site_css.cs'), + """<?cs +################################################################## +# Site CSS - Place custom CSS, including overriding styles here. +?> +""") + + def environment_needs_upgrade(self, db): + return False + + def upgrade_environment(self, db): + pass + + + # IRequestHandler methods + + anonymous_request = True + use_template = False + + def match_request(self, req): + match = re.match(r'/chrome/(?P<prefix>[^/]+)/(?P<filename>[/\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['prefix'] + filename = req.args['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]: + dirs.append(dir) + 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) + + self.log.warning('File %s not found in any of %s', filename, dirs) + raise HTTPNotFound('File %s not found', filename) + + # ITemplateProvider methods + + def get_htdocs_dirs(self): + from trac.config import default_dir + return [('common', default_dir('htdocs')), + ('site', self.env.get_htdocs_dir())] + + def get_templates_dirs(self): + return [self.env.get_templates_dir(), self.templates_dir] + + # IWikiSyntaxProvider methods + + def get_wiki_syntax(self): + return [] + + def get_link_resolvers(self): + yield ('htdocs', self._format_link) + + def _format_link(self, formatter, ns, file, label): + return html.A(label, href=formatter.href.chrome('site', file)) + + # 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.base_path) + req.hdf['chrome.href'] = href.chrome() + htdocs_location = self.htdocs_location or href.chrome('common') + req.hdf['htdocs_location'] = htdocs_location.rstrip('/') + '/' + + # HTML <head> links + add_link(req, 'start', req.href.wiki()) + add_link(req, 'search', req.href.search()) + add_link(req, 'help', req.href.wiki('TracGuide')) + add_stylesheet(req, 'common/css/trac.css') + add_script(req, 'common/js/trac.js') + + icon = self.env.project_icon + if icon: + if not icon.startswith('/') and icon.find('://') == -1: + if '/' in icon: + icon = href.chrome(icon) + else: + 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_src = self.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: + if '/' in logo_src: + logo_src = href.chrome(logo_src) + else: + logo_src = href.chrome('common', logo_src) + width = self.logo_width > -1 and self.logo_width + height = self.logo_height > -1 and self.logo_height + req.hdf['chrome.logo'] = { + 'link': self.logo_link, 'src': logo_src, + 'src_abs': logo_src_abs, 'alt': self.logo_alt, + 'width': width, 'height': height + } + else: + req.hdf['chrome.logo.link'] = self.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 = getattr(self, category + '_order') + 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