Edgewall Software

Changes between Version 41 and Version 42 of TracDev/PortingFromGenshiToJinja


Ignore:
Timestamp:
Jan 28, 2017, 12:38:17 AM (7 years ago)
Author:
Christian Boos
Comment:

major restructuring of the page, start with examples, continue with the code changes, finish with the detailed guide of the differences in the template syntax

Legend:

Unmodified
Added
Removed
Modified
  • TracDev/PortingFromGenshiToJinja

    v41 v42  
    1313The following documentation is primarily targeted at plugin developers who wish to adapt their Genshi templates to the Jinja2 template engine that will be used in Trac [milestone:1.4].
    1414
    15 For migrating your own templates, a good way to start is to learn from examples.
    16 Compare the  [source:cboos.git/trac/templates@jinja2-trunk-r15379 Jinja2 trac/templates] with their [source:trunk/trac/templates Genshi counterpart].
    17 
    18 In the first part of this document, we try to cover all the Genshi features used by Trac and present their Jinja2 equivalent. Whenever possible, we tried to minimize these differences by customizing the Jinja2 syntax. For example, we use `${...}` for variable expansion, like Genshi does, instead of `{{...}}`. Another aspect of our usage convention is that we favor [#ifthenelse line statements] over `{% ... %}`. So even someone familiar with the "default" Jinja2 syntax should glance through this document to see how "we" use Jinja2, as summarized in the table below.
    19 
    20 The last part of the document describes the Python code changes, focusing notably on how to replace the deprecated `ITemplateStreamFilter` interface.
    21 
    22 Note that Genshi will be supported concurrently with Jinja2 only for a short while, for the 1.3.x development period and for the 1.4-stable period. If for some reason you're stuck to having to support Genshi templates, you'll have to stick to Trac 1.2.x or 1.3.1. But you really should make the transition effort as Jinja2 templates are 5-10x faster than their Genshi equivalent, for only a 1/5th of the cost in memory usage.
    23 
    24 
    25 == The Jinja2 syntax
    26 
    27 The Jinja2 template engine is quite flexible and its syntax can be customized to some extent. We took this opportunity to make it as close as possible to the Genshi template syntax, in particular for the variable expansion.
    28 
    29 We use the following configuration:
    30 || key || value || example / explanation
    31 || extensions ||  jinja2.ext.with_  jinja2.ext.i18n  || `with` directive can be used ([#with more])
    32 || block_start_string ||  {%  || \
    33 {{{#!td rowspan=2
    34 `{% if cond %} value {% endif %}` possible (but discouraged)
    35 }}}
    36 |--
    37 || block_end_string ||  %}  ||
    38 || variable_start_string ||  ${  || \
    39 {{{#!td rowspan=2
    40 `${the_variable}` (but not `$the_variable`)
    41 ([#expandavariable more])
    42 }}}
    43 |--
    44 || variable_end_string ||  }  ||
    45 || line_statement_prefix ||  #  || \
    46 {{{#!td
    47 {{{
    48 # if cond:
    49 value
    50 # endif
    51 }}}
    52 (preferred form) ([#if more])
    53 }}}
    54 |--
    55 || line_comment_prefix ||  ##  || `## comments are good`
    56 || trim_blocks ||  yes  || whitespace removal after a block
    57 || lstrip_blocks ||  yes  || whitespace removal before a block
    58 || newstyle_gettext ||  yes  || i.e. like the Trac `gettext`
    59 
    60 
    61 == Changes in the HTML
    62 
    63 We took the opportunity to switch to HTML5 while performing this template overhaul.
    64 You should probably do the same.
    65 This usually won't change anything for your templates, except for a few details.
    66 
    67 The doctype and the <html> element should be changed from Genshi's:
    68 {{{#!html+genshi
    69 <!DOCTYPE html
    70     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    71     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    72 <html xmlns="http://www.w3.org/1999/xhtml"
    73       xmlns:py="http://genshi.edgewall.org/"
    74       xmlns:i18n="http://genshi.edgewall.org/i18n"
    75       xmlns:xi="http://www.w3.org/2001/XInclude">
    76 }}}
    77 
    78 to the somewhat simpler:
    79 {{{#!xml
    80 <!DOCTYPE html>
    81 <html>
    82 }}}
    83 
    84 Special care should be taken when dealing with //void elements//.
    85 In XHTML, it's fine to write:
    86 {{{#!xml
    87 <div id="description"/>
    88 }}}
    89 However, when going to HTML5, you should use instead:
    90 {{{#!xml
    91 <div id="description"></div>
    92 }}}
    93 The valid [https://www.w3.org/TR/html-markup/syntax.html#void-elements void elements] in HTML5 are limited to the following list:
    94  - area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr
    95 
    96 
    97 == Changes in the template syntax
    98 
    99 Most of the time, the porting is a straightforward operation.
    100 
    101 === expand a variable
    102  - Genshi [[html+genshi(<b>$the_variable</b>)]]
    103  - Jinja2 [[html+jinja(<b>${the_variable}</b>)]]
    104 
    105 Note that Jinja2 doesn't support the `$the_variable` style, the curly braces are mandatory.
    106 
    107 Tip:
    108 {{{#!shell
    109 sed -i -e 's,\$\([a-z_][a-z._]*\),${\1},g' template.html
    110 }}}
    111 
    112 === expand a simple computation
    113  - Genshi [[html+genshi(<b>${the_variable + 1}</b>)]]
    114  - Jinja2 [[html+jinja(<b>${the_variable + 1}</b>)]]
    115 
    116 However, Jinja2 expressions are only //similar// to Python expressions, there are a few differences and limitations, see [http://jinja.pocoo.org/docs/dev/templates/#expressions expressions] doc.
    117 
    118 See also [#set set complex variables] below for more involved examples.
    119 
    120 Another customization we made to Jinja2 is to avoid having a Python `None` value be expanded to the `"None"` string. Instead, we make it produce an empty string, like Genshi did.
    121 
    122 === include another template   #include
    123  - Genshi [[xml(<xi:include href="the_file.html"><xi:fallback/></xi:include>)]]
    124  - Jinja2 [[xml(# include "the_file.html" ignore missing)]]
    125 
    126 See [http://jinja.pocoo.org/docs/dev/templates/#include include] doc. Note that the `ignore missing` part is mandatory no templates are given to the `include` directive. This includes the following situation:
    127 {{{#!html+jinja
    128 # include "sometemplate.html" if false
    129 }}}
    130 This alone would raise an error. The correct thing to write is:
    131 {{{#!html+jinja
    132 # include "sometemplate.html" if false ignore missing
    133 }}}
    134 
    135 It is also possible to pass "parameters" to included templates.
    136 Those are not actually declared parameters, simply variables expected to be available in the template that can be set for the scope of one specific include. This works exactly like in Genshi, with a `with` statement:
    137 
    138  - Genshi
    139    {{{#!html+genshi
    140         <xi:include href="ticket_box.html" py:with="can_append = False; preview_mode = False"/>
    141    }}}
    142  - Jinja
    143    {{{#!html+jinja
    144       # with
    145       #   set can_append = false
    146       #   set preview_mode = false
    147       #   include "ticket_box.html"
    148       # endwith
    149    }}}
    150 
    151 See [http://jinja.pocoo.org/docs/dev/extensions/#with-statement with] doc.
    152 
    153 In passing, note how the boolean constants slightly differ from Python, `False` becomes `false`, `True` becomes `true` and `None` is `none`.
    154 
    155 Caveat: contrary to the Genshi way, one shouldn't include the `"layout.html"` template in order to inherit the default page layout. There's a big difference in the way template "inheritance" works between Genshi and Jinja2, see HtmlTemplates#Jinjaarchitecture for all the details.
    156 
    157 Despite these differences, we kept the same spirit and there's actually also a `"layout.html"` template that you can "inherit" from (as well as a `"theme.html"` template that the layout inherits in turn). But instead of "including" it in your template, you "extend" it:
    158 {{{#!html+jinja
    159 # extends "layout.html"
    160 }}}
    161 But this is not the place to go further in the details about how extending works, refer to the [http://jinja.pocoo.org/docs/dev/templates/#extends extends] documentation and to the link above for how this applies to Trac.
    162 
    163 
    164 === simple if...then (no else)   #if
    165  - Genshi [[xml(<py:if test="flag"><b>OK</b></py:if>)]] or simply: [[xml(<b py:if="flag">OK</b>)]]
    166  - Jinja2
    167    {{{#!html+jinja
    168    # if flag:
    169    <b>OK</b>
    170    # endif
    171    }}}
    172 
    173 See [http://jinja.pocoo.org/docs/dev/templates/#if if] doc.
    174 
    175 === if...then...else
    176  - Genshi
    177    {{{#!html+genshi
    178 <py:choose test="flag">
    179   <py:when test="True">
    180     <b>OK</b>
    181   </py:when>
    182   <py:otherwise>
    183    <i>!!!</i>
    184   </py:otherwise>
    185 </py:choose>
    186    }}}
    187    or simply:
    188    {{{#!html+genshi
    189 <py:choose>
    190  <b py:when="flag">OK</b>
    191  <i py:otherwise="">!!!</i>
    192 </py:choose>
    193    }}}
    194 
    195  - Jinja2
    196    {{{#!html+jinja
    197  # if flag:
    198  <b>OK</b>
    199  # else:
    200  <i>!!!</i>
    201  # endif
    202    }}}
    203 
    204 If you really have to, you can also use the block style:
    205 {{{#!html+jinja
    206 {{ if flag }}<b>OK</b>{{ else }}<i>!!!</i>{{ endif }}
    207 }}}
    208 However this goes against readability and processing via the [./Checker jinjachecker] tool,  so we really advise that you stick to the use of //[http://jinja.pocoo.org/docs/dev/templates/#line-statements line statements]//.
    209 
    210 === iterate over a collection   #for
    211  - Genshi
    212    {{{#!html+genshi
    213 <ul>
    214   <py:for each="element in list">
    215     <li>$element</li>
    216   </py:for>
    217 </ul>
    218    }}}
    219    or simply:
    220    {{{#!html+genshi
    221 <ul>
    222   <li py:for="element in list">$element</li>
    223 </ul>
    224    }}}
    225  - Jinja2
    226    {{{#!html+jinja
    227 <ul>
    228   # for element in list:
    229   <li>${element}</li>
    230   # endfor
    231 </ul>
    232    }}}
    233 See [http://jinja.pocoo.org/docs/dev/templates/#for for] doc.
    234 
    235 ==== No need for `enumerate`
    236 
    237  - Genshi:
    238 {{{#!html+genshi
    239           <tr py:for="idx,option in enumerate(section.options)"
    240               class="${'modified' if option.modified else None}">
    241             <th py:if="idx == 0" class="section"
    242                 rowspan="${len(section.options)}">${section.name}</th>
    243 
    244 }}}
    245  - Jinja2:
    246 {{{#!html+jinja
    247           #   for option in section.options:
    248           <tr ${{'class': 'modified' if option.modified}|htmlattr}>
    249             #   if loop.first:
    250             <th class="section"
    251                 rowspan="${len(section.options)}">${section.name}</th>
    252 
    253 }}}
    254 
    255 All common uses (and more) for such an `idx` variable are addressed by the special `loop` variable.
    256 See [http://jinja.pocoo.org/docs/dev/templates/#for loop] doc.
    257 
    258 === define a macro   #macro
    259  - Genshi
    260    {{{#!html+genshi
    261 <py:def function="entry(key, val='--')">
    262  <dt>$key</dt><dd>$val</dd>
    263 </py:def>
    264    }}}
    265  - Jinja2
    266    {{{#!html+jinja
    267  # macro entry(key, val='--')
    268  <dt>${key}</dt><dd>${val}</dd>
    269  # endmacro
    270    }}}
    271 See [http://jinja.pocoo.org/docs/dev/templates/#macros macros] doc.
    272 
    273 === set a variable
    274  - Genshi
    275    {{{#!html+genshi
    276 <py:with vars="count = len(collection)">
    277 We have ${count &gt; 10 and 'too much' or count} elements.
    278 </py:with>
    279    }}}
    280    Note that we had to use `&gt;` in Genshi, we can't use `>` directly.
    281  - Jinja2
    282    {{{#!html+jinja
    283 # set count = len(collection)
    284 We have ${'too much' if count is greaterthan(10) else count} elements.
    285    }}}
    286    Note that we avoid using `>` in Jinja2 expressions as well, but simply to avoid that XML/HTML text editors get confused. We added a few custom tests for that (`greaterthan`, `greaterthanorequal`, `lessthan`, `lessthanorequal`).
    287 See [http://jinja.pocoo.org/docs/dev/templates/#tests tests] doc.
    288 
    289 === set several variables in a scope #with
    290 
    291  - Genshi
    292    {{{#!html+genshi
    293 <html py:with="is_query = report.sql.startswith('query:');
    294                new_report = action == 'new' and not is_query;
    295                new_query = action == 'new' and is_query">
    296   ...
    297 </html>
    298    }}}
    299    The variables are set for the scope of the element in which they are set (here <html>,
    300    so the whole document)
    301  - Jinja2
    302    {{{#!html+jinja
    303     #   with
    304     #     set is_query = report.sql.startswith('query:')
    305     #     set new_report = action == 'new' and not is_query
    306     ...
    307     #   endwith
    308    }}}
    309 
    310 But actually you will only use `with` in specific situations, like for wrapping an include directive (see [#include]). If you're already within a `for`, a `block` or a `macro`, the scope of the assignment is already limited to that of this directive.
    311 
    312 See [http://jinja.pocoo.org/docs/dev/templates/#assignments set] and [http://jinja.pocoo.org/docs/dev/templates/#with-statement with] docs.
    313 
    314 In addition, `with` can also be used to better control how the successive `set` on a given variable are being applied (see for example [1e25c852/cboos.git] and [cc1b959e/cboos.git]).
    315 
    316 Finally be careful when using `with`: don't wrap a `block` directive within a `with`. If you want to set a global scope for the document (like in our <html> example above), it's tempting to use a single `with` statement, for clarity. But that would wrap all blocks defined in the template and strange results would ensue.
    317 
    318 
    319 === set HTML attributes   #htmlattr
    320 In Genshi, an attribute with a `None` value wouldn't be output. However, Jinja2 doesn't know what an attribute is (or anything else about HTML, XML for that matter), so we have to use a special filter, `htmlattr`, to reproduce this behavior:
    321 
    322  - Genshi:
    323 {{{#!html+genshi
    324           <tr class="${'disabled' if all(not component.enabled for module in plugin.modules.itervalues()
    325                                          for component in module.components.itervalues()) else None}">
    326 }}}
    327  - Jinja2:
    328 {{{#!html+jinja
    329           #   set components = plugin.modules|map(attribute='components')|flatten
    330           <tr${{'class': 'disabled' if not components|selectattr('enabled')
    331                }|htmlattr}>
    332 
    333 }}}
    334 (the `htmlattr` filter will add a space if needed; that way, if the condition is true, you end up with `<tr class="disabled">`, otherwise with just `<tr>`)
    335 
    336 If you wonder why the `if all(...)` expression morphed into `if not components|...`, it's because Jinja2 expressions are similar to Python expressions, but not quite the same.
    337 
    338 === set complex variables   #set
    339 Note that Jinja2 expressions are a subset of Python expressions, and for the sake of simplicity the generator expressions are not part of that subset. This limitation often requires one to make creative use of [http://jinja.pocoo.org/docs/dev/templates/#filters filters], [http://jinja.pocoo.org/docs/dev/templates/#builtin-filters built-in] or custom (`min`, `max`, `trim`, `flatten`).
    340 
    341 A few more examples:
    342 
    343 ||= Genshi ||= Jinja2 ||
    344 || `${', '.join([p.name for p in faulty_plugins])}` || \
    345 || `${faulty_plugins|map('name')|join(', ')}` ||
    346 || `sum(1 for change in changes if 'cnum' in change)` || \
    347 || `changes|selectattr('cnum')|list|count` ||
    348 || `sum(1 for change in changes if 'cnum' not in change)` || \
    349 || `changes|rejectattr('cnum')|list|count` ||
    350 || ... || ... ||
    351 
    352 
    353 === conditional wrappers
    354 There's no direct equivalent to the `py:strip`, but it can often be emulated with an `if/else/endif`.
    355  - Genshi
    356    {{{#!html+genshi
    357      <a py:strip="not url" href="$url">$plugin.name</a>
    358    }}}
    359  - Jinja2
    360    {{{#!html+jinja
    361      # if url:
    362      <a href="${url}">${plugin.name}</a>
    363      # else:
    364      ${plugin.name}
    365      # endif
    366    }}}
    367 If the repeated content is complex, one can use a //block assignment// (see below).
    368 
    369 === i18n   #trans
    370 
    371 Genshi had a pretty good notion of what was a piece of translatable text within an HTML template,
    372 but Jinja2 doesn't, so there's no "guessing" and no i18n will happen unless explicitly asked.
    373 
    374 This can be done in two ways. First, with translation expressions, using the familiar `_()` gettext function (`gettext` and `ngettext` also supported).
    375 
    376  - Genshi: [[xml(<strong>Trac detected an internal error:</strong>)]]
    377  - Jinja2: [[xml(<strong>${_("Trac detected an internal error:")}</strong>)]]
    378 
    379 Second, using `trans` directives.
    380 
    381  - Genshi:
    382    {{{#!html+genshi
    383                 <p i18n:msg="create">Otherwise, please ${create_ticket(tracker)} a new bug report
    384                 describing the problem and explain how to reproduce it.</p>
    385    }}}
    386  - Jinja2:
    387    {{{#!html+jinja
    388                 <p>
    389                   # trans create = create_ticket(tracker)
    390 
    391                   Otherwise, please ${create} a new bug report
    392                   describing the problem and explain how to reproduce it.
    393 
    394                   # endtrans
    395                 </p>
    396    }}}
    397    another example, without assignment:
    398    {{{#!html+jinja
    399             #   trans formatted
    400 
    401             In the default time zone, this would be displayed
    402             as <strong>${formatted}</strong>.
    403 
    404             #   endtrans
    405    }}}
    406    last example, two expanded variables, with and without assignment:
    407    {{{#!html+jinja
    408             #   trans tz = nowtz.tzname(), formatted
    409 
    410             In your time zone ${tz}, this would be displayed as
    411             <strong>${formatted}</strong>.
    412 
    413             #   endtrans
    414    }}}
    415 
    416 See [http://jinja.pocoo.org/docs/dev/templates/#i18n i18n] doc.
    417 
    418 Note that only direct variable expansions are supported in `trans` blocks, nothing more complex.
    419 So one way to deal with complex translatable content is to factor out the complex parts in variable blocks. See [http://jinja.pocoo.org/docs/dev/templates/#block-assignments block assignments] doc.
    420  - Genshi:
    421    {{{#!html+genshi
    422               <p i18n:msg="">There was an internal error in Trac.
    423                 It is recommended that you notify your local
    424                 <a py:strip="not project.admin" href="mailto:${project.admin}">
    425                     Trac administrator</a> with the information needed to
    426                 reproduce the issue.
    427               </p>
    428 
    429    }}}
    430  - Jinja2:
    431    {{{#!html+jinja
    432               <p>
    433                 # set trac_admin = _("Trac administrator")
    434                 # set project_admin
    435                 #   if project.admin:
    436                 <a href="mailto:${project.admin}">${trac_admin}</a>
    437                 #   else:
    438                 ${trac_admin}
    439                 #   endif
    440                 # endset
    441                 # trans project_admin
    442 
    443                 There was an internal error in Trac.
    444                 It is recommended that you notify your local
    445                 ${project_admin} with the information needed to
    446                 reproduce the issue.
    447 
    448                 # endtrans
    449               </p>
    450    }}}
    451 
    452 Note that another tricky case is when you want to use `gettext` and one of the variables is Markup. Using the `|safe` filter is needed, but that's not enough, as currently `gettext()` doesn't support Markup, you need to use `tgettext()` which is available with the `tag_` shortcut:
    453 {{{#!html+jinja
    454   <em>
    455     # set preferences_link
    456     <a href="${href.prefs()}" class="trac-target-new">${
    457       _("Preferences")}</a>
    458     # endset
    459     ${tag_("Set your email in %(preferences_link)s",
    460            preferences_link=preferences_link|safe)}
    461   </em>
    462 }}}
    463 
    464 
    465 == Examples
     15== Overview
     16
     17We start we some examples, showing both the legacy Genshi templates and the new Jinja2 templates, highlighting their main differences.
     18
     19The second part of the document describes the Python code changes, from what you need to change to trigger the use of the Jinja2 renderer instead of the legacy Genshi renderer which still kicks in if nothing changes, to the new ways of generating content. Finally we go to great length to explain the most difficult part of the migration, how to replace the deprecated `ITemplateStreamFilter` interface which has no direct equivalent with Jinja2.
     20
     21In the last part of this document, we try to cover all the Genshi features used by Trac and present their Jinja2 equivalent. Whenever possible, we tried to minimize these differences by customizing the Jinja2 syntax. For example, we use `${...}` for variable expansion, like Genshi does, instead of `{{...}}`. Another aspect of our usage convention is that we favor [#ifthenelse line statements] over `{% ... %}`. So even someone familiar with the "default" Jinja2 syntax should glance through this document to see how "we" use Jinja2, as summarized in the table below.
     22
     23Note that Genshi will be supported concurrently with Jinja2 only for a short while, for the 1.3.x development period and for the 1.4-stable period. This support will be removed in Trac [milestone:1.5.1]. If for some reason you're stuck to having to support Genshi templates, you'll have to stick to Trac 1.2.x or 1.3.1. But you really should make the transition effort as Jinja2 templates are 5-10x faster than their Genshi equivalent, for only a 1/5th of their cost in memory usage.
     24
     25== Examples!
     26
     27Before going into the details of the code changes involved and the precise differences in the template syntax between the two systems, let's see at a glance how the templates look like.
    46628
    46729=== Standalone template
     
    52385In this small example, there's no common Trac layout used (as the index is a bit special).
    52486For how a "normal" template looks like, see for example
    525 [source:trac/templates/diff_form.html@jinja2-trunk-r15379 diff_form.html], another small template.
     87[source:cboos.git/trac/templates/diff_view.html@jinja2-trunk-r15379 diff_form.html], another small template. The generic templates in [source:cboos.git/trac/templates@jinja2-trunk-r15379 trac/templates] are also interesting because we still have their Genshi equivalent close by, in [source:cboos.git/trac/templates/genshi@jinja2-trunk-r15379 trac/templates/genshi], so you can easily compare them if you're stuck during the migration of your own templates.
    52688
    52789Note that a Jinja2 .html template can usually be rendered directly in the browser, to have a rough idea about how it will look like:
     
    566128But we also have a complete conversion [./Example example], which displays the Genshi wiki_view.html template and the Jinja2 wiki_view.html template side-by-side, along with comments explaining the conversion choices.
    567129
    568 === Tips and tricks
    569 
    570  - when you need to output a plain "#" character at the beginning of a line, this will be parsed as a line statement; the trick here is to use an empty inline comment as a prefix: `{##}# ...` (see [61e8d9ec/cboos.git])
    571  - when you need to output indented text, this can be made difficult due to our `lstrip_block` configuration setting; you can work around this by embedding Python strings with the exact whitespace content you need in variable expressions: `    ${"\t  "}` (see [0fdef480/cboos.git])
     130
    572131
    573132== Changes in the controllers ==
     
    894453
    895454
     455== Migrating to Jinja2 templates
     456
     457=== The Jinja2 syntax used by Trac
     458
     459The Jinja2 template engine is quite flexible and its syntax can be customized to some extent. We took this opportunity to make it as close as possible to the Genshi template syntax, in particular for the variable expansion.
     460
     461We use the following configuration:
     462|| key || value || example / explanation
     463|| extensions ||  jinja2.ext.with_  jinja2.ext.i18n  || `with` directive can be used ([#with more])
     464|| block_start_string ||  {%  || \
     465{{{#!td rowspan=2
     466`{% if cond %} value {% endif %}` possible (but discouraged)
     467}}}
     468|--
     469|| block_end_string ||  %}  ||
     470|| variable_start_string ||  ${  || \
     471{{{#!td rowspan=2
     472`${the_variable}` (but not `$the_variable`)
     473([#expandavariable more])
     474}}}
     475|--
     476|| variable_end_string ||  }  ||
     477|| line_statement_prefix ||  #  || \
     478{{{#!td
     479{{{
     480# if cond:
     481value
     482# endif
     483}}}
     484(preferred form) ([#if more])
     485}}}
     486|--
     487|| line_comment_prefix ||  ##  || `## comments are good`
     488|| trim_blocks ||  yes  || whitespace removal after a block
     489|| lstrip_blocks ||  yes  || whitespace removal before a block
     490|| newstyle_gettext ||  yes  || i.e. like the Trac `gettext`
     491
     492
     493=== HTML differences between Jinja2 and Genshi templates
     494
     495We took the opportunity to **switch to HTML5** while performing this template overhaul.
     496You should probably do the same.
     497This usually won't change anything for your templates, except for a few details.
     498
     499The doctype and the <html> element should be changed from Genshi's:
     500{{{#!html+genshi
     501<!DOCTYPE html
     502    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     503    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
     504<html xmlns="http://www.w3.org/1999/xhtml"
     505      xmlns:py="http://genshi.edgewall.org/"
     506      xmlns:i18n="http://genshi.edgewall.org/i18n"
     507      xmlns:xi="http://www.w3.org/2001/XInclude">
     508}}}
     509
     510to the somewhat simpler:
     511{{{#!xml
     512<!DOCTYPE html>
     513<html>
     514}}}
     515
     516Special care should be taken when dealing with //void elements//.
     517In XHTML, it's fine to write:
     518{{{#!xml
     519<div id="description"/>
     520}}}
     521However, when going to HTML5, you should use instead:
     522{{{#!xml
     523<div id="description"></div>
     524}}}
     525The valid [https://www.w3.org/TR/html-markup/syntax.html#void-elements void elements] in HTML5 are limited to the following list:
     526 - area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr
     527
     528
     529=== Detailed guide of differences between Jinja2 and Genshi
     530
     531Most of the time, the porting is a straightforward operation.
     532
     533==== expand a variable
     534 - Genshi [[html+genshi(<b>$the_variable</b>)]]
     535 - Jinja2 [[html+jinja(<b>${the_variable}</b>)]]
     536
     537Note that Jinja2 doesn't support the `$the_variable` style, the curly braces are mandatory.
     538
     539Tip:
     540{{{#!shell
     541sed -i -e 's,\$\([a-z_][a-z._]*\),${\1},g' template.html
     542}}}
     543
     544==== expand a simple computation
     545 - Genshi [[html+genshi(<b>${the_variable + 1}</b>)]]
     546 - Jinja2 [[html+jinja(<b>${the_variable + 1}</b>)]]
     547
     548However, Jinja2 expressions are only //similar// to Python expressions, there are a few differences and limitations, see [http://jinja.pocoo.org/docs/dev/templates/#expressions expressions] doc.
     549
     550See also [#set set complex variables] below for more involved examples.
     551
     552Another customization we made to Jinja2 is to avoid having a Python `None` value be expanded to the `"None"` string. Instead, we make it produce an empty string, like Genshi did.
     553
     554==== include another template   #include
     555 - Genshi [[xml(<xi:include href="the_file.html"><xi:fallback/></xi:include>)]]
     556 - Jinja2 [[xml(# include "the_file.html" ignore missing)]]
     557
     558See [http://jinja.pocoo.org/docs/dev/templates/#include include] doc. Note that the `ignore missing` part is mandatory no templates are given to the `include` directive. This includes the following situation:
     559{{{#!html+jinja
     560# include "sometemplate.html" if false
     561}}}
     562This alone would raise an error. The correct thing to write is:
     563{{{#!html+jinja
     564# include "sometemplate.html" if false ignore missing
     565}}}
     566
     567It is also possible to pass "parameters" to included templates.
     568Those are not actually declared parameters, simply variables expected to be available in the template that can be set for the scope of one specific include. This works exactly like in Genshi, with a `with` statement:
     569
     570 - Genshi
     571   {{{#!html+genshi
     572        <xi:include href="ticket_box.html" py:with="can_append = False; preview_mode = False"/>
     573   }}}
     574 - Jinja
     575   {{{#!html+jinja
     576      # with
     577      #   set can_append = false
     578      #   set preview_mode = false
     579      #   include "ticket_box.html"
     580      # endwith
     581   }}}
     582
     583See [http://jinja.pocoo.org/docs/dev/extensions/#with-statement with] doc.
     584
     585In passing, note how the boolean constants slightly differ from Python, `False` becomes `false`, `True` becomes `true` and `None` is `none`.
     586
     587Caveat: contrary to the Genshi way, one shouldn't include the `"layout.html"` template in order to inherit the default page layout. There's a big difference in the way template "inheritance" works between Genshi and Jinja2, see HtmlTemplates#Jinjaarchitecture for all the details.
     588
     589Despite these differences, we kept the same spirit and there's actually also a `"layout.html"` template that you can "inherit" from (as well as a `"theme.html"` template that the layout inherits in turn). But instead of "including" it in your template, you "extend" it:
     590{{{#!html+jinja
     591# extends "layout.html"
     592}}}
     593But this is not the place to go further in the details about how extending works, refer to the [http://jinja.pocoo.org/docs/dev/templates/#extends extends] documentation and to the link above for how this applies to Trac.
     594
     595
     596==== simple if...then (no else)   #if
     597 - Genshi [[xml(<py:if test="flag"><b>OK</b></py:if>)]] or simply: [[xml(<b py:if="flag">OK</b>)]]
     598 - Jinja2
     599   {{{#!html+jinja
     600   # if flag:
     601   <b>OK</b>
     602   # endif
     603   }}}
     604
     605See [http://jinja.pocoo.org/docs/dev/templates/#if if] doc.
     606
     607==== if...then...else
     608 - Genshi
     609   {{{#!html+genshi
     610<py:choose test="flag">
     611  <py:when test="True">
     612    <b>OK</b>
     613  </py:when>
     614  <py:otherwise>
     615   <i>!!!</i>
     616  </py:otherwise>
     617</py:choose>
     618   }}}
     619   or simply:
     620   {{{#!html+genshi
     621<py:choose>
     622 <b py:when="flag">OK</b>
     623 <i py:otherwise="">!!!</i>
     624</py:choose>
     625   }}}
     626
     627 - Jinja2
     628   {{{#!html+jinja
     629 # if flag:
     630 <b>OK</b>
     631 # else:
     632 <i>!!!</i>
     633 # endif
     634   }}}
     635
     636If you really have to, you can also use the block style:
     637{{{#!html+jinja
     638{{ if flag }}<b>OK</b>{{ else }}<i>!!!</i>{{ endif }}
     639}}}
     640However this goes against readability and processing via the [./Checker jinjachecker] tool,  so we really advise that you stick to the use of //[http://jinja.pocoo.org/docs/dev/templates/#line-statements line statements]//.
     641
     642==== iterate over a collection   #for
     643 - Genshi
     644   {{{#!html+genshi
     645<ul>
     646  <py:for each="element in list">
     647    <li>$element</li>
     648  </py:for>
     649</ul>
     650   }}}
     651   or simply:
     652   {{{#!html+genshi
     653<ul>
     654  <li py:for="element in list">$element</li>
     655</ul>
     656   }}}
     657 - Jinja2
     658   {{{#!html+jinja
     659<ul>
     660  # for element in list:
     661  <li>${element}</li>
     662  # endfor
     663</ul>
     664   }}}
     665See [http://jinja.pocoo.org/docs/dev/templates/#for for] doc.
     666
     667===== No need for `enumerate`
     668
     669 - Genshi:
     670{{{#!html+genshi
     671          <tr py:for="idx,option in enumerate(section.options)"
     672              class="${'modified' if option.modified else None}">
     673            <th py:if="idx == 0" class="section"
     674                rowspan="${len(section.options)}">${section.name}</th>
     675
     676}}}
     677 - Jinja2:
     678{{{#!html+jinja
     679          #   for option in section.options:
     680          <tr ${{'class': 'modified' if option.modified}|htmlattr}>
     681            #   if loop.first:
     682            <th class="section"
     683                rowspan="${len(section.options)}">${section.name}</th>
     684
     685}}}
     686
     687All common uses (and more) for such an `idx` variable are addressed by the special `loop` variable.
     688See [http://jinja.pocoo.org/docs/dev/templates/#for loop] doc.
     689
     690==== define a macro   #macro
     691 - Genshi
     692   {{{#!html+genshi
     693<py:def function="entry(key, val='--')">
     694 <dt>$key</dt><dd>$val</dd>
     695</py:def>
     696   }}}
     697 - Jinja2
     698   {{{#!html+jinja
     699 # macro entry(key, val='--')
     700 <dt>${key}</dt><dd>${val}</dd>
     701 # endmacro
     702   }}}
     703See [http://jinja.pocoo.org/docs/dev/templates/#macros macros] doc.
     704
     705==== set a variable
     706 - Genshi
     707   {{{#!html+genshi
     708<py:with vars="count = len(collection)">
     709We have ${count &gt; 10 and 'too much' or count} elements.
     710</py:with>
     711   }}}
     712   Note that we had to use `&gt;` in Genshi, we can't use `>` directly.
     713 - Jinja2
     714   {{{#!html+jinja
     715# set count = len(collection)
     716We have ${'too much' if count is greaterthan(10) else count} elements.
     717   }}}
     718   Note that we avoid using `>` in Jinja2 expressions as well, but simply to avoid that XML/HTML text editors get confused. We added a few custom tests for that (`greaterthan`, `greaterthanorequal`, `lessthan`, `lessthanorequal`).
     719See [http://jinja.pocoo.org/docs/dev/templates/#tests tests] doc.
     720
     721==== set several variables in a scope #with
     722
     723 - Genshi
     724   {{{#!html+genshi
     725<html py:with="is_query = report.sql.startswith('query:');
     726               new_report = action == 'new' and not is_query;
     727               new_query = action == 'new' and is_query">
     728  ...
     729</html>
     730   }}}
     731   The variables are set for the scope of the element in which they are set (here <html>,
     732   so the whole document)
     733 - Jinja2
     734   {{{#!html+jinja
     735    #   with
     736    #     set is_query = report.sql.startswith('query:')
     737    #     set new_report = action == 'new' and not is_query
     738    ...
     739    #   endwith
     740   }}}
     741
     742But actually you will only use `with` in specific situations, like for wrapping an include directive (see [#include]). If you're already within a `for`, a `block` or a `macro`, the scope of the assignment is already limited to that of this directive.
     743
     744See [http://jinja.pocoo.org/docs/dev/templates/#assignments set] and [http://jinja.pocoo.org/docs/dev/templates/#with-statement with] docs.
     745
     746In addition, `with` can also be used to better control how the successive `set` on a given variable are being applied (see for example [1e25c852/cboos.git] and [cc1b959e/cboos.git]).
     747
     748Finally be careful when using `with`: don't wrap a `block` directive within a `with`. If you want to set a global scope for the document (like in our <html> example above), it's tempting to use a single `with` statement, for clarity. But that would wrap all blocks defined in the template and strange results would ensue.
     749
     750
     751==== set HTML attributes   #htmlattr
     752In Genshi, an attribute with a `None` value wouldn't be output. However, Jinja2 doesn't know what an attribute is (or anything else about HTML, XML for that matter), so we have to use a special filter, `htmlattr`, to reproduce this behavior:
     753
     754 - Genshi:
     755{{{#!html+genshi
     756          <tr class="${'disabled' if all(not component.enabled for module in plugin.modules.itervalues()
     757                                         for component in module.components.itervalues()) else None}">
     758}}}
     759 - Jinja2:
     760{{{#!html+jinja
     761          #   set components = plugin.modules|map(attribute='components')|flatten
     762          <tr${{'class': 'disabled' if not components|selectattr('enabled')
     763               }|htmlattr}>
     764
     765}}}
     766(the `htmlattr` filter will add a space if needed; that way, if the condition is true, you end up with `<tr class="disabled">`, otherwise with just `<tr>`)
     767
     768If you wonder why the `if all(...)` expression morphed into `if not components|...`, it's because Jinja2 expressions are similar to Python expressions, but not quite the same.
     769
     770=== set complex variables   #set
     771Note that Jinja2 expressions are a subset of Python expressions, and for the sake of simplicity the generator expressions are not part of that subset. This limitation often requires one to make creative use of [http://jinja.pocoo.org/docs/dev/templates/#filters filters], [http://jinja.pocoo.org/docs/dev/templates/#builtin-filters built-in] or custom (`min`, `max`, `trim`, `flatten`).
     772
     773A few more examples:
     774
     775||= Genshi ||= Jinja2 ||
     776|| `${', '.join([p.name for p in faulty_plugins])}` || \
     777|| `${faulty_plugins|map('name')|join(', ')}` ||
     778|| `sum(1 for change in changes if 'cnum' in change)` || \
     779|| `changes|selectattr('cnum')|list|count` ||
     780|| `sum(1 for change in changes if 'cnum' not in change)` || \
     781|| `changes|rejectattr('cnum')|list|count` ||
     782|| ... || ... ||
     783
     784
     785==== conditional wrappers
     786There's no direct equivalent to the `py:strip`, but it can often be emulated with an `if/else/endif`.
     787 - Genshi
     788   {{{#!html+genshi
     789     <a py:strip="not url" href="$url">$plugin.name</a>
     790   }}}
     791 - Jinja2
     792   {{{#!html+jinja
     793     # if url:
     794     <a href="${url}">${plugin.name}</a>
     795     # else:
     796     ${plugin.name}
     797     # endif
     798   }}}
     799If the repeated content is complex, one can use a //block assignment// (see below).
     800
     801==== i18n   #trans
     802
     803Genshi had a pretty good notion of what was a piece of translatable text within an HTML template,
     804but Jinja2 doesn't, so there's no "guessing" and no i18n will happen unless explicitly asked.
     805
     806This can be done in two ways. First, with translation expressions, using the familiar `_()` gettext function (`gettext` and `ngettext` also supported).
     807
     808 - Genshi: [[xml(<strong>Trac detected an internal error:</strong>)]]
     809 - Jinja2: [[xml(<strong>${_("Trac detected an internal error:")}</strong>)]]
     810
     811Second, using `trans` directives.
     812
     813 - Genshi:
     814   {{{#!html+genshi
     815                <p i18n:msg="create">Otherwise, please ${create_ticket(tracker)} a new bug report
     816                describing the problem and explain how to reproduce it.</p>
     817   }}}
     818 - Jinja2:
     819   {{{#!html+jinja
     820                <p>
     821                  # trans create = create_ticket(tracker)
     822
     823                  Otherwise, please ${create} a new bug report
     824                  describing the problem and explain how to reproduce it.
     825
     826                  # endtrans
     827                </p>
     828   }}}
     829   another example, without assignment:
     830   {{{#!html+jinja
     831            #   trans formatted
     832
     833            In the default time zone, this would be displayed
     834            as <strong>${formatted}</strong>.
     835
     836            #   endtrans
     837   }}}
     838   last example, two expanded variables, with and without assignment:
     839   {{{#!html+jinja
     840            #   trans tz = nowtz.tzname(), formatted
     841
     842            In your time zone ${tz}, this would be displayed as
     843            <strong>${formatted}</strong>.
     844
     845            #   endtrans
     846   }}}
     847
     848See [http://jinja.pocoo.org/docs/dev/templates/#i18n i18n] doc.
     849
     850Note that only direct variable expansions are supported in `trans` blocks, nothing more complex.
     851So one way to deal with complex translatable content is to factor out the complex parts in variable blocks. See [http://jinja.pocoo.org/docs/dev/templates/#block-assignments block assignments] doc.
     852 - Genshi:
     853   {{{#!html+genshi
     854              <p i18n:msg="">There was an internal error in Trac.
     855                It is recommended that you notify your local
     856                <a py:strip="not project.admin" href="mailto:${project.admin}">
     857                    Trac administrator</a> with the information needed to
     858                reproduce the issue.
     859              </p>
     860
     861   }}}
     862 - Jinja2:
     863   {{{#!html+jinja
     864              <p>
     865                # set trac_admin = _("Trac administrator")
     866                # set project_admin
     867                #   if project.admin:
     868                <a href="mailto:${project.admin}">${trac_admin}</a>
     869                #   else:
     870                ${trac_admin}
     871                #   endif
     872                # endset
     873                # trans project_admin
     874
     875                There was an internal error in Trac.
     876                It is recommended that you notify your local
     877                ${project_admin} with the information needed to
     878                reproduce the issue.
     879
     880                # endtrans
     881              </p>
     882   }}}
     883
     884Note that another tricky case is when you want to use `gettext` and one of the variables is Markup. Using the `|safe` filter is needed, but that's not enough, as currently `gettext()` doesn't support Markup, you need to use `tgettext()` which is available with the `tag_` shortcut:
     885{{{#!html+jinja
     886  <em>
     887    # set preferences_link
     888    <a href="${href.prefs()}" class="trac-target-new">${
     889      _("Preferences")}</a>
     890    # endset
     891    ${tag_("Set your email in %(preferences_link)s",
     892           preferences_link=preferences_link|safe)}
     893  </em>
     894}}}
     895
     896
     897==== Tips and tricks
     898
     899 - when you need to output a plain "#" character at the beginning of a line, this will be parsed as a line statement; the trick here is to use an empty inline comment as a prefix: `{##}# ...` (see [61e8d9ec/cboos.git])
     900 - when you need to output indented text, this can be made difficult due to our `lstrip_block` configuration setting; you can work around this by embedding Python strings with the exact whitespace content you need in variable expressions: `    ${"\t  "}` (see [0fdef480/cboos.git])
     901
     902