Changeset 706
- Timestamp:
- Aug 13, 2007, 2:40:56 PM (16 years ago)
- Location:
- trunk
- Files:
-
- 5 added
- 12 edited
-
ChangeLog (modified) (1 diff)
-
UPGRADE.txt (modified) (1 diff)
-
doc/plugin.txt (modified) (1 diff)
-
doc/templates.txt (modified) (2 diffs)
-
doc/text-templates.txt (modified) (18 diffs)
-
examples/bench/basic.py (modified) (2 diffs)
-
examples/bench/bigtable.py (modified) (4 diffs)
-
examples/bench/genshi_text (added)
-
examples/bench/genshi_text/footer.txt (added)
-
examples/bench/genshi_text/header.txt (added)
-
examples/bench/genshi_text/template.txt (added)
-
genshi/template/__init__.py (modified) (1 diff)
-
genshi/template/plugin.py (modified) (2 diffs)
-
genshi/template/tests/plugin.py (modified) (2 diffs)
-
genshi/template/tests/templates/new_syntax.txt (added)
-
genshi/template/tests/text.py (modified) (5 diffs)
-
genshi/template/text.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/ChangeLog
r704 r706 18 18 templates are basically inlined into the including template, which can 19 19 speed up rendering of that template a bit. 20 * Added new syntax for text templates, which is more powerful and flexible 21 with respect to white-space and line breaks. The old syntax is still 22 available and the default for now, but in a future release the new syntax 23 will become the default, and some time affter that the old syntax will be 24 removed. 20 25 21 26 -
trunk/UPGRADE.txt
r546 r706 1 1 Upgrading Genshi 2 2 ================ 3 4 Upgrading from Genshi 0.4.x to 0.5.x 5 ------------------------------------ 6 7 Genshi 0.5 introduces a new, alternative syntax for text templates, which is 8 more flexible and powerful compared to the old syntax. For backwards 9 compatibility, this new syntax is not used by default, though it will be in 10 a future version. It is recommended that you migrate to using this new syntax. 11 To do so, simply rename any references in your code to `TextTemplate` to 12 `NewTextTemplate`. To explicitly use the old syntax, use `OldTextTemplate` 13 instead, so that you can be sure you'll be using the same language when the 14 default in Genshi is changed (at least until the old implementation is 15 completely removed). 16 3 17 4 18 Upgrading from Genshi 0.3.x to 0.4.x -
trunk/doc/plugin.txt
r657 r706 223 223 site uses a larger number of templates, and you have enough memory to spare. 224 224 225 ``genshi.new_text_syntax`` 226 -------------------------- 227 Whether the new syntax for text templates should be used. Specify "yes" to 228 enable the new syntax, or "no" to use the old syntax. 229 230 In the version of Genshi, the default is to use the old syntax for 231 backwards-compatibility, but that will change in a future release. 232 225 233 ``genshi.search_path`` 226 234 ---------------------- -
trunk/doc/templates.txt
r654 r706 117 117 >>> tmpl = MarkupTemplate('<h1>Hello, $name!</h1>') 118 118 >>> stream = tmpl.generate(name='world') 119 >>> print stream.render( )119 >>> print stream.render('xhtml') 120 120 <h1>Hello, world!</h1> 121 122 .. note:: See the Serialization_ section of the `Markup Streams`_ page for 123 information on configuring template output options. 121 124 122 125 Using a text template is similar: … … 127 130 >>> tmpl = TextTemplate('Hello, $name!') 128 131 >>> stream = tmpl.generate(name='world') 129 >>> print stream.render( )132 >>> print stream.render('text') 130 133 Hello, world! 131 134 132 .. note:: See the Serialization_ section of the `Markup Streams`_ page for 133 information on configuring template output options. 135 .. note:: If you want to use text templates, you should consider using the 136 ``NewTextTemplate`` class instead of simply ``TextTemplate``. See 137 the `Text Template Language`_ page. 134 138 135 139 .. _serialization: streams.html#serialization 140 .. _`Text Template Language`: text-templates.html 136 141 .. _`Markup Streams`: streams.html 137 142 -
trunk/doc/text-templates.txt
r614 r706 7 7 In addition to the XML-based template language, Genshi provides a simple 8 8 text-based template language, intended for basic plain text generation needs. 9 The language is similar to Cheetah_ or Velocity_. 10 11 .. _cheetah: http://cheetahtemplate.org/ 12 .. _velocity: http://jakarta.apache.org/velocity/ 9 The language is similar to the Django_ template language. 13 10 14 11 This document describes the template language and will be most useful as … … 21 18 embedding Python code in templates. 22 19 20 .. note:: Actually, Genshi currently has two different syntaxes for text 21 templates languages: One implemented by the class ``OldTextTemplate`` 22 and another implemented by ``NewTextTemplate``. This documentation 23 concentrates on the latter, which is planned to completely replace the 24 older syntax. The older syntax is briefly described under legacy_. 25 26 .. _django: http://www.djangoproject.com/ 23 27 24 28 .. contents:: Contents … … 33 37 ------------------- 34 38 35 Directives are lines starting with a ``#`` character followed immediately by 36 the directive name. They can affect how the template is rendered in a number of 37 ways: Genshi provides directives for conditionals and looping, among others. 38 39 Directives must be on separate lines, and the ``#`` character must be be the 40 first non-whitespace character on that line. Each directive must be “closed” 41 using a ``#end`` marker. You can add after the ``#end`` marker, for example to 42 document which directive is being closed, or even the expression associated with 43 that directive. Any text after ``#end`` (but on the same line) is ignored, 44 and effectively treated as a comment. 45 46 If you want to include a literal ``#`` in the output, you need to escape it 47 by prepending a backslash character (``\``). Note that this is **not** required 48 if the ``#`` isn't immediately followed by a letter, or it isn't the first 49 non-whitespace character on the line. 39 Directives are template commands enclosed by ``{% ... %}`` characters. They can 40 affect how the template is rendered in a number of ways: Genshi provides 41 directives for conditionals and looping, among others. 42 43 Each directive must be terminated using an ``{% end %}`` marker. You can add 44 a string inside the ``{% end %}`` marker, for example to document which 45 directive is being closed, or even the expression associated with that 46 directive. Any text after ``end`` inside the delimiters is ignored, and 47 effectively treated as a comment. 48 49 If you want to include a literal delimiter in the output, you need to escape it 50 by prepending a backslash character (``\``). 50 51 51 52 … … 53 54 ==================== 54 55 55 .. _` #if`:56 57 `` #if``58 --------- 56 .. _`if`: 57 58 ``{% if %}`` 59 ------------ 59 60 60 61 The content is only rendered if the expression evaluates to a truth value: … … 62 63 .. code-block:: genshitext 63 64 64 #if foo65 {% if foo %} 65 66 ${bar} 66 #end67 {% end %} 67 68 68 69 Given the data ``foo=True`` and ``bar='Hello'`` in the template context, this … … 72 73 73 74 74 .. _` #choose`:75 .. _` #when`:76 .. _` #otherwise`:77 78 `` #choose``79 ------------- 80 81 The `` #choose`` directive, in combination with the directives ``#when`` and82 `` #otherwise``provides advanced contional processing for rendering one of83 several alternatives. The first matching `` #when`` branch is rendered, or, if84 no `` #when`` branch matches, the ``#otherwise`` branch is be rendered.85 86 If the `` #choose`` directive has no argument the nested ``#when`` directives87 willbe tested for truth:75 .. _`choose`: 76 .. _`when`: 77 .. _`otherwise`: 78 79 ``{% choose %}`` 80 ---------------- 81 82 The ``choose`` directive, in combination with the directives ``when`` and 83 ``otherwise``, provides advanced contional processing for rendering one of 84 several alternatives. The first matching ``when`` branch is rendered, or, if 85 no ``when`` branch matches, the ``otherwise`` branch is be rendered. 86 87 If the ``choose`` directive has no argument the nested ``when`` directives will 88 be tested for truth: 88 89 89 90 .. code-block:: genshitext 90 91 91 92 The answer is: 92 #choose 93 #when 0 == 1 94 0 95 #end 96 #when 1 == 1 97 1 98 #end 99 #otherwise 100 2 101 #end 102 #end 93 {% choose %} 94 {% when 0 == 1 %}0{% end %} 95 {% when 1 == 1 %}1{% end %} 96 {% otherwise %}2{% end %} 97 {% end %} 98 99 This would produce the following output:: 100 101 The answer is: 102 1 103 104 If the ``choose`` does have an argument, the nested ``when`` directives will 105 be tested for equality to the parent ``choose`` value: 106 107 .. code-block:: genshitext 108 109 The answer is: 110 {% choose 1 %}\ 111 {% when 0 %}0{% end %}\ 112 {% when 1 %}1{% end %}\ 113 {% otherwise %}2{% end %}\ 114 {% end %} 103 115 104 116 This would produce the following output:: … … 107 119 1 108 120 109 If the ``#choose`` does have an argument, the nested ``#when`` directives will110 be tested for equality to the parent ``#choose`` value:111 112 .. code-block:: genshitext113 114 The answer is:115 #choose 1116 #when 0117 0118 #end119 #when 1120 1121 #end122 #otherwise123 2124 #end125 #end126 127 This would produce the following output::128 129 The answer is:130 1131 132 121 133 122 Looping 134 123 ======= 135 124 136 .. _` #for`:137 138 `` #for``139 ---------- 125 .. _`for`: 126 127 ``{% for %}`` 128 ------------- 140 129 141 130 The content is repeated for every item in an iterable: … … 144 133 145 134 Your items: 146 #for item in items135 {% for item in items %}\ 147 136 * ${item} 148 #end137 {% end %} 149 138 150 139 Given ``items=[1, 2, 3]`` in the context data, this would produce:: … … 159 148 ============= 160 149 161 .. _` #def`:150 .. _`def`: 162 151 .. _`macros`: 163 152 164 `` #def``165 ---------- 166 167 The `` #def`` directive can be used to create macros, i.e. snippets of template153 ``{% def %}`` 154 ------------- 155 156 The ``def`` directive can be used to create macros, i.e. snippets of template 168 157 text that have a name and optionally some parameters, and that can be inserted 169 158 in other places: … … 171 160 .. code-block:: genshitext 172 161 173 #def greeting(name)162 {% def greeting(name) %} 174 163 Hello, ${name}! 175 #end164 {% end %} 176 165 ${greeting('world')} 177 166 ${greeting('everyone else')} … … 182 171 Hello, everyone else! 183 172 184 If a macro doesn't require parameters, it can be defined as well as called185 without theparenthesis. For example:186 187 .. code-block:: genshitext 188 189 #def greeting173 If a macro doesn't require parameters, it can be defined without the 174 parenthesis. For example: 175 176 .. code-block:: genshitext 177 178 {% def greeting %} 190 179 Hello, world! 191 #end192 ${greeting }180 {% end %} 181 ${greeting()} 193 182 194 183 The above would be rendered to:: … … 198 187 199 188 .. _includes: 200 .. _` #include`:201 202 `` #include``203 ------------ 189 .. _`include`: 190 191 ``{% include %}`` 192 ----------------- 204 193 205 194 To reuse common parts of template text across template files, you can include 206 other files using the `` #include`` directive:207 208 .. code-block:: genshitext 209 210 #include "base.txt"195 other files using the ``include`` directive: 196 197 .. code-block:: genshitext 198 199 {% include base.txt %} 211 200 212 201 Any content included this way is inserted into the generated output. The … … 221 210 relative paths, for example "``../base.txt``" to look in the parent directory. 222 211 223 Just like other directives, the argument to the `` #include`` directive accepts212 Just like other directives, the argument to the ``include`` directive accepts 224 213 any Python expression, so the path to the included template can be determined 225 214 dynamically: … … 227 216 .. code-block:: genshitext 228 217 229 #include '%s.txt' % filename218 {% include '%s.txt' % filename %} 230 219 231 220 Note that a ``TemplateNotFound`` exception is raised if an included file can't … … 238 227 ================ 239 228 240 .. _` #with`:241 242 `` #with``243 ----------- 244 245 The `` #with`` directive lets you assign expressions to variables, which can229 .. _`with`: 230 231 ``{% with %}`` 232 -------------- 233 234 The ``{% with %}`` directive lets you assign expressions to variables, which can 246 235 be used to make expressions inside the directive less verbose and more 247 236 efficient. For example, if you need use the expression ``author.posts`` more … … 254 243 255 244 Magic numbers! 256 #with y=7; z=x+10245 {% with y=7; z=x+10 %} 257 246 $x $y $z 258 #end247 {% end %} 259 248 260 249 Given ``x=42`` in the context data, this would produce:: … … 264 253 265 254 Note that if a variable of the same name already existed outside of the scope 266 of the `` #with`` directive, it will **not** be overwritten. Instead, it will267 have the same value it had prior to the `` #with`` assignment. Effectively,255 of the ``with`` directive, it will **not** be overwritten. Instead, it will 256 have the same value it had prior to the ``with`` assignment. Effectively, 268 257 this means that variables are immutable in Genshi. 258 259 260 .. _whitespace: 261 262 --------------------------- 263 White-space and Line Breaks 264 --------------------------- 265 266 Note that space or line breaks around directives is never automatically removed. 267 Consider the following example: 268 269 .. code-block:: genshitext 270 271 {% for item in items %} 272 {% if item.visible %} 273 ${item} 274 {% end %} 275 {% end %} 276 277 This will result in two empty lines above and beneath every item, plus the 278 spaces used for indentation. If you want to supress a line break, simply end 279 the line with a backslash: 280 281 .. code-block:: genshitext 282 283 {% for item in items %}\ 284 {% if item.visible %}\ 285 ${item} 286 {% end %}\ 287 {% end %}\ 288 289 Now there would be no empty lines between the items in the output. But you still 290 get the spaces used for indentation, and because the line breaks are removed, 291 they actually continue and add up between lines. There are numerous ways to 292 control white-space in the output while keeping the template readable, such as 293 moving the indentation into the delimiters, or moving the end delimiter on the 294 next line, and so on. 269 295 270 296 … … 275 301 -------- 276 302 277 Lines where the first non-whitespace characters are ``##`` are removed from 278 the output, and can thus be used for comments. This can be escaped using a 303 Parts in templates can be commented out using the delimiters ``{# ... #}``. 304 Any content in comments are removed from the output. 305 306 .. code-block:: genshitext 307 308 {# This won't end up in the output #} 309 This will. 310 311 Just like directive delimiters, these can be escaped by prefixing with a 279 312 backslash. 313 314 .. code-block:: genshitext 315 316 \{# This *will* end up in the output, including delimiters #} 317 This too. 318 319 320 .. _legacy: 321 322 --------------------------- 323 Legacy Text Template Syntax 324 --------------------------- 325 326 The syntax for text templates was redesigned in version 0.5 of Genshi to make 327 the language more flexible and powerful. The older syntax is based on line 328 starting with dollar signs, similar to e.g. Cheetah_ or Velocity_. 329 330 .. _cheetah: http://cheetahtemplate.org/ 331 .. _velocity: http://jakarta.apache.org/velocity/ 332 333 A simple template using the old syntax looked like this: 334 335 .. code-block:: genshitext 336 337 Dear $name, 338 339 We have the following items for you: 340 #for item in items 341 * $item 342 #end 343 344 All the best, 345 Foobar 346 347 Beyond the requirement of putting directives on separate lines prefixed with 348 dollar signs, the language itself is very similar to the new one. Except that 349 comments are lines that start with two ``#`` characters, and a line-break at the 350 end of a directive is removed automatically. 351 352 .. note:: If you're using this old syntax, it is strongly recommended to migrate 353 to the new syntax. Simply replace any references to ``TextTemplate`` 354 by ``NewTextTemplate``. On the other hand, if you want to stick with 355 the old syntax for a while longer, replace references to 356 ``TextTemplate`` by ``OldTextTemplate``; while ``TextTemplate`` is 357 still an alias for the old language at this point, that will change 358 in a future release. -
trunk/examples/bench/basic.py
r651 r706 10 10 import timeit 11 11 12 __all__ = ['clearsilver', 'mako', 'django', 'kid', 'genshi', 'simpletal'] 12 __all__ = ['clearsilver', 'mako', 'django', 'kid', 'genshi', 'genshi_text', 13 'simpletal'] 13 14 14 15 def genshi(dirname, verbose=False): … … 20 21 items=['Number %d' % num for num in range(1, 15)]) 21 22 return template.generate(**data).render('xhtml') 23 24 if verbose: 25 print render() 26 return render 27 28 def genshi_text(dirname, verbose=False): 29 from genshi.core import escape 30 from genshi.template import TemplateLoader, NewTextTemplate 31 loader = TemplateLoader([dirname], auto_reload=False) 32 template = loader.load('template.txt', cls=NewTextTemplate) 33 def render(): 34 data = dict(escape=escape, title='Just a test', user='joe', 35 items=['Number %d' % num for num in range(1, 15)]) 36 return template.generate(**data).render('text') 22 37 23 38 if verbose: -
trunk/examples/bench/bigtable.py
r652 r706 11 11 from StringIO import StringIO 12 12 from genshi.builder import tag 13 from genshi.template import MarkupTemplate 13 from genshi.template import MarkupTemplate, NewTextTemplate 14 14 15 15 try: … … 59 59 genshi_tmpl2 = MarkupTemplate(""" 60 60 <table xmlns:py="http://genshi.edgewall.org/">$table</table> 61 """) 62 63 genshi_text_tmpl = NewTextTemplate(""" 64 <table> 65 {% for row in table %}<tr> 66 {% for c in row.values() %}<td>$c</td>{% end %} 67 </tr>{% end %} 68 </table> 61 69 """) 62 70 … … 96 104 stream.render('html', strip_whitespace=False) 97 105 106 def test_genshi_text(): 107 """Genshi text template""" 108 stream = genshi_text_tmpl.generate(table=table) 109 stream.render('text') 110 98 111 def test_genshi_builder(): 99 112 """Genshi template + tag builder""" … … 184 197 185 198 def run(which=None, number=10): 186 tests = ['test_builder', 'test_genshi', 'test_genshi_ builder',187 'test_ mako', 'test_kid', 'test_kid_et', 'test_et', 'test_cet',188 'test_ clearsilver', 'test_django']199 tests = ['test_builder', 'test_genshi', 'test_genshi_text', 200 'test_genshi_builder', 'test_mako', 'test_kid', 'test_kid_et', 201 'test_et', 'test_cet', 'test_clearsilver', 'test_django'] 189 202 190 203 if which: -
trunk/genshi/template/__init__.py
r517 r706 19 19 from genshi.template.loader import TemplateLoader, TemplateNotFound 20 20 from genshi.template.markup import MarkupTemplate 21 from genshi.template.text import TextTemplate 21 from genshi.template.text import TextTemplate, OldTextTemplate, NewTextTemplate 22 22 23 23 __docformat__ = 'restructuredtext en' -
trunk/genshi/template/plugin.py
r654 r706 24 24 from genshi.template.loader import TemplateLoader 25 25 from genshi.template.markup import MarkupTemplate 26 from genshi.template.text import TextTemplate 26 from genshi.template.text import TextTemplate, NewTextTemplate 27 27 28 28 __all__ = ['ConfigurationError', 'AbstractTemplateEnginePlugin', … … 163 163 extension = '.txt' 164 164 default_format = 'text' 165 166 def __init__(self, extra_vars_func=None, options=None): 167 if options is None: 168 options = {} 169 170 new_syntax = options.get('genshi.new_text_syntax') 171 if isinstance(new_syntax, basestring): 172 new_syntax = new_syntax.lower() in ('1', 'on', 'yes', 'true') 173 if new_syntax: 174 self.template_class = NewTextTemplate 175 176 AbstractTemplateEnginePlugin.__init__(self, extra_vars_func, options) -
trunk/genshi/template/tests/plugin.py
r502 r706 19 19 from genshi.core import Stream 20 20 from genshi.output import DocType 21 from genshi.template import MarkupTemplate, TextTemplate 21 from genshi.template import MarkupTemplate, TextTemplate, NewTextTemplate 22 22 from genshi.template.plugin import ConfigurationError, \ 23 23 MarkupTemplateEnginePlugin, \ … … 186 186 self.assertEqual('iso-8859-15', plugin.default_encoding) 187 187 188 def test_init_with_new_syntax(self): 189 plugin = TextTemplateEnginePlugin(options={ 190 'genshi.new_text_syntax': 'yes', 191 }) 192 self.assertEqual(NewTextTemplate, plugin.template_class) 193 tmpl = plugin.load_template(PACKAGE + '.templates.new_syntax') 194 output = plugin.render({'foo': True}, template=tmpl) 195 self.assertEqual('bar', output) 196 188 197 def test_load_template_from_file(self): 189 198 plugin = TextTemplateEnginePlugin() -
trunk/genshi/template/tests/text.py
r616 r706 19 19 20 20 from genshi.template.loader import TemplateLoader 21 from genshi.template.text import TextTemplate22 23 24 class TextTemplateTestCase(unittest.TestCase):21 from genshi.template.text import OldTextTemplate, NewTextTemplate 22 23 24 class OldTextTemplateTestCase(unittest.TestCase): 25 25 """Tests for text template processing.""" 26 26 … … 32 32 33 33 def test_escaping(self): 34 tmpl = TextTemplate('\\#escaped')34 tmpl = OldTextTemplate('\\#escaped') 35 35 self.assertEqual('#escaped', str(tmpl.generate())) 36 36 37 37 def test_comment(self): 38 tmpl = TextTemplate('## a comment')38 tmpl = OldTextTemplate('## a comment') 39 39 self.assertEqual('', str(tmpl.generate())) 40 40 41 41 def test_comment_escaping(self): 42 tmpl = TextTemplate('\\## escaped comment')42 tmpl = OldTextTemplate('\\## escaped comment') 43 43 self.assertEqual('## escaped comment', str(tmpl.generate())) 44 44 45 45 def test_end_with_args(self): 46 tmpl = TextTemplate("""46 tmpl = OldTextTemplate(""" 47 47 #if foo 48 48 bar … … 52 52 def test_latin1_encoded(self): 53 53 text = u'$foo\xf6$bar'.encode('iso-8859-1') 54 tmpl = TextTemplate(text, encoding='iso-8859-1')54 tmpl = OldTextTemplate(text, encoding='iso-8859-1') 55 55 self.assertEqual(u'x\xf6y', unicode(tmpl.generate(foo='x', bar='y'))) 56 56 57 57 def test_unicode_input(self): 58 58 text = u'$foo\xf6$bar' 59 tmpl = TextTemplate(text)59 tmpl = OldTextTemplate(text) 60 60 self.assertEqual(u'x\xf6y', unicode(tmpl.generate(foo='x', bar='y'))) 61 61 62 62 def test_empty_lines1(self): 63 tmpl = TextTemplate("""Your items:63 tmpl = OldTextTemplate("""Your items: 64 64 65 65 #for item in items … … 74 74 75 75 def test_empty_lines2(self): 76 tmpl = TextTemplate("""Your items:76 tmpl = OldTextTemplate("""Your items: 77 77 78 78 #for item in items … … 106 106 107 107 loader = TemplateLoader([self.dirname]) 108 tmpl = loader.load('tmpl2.txt', cls= TextTemplate)108 tmpl = loader.load('tmpl2.txt', cls=OldTextTemplate) 109 109 self.assertEqual("""----- Included data below this line ----- 110 110 Included 111 111 ----- Included data above this line -----""", 112 112 tmpl.generate().render()) 113 113 114 115 class NewTextTemplateTestCase(unittest.TestCase): 116 """Tests for text template processing.""" 117 118 def setUp(self): 119 self.dirname = tempfile.mkdtemp(suffix='markup_test') 120 121 def tearDown(self): 122 shutil.rmtree(self.dirname) 123 124 def test_escaping(self): 125 tmpl = NewTextTemplate('\\{% escaped %}') 126 self.assertEqual('{% escaped %}', str(tmpl.generate())) 127 128 def test_comment(self): 129 tmpl = NewTextTemplate('{# a comment #}') 130 self.assertEqual('', str(tmpl.generate())) 131 132 def test_comment_escaping(self): 133 tmpl = NewTextTemplate('\\{# escaped comment #}') 134 self.assertEqual('{# escaped comment #}', str(tmpl.generate())) 135 136 def test_end_with_args(self): 137 tmpl = NewTextTemplate(""" 138 {% if foo %} 139 bar 140 {% end 'if foo' %}""") 141 self.assertEqual('\n', str(tmpl.generate(foo=False))) 142 143 def test_latin1_encoded(self): 144 text = u'$foo\xf6$bar'.encode('iso-8859-1') 145 tmpl = NewTextTemplate(text, encoding='iso-8859-1') 146 self.assertEqual(u'x\xf6y', unicode(tmpl.generate(foo='x', bar='y'))) 147 148 def test_unicode_input(self): 149 text = u'$foo\xf6$bar' 150 tmpl = NewTextTemplate(text) 151 self.assertEqual(u'x\xf6y', unicode(tmpl.generate(foo='x', bar='y'))) 152 153 def test_empty_lines1(self): 154 tmpl = NewTextTemplate("""Your items: 155 156 {% for item in items %}\ 157 * ${item} 158 {% end %}""") 159 self.assertEqual("""Your items: 160 161 * 0 162 * 1 163 * 2 164 """, tmpl.generate(items=range(3)).render('text')) 165 166 def test_empty_lines2(self): 167 tmpl = NewTextTemplate("""Your items: 168 169 {% for item in items %}\ 170 * ${item} 171 172 {% end %}""") 173 self.assertEqual("""Your items: 174 175 * 0 176 177 * 1 178 179 * 2 180 181 """, tmpl.generate(items=range(3)).render('text')) 182 183 def test_include(self): 184 file1 = open(os.path.join(self.dirname, 'tmpl1.txt'), 'w') 185 try: 186 file1.write("Included\n") 187 finally: 188 file1.close() 189 190 file2 = open(os.path.join(self.dirname, 'tmpl2.txt'), 'w') 191 try: 192 file2.write("""----- Included data below this line ----- 193 {% include tmpl1.txt %} 194 ----- Included data above this line -----""") 195 finally: 196 file2.close() 197 198 loader = TemplateLoader([self.dirname]) 199 tmpl = loader.load('tmpl2.txt', cls=NewTextTemplate) 200 self.assertEqual("""----- Included data below this line ----- 201 Included 202 ----- Included data above this line -----""", tmpl.generate().render()) 203 204 114 205 def suite(): 115 206 suite = unittest.TestSuite() 116 suite.addTest(doctest.DocTestSuite(TextTemplate.__module__)) 117 suite.addTest(unittest.makeSuite(TextTemplateTestCase, 'test')) 207 suite.addTest(doctest.DocTestSuite(NewTextTemplate.__module__)) 208 suite.addTest(unittest.makeSuite(OldTextTemplateTestCase, 'test')) 209 suite.addTest(unittest.makeSuite(NewTextTemplateTestCase, 'test')) 118 210 return suite 119 211 -
trunk/genshi/template/text.py
r616 r706 12 12 # history and logs, available at http://genshi.edgewall.org/log/. 13 13 14 """Plain text templating engine.""" 14 """Plain text templating engine. 15 16 This module implements two template language syntaxes, at least for a certain 17 transitional period. `OldTextTemplate` (aliased to just `TextTemplate`) defines 18 a syntax that was inspired by Cheetah/Velocity. `NewTextTemplate` on the other 19 hand is inspired by the syntax of the Django template language, which has more 20 explicit delimiting of directives, and is more flexible with regards to 21 white space and line breaks. 22 23 In a future release, `OldTextTemplate` will be phased out in favor of 24 `NewTextTemplate`, as the names imply. Therefore the new syntax is strongly 25 recommended for new projects, and existing projects may want to migrate to the 26 new syntax to remain compatible with future Genshi releases. 27 """ 15 28 16 29 import re … … 21 34 from genshi.template.interpolation import interpolate 22 35 23 __all__ = [' TextTemplate']36 __all__ = ['NewTextTemplate', 'OldTextTemplate', 'TextTemplate'] 24 37 __docformat__ = 'restructuredtext en' 25 38 26 39 27 class TextTemplate(Template): 28 """Implementation of a simple text-based template engine. 29 30 >>> tmpl = TextTemplate('''Dear $name, 40 class NewTextTemplate(Template): 41 r"""Implementation of a simple text-based template engine. This class will 42 replace `OldTextTemplate` in a future release. 43 44 It uses a more explicit delimiting style for directives: instead of the old 45 style which required putting directives on separate lines that were prefixed 46 with a ``#`` sign, directives and commenbtsr are enclosed in delimiter pairs 47 (by default ``{% ... %}`` and ``{# ... #}``, respectively). 48 49 Variable substitution uses the same interpolation syntax as for markup 50 languages: simple references are prefixed with a dollar sign, more complex 51 expression enclosed in curly braces. 52 53 >>> tmpl = NewTextTemplate('''Dear $name, 54 ... 55 ... {# This is a comment #} 56 ... We have the following items for you: 57 ... {% for item in items %} 58 ... * ${'Item %d' % item} 59 ... {% end %} 60 ... ''') 61 >>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render('text') 62 Dear Joe, 63 <BLANKLINE> 64 <BLANKLINE> 65 We have the following items for you: 66 <BLANKLINE> 67 * Item 1 68 <BLANKLINE> 69 * Item 2 70 <BLANKLINE> 71 * Item 3 72 <BLANKLINE> 73 <BLANKLINE> 74 75 By default, no spaces or line breaks are removed. If a line break should 76 not be included in the output, prefix it with a backslash: 77 78 >>> tmpl = NewTextTemplate('''Dear $name, 79 ... 80 ... {# This is a comment #}\ 81 ... We have the following items for you: 82 ... {% for item in items %}\ 83 ... * $item 84 ... {% end %}\ 85 ... ''') 86 >>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render('text') 87 Dear Joe, 88 <BLANKLINE> 89 We have the following items for you: 90 * 1 91 * 2 92 * 3 93 <BLANKLINE> 94 95 Backslashes are also used to escape the start delimiter of directives and 96 comments: 97 98 >>> tmpl = NewTextTemplate('''Dear $name, 99 ... 100 ... \{# This is a comment #} 101 ... We have the following items for you: 102 ... {% for item in items %}\ 103 ... * $item 104 ... {% end %}\ 105 ... ''') 106 >>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render('text') 107 Dear Joe, 108 <BLANKLINE> 109 {# This is a comment #} 110 We have the following items for you: 111 * 1 112 * 2 113 * 3 114 <BLANKLINE> 115 116 :since: version 0.5 117 """ 118 directives = [('def', DefDirective), 119 ('when', WhenDirective), 120 ('otherwise', OtherwiseDirective), 121 ('for', ForDirective), 122 ('if', IfDirective), 123 ('choose', ChooseDirective), 124 ('with', WithDirective)] 125 126 _DIRECTIVE_RE = r'((?<!\\)%s\s*(\w+)\s*(.*?)\s*%s|(?<!\\)%s.*?%s)' 127 _ESCAPE_RE = r'\\\n|\\(\\)|\\(%s)|\\(%s)' 128 129 def __init__(self, source, basedir=None, filename=None, loader=None, 130 encoding=None, lookup='lenient', allow_exec=False, 131 delims=('{%', '%}', '{#', '#}')): 132 self.delimiters = delims 133 Template.__init__(self, source, basedir=basedir, filename=filename, 134 loader=loader, encoding=encoding, lookup=lookup) 135 136 def _get_delims(self): 137 return self._delims 138 def _set_delims(self, delims): 139 if len(delims) != 4: 140 raise ValueError('delimiers tuple must have exactly four elements') 141 self._delims = delims 142 self._directive_re = re.compile(self._DIRECTIVE_RE % tuple( 143 map(re.escape, delims) 144 )) 145 self._escape_re = re.compile(self._ESCAPE_RE % tuple( 146 map(re.escape, delims[::2]) 147 )) 148 delimiters = property(_get_delims, _set_delims, """\ 149 The delimiters for directives and comments. This should be a four item tuple 150 of the form ``(directive_start, directive_end, comment_start, 151 comment_end)``, where each item is a string. 152 """) 153 154 def _parse(self, source, encoding): 155 """Parse the template from text input.""" 156 stream = [] # list of events of the "compiled" template 157 dirmap = {} # temporary mapping of directives to elements 158 depth = 0 159 160 source = source.read() 161 if isinstance(source, str): 162 source = source.decode(encoding or 'utf-8', 'replace') 163 offset = 0 164 lineno = 1 165 166 _escape_sub = self._escape_re.sub 167 def _escape_repl(mo): 168 groups = filter(None, mo.groups()) 169 if not groups: 170 return '' 171 return groups[0] 172 173 for idx, mo in enumerate(self._directive_re.finditer(source)): 174 start, end = mo.span(1) 175 if start > offset: 176 text = _escape_sub(_escape_repl, source[offset:start]) 177 for kind, data, pos in interpolate(text, self.basedir, 178 self.filename, lineno, 179 lookup=self.lookup): 180 stream.append((kind, data, pos)) 181 lineno += len(text.splitlines()) 182 183 lineno += len(source[start:end].splitlines()) 184 command, value = mo.group(2, 3) 185 if command: 186 if command == 'include': 187 pos = (self.filename, lineno, 0) 188 stream.append((INCLUDE, (value.strip(), []), pos)) 189 elif command == 'end': 190 depth -= 1 191 if depth in dirmap: 192 directive, start_offset = dirmap.pop(depth) 193 substream = stream[start_offset:] 194 stream[start_offset:] = [(SUB, ([directive], substream), 195 (self.filepath, lineno, 0))] 196 else: 197 cls = self._dir_by_name.get(command) 198 if cls is None: 199 raise BadDirectiveError(command) 200 directive = cls, value, None, (self.filepath, lineno, 0) 201 dirmap[depth] = (directive, len(stream)) 202 depth += 1 203 204 offset = end 205 206 if offset < len(source): 207 text = _escape_sub(_escape_repl, source[offset:]) 208 for kind, data, pos in interpolate(text, self.basedir, 209 self.filename, lineno, 210 lookup=self.lookup): 211 stream.append((kind, data, pos)) 212 213 return stream 214 215 216 class OldTextTemplate(Template): 217 """Legacy implementation of the old syntax text-based templates. This class 218 is provided in a transition phase for backwards compatibility. New code 219 should use the `NewTextTemplate` class and the improved syntax it provides. 220 221 >>> tmpl = OldTextTemplate('''Dear $name, 31 222 ... 32 223 ... We have the following items for you: … … 118 309 119 310 return stream 311 312 313 TextTemplate = OldTextTemplate
Note: See TracChangeset
for help on using the changeset viewer.
