Edgewall Software

Version 8 (modified by Christian Boos, 18 years ago) ( diff )

Fixing some source: links, now that the genshi branch is gone

TracDev/ApiChanges/0.11

Note: Development of Trac 0.11 has started with r3804 and the trunk now uses Genshi instead of ClearSilver, for its template engine. Nevertheless, you should have in mind that the information in this page corresponds to a work in progress.

Migrating away from Clearsilver

ClearSilver has proven a bit uncomfortable to work with, and search for better alternatives were done a few months ago. The Kid templating language was unanimously found appealing, to the point cmlenz did a porting of Trac to Kid, during the DrProject fork. This in turn was found painful, and prompted Christopher to start his own, enhanced, version of Kid currently maturing as Genshi.

The migration from ClearSilver to Genshi was done on trunk in r3832.

The following documentation is by no means a replacement for the excellent documentation you'll find in the Genshi site. You should go there for a more in-depth understanding of how Genshi actually works and should be used. Here we'll focus on the differences with Clearsilver.

For migrating your own templates, a good way to start is to learn by example. Compare the Clearsilver templates found in source:trunk/templates@3831 and their corresponding Genshi ones in source:sandbox/genshi/templates@3831.

Then, in the same way, compare the various web_ui.py controllers you'll find in both branches.

Changes in the template syntax

Most of the time, the porting is a straightforward operation.

expand a variable

  • Clearsilver
    <b><?cs var:the_variable ?></b>
    
  • Genshi
    <b>$the_variable</b>
    

expand a simple computation

  • Clearsilver
    <b><?cs var:the_variable+1 ?></b>
    
  • Genshi
    <b>${the_variable+1}</b>
    

include another template

  • Clearsilver
    <?cs include:the_file.cs ?>
    
  • Genshi
    <xi:include href="the_file.html"><xi:fallback/></xi:include>
    

simple if…then (no else)

  • Clearsilver
    <?cs if:flag ?><b>OK</b><?cs /if ?>
    
  • Genshi
    <py:if test="flag"><b>OK</b></py:if>
    
    or simply
    <b py:if="flag">OK</b>
    

if…then…else

  • Clearsilver
    <?cs if:flag ?>
     <b>OK</b>
    <?cs else ?>
     <i>!!!</i>
    <?cs /if ?>
    
  • Genshi
    <py:choose test="flag">
      <py:when test="True">
        <b>OK</b>
      </py:when>
      <py:otherwise>
       <i>!!!</i>
      </py:otherwise>
    </py:choose>
    
    or simply:
    <py:choose>
     <b py:when="flag">OK</b>
     <i py:otherwise="">!!!</i>
    </py:choose>
    
    The <py:choose>/<py:when>/<py:otherwise> is a bit heavy-weight for a simple if/else, but on the other hand, the construct is more general (think switch/case, or the equivalent choose/when/otherwise in XSLT).

iterate over a collection

  • Clearsilver
    <ul><?cs 
     each:element = list ?>
      <li><?cs var:element ?></li><?cs 
     /each ?>
    </ul>
    
  • Genshi
    <ul>
      <py:for each="element in list">
        <li>$element</li>
      </py:for>
    </ul>
    
    or simply:
    <ul>
      <li py:for="element in list">$element</li>
    </ul>
    

define a macro

  • Clearsilver
    <?cs def:entry(key, val)?>
     <dt><?cs var:key ?></dt><dd><?cs var:val ?></dd>
    <?cs /def ?>
    
  • Genshi
    <py:def function="entry(key, val='--')">
     <dt>$key</dt><dd>$val</dd>
    </py:def>
    
    As you can see, with Genshi it's also easy to specify default values for the macro arguments.

set a variable

  • Clearsilver
    <?cs set:count = len(collection) ?>
    We have <?cs if:count > 10 ?>too much<?cs else ?><?cs var:count ?><?cs /if ?> elements.
    
  • Genshi
    <py:with vars="count = len(collection)">
    We have ${count &gt; 10 and 'too much' or count} elements.
    </py:with>
    
    Note that we had to use &gt; in Genshi, instead of directly > as in Clearsilver.

Examples

