Edgewall Software

MacroBazaar: TracNav.3.2.py

File TracNav.3.2.py, 9.1 KB (added by moschny at ipd uni-karlsruhe de, 6 years ago)

Updated navigation bar for Trac (wiki-customizable hierarchical replacement for TracGuideToc). Revision 3060.

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: TracNav.py 3060 2005-11-26 12:19:26Z moschny $"
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
103    def format_toc(self, wikitext, out):
104        OneLinerFormatter.format(self, wikitext, out)
105        return self.link
106
107    def __init__(self, env):
108        OneLinerFormatter.__init__(self, env)
109        self.link = None
110
111    def _make_link(self, namespace, target, match, label):
112        if namespace == 'wiki':
113            self.link = target
114        return OneLinerFormatter._make_link(
115            self, namespace, target, match, label)
116
117    # FIXME: what about _make_relative_link() ?
118    # FIXME: CamelCase links are special and not handled by the Formatter...
119
120def format_toc_entry(wikitext, env):
121    out = StringIO()
122    link = TocFormatter(env).format_toc(wikitext, out)
123    return out.getvalue(), link
124
125
126def get_toc_entry(toc_text, env):
127    """
128    Parse and format the entries in toc_text.
129    """
130    for match in LISTRULE.finditer(toc_text):
131        indent = len(match.group('indent'))
132        label, link = format_toc_entry(match.group('rest'), env)
133        yield indent, link, label
134
135
136def get_toc_entry_and_indent(gen):
137    """
138    Filter for get_toc_entry().  The first call to next() returns the
139    indentation level of the next entry (or -1 if there are no more
140    entries) and the second call returns the entry itself.
141    """
142    while True:
143        try:
144            indent, link, label = gen.next()
145        except StopIteration:
146            yield -1
147            return
148        yield indent
149        yield link, label       
150
151
152def _parse_toc(gen, next_indent, level = 0):
153    toclist = []
154    if next_indent > level:
155        sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
156        if next_indent < level: # level is empty
157            return sublist, next_indent
158        else:                   # broken indentation structure
159            toclist.append((None, None, sublist))
160    while True:
161        if next_indent == level:
162            (link, label), next_indent = gen.next(), gen.next()
163            if next_indent > level:
164                sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
165                toclist.append((link, label, sublist))
166            else:
167                toclist.append((link, label, None))
168        else:
169            assert next_indent < level
170            return toclist, next_indent
171
172
173def parse_toc(toc_text, env):
174    """
175    Recursively construct the toc tree using _parse_toc().
176    """
177    gen = get_toc_entry_and_indent(get_toc_entry(toc_text, env))
178    toclist, _ = _parse_toc(gen, gen.next())
179    return toclist
180   
181
182def execute(hdf, args, env):
183    """
184    Main routine of the wiki macro.
185    """
186    preview = hdf.getValue('args.preview', "")
187    curpage = hdf.getValue('wiki.page_name', "")
188    name = args or 'TOC'
189
190    toc = parse_toc(get_toc(hdf, env, curpage, name), env)
191    if not toc:
192        msg = '<div class="system-message">' \
193              "<strong>Error: Table of contents does not exist or is empty."
194        if (not preview) and (hdf.getValue('trac.acl.WIKI_MODIFY', '')):
195            msg += ' Click here to <a href="%s?edit=yes">edit</a>.' % \
196                   env.href.wiki(name)
197        msg += '</strong></div>\n'
198        return msg
199
200    (found, filtered) = filter_toc(curpage, toc, 0)
201    if found:
202        return display_all(hdf, env, name, curpage, filtered, 0)
203    else:
204        return display_all(hdf, env, name, curpage, toc, 0)
205
206
207def filter_toc(curpage, toc, level):
208    found = 0
209    result = []
210    for name, title, sub in toc:
211        if sub == None:
212            if name == curpage:
213                found = 1
214            result.append((name, title, None))
215        else:
216            (subfound, subtoc) = filter_toc(curpage, sub, level + 1)
217            if subfound:
218                found = 1
219            if subfound or (name == None):
220                if level == 0 and name != None:
221                    prepended = [(name, title, subtoc)]
222                    prepended.extend(result)
223                    result = prepended
224                else:
225                    result.append((name, title, subtoc))
226            else:
227                result.append((name, title, []))
228    return (found, result)
229
230def indentation(col):
231    return ' ' * col
232
233def display_all(hdf, env, name, curpage, toc, col):
234    preview = hdf.getValue('args.preview', "")
235    html = '%s<div class="wiki-toc trac-nav">\n' % indentation(col)
236    col += 1
237    html += '%s<h2><a href="%s">TracNav</a> menu</h2>' % \
238            (indentation(col), TRACNAVHOME)
239
240    if (not preview) and hdf.getValue('trac.acl.WIKI_MODIFY', ''):
241        html += '%s<div class="edit"><a href="%s?edit=yes">edit</a></div>\n' % \
242                (indentation(col), env.href.wiki(name))
243    html += '%s<ul>\n' % indentation(col)
244    col += 1
245    html += display(curpage, toc, 0, col)
246    col -= 1
247    html += '%s</ul>\n' % indentation(col)
248    col -= 1
249    html += '%s</div>\n' % indentation(col)
250    return html
251
252def display(curpage, toc, depth, col):
253    html = ''
254    for name, title, sub in toc:
255        li_style = ' style="padding-left: %dem;"' % (depth + 1)
256        if sub == None:
257            if name == curpage:
258                cls = ' class="active"'
259            else:
260                cls = ''
261            html += '%s<li%s%s>%s</li>\n' % \
262                    (indentation(col), li_style, cls, title)
263        else:
264            html += '%s<li%s>\n' % (indentation(col), li_style)
265            col += 1
266            if name == None or sub:
267                html += '%s<h4>%s</h4>\n' % \
268                        (indentation(col), title)
269            else:
270                html += '%s<h4>%s...</h4>\n' % \
271                        (indentation(col), title)
272            col -= 1
273            html += '%s</li>\n' % indentation(col)
274            if len(sub) > 0:
275                html += display(curpage, sub, depth + 1, col)
276    return html
277