# vim: expandtab tabstop=4
from trac.core import *
from trac.util import escape
from trac.wiki.formatter import Formatter, OutlineFormatter, wiki_to_oneliner, wiki_to_outline
from trac.wiki.api import IWikiMacroProvider, WikiSystem
from trac.wiki.model import WikiPage
from StringIO import StringIO
import os, re, string, inspect
__all__ = ['TracTocMacro']
class NullOut(object):
def write(self, *args): pass
class MyOutlineFormatter(OutlineFormatter):
def format(self, active_page, page, text, out, min_depth, max_depth):
self.__page = page
# XXX Code copied straight out of OutlineFormatter
self.outline = []
class NullOut(object):
def write(self, data): pass
Formatter.format(self, text, NullOut())
if min_depth > max_depth:
min_depth, max_depth = max_depth, min_depth
max_depth = min(6, max_depth)
min_depth = max(1, min_depth)
curr_depth = min_depth - 1
for depth, link in self.outline:
active = ''
if '/%s' % active_page in link:
active = ' class="active"'
if depth < min_depth or depth > max_depth:
continue
if depth < curr_depth:
out.write('
' % active * (curr_depth - depth))
elif depth > curr_depth:
out.write('- ' % active * (depth - curr_depth))
else:
out.write("
- \n" % active)
curr_depth = depth
out.write(link)
out.write('
' * curr_depth)
def _heading_formatter(self, match, fullmatch):
Formatter._heading_formatter(self, match, fullmatch)
depth = min(len(fullmatch.group('hdepth')), 5)
heading = match[depth + 1:len(match) - depth - 1]
anchor = self._anchors[-1]
text = wiki_to_oneliner(heading, self.env, self.db, self._absurls)
text = re.sub(r'?a(?: .*?)?>', '', text) # Strip out link tags
self.outline.append((depth, '%s' %
(self.env.href.wiki(self.__page), anchor, text)))
class TracTocMacro(Component):
"""
Generate a table of contents for the current page or a set of pages.
If no arguments are given, a table of contents is generated for the
current page, with the top-level title stripped:
{{{
[[TOC]]
}}}
To generate a table of contents for a set of pages, simply pass them
as comma separated arguments to the TOC macro, e.g. as in
{{{
[[TOC(TracGuide, TracInstall, TracUpgrade, TracIni, TracAdmin, TracBackup, TracLogging,
TracPermissions, TracWiki, WikiFormatting, TracBrowser, TracRoadmap, TracChangeset,
TracTickets, TracReports, TracQuery, TracTimeline, TracRss, TracNotification)]]
}}}
The following ''control'' arguments change the default behaviour of
the TOC macro:
|| '''Argument''' || '''Meaning''' ||
|| {{{heading=}}} || Override the default heading of "Table of Contents" ||
|| {{{noheading}}} || Suppress display of the heading. ||
|| {{{depth=}}} || Display headings of ''subsequent'' pages to a maximum depth of ''''''. ||
|| {{{inline}}} || Display TOC inline rather than as a side-bar. ||
|| {{{titleindex}}} || Only display the page name and title of each page, similar to TitleIndex. ||
Note that the current page must also be specified if individual wiki
pages are given in the argument list.
"""
implements(IWikiMacroProvider)
def get_macros(self):
yield 'TOC'
def get_macro_description(self, name):
return inspect.getdoc(self.__class__)
def render_macro(self, req, name, args):
db = self.env.get_db_cnx()
formatter = MyOutlineFormatter(self.env)
# Bail out if we are in a no-float zone
if 'macro_no_float' in req.hdf:
return ''
# If this is a page preview, try to figure out where its from
current_page = req.hdf.getValue('wiki.page_name','WikiStart')
in_preview = False
if not req.hdf.has_key('wiki.page_name'):
if req.path_info.startswith('/wiki/'):
current_page = req.path_info[6:]
in_preview = True
else:
return ''
def get_page_text(pagename):
"""Return a tuple of (text, exists) for a page, taking into account previews."""
if in_preview and pagename == current_page:
return (req.args.get('text',''), True)
else:
page = WikiPage(self.env,pagename)
return (page.text, page.exists)
# Split the args
if not args: args = ''
args = [x.strip() for x in args.split(',')]
# Options
inline = False
heading = 'Table of Contents'
pagenames = []
root = ''
params = { 'title_index': False, 'min_depth': 1, 'max_depth': 6 }
# Global options
for arg in args:
if arg == 'inline':
inline = True
elif arg == 'noheading':
heading = ''
elif arg == 'notitle':
params['min_depth'] = 2 # Skip page title
elif arg == 'titleindex':
params['title_index'] = True
heading = ''
elif arg == 'nofloat':
return ''
elif arg.startswith('heading='):
heading = arg[8:]
elif arg.startswith('depth='):
params['max_depth'] = int(arg[6:])
elif arg.startswith('root='):
root = arg[5:]
elif arg != '':
pagenames.append(arg)
# Has the user supplied a list of pages?
if not pagenames:
pagenames.append(current_page)
root = ''
params['min_depth'] = 2 # Skip page title
out = StringIO()
if not inline:
out.write("\n")
if heading:
out.write("
%s
\n" % heading)
for pagename in pagenames:
if params['title_index']:
li_class = pagename.startswith(current_page) and ' class="active"' or ''
prefix = (pagename.split('/'))[0]
prefix = prefix.replace('\'', '\'\'')
all_pages = list(WikiSystem(self.env).get_pages(prefix))
if all_pages:
all_pages.sort()
out.write('
')
for page in all_pages:
page_text, _ = get_page_text(page)
formatter.format(current_page, page, page_text, NullOut(), 1, 1)
header = ''
if formatter.outline:
title = formatter.outline[0][1]
title = re.sub('<[^>]*>','', title) # Strip all tags
header = ': ' + wiki_to_oneliner(title, self.env)
out.write('- %s %s
\n' % (li_class, self.env.href.wiki(page), page, header))
out.write('
')
else :
out.write('
Error: No page matching %s found
' % prefix)
else:
page = root + pagename
page_text, page_exists = get_page_text(page)
if page_exists:
formatter.format(current_page, page, page_text, out, params['min_depth'], params['max_depth'])
else:
out.write('
Error: Page %s does not exist
' % pagename)
if not inline:
out.write("
\n")
return out.getvalue()