"""
The Yaml Extension adds the :class:`YamlOutputHandler` to render
output in pure Yaml, as well as the :class:`YamlConfigHandler` that allows
applications to use Yaml configuration files as a drop-in replacement of
the default :class:`cement.ext.ext_configparser.ConfigParserConfigHandler`.

Requirements
------------

 * pyYaml (``pip install pyYaml``)


Configuration
-------------

This extension does not honor any application configuration settings.


Usage
_____

**myapp.conf**

.. code-block:: Yaml

    ---
    myapp:
        foo: bar

**myapp.py**

.. code-block:: python

    from cement.core.foundation import CementApp

    class MyApp(CementApp):
        class Meta:
            label = 'myapp'
            extensions = ['Yaml']
            config_handler = 'Yaml'

            # you probably don't want to do this.. but you can
            # output_handler = 'Yaml'

    with MyApp() as app:
        app.run()

        # create some data
        data = dict(foo=app.config.get('myapp', 'foo'))

        app.render(data)


In general, you likely would not set ``output_handler`` to ``Yaml``, but
rather another type of output handler that displays readable output to the
end-user (i.e. Mustache, Genshi, or Tabulate).  By default Cement
adds the ``-o`` command line option to allow the end user to override the
output handler.  For example: passing ``-o Yaml`` will override the default
output handler and set it to ``YamlOutputHandler``.

See ``CementApp.Meta.handler_override_options``.

.. code-block:: console

    $ python myapp.py -o Yaml
    {foo: bar}

"""

import yaml
from ..core import output
from ..utils.misc import minimal_logger
from ..ext.ext_configparser import ConfigParserConfigHandler

LOG = minimal_logger(__name__)


def suppress_output_before_run(app):
    """
    This is a ``post_argument_parsing`` hook that suppresses console output if
    the ``YamlOutputHandler`` is triggered via command line.

    :param app: The application object.

    """
    if not hasattr(app.pargs, 'output_handler_override'):
        return
    elif app.pargs.output_handler_override == 'yaml':
        app._suppress_output()


def unsuppress_output_before_render(app, data):
    """
    This is a ``pre_render`` that unsuppresses console output if
    the ``YamlOutputHandler`` is triggered via command line so that the Yaml
    is the only thing in the output.

    :param app: The application object.

    """
    if not hasattr(app.pargs, 'output_handler_override'):
        return
    elif app.pargs.output_handler_override == 'yaml':
        app._unsuppress_output()


def suppress_output_after_render(app, out_text):
    """
    This is a ``post_render`` hook that suppresses console output again after
    rendering, only if the ``YamlOutputHandler`` is triggered via command
    line.

    :param app: The application object.

    """
    if not hasattr(app.pargs, 'output_handler_override'):
        return
    elif app.pargs.output_handler_override == 'yaml':
        app._suppress_output()


class YamlOutputHandler(output.CementOutputHandler):

    """
    This class implements the :ref:`IOutput <cement.core.output>`
    interface.  It provides Yaml output from a data dictionary and uses
    `pyYaml <http://pyYaml.org/wiki/PyYamlDocumentation>`_ to dump it to
    STDOUT.  Please see the developer documentation on
    :ref:`Output Handling <dev_output_handling>`.

    This handler forces Cement to suppress console output until
    ``app.render`` is called (keeping the output pure Yaml).  If
    troubleshooting issues, you will need to pass the ``--debug`` option in
    order to unsuppress output and see what's happening.

    """

    class Meta:

        """Handler meta-data."""

        interface = output.IOutput
        label = 'yaml'

        #: Whether or not to include ``Yaml`` as an available to choice
        #: to override the ``output_handler`` via command line options.
        overridable = True

    def __init__(self, *args, **kw):
        super(YamlOutputHandler, self).__init__(*args, **kw)
        self.config = None

    def _setup(self, app_obj):
        self.app = app_obj

    def render(self, data_dict, template=None, **kw):
        """
        Take a data dictionary and render it as Yaml output.  Note that the
        template option is received here per the interface, however this
        handler just ignores it.  Additional keyword arguments passed to
        ``yaml.dump()``.

        :param data_dict: The data dictionary to render.
        :keyword template: Ignored in this output handler implementation.
        :returns: A Yaml encoded string.
        :rtype: ``str``

        """
        LOG.debug("rendering output as yaml via %s" % self.__module__)
        return yaml.dump(data_dict, **kw)


class YamlConfigHandler(ConfigParserConfigHandler):

    """
    This class implements the :ref:`IConfig <cement.core.config>`
    interface, and provides the same functionality of
    :ref:`ConfigParserConfigHandler <cement.ext.ext_configparser>`
    but with Yaml configuration files.  See
    `pyYaml <http://pyYaml.org/wiki/PyYamlDocumentation>`_ for more
    information on pyYaml.

    **Note** This extension has an external dependency on `pyYaml`.  You must
    include `pyYaml` in your application's dependencies as Cement explicitly
    does *not* include external dependencies for optional extensions.

    """
    class Meta:
        label = 'yaml'

    def __init__(self, *args, **kw):
        super(YamlConfigHandler, self).__init__(*args, **kw)

    def _parse_file(self, file_path):
        """
        Parse Yaml configuration file settings from file_path, overwriting
        existing config settings.  If the file does not exist, returns False.

        :param file_path: The file system path to the Yaml configuration file.
        :returns: boolean

        """
        self.merge(yaml.load(open(file_path)))

        # FIX ME: Should check that file was read properly, however if not it
        # will likely raise an exception anyhow.
        return True


def load(app):
    app.hook.register('post_argument_parsing', suppress_output_before_run)
    app.hook.register('pre_render', unsuppress_output_before_render)
    app.hook.register('post_render', suppress_output_after_render)
    app.handler.register(YamlOutputHandler)
    app.handler.register(YamlConfigHandler)
