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

Last change on this file since 3077 was 3077, checked in by moschny, 9 years ago

Cosmetics.

  • Property svn:keywords set to Id
File size: 9.4 KB
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`htdocs` 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, 2006
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 3077 2006-02-25 10:30:28Z moschny $"
73
74import re
75from trac.wiki.api import WikiSystem
76from trac.wiki.model import WikiPage
77from trac.wiki.formatter import Formatter, OneLinerFormatter
78from StringIO import StringIO
79
80TRACNAVHOME = "http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav"
81LISTRULE = re.compile(r"^(?P<indent>[ \t\v]+)\* +(?P<rest>.*)$", re.M)
82ALLOWED_MACROS = ["image"]
83
84def get_toc(hdf, env, preview, name):
85    """
86    Fetch the wiki page containing the toc, if available.
87    """
88    if preview:
89        cur_path = hdf.getValue('HTTP.PathInfo', '')
90        toc_path = "/wiki/" + name
91        if cur_path == toc_path:
92            return hdf.getValue('args.text', '')
93
94    if WikiSystem(env).has_page(name):
95        return WikiPage(env, name).text
96    else:
97        return ''
98
99
100class TocFormatter(OneLinerFormatter):
101    """
102    Basically the OneLinerFormatter, but additionally remembers the
103    last wiki link.
104    """
105    def format_toc(self, wikitext):
106        self.link = None
107        out = StringIO()
108        OneLinerFormatter.format(self, wikitext, out)
109        return out.getvalue(), self.link
110
111    def __init__(self, env):
112        OneLinerFormatter.__init__(self, env)
113        self.link = None
114
115    def _make_link(self, namespace, target, match, label):
116        if namespace == 'wiki':
117            self.link = target
118        return OneLinerFormatter._make_link(
119            self, namespace, target, match, label)
120
121    def _macro_formatter(self, match, fullmatch):
122        name = fullmatch.group('macroname').lower()
123        if name == 'br':
124            return ' '
125        elif name in ALLOWED_MACROS:
126            # leapfrog the OneLinerFormatter
127            return Formatter._macro_formatter(self, match, fullmatch)
128        else:
129            return ''
130
131    # FIXME: what about _make_relative_link() ?
132    # FIXME: CamelCase links are special and not handled by the Formatter...
133
134
135def get_toc_entry(toc_text, env):
136    """
137    Parse and format the entries in toc_text.
138    """
139    formatter = TocFormatter(env)
140    for match in LISTRULE.finditer(toc_text):
141        indent = len(match.group('indent'))
142        label, link = formatter.format_toc(match.group('rest'))
143        yield indent, link, label
144
145
146def get_toc_entry_and_indent(gen):
147    """
148    Filter for get_toc_entry().  The first call to next() returns the
149    indentation level of the next entry (or -1 if there are no more
150    entries) and the second call returns the entry itself.
151    """
152    while True:
153        try:
154            indent, link, label = gen.next()
155        except StopIteration:
156            yield -1
157            return
158        yield indent
159        yield link, label       
160
161
162def _parse_toc(gen, next_indent, level = 0):
163    toclist = []
164    if next_indent > level:
165        sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
166        if next_indent < level: # level is empty
167            return sublist, next_indent
168        else:                   # broken indentation structure
169            toclist.append((None, None, sublist))
170    while True:
171        if next_indent == level:
172            (link, label), next_indent = gen.next(), gen.next()
173            if next_indent > level:
174                sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
175                toclist.append((link, label, sublist))
176            else:
177                toclist.append((link, label, None))
178        else:
179            assert next_indent < level
180            return toclist, next_indent
181
182
183def parse_toc(toc_text, env):
184    """
185    Recursively construct the toc tree using _parse_toc().
186    """
187    gen = get_toc_entry_and_indent(get_toc_entry(toc_text, env))
188    toc, _ = _parse_toc(gen, gen.next())
189    return toc
190   
191
192def execute(hdf, args, env):
193    """
194    Main routine of the wiki macro.
195    """
196    #env.log.debug("hdf: %s", hdf)
197    preview = hdf.getValue('args.preview', "")
198    curpage = hdf.getValue('wiki.page_name', "")
199
200    # split the argument to get the wiki page names to include
201    names = (args or "TOC").split('|')
202
203    # Parsing the tocS
204    tocs = []
205    for name in names:
206        toc = parse_toc(get_toc(hdf, env, preview, name), env)
207        if not toc:
208            toc = parse_toc(' * TOC "%s" is empty!' % name, env)
209        tocs.append((name, toc))
210
211    col = 0
212    html = '%s<div class="wiki-toc trac-nav">\n' % indentation(col)
213    col += 1
214    html += '%s<h2><a href="%s">TracNav</a> menu</h2>\n' % \
215            (indentation(col), TRACNAVHOME)
216
217    for name, toc in tocs:
218        (found, filtered) = filter_toc(curpage, toc, 0)
219        if found:
220            html += display_all(hdf, env, name, curpage, filtered, col)
221        else:
222            html += display_all(hdf, env, name, curpage, toc, col)
223    col -= 1
224    html += '%s</div>\n' % indentation(col)
225    return html
226
227
228
229def filter_toc(curpage, toc, level):
230    found = 0
231    result = []
232    for name, title, sub in toc:
233        if sub == None:
234            if name == curpage:
235                found = 1
236            result.append((name, title, None))
237        else:
238            (subfound, subtoc) = filter_toc(curpage, sub, level + 1)
239            if subfound:
240                found = 1
241            if subfound or (name == None):
242                if level == 0 and name != None:
243                    prepended = [(name, title, subtoc)]
244                    prepended.extend(result)
245                    result = prepended
246                else:
247                    result.append((name, title, subtoc))
248            else:
249                result.append((name, title, []))
250    return (found, result)
251
252def indentation(col):
253    return ' ' * col
254
255def display_all(hdf, env, name, curpage, toc, col):
256    preview = hdf.getValue('args.preview', "")
257    html = ''
258
259    if (not preview) and hdf.getValue('trac.acl.WIKI_MODIFY', ''):
260        html += '%s<div class="edit"><a href="%s?edit=yes">edit</a></div>\n' % \
261                (indentation(col), env.href.wiki(name))
262    html += '%s<ul>\n' % indentation(col)
263    col += 1
264    html += display(curpage, toc, 0, col)
265    col -= 1
266    html += '%s</ul>\n' % indentation(col)
267    return html
268
269def display(curpage, toc, depth, col):
270    html = ''
271    for name, title, sub in toc:
272        li_style = ' style="padding-left: %dem;"' % (depth + 1)
273        if sub == None:
274            if name == curpage:
275                cls = ' class="active"'
276            else:
277                cls = ''
278            html += '%s<li%s%s>%s</li>\n' % \
279                    (indentation(col), li_style, cls, title)
280        else:
281            html += '%s<li%s>\n' % (indentation(col), li_style)
282            col += 1
283            if name == None or sub:
284                html += '%s<h4>%s</h4>\n' % \
285                        (indentation(col), title)
286            else:
287                html += '%s<h4>%s...</h4>\n' % \
288                        (indentation(col), title)
289            col -= 1
290            html += '%s</li>\n' % indentation(col)
291            if len(sub) > 0:
292                html += display(curpage, sub, depth + 1, col)
293    return html
294
Note: See TracBrowser for help on using the repository browser.