Let's first take a simple full-contained example from the Trac source, the simple index.cs / index.html templates.

  • Clearsilver index.cs:
    <!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
    <head><title>Available Projects</title></head>
    <body><h1>Available Projects</h1><ul><?cs
     each:project = projects ?><li><?cs
      if:project.href ?>
       <a href="<?cs var:project.href ?>" title="<?cs var:project.description ?>">
        <?cs var:project.name ?></a><?cs
      else ?>
       <small><?cs var:project.name ?>: <em>Error</em> <br />
       (<?cs var:project.description ?>)</small><?cs
      /if ?>
      </li><?cs
     /each ?></ul></body>
    </html>
    
  • Genshi index.html:
    <!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      xmlns:xi="http://www.w3.org/2001/XInclude">
    
      <head><title>Available Projects</title></head>
    
      <body>
        <h1>Available Projects</h1>
        <ul>
          <li py:for="project in projects" py:choose="">
            <a py:when="project.href" href="$project.href" title="$project.description">
              $project.name</a>
            <py:otherwise>
              <small>$project.name: <em>Error</em> <br /> 
               ($project.description)</small>
            </py:otherwise>
          </li>
        </ul>
      </body>
    </html>
    

Some remarks:

  • Note the possible use of multiple genshi attributes in the same element (in the above, the <li> element has a py:for and a py:choose attribute).
  • When there's only one element to output conditionally, one should use a genshi attribute (the py:for="project in projets" and the py:when="project.href" in the above). Otherwise, one should use a genshi element (here, the <py:otherwise>).
  • In this small example, there's no common Trac layout used (as the index is a bit special). For how a "normal" template looks like, see for example diff_form.html, another small template.

Note that a Genshi template can usually be rendered directly to have a taste of how it will look like:


Available Projects


This comes from an important property of Genshi templates: they must themselves be well-formed XML documents.

That was not a constraint in Clearsilver, and sometimes the logic in those templates took "advantage" of that, e.g. by conditionally inserting end/start pairs of tags. Such templates are the hardest to port, because you actually have to think a bit… See for example the query.html template. Of course, the great benefit of this constraint is that you'll end up quite naturally with well-formed content, which was far from being a trivial achievement using Clearsilver templates. Granted, you could still insert directly some non well-formed Markup data in your template, but again, if you use the genshi.builder tag facility for this, that's hardly a risk.

Another example from Trac, a bit more complex. This illustrates how to use <py:def> and <py:with>, to convert a Clearsilver macro using <?cs def: ?> and <?cs set: ?>.

  • Clearsilver
    def:browser_path_links(path, file) ?><?cs
    <?cs set:first = #1 ?><?cs
      each:part = path ?><?cs
       set:last = name(part) == len(path) - #1 ?><a<?cs 
       if:first ?> class="first" title="Go to root directory"<?cs 
        set:first = #0 ?><?cs 
       else ?> title="View <?cs var:part.name ?>"<?cs
       /if ?> href="<?cs var:part.href ?>"><?cs var:part.name ?></a><?cs
       if:!last ?><span class="sep">/</span><?cs /if ?><?cs 
     /each ?><?cs
    /def ?><?cs
    
  • Genshi
      <py:def function="browser_path_links(path_links)">
        <py:for each="idx, part in enumerate(path_links)">
          <py:with vars="first = (idx == 0); last = (idx == len(path_links) - 1)">
            <a class="${first and 'first' or None}" 
              title="${first and 'Go to root directory' or 'View ' + part.name}"
              href="$part.href">$part.name</a>
            <py:if test="not last"><span class="sep">/</span></py:if>
          </py:with>
        </py:for>
      </py:def>
    

Changes in the controllers

Implementing the IRequestHandler interface

Previously, all the data fed to a template had to be placed inside the req.hdf HDF wrapper object. With Genshi, the data for the template is basically a dict, which has to be returned by process_request at the same time as the template name. Check trac.wiki.web_ui for an example.

Generating content

When one wants to directly render a template, the Chrome component facilities should be used. Check the Chrome.load_template and render_method methods. Note however that this API is still rapidly evolving.

Usage examples:

Note: See TracWiki for help on using the wiki.