Edgewall Software

Changes between Initial Version and Version 1 of TracDev/IWikiSyntaxProviderExample


Ignore:
Timestamp:
Dec 14, 2009, 7:47:33 AM (14 years ago)
Author:
Karsten Fuhrmann <karsten_fuhrmann@…>
Comment:

Legend:

Unmodified
Added
Removed
Modified
  • TracDev/IWikiSyntaxProviderExample

    v1 v1  
     1
     2
     3== Interface IWikiSyntaxProvider ==
     4=== File: trac/wiki/api.py ===
     5The IWikiSyntaxProvider is an Interface you could use to write plugins that allow you to bring your own wiki syntax into trac.
     6Like the e.g. #5 to see Ticket with id 5. Or the e.g {1} way to see report number 1.
     7
     8Here is the interface description from IWikiSyntaxProvider taken from the source file trac/wiki/api.py
     9
     10{{{
     11#!python
     12class IWikiSyntaxProvider(Interface):
     13 
     14    def get_wiki_syntax():
     15        """Return an iterable that provides additional wiki syntax.
     16
     17        Additional wiki syntax correspond to a pair of (regexp, cb),
     18        the `regexp` for the additional syntax and the callback `cb`
     19        which will be called if there's a match.
     20        That function is of the form cb(formatter, ns, match).
     21        """
     22 
     23    def get_link_resolvers():
     24        """Return an iterable over (namespace, formatter) tuples.
     25
     26        Each formatter should be a function of the form
     27        fmt(formatter, ns, target, label), and should
     28        return some HTML fragment.
     29        The `label` is already HTML escaped, whereas the `target` is not.
     30        """
     31}}}
     32Ok, lets try to implement the following example wiki syntax:
     33
     34We want to be able to use something like ''s:w:someword'' to generate a link to a search in the wiki pages for ''someword''.
     35Searching in Tickets and Milestones should work equally by using ''s:t:someword'' and ''s:m:someword''.
     36We need to implement the method '''get_link_resolvers()''' to make that happen.
     37
     38I know you could also use the search:?q=searchword&wiki=on syntax, but i think its a good example for demonstration anyway.
     39
     40As you can see in the source above, there are just 2 methods you must implement to get your own wiki syntax up and running.
     41
     42The easiest one is '''get_link_resolvers()'''.
     43You need to return an iterable of tuples consisting of a string used as the prefix for a new linktype in trac and a callback, to be called when the prefix matches. In our example the string will be ''s'' shorthand for search. The method is really simple:
     44{{{
     45#!python
     46def get_link_resolvers(self):
     47    return [ ('s', self._format_search_link) ]
     48
     49}}}
     50
     51It just returns one tuple, but you could return more if you want. This tuple tells trac that it should call our '''_format_search_link()''' method every time it finds the string s:''anystring'' in some wiki content.
     52
     53The real work will be done in our next method: '''_format_search_link'''. This method is called anytime trac encounters one of our prefixes (inside wiki context), we registered through '''get_link_resolvers()'''.
     54{{{
     55#!python
     56def _format_search_link(self, formatter, ns, target, label):
     57    self.env.log.debug('formatter: %s ns: %s' % (formatter, ns))
     58    domains = {'w': 'wiki', 't': 'ticket', 'm': 'milestone'}
     59    domain = 'wiki'
     60    searchword = None
     61    if ":" in target:
     62        domain, searchword = target.split(':')
     63    else:
     64        searchword = target
     65    domain = domains.get(domain, 'wiki')
     66    return tag.a(label, href=formatter.href.search() +
     67        '?q=' + searchword + '&' + domain +
     68        '=on', title="Search for %s in %s(s)" % (searchword, domain))
     69}}}
     70The method gets 4 Arguments:
     71 * a formatter object.
     72 * a namespace, this string is our prefix for the linktype, in this case ''s''
     73 * a target, this is the string which followed the namespace prefix of the link without the trailing '':'' after namespace.
     74 * a label, this the full string of the link, e.g. ''s:searchword''
     75
     76The return value should be some html, in our case it is a link to the specified search query.
     77The first thing the method does is splitting the target into the domain and searchword
     78{{{
     79#!python
     80domain, searchword = target.split(':')
     81
     82}}}
     83
     84Then we return a valid link to a search page with our given searchquery. If there is no domain specified we use default to ''wiki'' and use the ''target'' string as  ''searchword''.
     85
     86=== Part two ===
     87
     88In the second part we implement the following wiki syntax using the second interface method '''get_wiki_syntax()''':
     89
     90 * ?t_searchword? Creates a link to a search for '''searchword''' in the Ticket domain.
     91 * ?m_searchword? Creates a link to a search for '''searchword''' in the Milestone domain.
     92 * ?w_searchword? Creates a link to a search for '''searchword''' in the wiki domain.
     93
     94You are not limited in creating new link types with this Interface but mostly you would use WikiMacros instead.
     95
     96We have to implement get_wiki_syntax() to be able to use regular expressions in our new wiki syntax.
     97{{{
     98#!python
     99def get_wiki_syntax(self):
     100    #group numbers dont work, why? Must use group names inside the regex.
     101    yield ( r'\?(?P<domain>[tmw])_(?P<searchword>.+?)\?', self._format_regex_link)
     102
     103}}}
     104
     105All this method does is telling trac that whenever it encounters a hit with our given regular expression(s) (as in '''get_link_resolvers()''' you can have more than one) it should call the given callback method.
     106
     107== Attention ==
     108Using group numbers instead of group names inside the regular expression didn't work here. '''Anyone have a clue?'''
     109
     110The given callback method must take 3 Arguments:
     111 * a formatter object
     112 * a namespace,  this is full match of the regex as string, same as match.group(0)
     113 * a match, this is a regex match object.
     114
     115Here is the callback method:
     116{{{
     117#!python
     118def _format_regex_link(self, formatter, ns, match):
     119    domains = {'w': 'wiki', 't': 'ticket', 'm': 'milestone'}
     120    searchword = match.group('searchword') or 'test'
     121    label= match.group(0)
     122    domain= match.group('domain')
     123    domain = domains.get(domain,'wiki')
     124    return tag.a(label, href=formatter.href.search() +
     125        '?q=' + searchword + '&' + domain + '=on',
     126        title="Search for %s in %s(s)" % (searchword, domain))
     127
     128}}}
     129
     130All it does is extracting the ''domain'' and ''searchword'' out of the match object and then returning a link to the search page.
     131
     132
     133
     134Here the full source for our little IWikiSyntaxProvider Test
     135{{{
     136#!python
     137# encoding: utf-8
     138"""
     139WikiSyntaxProviderTest.py
     140
     141Created by Karsten Fuhrmann on 2009-12-14.
     142Copyright (c) 2009 Karsten Fuhrmann (karsten_fuhrmann AT web.de) All rights reserved.
     143"""
     144
     145from trac.core import *
     146from trac.wiki import IWikiSyntaxProvider
     147from genshi.builder import tag
     148
     149class WikiSyntaxProviderTest(Component):
     150    '''This is a test Component to demonstrate the use of the IWikiSyntaxProvider Interface of Trac.'''
     151    implements(IWikiSyntaxProvider)
     152
     153    # IWikiSyntaxProvider methods
     154
     155    def get_link_resolvers(self):
     156        return [ ('s', self._format_search_link) ]
     157
     158    def get_wiki_syntax(self):
     159        #group numbers dont work, why? Must use group names inside the regex.
     160        yield ( r'\?(?P<domain>[tmw])_(?P<searchword>.+?)\?', self._format_regex_link)
     161
     162    def _format_regex_link(self, formatter, ns, match):
     163        self.env.log.debug('formatter: %s ns: %s' % (formatter, ns))
     164        domains = {'w': 'wiki', 't': 'ticket', 'm': 'milestone'}
     165        searchword = match.group('searchword') or 'test'
     166        label= match.group(0)
     167        domain= match.group('domain')
     168        domain = domains.get(domain,'wiki')
     169        return tag.a(label, href=formatter.href.search() +
     170            '?q=' + searchword + '&' + domain +
     171            '=on', title="Search for %s in %s(s)" % (searchword, domain))
     172
     173    def _format_search_link(self, formatter, ns, target, label):
     174        self.env.log.debug('formatter: %s ns: %s' % (formatter, ns))
     175        domains = {'w': 'wiki', 't': 'ticket', 'm': 'milestone'}
     176        domain = 'wiki'
     177        searchword = None
     178        if ":" in target:
     179            domain, searchword = target.split(':')
     180        else:
     181            searchword = target
     182        domain = domains.get(domain, 'wiki')
     183        return tag.a(label, href=formatter.href.search() +
     184            '?q=' + searchword + '&' + domain +
     185            '=on', title="Search for %s in %s(s)" % (searchword, domain))
     186
     187}}}