source: trac/trunk/wiki-macros/TracNav.py @ 3061

Revision 3061, 9.0 KB checked in by moschny, 7 years ago (diff)
  • Reuse the TocFormatter object for formatting all toc entries - this might yield a performance enhancement (hard to measure).
  • Property svn:keywords set to Id
Line 
1# -*- coding: utf-8 -*-
2"""
3= TracNav: The navigation bar for Trac =
4
5This macro implements a fully customizable navigation bar for the Trac
6wiki engine. The contents of the navigation bar is a wiki page itself
7and can be edited like any other wiki page through the web
8interface. The navigation bar supports hierarchical ordering of
9topics. The design of TracNav mimics the design of the TracGuideToc
10that was originally supplied with Trac. The drawback of TracGuideToc
11is that it is not customizable without editing its source code and
12that it does not support hierarchical ordering.
13
14
15== Installation ==
16
17To install TracNav, place the file `TracNav.py` in the `wiki-macros`
18subdirectory and the accompanying `tracnav.css` file in the
19`templates` subdirectory of your Trac project. Add this line
20{{{
21@import url(<?cs var:chrome.href ?>/site/tracnav.css);
22}}}
23to the `templates/site_css.cs` file of your Trac project.
24
25The `tracnav.css` file defines the styles for displaying the
26navigation bar. These styles build upon the styles for !TracGuideToc
27that come with your Trac distribution. If you just install the macro
28but miss to install the style file, TracNav will work but look
29somewhat strange.
30
31
32== Usage ==
33
34To use TracNav, create an index page for your site and call the
35TracNav macro on each page, where the navigation bar should be
36displayed. The index page is a regular wiki page. The page with the
37table of contents must include an unordered list of links that should
38be displayed in the navigation bar.
39
40To display the navigation bar on a page, you must call the TracNav
41macro on that page an pass the name of your table of contents as
42argument.
43
44
45== Author and license ==
46
47Copyright 2005
48 *  Bernhard Haumacher (haui at haumacher.de)
49 *  Thomas Moschny (moschny at ipd.uni-karlsruhe.de)
50
51{{{
52This program is free software; you can redistribute it and/or modify
53it under the terms of the GNU General Public License as published by
54the Free Software Foundation; either version 2 of the License, or
55(at your option) any later version.
56
57This program is distributed in the hope that it will be useful,
58but WITHOUT ANY WARRANTY; without even the implied warranty of
59MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
60GNU General Public License for more details.
61
62You should have received a copy of the GNU General Public License
63along with this program; if not, write to the Free Software
64Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
65}}}
66
67== Additional information and a life example ==
68
69Please visit: http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav
70"""
71
72__revision__ = "$Id$"
73
74import re
75from trac.wiki.api import WikiSystem
76from trac.wiki.model import WikiPage
77from trac.wiki.formatter import OneLinerFormatter
78from StringIO import StringIO
79
80TRACNAVHOME = "http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav"
81LISTRULE = re.compile(r"^(?P<indent> +)\* +(?P<rest>.*)$", re.M)
82
83def get_toc(hdf, env, curpage, name):
84    """
85    Fetch the wiki page containing the toc, if available.
86    """
87    toc_text = " * Table of contents"
88
89    preview = hdf.getValue('args.preview', "")
90    if preview and (name == curpage):
91        toc_text = hdf.getValue('wiki.page_source', toc_text);
92    elif WikiSystem(env).has_page(name):
93        toc_text = WikiPage(env, name).text
94    return toc_text
95
96
97class TocFormatter(OneLinerFormatter):
98    """
99    Basically the OneLinerFormatter, but additionally remembers the
100    last wiki link.
101    """
102    def format_toc(self, wikitext):
103        self.link = None
104        out = StringIO()
105        OneLinerFormatter.format(self, wikitext, out)
106        return out.getvalue(), self.link
107
108    def __init__(self, env):
109        OneLinerFormatter.__init__(self, env)
110        self.link = None
111
112    def _make_link(self, namespace, target, match, label):
113        if namespace == 'wiki':
114            self.link = target
115        return OneLinerFormatter._make_link(
116            self, namespace, target, match, label)
117
118    # FIXME: what about _make_relative_link() ?
119    # FIXME: CamelCase links are special and not handled by the Formatter...
120
121
122def get_toc_entry(toc_text, env):
123    """
124    Parse and format the entries in toc_text.
125    """
126    formatter = TocFormatter(env)
127    for match in LISTRULE.finditer(toc_text):
128        indent = len(match.group('indent'))
129        label, link = formatter.format_toc(match.group('rest'))
130        yield indent, link, label
131
132
133def get_toc_entry_and_indent(gen):
134    """
135    Filter for get_toc_entry().  The first call to next() returns the
136    indentation level of the next entry (or -1 if there are no more
137    entries) and the second call returns the entry itself.
138    """
139    while True:
140        try:
141            indent, link, label = gen.next()
142        except StopIteration:
143            yield -1
144            return
145        yield indent
146        yield link, label       
147
148
149def _parse_toc(gen, next_indent, level = 0):
150    toclist = []
151    if next_indent > level:
152        sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
153        if next_indent < level: # level is empty
154            return sublist, next_indent
155        else:                   # broken indentation structure
156            toclist.append((None, None, sublist))
157    while True:
158        if next_indent == level:
159            (link, label), next_indent = gen.next(), gen.next()
160            if next_indent > level:
161                sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
162                toclist.append((link, label, sublist))
163            else:
164                toclist.append((link, label, None))
165        else:
166            assert next_indent < level
167            return toclist, next_indent
168
169
170def parse_toc(toc_text, env):
171    """
172    Recursively construct the toc tree using _parse_toc().
173    """
174    gen = get_toc_entry_and_indent(get_toc_entry(toc_text, env))
175    toc, _ = _parse_toc(gen, gen.next())
176    return toc
177   
178
179def execute(hdf, args, env):
180    """
181    Main routine of the wiki macro.
182    """
183    preview = hdf.getValue('args.preview', "")
184    curpage = hdf.getValue('wiki.page_name', "")
185    name = args or 'TOC'
186
187    toc = parse_toc(get_toc(hdf, env, curpage, name), env)
188    if not toc:
189        msg = '<div class="system-message">' \
190              "<strong>Error: Table of contents does not exist or is empty."
191        if (not preview) and (hdf.getValue('trac.acl.WIKI_MODIFY', '')):
192            msg += ' Click here to <a href="%s?edit=yes">edit</a>.' % \
193                   env.href.wiki(name)
194        msg += '</strong></div>\n'
195        return msg
196
197    (found, filtered) = filter_toc(curpage, toc, 0)
198    if found:
199        return display_all(hdf, env, name, curpage, filtered, 0)
200    else:
201        return display_all(hdf, env, name, curpage, toc, 0)
202
203
204def filter_toc(curpage, toc, level):
205    found = 0
206    result = []
207    for name, title, sub in toc:
208        if sub == None:
209            if name == curpage:
210                found = 1
211            result.append((name, title, None))
212        else:
213            (subfound, subtoc) = filter_toc(curpage, sub, level + 1)
214            if subfound:
215                found = 1
216            if subfound or (name == None):
217                if level == 0 and name != None:
218                    prepended = [(name, title, subtoc)]
219                    prepended.extend(result)
220                    result = prepended
221                else:
222                    result.append((name, title, subtoc))
223            else:
224                result.append((name, title, []))
225    return (found, result)
226
227def indentation(col):
228    return ' ' * col
229
230def display_all(hdf, env, name, curpage, toc, col):
231    preview = hdf.getValue('args.preview', "")
232    html = '%s<div class="wiki-toc trac-nav">\n' % indentation(col)
233    col += 1
234    html += '%s<h2><a href="%s">TracNav</a> menu</h2>' % \
235            (indentation(col), TRACNAVHOME)
236
237    if (not preview) and hdf.getValue('trac.acl.WIKI_MODIFY', ''):
238        html += '%s<div class="edit"><a href="%s?edit=yes">edit</a></div>\n' % \
239                (indentation(col), env.href.wiki(name))
240    html += '%s<ul>\n' % indentation(col)
241    col += 1
242    html += display(curpage, toc, 0, col)
243    col -= 1
244    html += '%s</ul>\n' % indentation(col)
245    col -= 1
246    html += '%s</div>\n' % indentation(col)
247    return html
248
249def display(curpage, toc, depth, col):
250    html = ''
251    for name, title, sub in toc:
252        li_style = ' style="padding-left: %dem;"' % (depth + 1)
253        if sub == None:
254            if name == curpage:
255                cls = ' class="active"'
256            else:
257                cls = ''
258            html += '%s<li%s%s>%s</li>\n' % \
259                    (indentation(col), li_style, cls, title)
260        else:
261            html += '%s<li%s>\n' % (indentation(col), li_style)
262            col += 1
263            if name == None or sub:
264                html += '%s<h4>%s</h4>\n' % \
265                        (indentation(col), title)
266            else:
267                html += '%s<h4>%s...</h4>\n' % \
268                        (indentation(col), title)
269            col -= 1
270            html += '%s</li>\n' % indentation(col)
271            if len(sub) > 0:
272                html += display(curpage, sub, depth + 1, col)
273    return html
274
Note: See TracBrowser for help on using the repository browser.