PROJECT MOVED -> https://lab.nexedi.com/nexedi/slapos.recipe.template
[slapos.recipe.template.git] / slapos / recipe / template / README.jinja2.txt
1 Jinja2 usage
2 ============
3
4 Getting started
5 ---------------
6
7 Example buildout demonstrating some types::
8
9     >>> write('buildout.cfg',
10     ... '''
11     ... [buildout]
12     ... parts = template
13     ...
14     ... [template]
15     ... recipe = slapos.recipe.template:jinja2
16     ... template = foo.in
17     ... rendered = foo
18     ... context =
19     ...     key     bar          section:key
20     ...     key     recipe       :recipe
21     ...     raw     knight       Ni !
22     ...     import  json_module  json
23     ...     section param_dict   parameter-collection
24     ...
25     ... [parameter-collection]
26     ... foo = 1
27     ... bar = bar
28     ...
29     ... [section]
30     ... key = value
31     ... ''')
32
33 And according Jinja2 template (kept simple, control structures are possible)::
34
35     >>> write('foo.in',
36     ...     '{{bar}}\n'
37     ...     'Knights who say "{{knight}}"\n'
38     ...     '${this:is_literal}\n'
39     ...     '${foo:{{bar}}}\n'
40     ...     'swallow: {{ json_module.dumps(("african", "european")) }}\n'
41     ...     'parameters from section: {{ param_dict | dictsort }}\n'
42     ...     'Rendered with {{recipe}}'
43     ... )
44
45 We run buildout::
46
47     >>> print system(join('bin', 'buildout')),
48     Installing template.
49
50 And the template has been rendered::
51
52     >>> cat('foo')
53     value
54     Knights who say "Ni !"
55     ${this:is_literal}
56     ${foo:value}
57     swallow: ["african", "european"]
58     parameters from section: [('bar', 'bar'), ('foo', '1')]
59     Rendered with slapos.recipe.template:jinja2
60
61 Parameters
62 ----------
63
64 Mandatory:
65
66 ``template``
67   Template url/path, as accepted by zc.buildout.download.Download.__call__ .
68   For very short template, it can make sense to put it directly into
69   buildout.cfg: the value is the template itself, prefixed by the string
70   "inline:" + an optional newline.
71
72 ``rendered``
73   Where rendered template should be stored.
74
75 Optional:
76
77 ``context``
78   Jinja2 context specification, one variable per line, with 3
79   whitespace-separated parts: type, name and expression. Available types are
80   described below. "name" is the variable name to declare. Expression semantic
81   varies depending on the type.
82
83   Available types:
84
85   ``raw``
86     Immediate literal string.
87
88   ``key``
89     Indirect literal string.
90
91   ``import``
92     Import a python module.
93
94   ``section``
95     Make a whole buildout section available to template, as a dictionary.
96
97   Indirection targets are specified as: [section]:key .
98   It is possible to use buildout's buit-in variable replacement instead instead
99   of ``key`` type, but keep in mind that different lines are different
100   variables for this recipe. It might be what you want (factorising context
101   chunk declarations), otherwise you should use indirect types.
102
103 ``md5sum``
104   Template's MD5, for file integrity checking. By default, no integrity check
105   is done.
106
107 ``mode``
108   Mode, in octal representation (no need for 0-prefix) to set output file to.
109   This is applied before storing anything in output file.
110
111 ``extensions``
112   Jinja2 extensions to enable when rendering the template,
113   whitespace-separated. By default, none is loaded.
114
115 ``import-delimiter``
116   Delimiter character for in-temlate imports.
117   Defaults to ``/``.
118   See also: import-list
119
120 ``import-list``
121   Declares a list of import paths. Format is similar to ``context``.
122   "name" becomes import's base name.
123
124   Available types:
125
126   ``rawfile``
127     Literal path of a file.
128
129   ``file``
130     Indirect path of a file.
131
132   ``rawfolder``
133     Literal path of a folder. Any file in such folder can be imported.
134
135   ``folder``
136     Indirect path of a folder. Any file in such folder can be imported.
137
138 FAQ
139 ---
140
141 Q: How do I generate ${foo:bar} where foo comes from a variable ?
142
143 A: ``{{ '${' ~ foo_var ~ ':bar}' }}``
144    This is required as jinja2 fails parsing "${{{ foo_var }}:bar}". Though,
145    jinja2 succeeds at parsing "${foo:{{ bar_var }}}" so this trick isn't
146    needed for that case.
147
148 Use jinja2 extensions
149 ~~~~~~~~~~~~~~~~~~~~~
150
151     >>> write('foo.in',
152     ... '''{% set foo = ['foo'] -%}
153     ... {% do foo.append(bar) -%}
154     ... {{ foo | join(', ') }}''')
155     >>> write('buildout.cfg',
156     ... '''
157     ... [buildout]
158     ... parts = template
159     ...
160     ... [template]
161     ... recipe = slapos.recipe.template:jinja2
162     ... template = foo.in
163     ... rendered = foo
164     ... context = key bar buildout:parts
165     ... # We don't actually use all those extensions in this minimal example.
166     ... extensions = jinja2.ext.do jinja2.ext.loopcontrols
167     ...   jinja2.ext.with_
168     ... ''')
169     >>> print system(join('bin', 'buildout')),
170     Uninstalling template.
171     Installing template.
172
173     >>> cat('foo')
174     foo, template
175
176 Check file integrity
177 ~~~~~~~~~~~~~~~~~~~~
178
179 Compute template's MD5 sum::
180
181     >>> write('foo.in', '{{bar}}')
182     >>> import md5
183     >>> md5sum = md5.new(open('foo.in', 'r').read()).hexdigest()
184     >>> write('buildout.cfg',
185     ... '''
186     ... [buildout]
187     ... parts = template
188     ...
189     ... [template]
190     ... recipe = slapos.recipe.template:jinja2
191     ... template = foo.in
192     ... rendered = foo
193     ... context = key bar buildout:parts
194     ... md5sum = ''' + md5sum + '''
195     ... ''')
196     >>> print system(join('bin', 'buildout')),
197     Uninstalling template.
198     Installing template.
199
200     >>> cat('foo')
201     template
202
203 If the md5sum doesn't match, the buildout fail::
204
205     >>> write('buildout.cfg',
206     ... '''
207     ... [buildout]
208     ... parts = template
209     ...
210     ... [template]
211     ... recipe = slapos.recipe.template:jinja2
212     ... template = foo.in
213     ... rendered = foo
214     ... context = key bar buildout:parts
215     ... md5sum = 0123456789abcdef0123456789abcdef
216     ... ''')
217     >>> print system(join('bin', 'buildout')),
218     While:
219       Installing.
220       Getting section template.
221       Initializing part template.
222     Error: MD5 checksum mismatch for local resource at 'foo.in'.
223
224
225 Specify filesystem permissions
226 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
227
228 You can specify the mode for rendered file::
229
230     >>> write('template.in', '{{bar}}')
231     >>> write('buildout.cfg',
232     ... '''
233     ... [buildout]
234     ... parts = template
235     ...
236     ... [template]
237     ... recipe = slapos.recipe.template:jinja2
238     ... template = template.in
239     ... rendered = foo
240     ... context = key bar buildout:parts
241     ... mode = 205
242     ... ''')
243     >>> print system(join('bin', 'buildout')),
244     Uninstalling template.
245     Installing template.
246
247 And the generated file with have the right permissions::
248
249     >>> import stat
250     >>> import os
251     >>> print oct(stat.S_IMODE(os.stat('foo').st_mode))
252     0205
253
254 Note that Buildout will not allow you to have write permission for others
255 and will silently remove it (i.e a 207 mode will become 205).
256
257 Template imports
258 ~~~~~~~~~~~~~~~~
259
260 Here is a simple template importing an equaly-simple library:
261
262     >>> write('template.in', '''
263     ... {%- import "library" as library -%}
264     ... {{ library.foo() }}
265     ... ''')
266     >>> write('library.in', '{% macro foo() %}FOO !{% endmacro %}')
267
268 To import a template from rendered template, you need to specify what can be
269 imported::
270
271     >>> write('buildout.cfg', '''
272     ... [buildout]
273     ... parts = template
274     ...
275     ... [template]
276     ... recipe = slapos.recipe.template:jinja2
277     ... template = template.in
278     ... rendered = bar
279     ... import-list = rawfile library library.in
280     ... ''')
281     >>> print system(join('bin', 'buildout')),
282     Uninstalling template.
283     Installing template.
284     >>> cat('bar')
285     FOO !
286
287 Just like context definition, it also works with indirect values::
288
289     >>> write('buildout.cfg', '''
290     ... [buildout]
291     ... parts = template
292     ...
293     ... [template-library]
294     ... path = library.in
295     ...
296     ... [template]
297     ... recipe = slapos.recipe.template:jinja2
298     ... template = template.in
299     ... rendered = bar
300     ... import-list = file library template-library:path
301     ... ''')
302     >>> print system(join('bin', 'buildout')),
303     Uninstalling template.
304     Installing template.
305     >>> cat('bar')
306     FOO !
307
308 This also works to allow importing from identically-named files in different
309 directories::
310
311     >>> write('template.in', '''
312     ... {%- import "dir_a/1.in" as a1 -%}
313     ... {%- import "dir_a/2.in" as a2 -%}
314     ... {%- import "dir_b/1.in" as b1 -%}
315     ... {%- import "dir_b/c/1.in" as bc1 -%}
316     ... {{ a1.foo() }}
317     ... {{ a2.foo() }}
318     ... {{ b1.foo() }}
319     ... {{ bc1.foo() }}
320     ... ''')
321     >>> mkdir('a')
322     >>> mkdir('b')
323     >>> mkdir(join('b', 'c'))
324     >>> write(join('a', '1.in'), '{% macro foo() %}a1foo{% endmacro %}')
325     >>> write(join('a', '2.in'), '{% macro foo() %}a2foo{% endmacro %}')
326     >>> write(join('b', '1.in'), '{% macro foo() %}b1foo{% endmacro %}')
327     >>> write(join('b', 'c', '1.in'), '{% macro foo() %}bc1foo{% endmacro %}')
328
329 All templates can be accessed inside both folders::
330
331     >>> write('buildout.cfg', '''
332     ... [buildout]
333     ... parts = template
334     ...
335     ... [template-library]
336     ... path = library.in
337     ...
338     ... [template]
339     ... recipe = slapos.recipe.template:jinja2
340     ... template = template.in
341     ... rendered = bar
342     ... import-list =
343     ...     rawfolder dir_a a
344     ...     rawfolder dir_b b
345     ... ''')
346     >>> print system(join('bin', 'buildout')),
347     Uninstalling template.
348     Installing template.
349     >>> cat('bar')
350     a1foo
351     a2foo
352     b1foo
353     bc1foo
354
355 It is possible to override default path delimiter (without any effect on final
356 path)::
357
358     >>> write('template.in', r'''
359     ... {%- import "dir_a\\1.in" as a1 -%}
360     ... {%- import "dir_a\\2.in" as a2 -%}
361     ... {%- import "dir_b\\1.in" as b1 -%}
362     ... {%- import "dir_b\\c\\1.in" as bc1 -%}
363     ... {{ a1.foo() }}
364     ... {{ a2.foo() }}
365     ... {{ b1.foo() }}
366     ... {{ bc1.foo() }}
367     ... ''')
368     >>> write('buildout.cfg', r'''
369     ... [buildout]
370     ... parts = template
371     ...
372     ... [template-library]
373     ... path = library.in
374     ...
375     ... [template]
376     ... recipe = slapos.recipe.template:jinja2
377     ... template = template.in
378     ... rendered = bar
379     ... import-delimiter = \
380     ... import-list =
381     ...     rawfolder dir_a a
382     ...     rawfolder dir_b b
383     ... ''')
384     >>> print system(join('bin', 'buildout')),
385     Uninstalling template.
386     Installing template.
387     >>> cat('bar')
388     a1foo
389     a2foo
390     b1foo
391     bc1foo
392
393 Section dependency
394 ------------------
395
396 You can use other part of buildout in the template. This way this parts
397 will be installed as dependency::
398
399     >>> write('buildout.cfg', '''
400     ... [buildout]
401     ... parts = template
402     ...
403     ... [template]
404     ... recipe = slapos.recipe.template:jinja2
405     ... template = inline:{{bar}}
406     ... rendered = foo
407     ... context = key bar dependency:foobar
408     ...
409     ... [dependency]
410     ... foobar = dependency content
411     ... recipe = zc.buildout:debug
412     ... ''')
413
414     >>> print system(join('bin', 'buildout')),
415     Uninstalling template.
416     Installing dependency.
417       foobar='dependency content'
418       recipe='zc.buildout:debug'
419     Installing template.
420
421 This way you can get options which are computed in the ``__init__`` of
422 the dependent recipe.
423
424 Let's create a sample recipe modifying its option dict::
425
426     >>> write('setup.py',
427     ... '''
428     ... from setuptools import setup
429     ...
430     ... setup(name='samplerecipe',
431     ...       entry_points = {
432     ...           'zc.buildout': [
433     ...                'default = main:Recipe',
434     ...           ],
435     ...       }
436     ...      )
437     ... ''')
438     >>> write('main.py',
439     ... '''
440     ... class Recipe(object):
441     ...
442     ...     def __init__(self, buildout, name, options):
443     ...         options['data'] = 'foobar'
444     ...
445     ...     def install(self):
446     ...         return []
447     ... ''')
448
449 Let's just use ``buildout.cfg`` using this egg::
450
451     >>> write('buildout.cfg',
452     ... '''
453     ... [buildout]
454     ... develop = .
455     ... parts = template
456     ...
457     ... [template]
458     ... recipe = slapos.recipe.template:jinja2
459     ... template = inline:
460     ...   {{bar}}
461     ... rendered = foo
462     ... context = key bar sample:data
463     ...
464     ... [sample]
465     ... recipe = samplerecipe
466     ... ''')
467     >>> print system(join('bin', 'buildout')),
468     Develop: ...
469     Uninstalling template.
470     Uninstalling dependency.
471     Installing sample.
472     Installing template.
473     >>> cat('foo')
474     foobar
475