Changeset 3083


Ignore:
Timestamp:
03/23/06 00:18:07 (7 years ago)
Author:
moschny
Message:

Converted the TracNav macro into a Trac plugin. While this may need
some more polishing, using a plugin has some advantages:

  • Simpler Installation. The user can simply drop the an .egg into the project's plugin dir. No need to fiddle with the stylesheet or site_css.cs anymore.
  • We get access to the request object. It can e.g. be passed to the Image macro to overcome the limitation sketched in changeset:3063.
Location:
trac/plugins
Files:
9 added
2 copied

Legend:

Unmodified
Added
Removed
  • trac/plugins/tracnav/tracnav/tracnav.py

    r3082 r3083  
    11# -*- coding: utf-8 -*- 
    22""" 
    3 = TracNav: The Navigation Bar for Trac = 
     3= !TracNav: The Navigation Bar for Trac = 
    44 
    55This macro implements a fully customizable navigation bar for the Trac 
     
    77and can be edited like any other wiki page through the web 
    88interface. 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 
     9topics. The design of !TracNav mimics the design of the !TracGuideToc 
     10that was originally supplied with Trac. The drawback of !TracGuideToc 
    1111is that it is not customizable without editing its source code and 
    1212that it does not support hierarchical ordering. 
    1313 
    14  
    1514== Installation == 
    1615 
    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  
     16See http://projects.edgewall.com/trac/wiki/TracPlugins. 
    3117 
    3218== Usage == 
    3319 
    34 To use TracNav, create an index page for your site and call the 
     20To use !TracNav, create an index page for your site and call the 
    3521TracNav macro on each page, where the navigation bar should be 
    3622displayed. The index page is a regular wiki page. The page with the 
     
    3824be displayed in the navigation bar. 
    3925 
    40 To display the navigation bar on a page, you must call the TracNav 
     26To display the navigation bar on a page, you must call the !TracNav 
    4127macro on that page an pass the name of your table of contents as 
    4228argument. 
    4329 
     30== Additional information and a life example == 
     31 
     32Please visit: http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav. 
    4433 
    4534== Author and License == 
     
    6554}}} 
    6655 
    67 == Additional information and a life example == 
    68  
    69 Please visit: http://svn.ipd.uka.de/trac/javaparty/wiki/TracNav 
    7056""" 
    7157 
     
    7359 
    7460import re 
    75 from trac.wiki.api import WikiSystem 
     61from trac.core import Component 
     62from trac.wiki.api import WikiSystem, IWikiMacroProvider 
     63from trac.web.chrome import ITemplateProvider 
    7664from trac.wiki.model import WikiPage 
    7765from trac.wiki.formatter import Formatter, OneLinerFormatter 
     
    8169LISTRULE = re.compile(r"^(?P<indent>[ \t\v]+)\* +(?P<rest>.*)$", re.M) 
    8270ALLOWED_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 '' 
    9871 
    9972 
     
    10982        return out.getvalue(), self.link 
    11083 
    111     def __init__(self, env): 
     84    def __init__(self, env, req = None): 
    11285        OneLinerFormatter.__init__(self, env) 
     86        # OneLinerFormatter sets req to None 
     87        self.req = req 
    11388        self.link = None 
    11489 
     
    133108 
    134109 
    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)) 
     110class TracNav(Component): 
     111 
     112    from trac.core import implements 
     113    implements(IWikiMacroProvider, ITemplateProvider) 
     114 
     115    def get_toc(self, req, name): 
     116        """ 
     117        Fetch the wiki page containing the toc, if available. 
     118        """ 
     119        preview = req.hdf.getValue('args.preview', "") 
     120 
     121        if  preview: 
     122            cur_path = req.hdf.getValue('HTTP.PathInfo', '') 
     123            toc_path = "/wiki/" + name 
     124            if cur_path == toc_path: 
     125                return req.hdf.getValue('args.text', '') 
     126 
     127        if WikiSystem(self.env).has_page(name): 
     128            return WikiPage(self.env, name).text 
     129        else: 
     130            return '' 
     131 
     132 
     133    def get_toc_entry(self, toc_text, req): 
     134        """ 
     135        Parse and format the entries in toc_text. 
     136        """ 
     137        formatter = TocFormatter(self.env, req) 
     138        for match in LISTRULE.finditer(toc_text): 
     139            indent = len(match.group('indent')) 
     140            label, link = formatter.format_toc(match.group('rest')) 
     141            yield indent, link, label 
     142 
     143 
     144    def get_toc_entry_and_indent(self, gen): 
     145        """ 
     146        Filter for get_toc_entry().  The first call to next() returns the 
     147        indentation level of the next entry (or -1 if there are no more 
     148        entries) and the second call returns the entry itself. 
     149        """ 
     150        while True: 
     151            try: 
     152                indent, link, label = gen.next() 
     153            except StopIteration: 
     154                yield -1 
     155                return 
     156            yield indent 
     157            yield link, label         
     158 
     159 
     160    def _parse_toc(self, gen, next_indent, level = 0): 
     161        toclist = [] 
     162        if next_indent > level: 
     163            sublist, next_indent = self._parse_toc(gen, next_indent, level + 1) 
     164            if next_indent < level: # level is empty 
     165                return sublist, next_indent 
     166            else:                   # broken indentation structure 
     167                toclist.append((None, None, sublist)) 
     168        while True: 
     169            if next_indent == level: 
     170                (link, label), next_indent = gen.next(), gen.next() 
     171                if next_indent > level: 
     172                    sublist, next_indent = self._parse_toc(gen, next_indent, level + 1) 
     173                    toclist.append((link, label, sublist)) 
     174                else: 
     175                    toclist.append((link, label, None)) 
    176176            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 
     177                assert next_indent < level 
     178                return toclist, next_indent 
     179 
     180 
     181    def parse_toc(self, toc_text, req = None): 
     182        """ 
     183        Recursively construct the toc tree using _parse_toc(). 
     184        """ 
     185        gen = self.get_toc_entry(toc_text, req) 
     186        gen = self.get_toc_entry_and_indent(gen) 
     187        toc, _ = self._parse_toc(gen, gen.next()) 
     188        return toc 
     189 
     190 
     191    def execute(self, req, args): 
     192        """ 
     193        Main routine of the wiki macro. 
     194        """ 
     195        curpage = req.hdf.getValue('wiki.page_name', "") 
     196 
     197        # split the argument to get the wiki page names to include 
     198        names = (args or "TOC").split('|') 
     199 
     200        # Parsing the tocS 
     201        tocs = [] 
     202        for name in names: 
     203            toc_text = self.get_toc(req, name) 
     204            toc = self.parse_toc(toc_text, req) 
     205            if not toc: 
     206                toc = self.parse_toc(' * TOC "%s" is empty!' % name) 
     207            tocs.append((name, toc)) 
     208 
     209        col = 0 
     210        html = '%s<div class="wiki-toc trac-nav">\n' % self.indentation(col) 
     211        col += 1 
     212        html += '%s<h2><a href="%s">TracNav</a> menu</h2>\n' % \ 
     213                (self.indentation(col), TRACNAVHOME) 
     214 
     215        for name, toc in tocs: 
     216            (found, filtered) = self.filter_toc(curpage, toc) 
     217            if found: 
     218                html += self.display_all(req, name, filtered, col) 
     219            else: 
     220                html += self.display_all(req, name, toc, col) 
     221        col -= 1 
     222        html += '%s</div>\n' % self.indentation(col) 
     223 
     224        from trac.web.chrome import add_stylesheet 
     225        add_stylesheet(req, 'tracnav/css/tracnav.css') 
     226        return html 
     227 
     228 
     229    def filter_toc(self, curpage, toc, level = 0): 
     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) = self.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)) 
    246248                else: 
    247                     result.append((name, title, subtoc)) 
     249                    result.append((name, title, [])) 
     250        return (found, result) 
     251 
     252 
     253    def indentation(self, col): 
     254        return ' ' * col 
     255 
     256 
     257    def display_all(self, req, name, toc, col): 
     258        preview = req.hdf.getValue('args.preview', "") 
     259        curpage = req.hdf.getValue('wiki.page_name', "") 
     260        html = '' 
     261 
     262        if (not preview) and req.hdf.getValue('trac.acl.WIKI_MODIFY', ''): 
     263            html += '%s<div class="edit"><a href="%s?edit=yes">edit</a></div>\n' % \ 
     264                    (self.indentation(col), self.env.href.wiki(name)) 
     265        html += '%s<ul>\n' % self.indentation(col) 
     266        col += 1 
     267        html += self.display(curpage, toc, 0, col) 
     268        col -= 1 
     269        html += '%s</ul>\n' % self.indentation(col) 
     270        return html 
     271 
     272 
     273    def display(self, curpage, toc, depth, col): 
     274        html = '' 
     275        for name, title, sub in toc: 
     276            li_style = ' style="padding-left: %dem;"' % (depth + 1) 
     277            if sub == None: 
     278                if name == curpage: 
     279                    cls = ' class="active"' 
     280                else: 
     281                    cls = '' 
     282                html += '%s<li%s%s>%s</li>\n' % \ 
     283                        (self.indentation(col), li_style, cls, title) 
    248284            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  
     285                html += '%s<li%s>\n' % (self.indentation(col), li_style) 
     286                col += 1 
     287                if name == None or sub: 
     288                    html += '%s<h4>%s</h4>\n' % \ 
     289                            (self.indentation(col), title) 
     290                else: 
     291                    html += '%s<h4>%s...</h4>\n' % \ 
     292                            (self.indentation(col), title) 
     293                col -= 1 
     294                html += '%s</li>\n' % self.indentation(col) 
     295                if len(sub) > 0: 
     296                    html += self.display(curpage, sub, depth + 1, col) 
     297        return html 
     298 
     299 
     300    def get_macros(self): 
     301        yield 'TracNav' 
     302 
     303 
     304    def render_macro(self, req, name, args): 
     305        return self.execute(req, args) 
     306     
     307 
     308    def get_macro_description(self, name): 
     309        from inspect import getdoc, getmodule 
     310        return getdoc(getmodule(self)) 
     311 
     312 
     313    def get_htdocs_dirs(self): 
     314        from pkg_resources import resource_filename 
     315        return [('tracnav', resource_filename(__name__, 'htdocs'))] 
     316 
     317 
     318    def get_templates_dirs(self): 
     319        # we don't provide templates 
     320        return [] 
     321     
Note: See TracChangeset for help on using the changeset viewer.