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

Revision 3059, 9.0 KB checked in by moschny, 7 years ago (diff)
  • Simplifications.
  • 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 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.