# -*- coding: utf-8 -*- """ = TracNav: The navigation bar for Trac = This macro implements a fully customizable navigation bar for the Trac wiki engine. The contents of the navigation bar is a wiki page itself and can be edited like any other wiki page through the web interface. The navigation bar supports hierarchical ordering of topics. The design of TracNav mimics the design of the TracGuideToc that was originally supplied with Trac. The drawback of TracGuideToc is that it is not customizable without editing its source code and that it does not support hierarchical ordering. == Installation == To install TracNav, place the file `TracNav.py` in the `wiki-macros` subdirectory and the accompanying `tracnav.css` file in the `templates` subdirectory of your Trac project. Add this line {{{ @import url(/site/tracnav.css); }}} to the `templates/site_css.cs` file of your Trac project. The `tracnav.css` file defines the styles for displaying the navigation bar. These styles build upon the styles for !TracGuideToc that come with your Trac distribution. If you just install the macro but miss to install the style file, TracNav will work but look somewhat strange. == Usage == To use TracNav, create an index page for your site and call the TracNav macro on each page, where the navigation bar should be displayed. The index page is a regular wiki page. The page with the table of contents must include an unordered list of links that should be displayed in the navigation bar. To display the navigation bar on a page, you must call the TracNav macro on that page an pass the name of your table of contents as argument. == Author and license == Copyright 2005 * Bernhard Haumacher (haui at haumacher.de) * Thomas Moschny (moschny at ipd.uni-karlsruhe.de) {{{ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA }}} == Additional information and a life example == Please visit: http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav """ __revision__ = "$Id$" import re from trac.wiki.api import WikiSystem from trac.wiki.model import WikiPage from trac.wiki.formatter import OneLinerFormatter from StringIO import StringIO TRACNAVHOME = "http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav" LISTRULE = re.compile(r"^(?P +)\* +(?P.*)$", re.M) def get_toc(hdf, env, curpage, name): """ Fetch the wiki page containing the toc, if available. """ toc_text = " * Table of contents" preview = hdf.getValue('args.preview', "") if preview and (name == curpage): toc_text = hdf.getValue('wiki.page_source', toc_text); elif WikiSystem(env).has_page(name): toc_text = WikiPage(env, name).text return toc_text class TocFormatter(OneLinerFormatter): """ Basically the OneLinerFormatter, but additionally remembers the last wiki link. """ def format_toc(self, wikitext, out): OneLinerFormatter.format(self, wikitext, out) return self.link def __init__(self, env): OneLinerFormatter.__init__(self, env) self.link = None self.myenv = env def _make_link(self, namespace, target, match, label): if namespace == 'wiki': self.link = target return OneLinerFormatter._make_link( self, namespace, target, match, label) # FIXME: what about _make_relative_link() ? # FIXME: CamelCase links are special and not handled by the Formatter... def format_toc_entry(wikitext, env): out = StringIO() link = TocFormatter(env).format_toc(wikitext, out) return out.getvalue(), link def get_toc_entry(toc_text, env): """ Filter the toc_text for toc entries. """ for match in LISTRULE.finditer(toc_text): indent = len(match.group('indent')) label, link = format_toc_entry(match.group('rest'), env) # env.log.debug("link is '%s'" % link) yield indent, link, label def get_toc_entry_and_indent(gen): """ Filter for get_toc_entry(). Returns link and label of the current toc entry and the indentation level of the next entry or -1 if there are no more entries. The first call to next() returns the indentation of the first entry. """ while True: try: indent, link, label = gen.next() except StopIteration: yield -1 return yield indent yield link, label def _parse_toc(gen, next_indent, level = 0): toclist = [] if next_indent > level: sublist, next_indent = _parse_toc(gen, next_indent, level + 1) if next_indent < level: # level is empty return sublist, next_indent else: # broken indentation structure toclist.append((None, None, sublist)) while 1: if next_indent == level: (link, label), next_indent = gen.next(), gen.next() if next_indent > level: sublist, next_indent = _parse_toc(gen, next_indent, level + 1) toclist.append((link, label, sublist)) else: toclist.append((link, label, None)) else: assert next_indent < level return toclist, next_indent def parse_toc(toc_text, env): gen = get_toc_entry_and_indent(get_toc_entry(toc_text, env)) toclist, _ = _parse_toc(gen, gen.next()) return toclist def execute(hdf, args, env): preview = hdf.getValue('args.preview', "") curpage = hdf.getValue('wiki.page_name', "") name = args if not name: name = 'TOC' toc = parse_toc(get_toc(hdf, env, curpage, name), env) if not toc: msg = '
' \ "Error: Table of contents does not exist or is empty." if (not preview) and (hdf.getValue('trac.acl.WIKI_MODIFY', '')): msg += ' Click here to edit.' % \ env.href.wiki(name) msg += '
\n' return msg (found, filtered) = filter_toc(curpage, toc, 0) if found: return display_all(hdf, env, name, curpage, filtered, 0) else: return display_all(hdf, env, name, curpage, toc, 0) def filter_toc(curpage, toc, level): found = 0 result = [] for name, title, sub in toc: if sub == None: if name == curpage: found = 1 result.append((name, title, None)) else: (subfound, subtoc) = filter_toc(curpage, sub, level + 1) if subfound: found = 1 if subfound or (name == None): if level == 0 and name != None: prepended = [(name, title, subtoc)] prepended.extend(result) result = prepended else: result.append((name, title, subtoc)) else: result.append((name, title, [])) return (found, result) def indentation(col): return ' ' * col def display_all(hdf, env, name, curpage, toc, col): preview = hdf.getValue('args.preview', "") html = '%s
\n' % indentation(col) col += 1 html += '%s

TracNav menu

' % \ (indentation(col), TRACNAVHOME) if (not preview) and hdf.getValue('trac.acl.WIKI_MODIFY', ''): html += '%s\n' % \ (indentation(col), env.href.wiki(name)) html += '%s
    \n' % indentation(col) col += 1 html += display(env, curpage, toc, 0, col) col -= 1 html += '%s
\n' % indentation(col) col -= 1 html += '%s
\n' % indentation(col) return html def display(env, curpage, toc, depth, col): html = '' for name, title, sub in toc: li_style = ' style="padding-left: %dem;"' % (depth + 1) if sub == None: if name == curpage: cls = ' class="active"' else: cls = '' html += '%s%s\n' % \ (indentation(col), li_style, cls, title) else: html += '%s\n' % (indentation(col), li_style) col += 1 if name == None or sub: html += '%s

%s

\n' % \ (indentation(col), title) else: html += '%s

%s...

\n' % \ (indentation(col), title) col -= 1 html += '%s\n' % indentation(col) if len(sub) > 0: html += display(env, curpage, sub, depth + 1, col) return html