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

Revision 3057, 9.1 KB checked in by moschny, 8 years ago (diff)

Use the OneLineFormatter of Trac's wiki system instead of our own
parsing routine. This has the advantage that the toc entries can
contain any wiki syntax element recognized by the OneLineFormatter.

This should close #247 and #252, although the collapse/expand
mechanism currently doesn't work for pages referenced indirectly
through references to any of their paragraphs.

  • 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 Bernhard Haumacher (haui at haumacher.de)
48
49{{{
50This program is free software; you can redistribute it and/or modify
51it under the terms of the GNU General Public License as published by
52the Free Software Foundation; either version 2 of the License, or
53(at your option) any later version.
54
55This program is distributed in the hope that it will be useful,
56but WITHOUT ANY WARRANTY; without even the implied warranty of
57MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
58GNU General Public License for more details.
59
60You should have received a copy of the GNU General Public License
61along with this program; if not, write to the Free Software
62Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
63}}}
64
65== Additional information and a life example ==
66
67Please visit: http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav
68"""
69
70__revision__ = "$Id$"
71
72import re
73from trac.wiki.api import WikiSystem
74from trac.wiki.model import WikiPage
75from trac.wiki.formatter import OneLinerFormatter
76from StringIO import StringIO
77
78TRACNAVHOME = "http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav"
79LISTRULE = re.compile(r"^(?P<indent> +)\* +(?P<rest>.*)$", re.M)
80
81def get_toc(hdf, env, curpage, name):
82    """
83    Fetch the wiki page containing the toc, if available.
84    """
85    toc_text = " * Table of contents"
86
87    preview = hdf.getValue('args.preview', "")
88    if preview and (name == curpage):
89        toc_text = hdf.getValue('wiki.page_source', toc_text);
90    elif WikiSystem(env).has_page(name):
91        toc_text = WikiPage(env, name).text
92    return toc_text
93
94
95class TocFormatter(OneLinerFormatter):
96    """
97    Basically the OneLinerFormatter, but additionally remembers the
98    last wiki link.
99    """
100
101    def format_toc(self, wikitext, out):
102        OneLinerFormatter.format(self, wikitext, out)
103        return self.link
104
105    def __init__(self, env):
106        OneLinerFormatter.__init__(self, env)
107        self.link = None
108        self.myenv = env
109
110    def _make_link(self, namespace, target, match, label):
111        if namespace == 'wiki':
112            self.link = target
113        return OneLinerFormatter._make_link(
114            self, namespace, target, match, label)
115
116    # FIXME: what about _make_relative_link() ?
117    # FIXME: CamelCase links are special and not handled by the Formatter...
118
119def format_toc_entry(wikitext, env):
120    out = StringIO()
121    link = TocFormatter(env).format_toc(wikitext, out)
122    return out.getvalue(), link
123
124
125def get_toc_entry(toc_text, env):
126    """
127    Filter the toc_text for toc entries.
128    """
129    for match in LISTRULE.finditer(toc_text):
130        indent = len(match.group('indent'))
131        label, link = format_toc_entry(match.group('rest'), env)
132        # env.log.debug("link is '%s'" % link)
133        yield indent, link, label
134
135
136def get_toc_entry_and_indent(gen):
137    """
138    Filter for get_toc_entry().  Returns link and label of the current
139    toc entry and the indentation level of the next entry or -1 if
140    there are no more entries. The first call to next() returns the
141    indentation of the first entry.
142    """
143    try:
144        indent, link, label = gen.next()
145    except StopIteration:
146        indent = -1
147    yield indent
148
149    ready = False
150    while not ready:
151        yield link, label
152        try:
153            indent, link, label = gen.next()
154        except StopIteration:
155            indent, ready = -1, True
156        yield indent
157
158
159def _parse_toc(gen, next_indent, level = 0):
160    toclist = []
161    if next_indent > level:
162        sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
163        if next_indent < level: # level is empty
164            return sublist, next_indent
165        else:                   # broken indentation structure
166            toclist.append((None, None, sublist))
167    while 1:
168        if next_indent == level:
169            (link, label), next_indent = gen.next(), gen.next()
170            if next_indent > level:
171                sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
172                toclist.append((link, label, sublist))
173            else:
174                toclist.append((link, label, None))
175        else:
176            assert next_indent < level
177            return toclist, next_indent
178
179
180def parse_toc(toc_text, env):
181    gen = get_toc_entry_and_indent(get_toc_entry(toc_text, env))
182    toclist, _ = _parse_toc(gen, gen.next())
183    return toclist
184   
185
186def execute(hdf, args, env):
187    preview = hdf.getValue('args.preview', "")
188    curpage = hdf.getValue('wiki.page_name', "")
189    name = args
190    if not name:
191        name = 'TOC'
192
193    toc = parse_toc(get_toc(hdf, env, curpage, name), env)
194    if not toc:
195        msg = '<div class="system-message">' \
196              "<strong>Error: Table of contents does not exist or is empty."
197        if (not preview) and (hdf.getValue('trac.acl.WIKI_MODIFY', '')):
198            msg += ' Click here to <a href="%s?edit=yes">edit</a>.' % \
199                   env.href.wiki(name)
200        msg += '</strong></div>\n'
201        return msg
202
203    (found, filtered) = filter_toc(curpage, toc, 0)
204    if found:
205        return display_all(hdf, env, name, curpage, filtered, 0)
206    else:
207        return display_all(hdf, env, name, curpage, toc, 0)
208
209
210def filter_toc(curpage, toc, level):
211    found = 0
212    result = []
213    for name, title, sub in toc:
214        if sub == None:
215            if name == curpage:
216                found = 1
217            result.append((name, title, None))
218        else:
219            (subfound, subtoc) = filter_toc(curpage, sub, level + 1)
220            if subfound:
221                found = 1
222            if subfound or (name == None):
223                if level == 0 and name != None:
224                    prepended = [(name, title, subtoc)]
225                    prepended.extend(result)
226                    result = prepended
227                else:
228                    result.append((name, title, subtoc))
229            else:
230                result.append((name, title, []))
231    return (found, result)
232
233def indentation(col):
234    return ' ' * col
235
236def display_all(hdf, env, name, curpage, toc, col):
237    preview = hdf.getValue('args.preview', "")
238    html = '%s<div class="wiki-toc trac-nav">\n' % indentation(col)
239    col += 1
240    html += '%s<h2><a href="%s">TracNav</a> menu</h2>' % \
241            (indentation(col), TRACNAVHOME)
242
243    if (not preview) and hdf.getValue('trac.acl.WIKI_MODIFY', ''):
244        html += '%s<div class="edit"><a href="%s?edit=yes">edit</a></div>\n' % \
245                (indentation(col), env.href.wiki(name))
246    html += '%s<ul>\n' % indentation(col)
247    col += 1
248    html += display(env, curpage, toc, 0, col)
249    col -= 1
250    html += '%s</ul>\n' % indentation(col)
251    col -= 1
252    html += '%s</div>\n' % indentation(col)
253    return html
254
255def display(env, curpage, toc, depth, col):
256    html = ''
257    for name, title, sub in toc:
258        li_style = ' style="padding-left: %dem;"' % (depth + 1)
259        if sub == None:
260            if name == curpage:
261                cls = ' class="active"'
262            else:
263                cls = ''
264            html += '%s<li%s%s>%s</li>\n' % \
265                    (indentation(col), li_style, cls, title)
266        else:
267            html += '%s<li%s>\n' % (indentation(col), li_style)
268            col += 1
269            if name == None or sub:
270                html += '%s<h4>%s</h4>\n' % \
271                        (indentation(col), title)
272            else:
273                html += '%s<h4>%s...</h4>\n' % \
274                        (indentation(col), title)
275            col -= 1
276            html += '%s</li>\n' % indentation(col)
277            if len(sub) > 0:
278                html += display(env, curpage, sub, depth + 1, col)
279    return html
280
Note: See TracBrowser for help on using the repository browser.