============================
XML Configuration Processing
============================

We have an xml configuration layer for the project builder.

    >>> from z3c.feature.core import xml

Getting a node
--------------

We can get a node in a number of different ways:

- from string

    >>> node = xml.getNode("<root>this is the root node</root>")
    >>> node
    <Element root at ...>

- from a file

    >>> import StringIO
    >>> io = StringIO.StringIO('<root>this is the root node</root>')

    >>> xml.getNode(io)
    <Element root at ...>

- from a node (just returns the node)

    >>> xml.getNode(node)
    <Element root at ...>

Or throws type error for something it doesn't know what to do with:

    >>> xml.getNode(False)
    Traceback (most recent call last):
    ...
    TypeError: Could not get a lxml.etree.Element object from False


Extracting Data
---------------

We can extract data from an xml block based on an interface.  It will
do the data conversion automatically if it can.

  >>> import zope.interface
  >>> import zope.schema

  >>> class IMyFeature(zope.interface.Interface):
  ...     name = zope.schema.TextLine()
  ...     is_cool = zope.schema.Bool()
  ...     developers = zope.schema.Int()
  ...     aList = zope.schema.List()
  ...     inTemplate = zope.schema.TextLine()
  >>> node = '''
  ... <feature type="my-feature">
  ...   <name>myproject</name>
  ...   <is-cool>t</is-cool>
  ...   <developers>3</developers>
  ...   <aList><item>one</item><item>two</item></aList>
  ...   <inTemplate>?</inTemplate>
  ... </feature>
  ... '''

  >>> sorted(xml.extractData(node, IMyFeature).items())
  [('aList', [u'one', u'two']),
   ('developers', 3),
   ('is_cool', False),
   ('name', u'myproject')]


Applying Features
-----------------

Once features are loaded they need to be applied on their context. By default
the context is a project:

  >>> from z3c.builder.core import project
  >>> context = project.BuildoutProjectBuilder(u'myproject')

  >>> node = xml.getNode('''\
  ... <project name="sampleproject">
  ...
  ...   <feature type="z3c.feature.core:meta-data">
  ...     <license>ZPL</license>
  ...     <version>0.1.0</version>
  ...     <author>Paul Carduner and Stephan Richter</author>
  ...     <author-email>zope-dev@zope.org</author-email>
  ...     <description>A utility to start Zope 3 projects</description>
  ...     <license>ZPL 2.1</license>
  ...     <keywords>
  ...       <item>zope3</item>
  ...       <item>project</item>
  ...       <item>builder</item>
  ...     </keywords>
  ...     <url>http://pypi.python.org/pypi/sampleproject</url>
  ...   </feature>
  ...
  ...   <feature type="z3c.feature.core:python-interpreter">
  ...     <executableName>py</executableName>
  ...   </feature>
  ... </project>
  ... ''')

We can extract all the features from this node:

  >>> from pprint import pprint
  >>> features = xml.getFeatures(node)
  >>> pprint(sorted(features))
  [<MetaDataFeature u'Metadata'>,
   <PythonInterpreterFeature u'Python Interpreter'>]

Let's now apply the feature onto the context.

  >>> from z3c.feature.core import base
  >>> base.applyFeatures(features, context)


Creating a Project
------------------

We can create a simple project:

  >>> project = xml.xmlToProject('<project name="helloworld"></project>')
  >>> project
  <BuildoutProjectBuilder u'helloworld'>

Let's now render the project:

  >>> project.update()
  >>> project.write(buildPath)

  >>> ls(buildPath)
  helloworld/
    ZBOILER.txt
    bootstrap.py
    buildout.cfg
    setup.py
    src/
      helloworld/
        __init__.py

As you can see, when building a project from features, the project creates a
`ZBOILER.txt` file that documents all applied features:

  >>> more(buildPath, 'helloworld', 'ZBOILER.txt')
  =========================================================
  Documentation About Project Features Generated by ZBoiler
  =========================================================
  <BLANKLINE>
  ZBoiler (http://zboiler.com) was used to generate the boiler plate
  code for this project.  To build your project, run these commands::
  <BLANKLINE>
    $ python bootstrap.py
    $ ./bin/buildout
  <BLANKLINE>
  What happens next depends on the features you used to generate your
  project.  Check out more information about each feature below.

The set pf all applied features is collected in the project, which now has a
special interface it directly provides:

  >>> from zope.interface.verify import verifyObject
  >>> from z3c.feature.core import interfaces

  >>> verifyObject(interfaces.IHaveAppliedFeatures, project)
  True

  >>> project.appliedFeatures
  []

Since our simple project does not have any features, none are lsited and the
text file was rather empty. Let's change that situation:

  >>> from z3c.feature.core.python import ScriptFeature

  >>> feature = ScriptFeature()
  >>> feature.applyTo(project)

Let's now render the project again:

  >>> project.update()
  >>> project.write(buildPath, True)

  >>> more(buildPath, 'helloworld', 'ZBOILER.txt')
  =========================================================
  Documentation About Project Features Generated by ZBoiler
  =========================================================
  <BLANKLINE>
  ZBoiler (http://zboiler.com) was used to generate the boiler plate
  code for this project.  To build your project, run these commands::
  <BLANKLINE>
    $ python bootstrap.py
    $ ./bin/buildout
  <BLANKLINE>
  What happens next depends on the features you used to generate your
  project.  Check out more information about each feature below.
  <BLANKLINE>
  <BLANKLINE>
  <BLANKLINE>
  Command Line Script
  -------------------
  <BLANKLINE>
  The Command Line Script Feature exposes a python function in your
  project as a command line script.  There are several pieces to this:
  <BLANKLINE>
    #. **The script file.**
  <BLANKLINE>
          There is a script file located at script.py with a function
          in it called main.  This is what you should modify to
          make your script actually do something.
  <BLANKLINE>
    #. **Setuptools entry point.**
  <BLANKLINE>
         When someone installs your project using setuptools, for example
         with the command::
  <BLANKLINE>
           $ easy_install yourproject
  <BLANKLINE>
         any entry points in the console_script group will turn into
         executable scripts that the end user can just run.  This make your
         project into an application and not just a set of python
         libraries.
  <BLANKLINE>
         The entry point is created by modifying the ``setup.py`` file.
         Look for the keyword parameter called entry_points.
  <BLANKLINE>
    #. ``scripts`` **buildout part.**
  <BLANKLINE>
         Finally there is also a part added to buildout.cfg that makes the
         script defined in the entry point available in the projects bin/
         directory.  This is only for development purposes.
