Edgewall Software
Home
Trac
Trac Hacks
Genshi
Babel
Bitten
Home
Download
Documentation
Mailing Lists
License
FAQ
Search:
Login
Preferences
Help/Guide
About Trac
Wiki
Timeline
Roadmap
Browse Source
View Tickets
New Ticket
Search
Context Navigation
+0
Start Page
Index
History
Editing TracDev/PortingFromClearSilverToGenshi
Adjust edit area height:
8
12
16
20
24
28
32
36
40
Edit side-by-side
[[PageOutline(2-3,Contents)]] = Porting Templates from ClearSilver to Genshi This page describes some of the differences between Genshi and Clearsilver. It is not a replacement for the [http://genshi.edgewall.org/ Genshi documentation] and you should go there for a more in-depth understanding of how Genshi actually works and should be used. 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. Note that ClearSilver is supported up to Trac 0.12.x, although no changes happened in that area since Trac 0.11. In Trac [milestone:0.13], this support has been dropped (r10405), effectively making the migration to Genshi templates mandatory. == Changes in the template syntax Most of the time, the porting is a straightforward operation. === expand a variable - Clearsilver [[xml(<b><?cs var:the_variable ?></b>)]] - Genshi [[xml(<b>$the_variable</b>)]] === expand a simple computation - Clearsilver [[xml(<b><?cs var:the_variable+1 ?></b>)]] - Genshi [[xml(<b>${the_variable+1}</b>)]] === include another template - Clearsilver [[xml(<?cs include:the_file.cs ?>)]] - Genshi [[xml(<xi:include href="the_file.html"><xi:fallback/></xi:include>)]] === simple if...then (no else) - Clearsilver [[xml(<?cs if:flag ?><b>OK</b><?cs /if ?>)]] - Genshi [[xml(<py:if test="flag"><b>OK</b></py:if>)]] or simply [[xml(<b py:if="flag">OK</b>)]] === if...then...else - Clearsilver {{{#!xml <?cs if:flag ?> <b>OK</b> <?cs else ?> <i>!!!</i> <?cs /if ?> }}} - Genshi {{{#!xml <py:choose test="flag"> <py:when test="True"> <b>OK</b> </py:when> <py:otherwise> <i>!!!</i> </py:otherwise> </py:choose> }}} or simply: {{{#!xml <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 {{{#!xml <ul><?cs each:element = list ?> <li><?cs var:element ?></li><?cs /each ?> </ul> }}} - Genshi {{{#!xml <ul> <py:for each="element in list"> <li>$element</li> </py:for> </ul> }}} or simply: {{{#!xml <ul> <li py:for="element in list">$element</li> </ul> }}} === define a macro - Clearsilver {{{#!xml <?cs def:entry(key, val)?> <dt><?cs var:key ?></dt><dd><?cs var:val ?></dd> <?cs /def ?> }}} - Genshi {{{#!xml <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 {{{#!xml <?cs set:count = len(collection) ?> We have <?cs if:count > 10 ?>too much<?cs else ?><?cs var:count ?><?cs /if ?> elements. }}} - Genshi {{{#!xml <py:with vars="count = len(collection)"> We have ${count > 10 and 'too much' or count} elements. </py:with> }}} Note that we had to use `>` 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 [source:trunk/templates/index.cs@3725 index.cs]: {{{#!xml <!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 [source:sandbox/genshi/templates/index.html@3728 index.html]: {{{#!xml <!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, use a Genshi attribute: the `py:for="project in projects"` and the `py:when="project.href"` in the above. Otherwise, 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 [source:sandbox/genshi/templates/diff_form.html@3831 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: ---- {{{#!html <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> }}} ---- 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 [source:sandbox/genshi/templates/query.html@3831 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. You could still insert directly some non well-formed `Markup` data in your template, but if you use the [http://genshi.edgewall.org/wiki/Documentation/builder.html 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 {{{#!xml 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 {{{#!xml <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 [source:sandbox/genshi/trac/wiki/web_ui.py@3831 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 [source:sandbox/genshi/trac/web/chrome.py@3730#L422 Chrome.load_template] and `render_method` methods. Note however that this API is still rapidly evolving. Usage examples: - Implementing [source:sandbox/genshi/trac/ticket/query.py@3725#L740 Ticket Query] macro (''table'' mode) - Sending [source:sandbox/genshi/trac/notification.py@3725#L99 notification] e-mails == Debugging Genshi is very different from ClearSilver. For ClearSilver the possibilities were essentially defined by the syntax + the HDF dataset that was available. Genshi evaluates Python, and operates in a Python context that makes a large number of objects directly available for use. However, doing `?hdfdump=1` on a Genshi template will only show a fraction of this content - whatever is added to the dictionary returned from the request handler and post-processors. Where is the project name? Where is the chrome links? Permissions? Here is a starting point for getting insight into the context, and help debugging your own templates. It is an example `site.html` file that can be added to global or project 'templates' folder, and which adds a debug output to all pages viewed. If you already have a `site.html`, just add the `<body py:match=...` element to the bottom of your own file. It contains massive amounts of information; use, trim, add to, and re-style to suit your particular needs. '''Note of warning:''' The debug prints out a lot of things. More often than not that works fine. However, in certain circumstances it could be that it tries to access something that cannot be represented as intended. The response is usually a somewhat cryptic error. When developing, the first instinct is often to re-check your own code (as you were developing when the error occured). Your first instinct should actually be: Comment out/disable the debug printing and try again - make sure it is not an error provoked by rummaging around in areas that were never meant for wrapping in `repr()`. {{{#!xml <html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" xmlns:xi="http://www.w3.org/2001/XInclude" py:strip=""> <!--! A new debug information <div> at the bottom of all pages --> <py:match path="body" once="True"> <body py:attrs="select('@*')"> ${select('*')} <div id="debug" style="width: 98%; margin: 5px; border: 2px solid green; padding: 10px; font-family: courier;" py:with="b_dir = globals()['__builtins__'].dir"> <p style="font-size: 1.15em;"><strong>Debug output - showing information from the rendering context, and edit <code>site.html</code> to test own expressions:</strong></p> <p>Checking some href information:<br /> req.path_info: ${repr(req.path_info)}<br /> req.base_path: ${repr(req.base_path)}<br /> req.href(): ${repr(req.href())}<br /> req.base_url: ${repr(req.base_url)}<br /> req.abs_href(): ${repr(req.abs_href())}<br /> </p> <p>Try using reg.hef(): ${req.href('wiki')}</p> <p>req.args: ${repr(req.args)}</p> <p>Test fetching an element: ${select('div[@id="mainnav"]')}</p> <div style="text-indent: -30px; padding-left: 30px;"> <!--! Some potentially very long lists... --> <p style="">perm for ${perm.username}: ${repr(perm.permissions())}</p> <p>project: ${repr(project)}</p> <p>trac: ${repr(trac or 'not defined')}</p> <p>context: ${repr(context)}</p> <p>context members: ${repr(b_dir(context))}</p> <p><strong>context __dict__:</strong> <div py:for="item in sorted(context.__dict__.keys())"> ${item}: ${repr(context.__dict__[item])}</div></p> <p><strong>req.environ:</strong> <div py:for="item in sorted(req.environ.keys())"> ${item}: ${repr(req.environ[item])}</div></p> <p><strong>req members:</strong> ${repr(b_dir(req))}</p> <p><strong>req __dict__:</strong> <div py:for="item in sorted(req.__dict__.keys())"> ${item}: ${repr(req.__dict__[item])}</div></p> <p><strong>all objects from locals().['__data__']:</strong> <div py:for="item in sorted(locals()['__data__'].keys())"> ${item}: ${repr(locals()['__data__'][item])}</div></p> <p><strong>__builtins__:</strong> <div py:for="key in sorted(globals()['__builtins__'].keys())"> ${key}: ${repr(globals()['__builtins__'][key])}</div></p> <p py:with="sys = __import__('sys')"> <strong>sys.path:</strong><br /> ${pprint(sys.path)}</p> </div> </div> </body> </py:match> </html> }}}
Note:
See
WikiFormatting
and
TracWiki
for help on editing wiki content.
Change information
Your email or username:
E-mail address and name can be saved in the
Preferences
Comment about this change (optional):
Note:
See
TracWiki
for help on using the wiki.