Tests
=====

This file contains doctests that are mainly test rather than
documentation.  The documentation doctests can be found in README.txt

Multiple files
--------------

The recipe can subsitute the same set of variables on several files at
the same time:

    >>> write(sample_buildout, 'helloworld.txt.in',
    ... """
    ... Hello ${world}!
    ... """)

    >>> write(sample_buildout, 'goodbyeworld.txt.in',
    ... """
    ... Goodbye ${world}!
    ... """)

File names are separated by any kind of whitespace:

    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = multiple
    ...
    ... [multiple]
    ... recipe = z3c.recipe.filetemplate
    ... files = helloworld.txt
    ...         goodbyeworld.txt
    ... world = Philipp
    ... """)

After executing buildout, we can see that ``$world`` has indeed been
replaced by ``Philipp``:

    >>> print system(buildout)
    Installing multiple.

Absolute paths
--------------

The recipe only accepts relative file paths.  For example, consider
this invalid buildout configuration:

    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = evil
    ...
    ... [evil]
    ... recipe = z3c.recipe.filetemplate
    ... 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:
      Installing.
      Getting section evil.
      Initializing part evil.
    Error: /etc/passwd.in is an absolute path. Paths must be relative to the buildout directory.

Missing template
----------------

The recipe will also complain with an error if you specify a file name
for which no template can be found:

    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = notthere
    ...
    ... [notthere]
    ... recipe = z3c.recipe.filetemplate
    ... files = doesntexist
    ... """)

    >>> print system(buildout)
    notthere: No template found for these file names: doesntexist.in
    While:
      Installing.
      Getting section notthere.
      Initializing part notthere.
    Error: No template found for these file names: doesntexist.in

Already existing file
---------------------

Another case where the recipe will complain is when you're trying to
replace a file that's already there:

    >>> write(sample_buildout, 'alreadyhere.txt',
    ... """
    ... 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]
    ... parts = alreadythere
    ...
    ... [alreadythere]
    ... recipe = z3c.recipe.filetemplate
    ... files = alreadyhere.txt
    ... """)

    >>> print system(buildout)
    Uninstalling multiple.
    Installing alreadythere.
    alreadythere: Destinations already exist: alreadyhere.txt. Please make
                  sure that you really want to generate these automatically.
                  Then move them away.
    While:
      Installing alreadythere.
    Error: Destinations already exist: alreadyhere.txt. Please make sure
           that you really want to generate these automatically.  Then move
           them away.

Missing variables
-----------------

The recipe will also fail to execute if a template refers to variables
that aren't defined in ``buildout.cfg``:

    >>> write(sample_buildout, 'missing.txt.in',
    ... """
    ... Hello ${world}!
    ... """)

    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = missing
    ...
    ... [missing]
    ... recipe = z3c.recipe.filetemplate
    ... files = missing.txt
    ... """)

    >>> print system(buildout) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
    Installing missing.
    While:
      Installing missing.
    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
-------------------------------

If there are no changes in the buildout section, or in the files it will build,
the recipe will update, which is a no-op.

    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = message
    ...
    ... [message]
    ... recipe = z3c.recipe.filetemplate
    ... files = helloworld.txt
    ... world = Philipp
    ... """)

    >>> print system(buildout)
    Installing message.
    >>> print system(buildout)
    Updating message.

Changes in a source directory cause a re-install
------------------------------------------------

