root/trac/trunk/wiki-macros/TracNav.py

Revision 3077, 9.4 kB (checked in by moschny, 6 years ago)

Cosmetics.

  • Property svn:keywords set to Id
Line 
1 # -*- coding: utf-8 -*-
2 """
3 = TracNav: The Navigation Bar for Trac =
4
5 This macro implements a fully customizable navigation bar for the Trac
6 wiki engine. The contents of the navigation bar is a wiki page itself
7 and can be edited like any other wiki page through the web
8 interface. The navigation bar supports hierarchical ordering of
9 topics. The design of TracNav mimics the design of the TracGuideToc
10 that was originally supplied with Trac. The drawback of TracGuideToc
11 is that it is not customizable without editing its source code and
12 that it does not support hierarchical ordering.
13
14
15 == Installation ==
16
17 To install TracNav, place the file `TracNav.py` in the `wiki-macros`
18 subdirectory 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 }}}
23 to the `templates/site_css.cs` file of your Trac project.
24
25 The `tracnav.css` file defines the styles for displaying the
26 navigation bar. These styles build upon the styles for !TracGuideToc
27 that come with your Trac distribution. If you just install the macro
28 but miss to install the style file, TracNav will work but look
29 somewhat strange.
30
31
32 == Usage ==
33
34 To use TracNav, create an index page for your site and call the
35 TracNav macro on each page, where the navigation bar should be
36 displayed. The index page is a regular wiki page. The page with the
37 table of contents must include an unordered list of links that should
38 be displayed in the navigation bar.
39
40 To display the navigation bar on a page, you must call the TracNav
41 macro on that page an pass the name of your table of contents as
42 argument.
43
44
45 == Author and License ==
46
47 Copyright 2005, 2006
48  *  Bernhard Haumacher (haui at haumacher.de)
49  *  Thomas Moschny (moschny at ipd.uni-karlsruhe.de)
50
51 {{{
52 This program is free software; you can redistribute it and/or modify
53 it under the terms of the GNU General Public License as published by
54 the Free Software Foundation; either version 2 of the License, or
55 (at your option) any later version.
56
57 This program is distributed in the hope that it will be useful,
58 but WITHOUT ANY WARRANTY; without even the implied warranty of
59 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
60 GNU General Public License for more details.
61
62 You should have received a copy of the GNU General Public License
63 along with this program; if not, write to the Free Software
64 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
65 }}}
66
67 == Additional information and a life example ==
68
69 Please visit: http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav
70 """
71
72 __revision__ = "$Id$"
73
74 import re
75 from trac.wiki.api import WikiSystem
76 from trac.wiki.model import WikiPage
77 from trac.wiki.formatter import Formatter, OneLinerFormatter
78 from StringIO import StringIO
79
80 TRACNAVHOME = "http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav"
81 LISTRULE = re.compile(r"^(?P<indent>[ \t\v]+)\* +(?P<rest>.*)$", re.M)
82 ALLOWED_MACROS = ["image"]
83
84 def 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
100 class 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
135 def 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
146 def 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
162 def _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
183 def 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
192 def 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
229 def 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
252 def indentation(col):
253     return ' ' * col
254
255 def 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
269 def 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 browser.