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
Copyright (C) 2012-2017 Edgewall Software