Buildout recipe to generate a text file from a template
This recipe can be used to generate textfiles from a (text) template.
Contents
A short example:
[buildout] parts = message [message] recipe = collective.recipe.template input = templates/message.in output = ${buildout:parts-directory}/etc/message mymessage = Hello, World!
In the template you can use the exact same variables as you can use in the buildout configuration. For example an input file can look like this:
My top level directory is ${buildout:directory} Executables are stored in ${buildout:bin-directory}
As an extension to the buildout syntax you can reference variables from the current buildout part directly. For example:
My message is: ${mymessage}
A short example:
[buildout] parts = message [message] recipe = collective.recipe.template[genshi]:genshi input = templates/message.in output = ${buildout:parts-directory}/etc/message some-option = value mymessage = Hello, World!
In the template you can use the exact same variables as you can use in the buildout configuration, but instead of colons as the separator you either have to use attribute access, or for options with a dash dictionary syntax. The global buildout config is accessible through parts, the current part through options.
For example an input file can look like this:
My top level directory is ${parts.buildout.directory} Executables are stored in ${parts.buildout['bin-directory']} Accessing the current part: ${options['some-option']}
Both iw.recipe.template and inquant.recipe.textfile claim to do the same thing. I have found them to be undocumented and too buggy for real world use, and neither are in a public repository where I could fix them. In addition this implementation leverages the buildout variable substitution code, making it a lot simpler.
Lets create a minimal buildout.cfg file:
>>> write('buildout.cfg', ... ''' ... [buildout] ... parts = template ... offline = true ... ... [template] ... recipe = collective.recipe.template ... input = template.in ... output = template ... ''')
We create a template file:
>>> write('template.in', ... '''# ... My template knows about buildout path: ... ${buildout:directory} ... ''')
Now we can run buildout:
>>> print system(join('bin', 'buildout')), Installing template.
The template was indeed created:
>>> cat('template') # My template knows about buildout path: .../sample-buildout
The variable buildout:directory was also substituted by a path.
By default re-execute buildout, makes that output file is overwrited, by new output file. But, if you want generate this file ONLY when it doesn't exist, you can use overwrite option:
Once again check output file content:
>>> cat('template') # My template knows about buildout path: .../sample-buildout
>>> print system("sed 's/sample-buildout/spam-ham-eggs/g' template > out && mv out template") <BLANKLINE>
Let's check content now:
>>> cat('template') # My template knows about buildout path: .../spam-ham-eggs
Now try re-execute buildout, and then check our file again:
>>> print system(join('bin', 'buildout')), Updating template. >>> cat('template') # My template knows about buildout path: .../sample-buildout
Like you see, re-execute buildout, caused overwrite ourmodified file. Let's try to prevent this behavior. So we must modify buildout.cfg, re-execute buildout, and then modify again output file:
>>> write('buildout.cfg', ... ''' ... [buildout] ... parts = template ... offline = true ... ... [template] ... recipe = collective.recipe.template ... input = template.in ... output = template ... overwrite = false ... ''') >>> print system(join('bin', 'buildout')), Uninstalling template. Installing template. >>> cat('template') # My template knows about buildout path: .../sample-buildout >>> print system("sed 's/sample-buildout/spam-ham-eggs/g' template > out && mv out template") <BLANKLINE> >>> cat('template') # My template knows about buildout path: .../spam-ham-eggs
Let's check output file again - it shouldn't be modyfied this time:
>>> print system(join('bin', 'buildout')), Updating template. >>> cat('template') # My template knows about buildout path: .../spam-ham-eggs
For very short script it can make sense to put the source directly into buildout.cfg:
>>> write('buildout.cfg', ... ''' ... [buildout] ... parts = template ... offline = true ... ... [template] ... recipe = collective.recipe.template ... input = inline: ... #!/bin/bash ... echo foo ... output = ${buildout:parts-directory}/template ... ''')
Now we can run buildout:
>>> print system(join('bin', 'buildout')), Uninstalling template. Installing template.
The template should have been created:
>>> cat('parts', 'template') #!/bin/bash echo foo
Normally the file mode gets copied from the template, but it can also be specified manually, which especially makes sense in this case:
>>> write('buildout.cfg', ... ''' ... [buildout] ... parts = template ... offline = true ... ... [template] ... recipe = collective.recipe.template ... inline = ... #!/bin/bash ... echo foo ... output = ${buildout:parts-directory}/template ... mode = 755 ... ''')
Run buildout again
>>> print system(join('bin', 'buildout')), Uninstalling template. Installing template.
The template should have the specified file mode:
>>> from os import stat >>> from stat import S_IMODE >>> print '%o' % S_IMODE(stat('parts/template').st_mode) 755
Warning
There is a security risk inherent with using URL input. Please be careful.
Similarly, you may want to read input from a URL, e.g.:
>>> write('buildout.cfg', ... ''' ... [buildout] ... parts = template ... ... [template] ... recipe = collective.recipe.template ... url = file:///tmp/template.in ... output = template ... ''')
To demonstrate this, first we create a template file:
>>> write('/tmp/template.in', ... '''# ... My template knows about buildout path: ... ${buildout:directory} ... ''')
Now we can run buildout:
>>> print system(join('bin', 'buildout')), Uninstalling template. Installing template.
The template should have been created:
>>> cat('template') # My template knows about buildout path: .../sample-buildout
Lets create a minimal buildout.cfg file. This time the output should happen in a variable path:
>>> write('buildout.cfg', ... ''' ... [buildout] ... parts = template ... offline = true ... ... [template] ... recipe = collective.recipe.template ... input = template.in ... output = ${buildout:parts-directory}/template ... ''')
Now we can run buildout:
>>> print system(join('bin', 'buildout')), Uninstalling template. Installing template.
The template was indeed created:
>>> cat('parts', 'template') # My template knows about buildout path: .../sample-buildout
If an output file should be created in a path that does not yet exist, then the missing items will be created for us:
>>> write('buildout.cfg', ... ''' ... [buildout] ... parts = template ... offline = true ... ... [template] ... recipe = collective.recipe.template ... input = template.in ... output = ${buildout:parts-directory}/etc/template ... ''') >>> print system(join('bin', 'buildout')), Uninstalling template. Installing template.
Also creation of several subdirectories is supported:
>>> write('buildout.cfg', ... ''' ... [buildout] ... parts = template ... offline = true ... ... [template] ... recipe = collective.recipe.template ... input = template.in ... output = ${buildout:parts-directory}/foo/bar/template ... ''') >>> print system(join('bin', 'buildout')), Uninstalling template. Installing template. >>> cat('parts', 'foo', 'bar', 'template') # My template knows about buildout path: .../sample-buildout
When changes happen to the output path, then the old path is removed on uninstall. Therefore the etc/ directory created above has vanished now:
>>> ls('parts') d foo
When substituting variables in a template, dependencies on other buildout parts can occur. Buildout will resolve them by determining the values of those other parts' options first. To see this, we create a buildout involving a template that uses a variable computed by a part that would not otherwise be built:
>>> write('dummy.py', ... ''' ... class Recipe(object): ... ... def __init__(self, buildout, name, options): ... options['foo'] = 'bar' ... ... def install(self): ... return () ... ... def update(self): ... pass ... ''')>>> write('setup.py', ... ''' ... from setuptools import setup ... ... setup(name='dummyrecipe', ... entry_points = {'zc.buildout': ['default = dummy:Recipe']}) ... ''')>>> write('buildout.cfg', ... ''' ... [buildout] ... develop = . ... parts = template ... offline = true ... ... [template] ... recipe = collective.recipe.template ... input = template.in ... output = template ... ... [other] ... recipe = dummyrecipe ... ''')>>> write('template.in', ... '''# ... My template knows about another buildout part: ... ${other:foo} ... ''')>>> print system(join('bin', 'buildout')), Develop: '/sample-buildout/.' Uninstalling template. Installing other. Installing template.>>> cat('template') # My template knows about another buildout part: bar
Add support for output path creation. You can do:
output = /path/to/target
and intermediate path items will be created if they do not exist. [ulif]
Add tests. [ulif]
(By accident the 1.1 release was marked as 1.2. So in fact they are the same.)