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

Revision 3058, 9.1 KB checked in by moschny, 7 years ago (diff)
  • Simplify get_toc_entry_and_indent().
  • Update copyright notice.
  • 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
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        self.myenv = env
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
121def format_toc_entry(wikitext, env):
122    out = StringIO()
123    link = TocFormatter(env).format_toc(wikitext, out)
124    return out.getvalue(), link
125
126
127def get_toc_entry(toc_text, env):
128    """
129    Filter the toc_text for toc entries.
130    """
131    for match in LISTRULE.finditer(toc_text):
132        indent = len(match.group('indent'))
133        label, link = format_toc_entry(match.group('rest'), env)
134        # env.log.debug("link is '%s'" % link)
135        yield indent, link, label
136
137
138def get_toc_entry_and_indent(gen):
139    """
140    Filter for get_toc_entry().  Returns link and label of the current
141    toc entry and the indentation level of the next entry or -1 if
142    there are no more entries. The first call to next() returns the
143    indentation of the first entry.
144    """
145    while True:
146        try:
147            indent, link, label = gen.next()
148        except StopIteration:
149            yield -1
150            return
151        yield indent
152        yield link, label       
153
154
155def _parse_toc(gen, next_indent, level = 0):
156    toclist = []
157    if next_indent > level:
158        sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
159        if next_indent < level: # level is empty
160            return sublist, next_indent
161        else:                   # broken indentation structure
162            toclist.append((None, None, sublist))
163    while 1:
164        if next_indent == level:
165            (link, label), next_indent = gen.next(), gen.next()
166            if next_indent > level:
167                sublist, next_indent = _parse_toc(gen, next_indent, level + 1)
168                toclist.append((link, label, sublist))
169            else:
170                toclist.append((link, label, None))
171        else:
172            assert next_indent < level
173            return toclist, next_indent
174
175
176def parse_toc(toc_text, env):
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    preview = hdf.getValue('args.preview', "")
184    curpage = hdf.getValue('wiki.page_name', "")
185    name = args
186    if not name:
187        name = 'TOC'
188
189    toc = parse_toc(get_toc(hdf, env, curpage, name), env)
190    if not toc:
191        msg = '<div class="system-message">' \
192              "<strong>Error: Table of contents does not exist or is empty."
193        if (not preview) and (hdf.getValue('trac.acl.WIKI_MODIFY', '')):
194            msg += ' Click here to <a href="%s?edit=yes">edit</a>.' % \
195                   env.href.wiki(name)
196        msg += '</strong></div>\n'
197        return msg
198
199    (found, filtered) = filter_toc(curpage, toc, 0)
200    if found:
201        return display_all(hdf, env, name, curpage, filtered, 0)
202    else:
203        return display_all(hdf, env, name, curpage, toc, 0)
204
205
206def filter_toc(curpage, toc, level):
207    found = 0
208    result = []
209    for name, title, sub in toc:
210        if sub == None:
211            if name == curpage:
212                found = 1
213            result.append((name, title, None))
214        else:
215            (subfound, subtoc) = filter_toc(curpage, sub, level + 1)
216            if subfound:
217                found = 1
218            if subfound or (name == None):
219                if level == 0 and name != None:
220                    prepended = [(name, title, subtoc)]
221                    prepended.extend(result)
222                    result = prepended
223                else:
224                    result.append((name, title, subtoc))
225            else:
226                result.append((name, title, []))
227    return (found, result)
228
229def indentation(col):
230    return ' ' * col
231
232def display_all(hdf, env, name, curpage, toc, col):
233    preview = hdf.getValue('args.preview', "")
234    html = '%s<div class="wiki-toc trac-nav">\n' % indentation(col)
235    col += 1
236    html += '%s<h2><a href="%s">TracNav</a> menu</h2>' % \
237            (indentation(col), TRACNAVHOME)
238
239    if (not preview) and hdf.getValue('trac.acl.WIKI_MODIFY', ''):
240        html += '%s<div class="edit"><a href="%s?edit=yes">edit</a></div>\n' % \
241                (indentation(col), env.href.wiki(name))
242    html += '%s<ul>\n' % indentation(col)
243    col += 1
244    html += display(env, curpage, toc, 0, col)
245    col -= 1
246    html += '%s</ul>\n' % indentation(col)
247    col -= 1
248    html += '%s</div>\n' % indentation(col)
249    return html
250
251def display(env, curpage, toc, depth, col):
252    html = ''
253    for name, title, sub in toc:
254        li_style = ' style="padding-left: %dem;"' % (depth + 1)
255        if sub == None:
256            if name == curpage:
257                cls = ' class="active"'
258            else:
259                cls = ''
260            html += '%s<li%s%s>%s</li>\n' % \
261                    (indentation(col), li_style, cls, title)
262        else:
263            html += '%s<li%s>\n' % (indentation(col), li_style)
264            col += 1
265            if name == None or sub:
266                html += '%s<h4>%s</h4>\n' % \
267                        (indentation(col), title)
268            else:
269                html += '%s<h4>%s...</h4>\n' % \
270                        (indentation(col), title)
271            col -= 1
272            html += '%s</li>\n' % indentation(col)
273            if len(sub) > 0:
274                html += display(env, curpage, sub, depth + 1, col)
275    return html
276
Note: See TracBrowser for help on using the repository browser.