[Checkins] SVN: z3c.recipe.filetemplate/trunk/ merge work on lp:~gary/z3c.recipe.filetemplate/relative-paths
Gary Poster
gary.poster at canonical.com
Wed Apr 21 13:55:06 EDT 2010
Log message for revision 111210:
merge work on lp:~gary/z3c.recipe.filetemplate/relative-paths
Changed:
A z3c.recipe.filetemplate/trunk/.bzrignore
U z3c.recipe.filetemplate/trunk/CHANGES.txt
A z3c.recipe.filetemplate/trunk/MANIFEST.in
U z3c.recipe.filetemplate/trunk/buildout.cfg
U z3c.recipe.filetemplate/trunk/setup.py
U z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/README.txt
U z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/__init__.py
U z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/tests.py
U z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/tests.txt
-=-
Added: z3c.recipe.filetemplate/trunk/.bzrignore
===================================================================
--- z3c.recipe.filetemplate/trunk/.bzrignore (rev 0)
+++ z3c.recipe.filetemplate/trunk/.bzrignore 2010-04-21 17:55:06 UTC (rev 111210)
@@ -0,0 +1,7 @@
+.installed.cfg
+bin
+develop-eggs
+eggs
+parts
+z3c.recipe.filetemplate.egg-info
+dist
Modified: z3c.recipe.filetemplate/trunk/CHANGES.txt
===================================================================
--- z3c.recipe.filetemplate/trunk/CHANGES.txt 2010-04-21 17:13:11 UTC (rev 111209)
+++ z3c.recipe.filetemplate/trunk/CHANGES.txt 2010-04-21 17:55:06 UTC (rev 111210)
@@ -9,9 +9,51 @@
Features
--------
-- None yet.
+- Enable cross-platform paths by allowing an extended syntax for path
+ suffixes. Example: If ``${buildout:directory}`` resolves to
+ ``/sample_buildout`` on a POSIX system and ``C:\sample_buildout`` in
+ Windows, ``${buildout:directory/foo.txt}`` will resolve to
+ ``/sample_buildout/foo.txt`` and ``C:\sample_buildout\foo.txt``,
+ respectively.
+- Add filters via a pipe syntax, reminiscent of UNIX pipes or Django template
+ filters. Simple example: if ``${name}`` resolves to ``harry`` then
+ ``${name|upper}`` resolves to ``HARRY``. Simple string filters are
+ upper, lower, title, and capitalize, just like the Python string
+ methods. Also see the next bullet.
+- Added support for the buildout relative-paths option. Shell scripts should
+ include ``${shell-relative-path-setup}`` before commands with
+ buildout-generated paths are executed. Python scripts should use
+ ``${python-relative-path-setup}`` similarly. ``${os-paths}`` (shell),
+ ``${space-paths}`` (shell), and ``${string-paths}`` (Python) will have
+ relative paths if the buildout relative-paths option is used. To convert
+ individual absolute paths to relative paths, use the ``path-repr`` filter
+ in Python scripts and the ``shell-path`` filter in shell scripts. Path
+ suffixes can be combined with these filters, so, if buildout's
+ relative-paths option is true, ``${buildout:directory/foo.txt|path-repr}``
+ will produce a buildout-relative, platform appropriate path to
+ foo.txt. Note that for shell scripts, Windows is not supported at
+ this time.
+
+- Support escaping ``${...}`` with ``$${...}`` in templates. This is
+ particularly useful for *NIX shell scripts.
+
+-----
+Fixes
+-----
+
+- Make tests less susceptible to timing errors.
+
+-------
+Changes
+-------
+
+- ``${os-paths}`` and ``${space-paths}`` no longer filter out .zip paths.
+
+- The entries in ``${string-paths}`` now are separated by newlines. Each
+ entry is indented to the level of the initial placement of the marker.
+
2.0.3 (2009-07-02)
==================
Added: z3c.recipe.filetemplate/trunk/MANIFEST.in
===================================================================
--- z3c.recipe.filetemplate/trunk/MANIFEST.in (rev 0)
+++ z3c.recipe.filetemplate/trunk/MANIFEST.in 2010-04-21 17:55:06 UTC (rev 111210)
@@ -0,0 +1,3 @@
+include *.txt
+recursive-include z3c *.txt
+exclude MANIFEST.in buildout.cfg .bzrignore
Modified: z3c.recipe.filetemplate/trunk/buildout.cfg
===================================================================
--- z3c.recipe.filetemplate/trunk/buildout.cfg 2010-04-21 17:13:11 UTC (rev 111209)
+++ z3c.recipe.filetemplate/trunk/buildout.cfg 2010-04-21 17:55:06 UTC (rev 111210)
@@ -1,7 +1,14 @@
[buildout]
develop = .
parts = test
+ interpreter
[test]
recipe = zc.recipe.testrunner
eggs = z3c.recipe.filetemplate
+
+[interpreter]
+recipe = zc.recipe.egg
+interpreter = py
+eggs = z3c.recipe.filetemplate
+
Modified: z3c.recipe.filetemplate/trunk/setup.py
===================================================================
--- z3c.recipe.filetemplate/trunk/setup.py 2010-04-21 17:13:11 UTC (rev 111209)
+++ z3c.recipe.filetemplate/trunk/setup.py 2010-04-21 17:55:06 UTC (rev 111210)
@@ -19,7 +19,7 @@
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
setup(name='z3c.recipe.filetemplate',
- version = '2.1dev',
+ version = '2.1',
license='ZPL 2.1',
url='http://pypi.python.org/pypi/z3c.recipe.filetemplate',
description="zc.buildout recipe for creating files from file templates",
Modified: z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/README.txt
===================================================================
--- z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/README.txt 2010-04-21 17:13:11 UTC (rev 111209)
+++ z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/README.txt 2010-04-21 17:55:06 UTC (rev 111210)
@@ -40,7 +40,7 @@
... world = Philipp
... """)
-After executing buildout, we can see that ``$world`` has indeed been
+After executing buildout, we can see that ``${world}`` has indeed been
replaced by ``Philipp``:
>>> print system(buildout)
@@ -49,6 +49,35 @@
>>> cat(sample_buildout, 'helloworld.txt')
Hello Philipp!
+If you need to escape the ${...} pattern, you can do so by repeating the dollar
+sign.
+
+ >>> update_file(sample_buildout, 'helloworld.txt.in',
+ ... """
+ ... Hello world! The double $${dollar-sign} escapes!
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'helloworld.txt')
+ Hello world! The double ${dollar-sign} escapes!
+
+Note that dollar signs alone, without curly braces, are not parsed.
+
+ >>> update_file(sample_buildout, 'helloworld.txt.in',
+ ... """
+ ... $Hello $$world! $$$profit!
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'helloworld.txt')
+ $Hello $$world! $$$profit!
+
Note that the output file uses the same permission bits as found on the input
file.
@@ -195,6 +224,12 @@
Also note that, if you use a source directory and your ``files`` specify a
directory, the directory must match precisely.
+ >>> # Clean up for later test.
+ >>> import shutil
+ >>> shutil.rmtree(os.path.join(sample_buildout, 'template', 'etc'))
+ >>> os.remove(os.path.join(
+ ... sample_buildout, 'template', 'bin', 'helloworld.sh.in'))
+
==============
Advanced Usage
==============
@@ -206,7 +241,7 @@
standard buildout syntax, but used in the template. Notice
``${buildout:parts}`` in the template below.
- >>> write(sample_buildout, 'helloworld.txt.in',
+ >>> update_file(sample_buildout, 'helloworld.txt.in',
... """
... Hello ${world}. I used these parts: ${buildout:parts}.
... """)
@@ -228,21 +263,118 @@
>>> cat(sample_buildout, 'helloworld.txt')
Hello Philipp. I used these parts: message.
-Sharing variables
+Path Extensions
+===============
+
+Substitutions can have path suffixes using the POSIX "/" path separator.
+The template will convert these to the proper path separator for the current
+OS. They also then are part of the value passed to filters, the feature
+described next. Notice ``${buildout:directory/foo/bar.txt}`` in the template
+below.
+
+ >>> update_file(sample_buildout, 'helloworld.txt.in',
+ ... """
+ ... Here's foo/bar.txt in the buildout:
+ ... ${buildout:directory/foo/bar.txt}
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'helloworld.txt') # doctest: +ELLIPSIS
+ Here's foo/bar.txt in the buildout:
+ /.../sample-buildout/foo/bar.txt
+
+Filters
+=======
+
+You can use pipes within a substitution to filter the original value. This
+recipe provides several filters for you to use. The syntax is reminiscent of
+(and inspired by) POSIX pipes and Django template filters. For example,
+if world = Philipp, ``HELLO ${world|upper}!`` would result in ``HELLO
+PHILIPP!``.
+
+A few simple Python string methods are exposed as filters right now:
+
+- capitalize: First letter in string is capitalized.
+- lower: All letters in string are lowercase.
+- title: First letter of each word in string is capitalized.
+- upper: All letters in string are uppercase.
+
+Other filters are important for handling paths if buildout's relative-paths
+option is true. See `Working with Paths`_ for more details.
+
+- path-repr: Converts the path to a Python expression for the path. If
+ buildout's relative-paths option is false, this will simply be a repr
+ of the absolute path. If relative-paths is true, this will be a
+ function call to convert a buildout-relative path to an absolute path;
+ it requires that ``${python-relative-path-setup}`` be included earlier
+ in the template.
+
+- shell-path: Converts the path to a shell expression for the path. Only
+ POSIX is supported at this time. If buildout's relative-paths option
+ is false, this will simply be the absolute path. If relative-paths is
+ true, this will be an expression to convert a buildout-relative path
+ to an absolute path; it requires that ``${shell-relative-path-setup}``
+ be included earlier in the template.
+
+Combining the three advanced features described so far, then, if the
+buildout relative-paths option were false, we were in a POSIX system, and
+the sample buildout were in the root of the system, the template
+expression ``${buildout:bin-directory/data/initial.csv|path-repr}``
+would result in ``'/sample-buildout/bin/data/initial.csv'``.
+
+Here's a real, working example of the string method filters. We'll have
+examples of the path filters in the `Working with Paths`_ section.
+
+ >>> update_file(sample_buildout, 'helloworld.txt.in',
+ ... """
+ ... HELLO ${world|upper}!
+ ... hello ${world|lower}.
+ ... ${name|title} and the Chocolate Factory
+ ... ${sentence|capitalize}
+ ... """)
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... parts = message
+ ...
+ ... [message]
+ ... recipe = z3c.recipe.filetemplate
+ ... files = helloworld.txt
+ ... world = Philipp
+ ... name = willy wonka
+ ... sentence = that is a good book.
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'helloworld.txt') # doctest: +ELLIPSIS
+ HELLO PHILIPP!
+ hello philipp.
+ Willy Wonka and the Chocolate Factory
+ That is a good book.
+
+Sharing Variables
=================
-The recipe allows extending one or more sections, to decrease repetition, using
-the ``extends`` option. For instance, consider the following buildout.
+The recipe allows extending one or more sections, to decrease
+repetition, using the ``extends`` option. For instance, consider the
+following buildout.
>>> write(sample_buildout, 'buildout.cfg',
... """
... [buildout]
... parts = message
- ...
+ ...
... [template_defaults]
... mygreeting = Hi
... myaudience = World
- ...
+ ...
... [message]
... recipe = z3c.recipe.filetemplate
... files = helloworld.txt
@@ -255,7 +387,7 @@
section, and overwritten locally. A template of
``${mygreeting}, ${myaudience}!``...
- >>> write(sample_buildout, 'helloworld.txt.in',
+ >>> update_file(sample_buildout, 'helloworld.txt.in',
... """
... ${mygreeting}, ${myaudience}!
... """)
@@ -269,32 +401,287 @@
>>> cat(sample_buildout, 'helloworld.txt')
Hi, everybody!
-Specifying paths
-================
+Defining options in Python
+==========================
-You can specify eggs and extra-paths in the recipe. If you do, three
-predefined options will be available in the recipe's options for the template.
-If "paths" are the non-zip paths, and "all_paths" are all paths, then the
-options would be defined roughly as given here:
+You can specify that certain variables should be interpreted as Python using
+``interpreted-options``. This takes zero or more lines. Each line should
+specify an option. It can define immediately (see ``silly-range`` in
+the example below) or point to an option to be interepreted, which can
+be useful if you want to define a multi-line expression (see
+``first-interpreted-option`` and ``message-reversed-is-egassem``).
-``os-paths``
- ``(os.pathsep).join(paths)``
-
-``string-paths``
- ``', '.join(repr(p) for p in all_paths)``
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... parts = message
+ ...
+ ... [message]
+ ... recipe = z3c.recipe.filetemplate
+ ... files = helloworld.txt
+ ... interpreted-options = silly-range = repr(range(5))
+ ... first-interpreted-option
+ ... message-reversed-is-egassem
+ ... first-interpreted-option =
+ ... options['interpreted-options'].splitlines()[0].strip()
+ ... message-reversed-is-egassem=
+ ... ''.join(
+ ... reversed(
+ ... buildout['buildout']['parts']))
+ ... not-interpreted=hello world
+ ... """)
-``space-paths``
- ``' '.join(paths)``
+ >>> update_file(sample_buildout, 'helloworld.txt.in', """\
+ ... ${not-interpreted}!
+ ... silly-range: ${silly-range}
+ ... first-interpreted-option: ${first-interpreted-option}
+ ... message-reversed-is-egassem: ${message-reversed-is-egassem}
+ ... """)
-For instance, consider this example.
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+ >>> cat(sample_buildout, 'helloworld.txt') # doctest:+ELLIPSIS
+ hello world!
+ silly-range: [0, 1, 2, 3, 4]
+ first-interpreted-option: silly-range = repr(range(5))
+ message-reversed-is-egassem: egassem
+
+Working with Paths
+==================
+
+We've already mentioned how to handle buildout's relative-paths option
+in the discussion of filters. This section has some concrete examples
+and discussion of that. It also introduces how to get a set of paths
+from specifying dependencies.
+
+Here are concrete examples of the path-repr and shell-path filters.
+We'll show results when relative-paths is true and when it is false.
+
+------------------------------
+Demonstration of ``path-repr``
+------------------------------
+
+Let's say we want to make a custom Python script in the bin directory.
+It will print some information from a file in a ``data`` directory
+within the buildout root. Here's the template.
+
+ >>> write(sample_buildout, 'template', 'bin', 'dosomething.py.in', '''\
+ ... #!${buildout:executable}
+ ... ${python-relative-path-setup}
+ ... f = open(${buildout:directory/data/info.csv|path-repr})
+ ... print f.read()
+ ... ''')
+ >>> os.chmod(
+ ... os.path.join(
+ ... sample_buildout, 'template', 'bin', 'dosomething.py.in'),
+ ... 0711)
+
+If we evaluate that template with relative-paths set to false, the results
+shouldn't be too surprising.
+
>>> write(sample_buildout, 'buildout.cfg',
... """
... [buildout]
... parts = message
- ...
+ ...
... [message]
... recipe = z3c.recipe.filetemplate
+ ... source-directory = template
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'bin', 'dosomething.py') # doctest: +ELLIPSIS
+ #!...
+ <BLANKLINE>
+ f = open('/.../sample-buildout/data/info.csv')
+ print f.read()
+
+``${python-relative-path-setup}`` evaluated to an empty string. The path
+is absolute and quoted.
+
+If we evaluate it with relative-paths set to true, the results are much...
+bigger.
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... parts = message
+ ... relative-paths = true
+ ...
+ ... [message]
+ ... recipe = z3c.recipe.filetemplate
+ ... source-directory = template
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'bin', 'dosomething.py') # doctest: +ELLIPSIS
+ #!...
+ import os, imp
+ # Get path to this file.
+ if __name__ == '__main__':
+ _z3c_recipe_filetemplate_filename = __file__
+ else:
+ # If this is an imported module, we want the location of the .py
+ # file, not the .pyc, because the .py file may have been symlinked.
+ _z3c_recipe_filetemplate_filename = imp.find_module(__name__)[1]
+ # Get the full, non-symbolic-link directory for this file.
+ _z3c_recipe_filetemplate_base = os.path.dirname(
+ os.path.abspath(os.path.realpath(_z3c_recipe_filetemplate_filename)))
+ # Ascend to buildout root.
+ _z3c_recipe_filetemplate_base = os.path.dirname(
+ _z3c_recipe_filetemplate_base)
+ def _z3c_recipe_filetemplate_path_repr(path):
+ "Return absolute version of buildout-relative path."
+ return os.path.join(_z3c_recipe_filetemplate_base, path)
+ <BLANKLINE>
+ f = open(_z3c_recipe_filetemplate_path_repr('data/info.csv'))
+ print f.read()
+
+That's quite a bit of code. You might wonder why we don't just use '..' for
+parent directories. The reason is that we want our scripts to be usable
+from any place on the filesystem. If we used '..' to construct paths
+relative to the generated file, then the paths would only work from
+certain directories.
+
+So that's how path-repr works. It can really come in handy if you want
+to support relative paths in buildout. Now let's look at the shell-path
+filter.
+
+-------------------------------
+Demonstration of ``shell-path``
+-------------------------------
+
+Maybe you want to write some shell scripts. The shell-path filter will help
+you support buildout relative-paths fairly painlessly.
+
+Right now, only POSIX is supported with the shell-path filter, as mentioned
+before.
+
+Usage is very similar to the ``path-repr`` filter. You need to include
+``${shell-relative-path-setup}`` before you use it, just as you include
+``${python-relative-path-setup}`` before using ``path-repr``.
+
+Let's say we want to make a custom shell script in the bin directory.
+It will print some information from a file in a ``data`` directory
+within the buildout root. Here's the template.
+
+ >>> write(sample_buildout, 'template', 'bin', 'dosomething.sh.in', '''\
+ ... #!/bin/sh
+ ... ${shell-relative-path-setup}
+ ... cat ${buildout:directory/data/info.csv|shell-path}
+ ... ''')
+ >>> os.chmod(
+ ... os.path.join(
+ ... sample_buildout, 'template', 'bin', 'dosomething.sh.in'),
+ ... 0711)
+
+If relative-paths is set to false (the default), the results are simple.
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... parts = message
+ ...
+ ... [message]
+ ... recipe = z3c.recipe.filetemplate
+ ... source-directory = template
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'bin', 'dosomething.sh') # doctest: +ELLIPSIS
+ #!/bin/sh
+ <BLANKLINE>
+ cat /.../sample-buildout/data/info.csv
+
+``${shell-relative-path-setup}`` evaluated to an empty string. The path
+is absolute.
+
+Now let's look at the larger code when relative-paths is set to true.
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... parts = message
+ ... relative-paths = true
+ ...
+ ... [message]
+ ... recipe = z3c.recipe.filetemplate
+ ... source-directory = template
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'bin', 'dosomething.sh') # doctest: +ELLIPSIS
+ #!/bin/sh
+ # Get full, non-symbolic-link path to this file.
+ Z3C_RECIPE_FILETEMPLATE_FILENAME=`\
+ readlink -f "$0" 2>/dev/null || \
+ realpath "$0" 2>/dev/null || \
+ type -P "$0" 2>/dev/null`
+ # Get directory of file.
+ Z3C_RECIPE_FILETEMPLATE_BASE=`dirname ${Z3C_RECIPE_FILETEMPLATE_FILENAME}`
+ # Ascend to buildout root.
+ Z3C_RECIPE_FILETEMPLATE_BASE=`dirname ${Z3C_RECIPE_FILETEMPLATE_BASE}`
+ <BLANKLINE>
+ cat "$Z3C_RECIPE_FILETEMPLATE_BASE"/data/info.csv
+
+As with the Python code, we don't just use '..' for
+parent directories because we want our scripts to be usable
+from any place on the filesystem.
+
+----------------------------------
+Getting Arbitrary Dependency Paths
+----------------------------------
+
+You can specify ``eggs`` and ``extra-paths`` in the recipe. The
+mechanism is the same as the one provided by the zc.recipe.egg, so
+pertinent options such as find-links and index are available.
+
+If you do, the paths for the dependencies will be calculated. They will
+be available as a list in the namespace of the interpreted options as
+``paths``. Also, three predefined options will be available in the
+recipe's options for the template.
+
+If ``paths`` are the paths, ``shell_path`` is the ``shell-path`` filter, and
+``path_repr`` is the ``path-repr`` filter, then the pre-defined options
+would be defined roughly as given here:
+
+``os-paths`` (for shell scripts)
+ ``(os.pathsep).join(shell_path(path) for path in paths)``
+
+``string-paths`` (for Python scripts)
+ ``',\n '.join(path_repr(path) for path in paths)``
+
+``space-paths`` (for shell scripts)
+ ``' '.join(shell_path(path) for path in paths)``
+
+Therefore, if you want to support the relative-paths option, you should
+include ``${shell-relative-path-setup}`` (for ``os-paths`` and
+``space-paths``) or ``${python-relative-path-setup}`` (for ``string-paths``)
+as appropriate at the top of your template.
+
+Let's consider a simple example.
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... parts = message
+ ...
+ ... [message]
+ ... recipe = z3c.recipe.filetemplate
... files = helloworld.txt
... eggs = demo<0.3
...
@@ -302,6 +689,7 @@
... index = %(server)s/index
... """ % dict(server=link_server))
+The relative-paths option is false, the default.
>>> write(sample_buildout, 'helloworld.txt.in',
... """
@@ -327,21 +715,23 @@
>>> cat(sample_buildout, 'helloworld.txt') # doctest:+ELLIPSIS
Hello! Here are the paths for the demo<0.3 eggs.
OS paths:
- .../eggs/demo-0.2...egg:.../eggs/demoneeded-1.2c1...egg
+ /.../eggs/demo-0.2...egg:/.../eggs/demoneeded-1.2c1...egg
---
String paths:
- '.../eggs/demo-0.2...egg', '.../eggs/demoneeded-1.2c1...egg'
+ '/.../eggs/demo-0.2...egg',
+ '/.../eggs/demoneeded-1.2c1...egg'
---
Space paths:
- .../eggs/demo-0.2...egg .../eggs/demoneeded-1.2c1...egg
+ /.../eggs/demo-0.2...egg /.../eggs/demoneeded-1.2c1...egg
-You can specify extra-paths as well, which will go at the end of the egg paths.
+You can specify extra-paths as well, which will go at the end of the egg
+paths.
>>> write(sample_buildout, 'buildout.cfg',
... """
... [buildout]
... parts = message
- ...
+ ...
... [message]
... recipe = z3c.recipe.filetemplate
... files = helloworld.txt
@@ -359,70 +749,116 @@
>>> cat(sample_buildout, 'helloworld.txt') # doctest:+ELLIPSIS
Hello! Here are the paths for the demo<0.3 eggs.
OS paths:
- ...demo...:...demoneeded...:.../sample-buildout/foo
+ /...demo...:/...demoneeded...:/.../sample-buildout/foo
---
String paths:
- '...demo...', '...demoneeded...', '.../sample-buildout/foo'
+ '/...demo...',
+ '/...demoneeded...',
+ '/.../sample-buildout/foo'
---
Space paths:
- ...demo... ...demoneeded... .../sample-buildout/foo
+ /...demo... /...demoneeded... .../sample-buildout/foo
-Defining options in Python
-==========================
+To emphasize the effect of the relative-paths option, let's see what it looks
+like when we set relative-paths to True.
-You can specify that certain variables should be interpreted as Python using
-``interpreted-options``. This takes zero or more lines. Each line should
-specify an option. It can define immediately (see ``duplicate-os-paths``,
-``foo-paths``, and ``silly-range`` in the example below) or point to an option
-to be interepreted, which can be useful if you want to define a
-multi-line expression (see ``first-interpreted-option`` and
-``message-reversed-is-egassem``).
-
>>> write(sample_buildout, 'buildout.cfg',
... """
... [buildout]
... parts = message
- ...
+ ... relative-paths = true
+ ...
... [message]
... recipe = z3c.recipe.filetemplate
... files = helloworld.txt
... eggs = demo<0.3
- ... interpreted-options = duplicate-os-paths=(os.pathsep).join(paths)
- ... foo-paths='FOO'.join(all_paths)
- ... silly-range = repr(range(5))
- ... first-interpreted-option
- ... message-reversed-is-egassem
- ... first-interpreted-option =
- ... options['interpreted-options'].split()[0].strip()
- ... message-reversed-is-egassem=
- ... ''.join(
- ... reversed(
- ... buildout['buildout']['parts']))
- ... not-interpreted=hello world
+ ... extra-paths = ${buildout:directory}/foo
...
... find-links = %(server)s
... index = %(server)s/index
... """ % dict(server=link_server))
- >>> write(sample_buildout, 'helloworld.txt.in',
- ... """
- ... ${not-interpreted}!
- ... duplicate-os-paths: ${duplicate-os-paths}
- ... foo-paths: ${foo-paths}
- ... silly-range: ${silly-range}
- ... first-interpreted-option: ${first-interpreted-option}
- ... message-reversed-is-egassem: ${message-reversed-is-egassem}
- ... """)
-
>>> print system(buildout)
Uninstalling message.
Installing message.
>>> cat(sample_buildout, 'helloworld.txt') # doctest:+ELLIPSIS
- hello world!
- duplicate-os-paths: ...demo-0.2...egg:...demoneeded-1.2c1...egg
- foo-paths: ...demo-0.2...eggFOO...demoneeded-1.2c1...egg
- silly-range: [0, 1, 2, 3, 4]
- first-interpreted-option: duplicate-os-paths=(os.pathsep).join(paths)
- message-reversed-is-egassem: egassem
+ Hello! Here are the paths for the demo<0.3 eggs.
+ OS paths:
+ "$Z3C_RECIPE_FILETEMPLATE_BASE"/eggs/demo-0.2-py...egg:"$Z3C_RECIPE_FILETEMPLATE_BASE"/eggs/demoneeded-1.2c1-py...egg:"$Z3C_RECIPE_FILETEMPLATE_BASE"/foo
+ ---
+ String paths:
+ _z3c_recipe_filetemplate_path_repr('eggs/demo-0.2-py...egg'),
+ _z3c_recipe_filetemplate_path_repr('eggs/demoneeded-1.2c1-py...egg'),
+ _z3c_recipe_filetemplate_path_repr('foo')
+ ---
+ Space paths:
+ "$Z3C_RECIPE_FILETEMPLATE_BASE"/eggs/demo-0.2-py...egg "$Z3C_RECIPE_FILETEMPLATE_BASE"/eggs/demoneeded-1.2c1-py...egg "$Z3C_RECIPE_FILETEMPLATE_BASE"/foo
+
+Remember, your script won't really work unless you include
+``${shell-relative-path-setup}`` (for ``os-paths`` and ``space-paths``)
+or ``${python-relative-path-setup}`` (for ``string-paths``) as
+appropriate at the top of your template.
+
+Getting Dependency Paths from ``zc.recipe.egg``
+-----------------------------------------------
+
+You can get the ``eggs`` and ``extra-paths`` from another section using
+zc.recipe.egg by using the ``extends`` option from the `Sharing Variables`_
+section above. Then you can use the template options described above to
+build your paths in your templates.
+
+Getting Dependency Paths from ``z3c.recipe.scripts``
+----------------------------------------------------
+
+If, like the Launchpad project, you are using Gary Poster's unreleased
+package ``z3c.recipe.scripts`` to generate your scripts, and you want to
+have your scripts use the same Python environment as generated by that
+recipe, you can just use the path-repr and shell-path filters with standard
+buildout directories. Here is an example buildout.cfg.
+
+::
+
+ [buildout]
+ parts = scripts message
+ relative-paths = true
+
+ [scripts]
+ recipe = z3c.recipe.scripts
+ eggs = demo<0.3
+
+ [message]
+ recipe = z3c.recipe.filetemplate
+ files = helloworld.py
+
+Then the template to use this would want to simply put
+``${scripts:parts-directory|path-repr}`` at the beginning of Python's path.
+
+You can do this for subprocesses with PYTHONPATH.
+
+ ${python-relative-path-setup}
+ import os
+ import subprocess
+ env = os.environ.copy()
+ env['PYTHONPATH'] = ${scripts:parts-directory|path-repr}
+ subprocess.call('myscript', env=env)
+
+That's it.
+
+Similarly, here's an approach to making a script that will have the
+right environment. You want to put the parts directory of the
+z3c.recipe.scripts section in the sys.path before site.py is loaded.
+This is usually handled by z3c.recipe.scripts itself, but sometimes you
+may want to write Python scripts in your template for some reason.
+
+ #!/usr/bin/env python -S
+ ${python-relative-path-setup}
+ import sys
+ sys.path.insert(0, ${scripts:parts-directory|path-repr})
+ import site
+ # do stuff...
+
+If you do this for many scripts, put this entire snippet in an option in the
+recipe and use this snippet as a single substitution in the top of your
+scripts.
Modified: z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/__init__.py
===================================================================
--- z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/__init__.py 2010-04-21 17:13:11 UTC (rev 111209)
+++ z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/__init__.py 2010-04-21 17:55:06 UTC (rev 111210)
@@ -29,10 +29,15 @@
class FileTemplate(object):
+ filters = {}
+ dynamic_options = {}
+
def __init__(self, buildout, name, options):
self.buildout = buildout
self.name = name
self.options = options
+ self.buildout_root = zc.buildout.easy_install.realpath(
+ buildout['buildout']['directory'])
self.logger=logging.getLogger(self.name)
# get defaults from extended sections
defaults = {}
@@ -42,23 +47,33 @@
defaults.update(self.buildout[section_name])
for key, value in defaults.items():
self.options.setdefault(key, value)
+ relative_paths = self.options.setdefault(
+ 'relative-paths',
+ buildout['buildout'].get('relative-paths', 'false')
+ )
+ if relative_paths not in ('true', 'false'):
+ self._user_error(
+ 'The relative-paths option must have the value of '
+ 'true or false.')
+ self.relative_paths = relative_paths = (relative_paths == 'true')
+ self.paths = paths = []
# set up paths for eggs, if given
- if 'eggs' in self.options:
- self.eggs = zc.recipe.egg.Scripts(buildout, name, options)
- orig_distributions, ws = self.eggs.working_set()
- # we want ws, eggs.extra_paths, eggs._relative_paths
- all_paths = [
+ if 'eggs' in options:
+ eggs = zc.recipe.egg.Scripts(buildout, name, options)
+ orig_distributions, ws = eggs.working_set()
+ paths.extend(
zc.buildout.easy_install.realpath(dist.location)
- for dist in ws]
- all_paths.extend(
+ for dist in ws)
+ paths.extend(
zc.buildout.easy_install.realpath(path)
- for path in self.eggs.extra_paths)
+ for path in eggs.extra_paths)
else:
- all_paths = []
- paths = [path for path in all_paths if not path.endswith('.zip')]
- self.options['os-paths'] = (os.pathsep).join(paths)
- self.options['string-paths'] = ', '.join(repr(p) for p in all_paths)
- self.options['space-paths'] = ' '.join(paths)
+ paths.extend(
+ os.path.join(buildout.options['directory'], p.strip())
+ for p in options.get('extra-paths', '').split('\n')
+ if p.strip()
+ )
+ options['_paths'] = '\n'.join(paths)
# get and check the files to be created
self.filenames = self.options.get('files', '*').split()
self.source_dir = self.options.get('source-directory', '').strip()
@@ -153,7 +168,7 @@
if interpreted:
globs = {'__builtins__': __builtins__, 'os': os, 'sys': sys}
locs = {'name': name, 'options': options, 'buildout': buildout,
- 'paths': paths, 'all_paths': all_paths}
+ 'paths': paths, 'all_paths': paths}
for value in interpreted.split('\n'):
if value:
value = value.split('=', 1)
@@ -179,6 +194,7 @@
'a %s.',
key, expression, evaluated, type(evaluated))
options[key] = evaluated
+
def _user_error(self, msg, *args):
msg = msg % args
self.logger.error(msg)
@@ -195,17 +211,21 @@
'Destinations already exist: %s. Please make sure that '
'you really want to generate these automatically. Then '
'move them away.', ', '.join(already_exists))
+ self.seen = []
+ # We throw ``seen`` away right now, but could move template
+ # processing up to __init__ if valuable. That would mean that
+ # templates would be rewritten even if a value in another
+ # section had been referenced; however, it would also mean that
+ # __init__ would do virtually all of the work, with install only
+ # doing the writing.
for rel_path, last_mod, st_mode in self.actions:
source = os.path.join(self.source_dir, rel_path)
dest = os.path.join(self.destination_dir, rel_path[:-3])
mode=stat.S_IMODE(st_mode)
- template=open(source).read()
- template=re.sub(r"\$\{([^:]+?)\}", r"${%s:\1}" % self.name,
- template)
- self._create_paths(os.path.dirname(dest))
# we process the file first so that it won't be created if there
# is a problem.
- processed = self.options._sub(template, [])
+ processed = Template(source, dest, self).substitute()
+ self._create_paths(os.path.dirname(dest))
result=open(dest, "wt")
result.write(processed)
result.close()
@@ -219,5 +239,313 @@
os.mkdir(path)
self.options.created(path)
+ def _call_and_log(self, callable, args, message_generator):
+ try:
+ return callable(*args)
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ # Argh. Would like to raise wrapped exception.
+ colno, lineno = self.get_colno_lineno(start)
+ msg = message_generator(lineno, colno)
+ self.logger.error(msg, exc_info=True)
+ raise
+
def update(self):
pass
+
+
+class Template:
+ # Heavily hacked from--"inspired by"?--string.Template
+ pattern = re.compile(r"""
+ \$(?:
+ \${(?P<escaped>[^}]*)} | # Escape sequence of two delimiters.
+
+ {((?P<section>[-a-z0-9 ._]+):)? # Optional section name.
+ (?P<option>[-a-z0-9 ._]+) # Required option name.
+ (?P<path_extension>/[^|}]+/?)? # Optional path extensions.
+ ([ ]*(?P<filters>(\|[ ]*[-a-z0-9._]+[ ]*)+))?
+ # Optional filters.
+ } |
+
+ {(?P<invalid>[^}]*}) # Other ill-formed delimiter exprs.
+ )
+ """, re.IGNORECASE | re.VERBOSE)
+
+ def __init__(self, source, destination, recipe):
+ self.source = source
+ self.destination = zc.buildout.easy_install.realpath(destination)
+ self.recipe = recipe
+ self.template = open(source).read()
+
+ def get_colno_lineno(self, i):
+ lines = self.template[:i].splitlines(True)
+ if not lines:
+ colno = 1
+ lineno = 1
+ else:
+ colno = len(lines[-1]) + 1
+ lineno = len(lines)
+ return colno, lineno
+
+ def _get(self, section, option, start):
+ if section is None:
+ section = self.recipe.name # This sets up error messages properly.
+ if section == self.recipe.name:
+ factory = self.recipe.dynamic_options.get(option)
+ if factory is not None:
+ return self.recipe._call_and_log(
+ factory, (self, start, option),
+ lambda lineno, colno: (
+ 'Dynamic option %r in line %d, col %d of %s '
+ 'crashed.') % (option, lineno, colno, self.source))
+ # else...
+ options = self.recipe.options
+ elif section in self.recipe.buildout:
+ options = self.recipe.buildout[section]
+ else:
+ value = options = None
+ if options is not None:
+ value = options.get(option, None, self.recipe.seen)
+ if value is None:
+ colno, lineno = self.get_colno_lineno(start)
+ raise zc.buildout.buildout.MissingOption(
+ "Option '%s:%s', referenced in line %d, col %d of %s, "
+ "does not exist." %
+ (section, option, lineno, colno, self.source))
+ return value
+
+ def substitute(self):
+ def convert(mo):
+ start = mo.start()
+ # Check the most common path first.
+ option = mo.group('option')
+ if option is not None:
+ section = mo.group('section')
+ val = self._get(section, option, start)
+ path_extension = mo.group('path_extension')
+ filters = mo.group('filters')
+ if path_extension is not None:
+ val = os.path.join(val, *path_extension.split('/')[1:])
+ if filters is not None:
+ for filter_name in filters.split('|')[1:]:
+ filter_name = filter_name.strip()
+ filter = self.recipe.filters.get(filter_name)
+ if filter is None:
+ colno, lineno = self.get_colno_lineno(start)
+ raise ValueError(
+ 'Unknown filter %r '
+ 'in line %d, col %d of %s' %
+ (filter_name, lineno, colno, self.source))
+ val = self.recipe._call_and_log(
+ filter, (val, self, start, filter_name),
+ lambda lineno, colno: (
+ 'Filter %r in line %d, col %d of %s '
+ 'crashed processing value %r') % (
+ filter_name, lineno, colno, self.source, val))
+ # We use this idiom instead of str() because the latter will
+ # fail if val is a Unicode containing non-ASCII characters.
+ return '%s' % (val,)
+ escaped = mo.group('escaped')
+ if escaped is not None:
+ return '${%s}' % (escaped,)
+ invalid = mo.group('invalid')
+ if invalid is not None:
+ colno, lineno = self.get_colno_lineno(mo.start('invalid'))
+ raise ValueError(
+ 'Invalid placeholder %r in line %d, col %d of %s' %
+ (mo.group('invalid'), lineno, colno, self.source))
+ raise ValueError('Unrecognized named group in pattern',
+ self.pattern) # programmer error, AFAICT
+ return self.pattern.sub(convert, self.template)
+
+
+############################################################################
+# Filters
+def filter(func):
+ "Helper function to register filter functions."
+ FileTemplate.filters[func.__name__.replace('_', '-')] = func
+ return func
+
+ at filter
+def capitalize(val, template, start, filter):
+ return val.capitalize()
+
+ at filter
+def title(val, template, start, filter):
+ return val.title()
+
+ at filter
+def upper(val, template, start, filter):
+ return val.upper()
+
+ at filter
+def lower(val, template, start, filter):
+ return val.lower()
+
+ at filter
+def path_repr(val, template, start, filter):
+ # val is a path.
+ return _maybe_relativize(
+ val, template,
+ lambda p: "_z3c_recipe_filetemplate_path_repr(%r)" % (p,),
+ repr)
+
+ at filter
+def shell_path(val, template, start, filter):
+ # val is a path.
+ return _maybe_relativize(
+ val, template,
+ lambda p: '"$Z3C_RECIPE_FILETEMPLATE_BASE"/%s' % (p,),
+ lambda p: p)
+
+# Helpers hacked from zc.buildout.easy_install.
+def _maybe_relativize(path, template, relativize, absolutize):
+ path = zc.buildout.easy_install.realpath(path)
+ if template.recipe.relative_paths:
+ buildout_root = template.recipe.buildout_root
+ if path == buildout_root:
+ return relativize(os.curdir)
+ destination = template.destination
+ common = os.path.dirname(os.path.commonprefix([path, destination]))
+ if (common == buildout_root or
+ common.startswith(os.path.join(buildout_root, ''))
+ ):
+ return relativize(_relative_path(common, path))
+ return absolutize(path)
+
+def _relative_path(common, path):
+ """Return the relative path from ``common`` to ``path``.
+
+ This is a helper for _relativitize, which is a helper to
+ _relative_path_and_setup.
+ """
+ r = []
+ while 1:
+ dirname, basename = os.path.split(path)
+ r.append(basename)
+ if dirname == common:
+ break
+ assert dirname != path, "dirname of %s is the same" % dirname
+ path = dirname
+ r.reverse()
+ return os.path.join(*r)
+
+
+############################################################################
+# Dynamic options
+def dynamic_option(func):
+ "Helper function to register dynamic options."
+ FileTemplate.dynamic_options[func.__name__.replace('_', '-')] = func
+ return func
+
+ at dynamic_option
+def os_paths(template, start, name):
+ return os.pathsep.join(
+ shell_path(path, template, start, 'os-paths')
+ for path in template.recipe.paths)
+
+ at dynamic_option
+def string_paths(template, start, name):
+ colno, lineno = template.get_colno_lineno(start)
+ separator = ',\n' + ((colno - 1) * ' ')
+ return separator.join(
+ path_repr(path, template, start, 'string-paths')
+ for path in template.recipe.paths)
+
+ at dynamic_option
+def space_paths(template, start, name):
+ return ' '.join(
+ shell_path(path, template, start, 'space-paths')
+ for path in template.recipe.paths)
+
+ at dynamic_option
+def shell_relative_path_setup(template, start, name):
+ if template.recipe.relative_paths:
+ depth = _relative_depth(
+ template.recipe.buildout['buildout']['directory'],
+ template.destination)
+ value = SHELL_RELATIVE_PATH_SETUP
+ if depth:
+ value += '# Ascend to buildout root.\n'
+ value += depth * SHELL_DIRNAME
+ else:
+ value += '# This is the buildout root.\n'
+ return value
+ else:
+ return ''
+
+SHELL_RELATIVE_PATH_SETUP = '''\
+# Get full, non-symbolic-link path to this file.
+Z3C_RECIPE_FILETEMPLATE_FILENAME=`\\
+ readlink -f "$0" 2>/dev/null || \\
+ realpath "$0" 2>/dev/null || \\
+ type -P "$0" 2>/dev/null`
+# Get directory of file.
+Z3C_RECIPE_FILETEMPLATE_BASE=`dirname ${Z3C_RECIPE_FILETEMPLATE_FILENAME}`
+'''
+
+SHELL_DIRNAME = '''\
+Z3C_RECIPE_FILETEMPLATE_BASE=`dirname ${Z3C_RECIPE_FILETEMPLATE_BASE}`
+'''
+
+ at dynamic_option
+def python_relative_path_setup(template, start, name):
+ if template.recipe.relative_paths:
+ depth = _relative_depth(
+ template.recipe.buildout['buildout']['directory'],
+ template.destination)
+ value = PYTHON_RELATIVE_PATH_SETUP_START
+ if depth:
+ value += '# Ascend to buildout root.\n'
+ value += depth * PYTHON_DIRNAME
+ else:
+ value += '# This is the buildout root.\n'
+ value += PYTHON_RELATIVE_PATH_SETUP_END
+ return value
+ else:
+ return ''
+
+PYTHON_RELATIVE_PATH_SETUP_START = '''\
+import os, imp
+# Get path to this file.
+if __name__ == '__main__':
+ _z3c_recipe_filetemplate_filename = __file__
+else:
+ # If this is an imported module, we want the location of the .py
+ # file, not the .pyc, because the .py file may have been symlinked.
+ _z3c_recipe_filetemplate_filename = imp.find_module(__name__)[1]
+# Get the full, non-symbolic-link directory for this file.
+_z3c_recipe_filetemplate_base = os.path.dirname(
+ os.path.abspath(os.path.realpath(_z3c_recipe_filetemplate_filename)))
+'''
+
+PYTHON_DIRNAME = '''\
+_z3c_recipe_filetemplate_base = os.path.dirname(
+ _z3c_recipe_filetemplate_base)
+'''
+
+PYTHON_RELATIVE_PATH_SETUP_END = '''\
+def _z3c_recipe_filetemplate_path_repr(path):
+ "Return absolute version of buildout-relative path."
+ return os.path.join(_z3c_recipe_filetemplate_base, path)
+'''
+
+def _relative_depth(common, path):
+ # Helper ripped from zc.buildout.easy_install.
+ """Return number of dirs separating ``path`` from ancestor, ``common``.
+
+ For instance, if path is /foo/bar/baz/bing, and common is /foo, this will
+ return 2--in UNIX, the number of ".." to get from bing's directory
+ to foo.
+ """
+ n = 0
+ while 1:
+ dirname = os.path.dirname(path)
+ if dirname == path:
+ raise AssertionError("dirname of %s is the same" % dirname)
+ if dirname == common:
+ break
+ n += 1
+ path = dirname
+ return n
Modified: z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/tests.py
===================================================================
--- z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/tests.py 2010-04-21 17:13:11 UTC (rev 111209)
+++ z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/tests.py 2010-04-21 17:55:06 UTC (rev 111210)
@@ -12,12 +12,32 @@
#
##############################################################################
+import os
+import time
import zc.buildout.testing
import zc.buildout.tests
from zope.testing import doctest
+
+def update_file(dir, *args):
+ """Update a file.
+
+ Make sure that the mtime of the file is updated so that buildout notices
+ the changes. The resolution of mtime is system dependent, so we keep
+ trying to write until mtime has actually changed."""
+ path = os.path.join(dir, *(args[:-1]))
+ original = os.stat(path).st_mtime
+ while True:
+ f = open(path, 'w')
+ f.write(args[-1])
+ f.flush()
+ if os.stat(path).st_mtime != original:
+ break
+ time.sleep(0.2)
+
def setUp(test):
zc.buildout.tests.easy_install_SetUp(test)
+ test.globs['update_file'] = update_file
zc.buildout.testing.install_develop('z3c.recipe.filetemplate', test)
def test_suite():
Modified: z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/tests.txt
===================================================================
--- z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/tests.txt 2010-04-21 17:13:11 UTC (rev 111209)
+++ z3c.recipe.filetemplate/trunk/z3c/recipe/filetemplate/tests.txt 2010-04-21 17:55:06 UTC (rev 111210)
@@ -14,7 +14,7 @@
... """
... Hello ${world}!
... """)
-
+
>>> write(sample_buildout, 'goodbyeworld.txt.in',
... """
... Goodbye ${world}!
@@ -56,7 +56,7 @@
... files = /etc/passwd.in
... root = me
... """)
-
+
>>> print system(buildout)
evil: /etc/passwd.in is an absolute path. Paths must be relative to the buildout directory.
While:
@@ -80,7 +80,7 @@
... recipe = z3c.recipe.filetemplate
... files = doesntexist
... """)
-
+
>>> print system(buildout)
notthere: No template found for these file names: doesntexist.in
While:
@@ -99,12 +99,12 @@
... """
... I'm already here
... """)
-
+
>>> write(sample_buildout, 'alreadyhere.txt.in',
... """
... I'm the template that's supposed to replace the file above.
... """)
-
+
>>> write(sample_buildout, 'buildout.cfg',
... """
... [buildout]
@@ -137,7 +137,7 @@
... """
... Hello ${world}!
... """)
-
+
>>> write(sample_buildout, 'buildout.cfg',
... """
... [buildout]
@@ -147,12 +147,13 @@
... recipe = z3c.recipe.filetemplate
... files = missing.txt
... """)
-
- >>> print system(buildout)
+
+ >>> print system(buildout) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Installing missing.
While:
Installing missing.
- Error: Referenced option does not exist: missing world
+ Error: Option 'missing:world', referenced in line 2, col 7 of
+ .../sample-buildout/missing.txt.in, does not exist.
No changes means just an update
-------------------------------
@@ -365,3 +366,70 @@
d parts
d template
+Specifying files with relative paths at the buildout root
+---------------------------------------------------------
+
+Working at the buildout root follows some different code paths with relative
+paths so we explore those here. We also evaluate paths at the directory root.
+
+ >>> rmdir(sample_buildout, 'template')
+ >>> mkdir(sample_buildout, 'template')
+ >>> write(sample_buildout, 'template', 'dosomething.py.in', '''\
+ ... #!${buildout:executable}
+ ... ${python-relative-path-setup}
+ ... root = ${buildout:directory|path-repr}
+ ... ''')
+
+ >>> write(sample_buildout, 'template', 'dosomething.sh.in', '''\
+ ... #!/bin/sh
+ ... ${shell-relative-path-setup}
+ ... cat ${buildout:directory|shell-path}
+ ... ''')
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... parts = message
+ ... relative-paths = true
+ ...
+ ... [message]
+ ... recipe = z3c.recipe.filetemplate
+ ... source-directory = template
+ ... """)
+
+ >>> print system(buildout)
+ Uninstalling message.
+ Installing message.
+
+ >>> cat(sample_buildout, 'dosomething.py') # doctest: +ELLIPSIS
+ #!...
+ import os, imp
+ # Get path to this file.
+ if __name__ == '__main__':
+ _z3c_recipe_filetemplate_filename = __file__
+ else:
+ # If this is an imported module, we want the location of the .py
+ # file, not the .pyc, because the .py file may have been symlinked.
+ _z3c_recipe_filetemplate_filename = imp.find_module(__name__)[1]
+ # Get the full, non-symbolic-link directory for this file.
+ _z3c_recipe_filetemplate_base = os.path.dirname(
+ os.path.abspath(os.path.realpath(_z3c_recipe_filetemplate_filename)))
+ # This is the buildout root.
+ def _z3c_recipe_filetemplate_path_repr(path):
+ "Return absolute version of buildout-relative path."
+ return os.path.join(_z3c_recipe_filetemplate_base, path)
+ <BLANKLINE>
+ root = _z3c_recipe_filetemplate_path_repr('.')
+
+ >>> cat(sample_buildout, 'dosomething.sh') # doctest: +ELLIPSIS
+ #!/bin/sh
+ # Get full, non-symbolic-link path to this file.
+ Z3C_RECIPE_FILETEMPLATE_FILENAME=`\
+ readlink -f "$0" 2>/dev/null || \
+ realpath "$0" 2>/dev/null || \
+ type -P "$0" 2>/dev/null`
+ # Get directory of file.
+ Z3C_RECIPE_FILETEMPLATE_BASE=`dirname ${Z3C_RECIPE_FILETEMPLATE_FILENAME}`
+ # This is the buildout root.
+ <BLANKLINE>
+ cat "$Z3C_RECIPE_FILETEMPLATE_BASE"/.
More information about the checkins
mailing list