The recipe keeps track of what files were installed, and so adding and removing
files causes a reinstall with the expected behavior.

    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = message
    ...
    ... [message]
    ... recipe = z3c.recipe.filetemplate
    ... source-directory = template
    ... world = Philipp
    ... """)
    >>> mkdir(sample_buildout, 'template')
    >>> mkdir(sample_buildout, 'template', 'etc')
    >>> write(sample_buildout, 'template', 'etc', 'helloworld.sh.in',
    ... """
    ... Hello ${world} from the .sh file!
    ... """)
    >>> print system(buildout)
    Uninstalling message.
    Installing message.
    >>> ls(sample_buildout, 'etc')
    -  helloworld.sh
    >>> write(sample_buildout, 'template', 'etc', 'helloworld.conf.in',
    ... """
    ... Hello ${world} from the etc dir!
    ... """)
    >>> print system(buildout)
    Uninstalling message.
    Installing message.
    >>> ls(sample_buildout, 'etc')
    -  helloworld.conf
    -  helloworld.sh
    >>> remove(sample_buildout, 'template', 'etc', 'helloworld.sh.in')
    >>> print system(buildout)
    Uninstalling message.
    Installing message.
    >>> ls(sample_buildout, 'etc')
    -  helloworld.conf

The main README also has an example of modifying a file, which will also cause
a reinstall.

More than one file in ``files`` with ``source-directory``
---------------------------------------------------------

When using ``source-directory``, ``files`` is a glob-based filter.  This is
shown in the main README.  What is not shown is that you can have more than
one ``files`` filter.  Here's an example.

    >>> write(sample_buildout, 'template', 'etc', 'helloworld.sh.in',
    ... """
    ... Hello ${world} from the .sh file!
    ... """)
    >>> write(sample_buildout, 'template', 'etc', 'helloworld.cfg.in',
    ... """
    ... Hello ${world} from the .cfg file!
    ... """)

    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = message
    ...
    ... [message]
    ... recipe = z3c.recipe.filetemplate
    ... source-directory = template
    ... files = *.conf
    ...         helloworld.cfg
    ... world = Philipp
    ... """)
    >>> print system(buildout)
    Uninstalling message.
    Installing message.
    >>> ls(sample_buildout, 'etc')
    -  helloworld.cfg
    -  helloworld.conf

``files`` with a directory when using a ``source-directory``
------------------------------------------------------------

If you use a source directory and your ``files`` specify a directory, the
directory must match precisely.

    >>> mkdir(sample_buildout, 'template', 'etc', 'in')
    >>> write(sample_buildout, 'template', 'etc', 'in', 'helloworld.cfg.in',
    ... """
    ... Hello ${world} from the inner .cfg file!
    ... """)
    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = message
    ...
    ... [message]
    ... recipe = z3c.recipe.filetemplate
    ... source-directory = template
    ... files = *.sh
    ...         etc/helloworld.cfg
    ... world = Philipp
    ... """)
    >>> print system(buildout)
    Uninstalling message.
    Installing message.
    >>> ls(sample_buildout, 'etc')
    -  helloworld.cfg
    -  helloworld.sh

    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = message
    ...
    ... [message]
    ... recipe = z3c.recipe.filetemplate
    ... source-directory = template
    ... files = *.sh
    ...         etc/in/helloworld.cfg
    ... world = Philipp
    ... """)
    >>> print system(buildout)
    Uninstalling message.
    Installing message.
    >>> ls(sample_buildout, 'etc')
    -  helloworld.sh
    d  in
    >>> ls(sample_buildout, 'etc', 'in')
    -  helloworld.cfg

This works with the ./ directory also, so you can specify a file in the root
of the source directory.

At the start, we have the ``etc`` directory but not ``helloworld.cfg``.

    >>> ls(sample_buildout)
    -  .installed.cfg
    -  alreadyhere.txt
    -  alreadyhere.txt.in
    d  bin
    -  buildout.cfg
    d  develop-eggs
    d  eggs
    d  etc
    -  goodbyeworld.txt.in
    -  helloworld.txt.in
    -  missing.txt.in
    d  parts
    d  template

    >>> write(sample_buildout, 'template', 'helloworld.cfg.in',
    ... """
    ... Hello ${world} from the top-level .cfg file!
    ... """)
    >>> write(sample_buildout, 'buildout.cfg',
    ... """
    ... [buildout]
    ... parts = message
    ...
    ... [message]
    ... recipe = z3c.recipe.filetemplate
    ... source-directory = template
    ... files = ./helloworld.cfg
    ... world = Philipp
    ... """)
    >>> print system(buildout)
    Uninstalling message.
    Installing message.

Now the reverse is true.

    >>> ls(sample_buildout)
    -  .installed.cfg
    -  alreadyhere.txt
    -  alreadyhere.txt.in
    d  bin
    -  buildout.cfg
    d  develop-eggs
    d  eggs
    -  goodbyeworld.txt.in
    -  helloworld.cfg
    -  helloworld.txt.in
    -  missing.txt.in
    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"/.
