# -*- coding: utf-8 -*-
"""
    jinja.environment
    ~~~~~~~~~~~~~~~~~

    Provides a class that holds runtime and parsing time options.

    :copyright: 2007 by Armin Ronacher.
    :license: BSD, see LICENSE for more details.
"""
from jinja.lexer import Lexer
from jinja.parser import Parser
from jinja.loaders import LoaderWrapper
from jinja.datastructure import SilentUndefined, Markup, Context, FakeTranslator
from jinja.utils import collect_translations, get_attribute
from jinja.exceptions import FilterNotFound, TestNotFound, \
     SecurityException, TemplateSyntaxError
from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE


__all__ = ['Environment']


#: minor speedup
_getattr = getattr


class Environment(object):
    """
    The Jinja environment.

    The core component of Jinja is the `Environment`. It contains
    important shared variables like configuration, filters, tests,
    globals and others.
    """

    def __init__(self,
                 block_start_string='{%',
                 block_end_string='%}',
                 variable_start_string='{{',
                 variable_end_string='}}',
                 comment_start_string='{#',
                 comment_end_string='#}',
                 trim_blocks=False,
                 auto_escape=False,
                 default_filters=None,
                 template_charset='utf-8',
                 charset='utf-8',
                 namespace=None,
                 loader=None,
                 filters=None,
                 tests=None,
                 context_class=Context,
                 undefined_singleton=SilentUndefined,
                 disable_regexps=False,
                 friendly_traceback=True,
                 translator_factory=None):
        """
        Here the possible initialization parameters:

        ========================= ============================================
        `block_start_string` *    the string marking the begin of a block.
                                  this defaults to ``'{%'``.
        `block_end_string` *      the string marking the end of a block.
                                  defaults to ``'%}'``.
        `variable_start_string` * the string marking the begin of a print
                                  statement. defaults to ``'{{'``.
        `comment_start_string` *  the string marking the begin of a
                                  comment. defaults to ``'{#'``.
        `comment_end_string` *    the string marking the end of a comment.
                                  defaults to ``'#}'``.
        `trim_blocks` *           If this is set to ``True`` the first newline
                                  after a block is removed (block, not
                                  variable tag!). Defaults to ``False``.
        `auto_escape`             If this is set to ``True`` Jinja will
                                  automatically escape all variables using xml
                                  escaping methods. If you don't want to
                                  escape a string you have to wrap it in a
                                  ``Markup`` object from the
                                  ``jinja.datastructure`` module. If
                                  `auto_escape` is ``True`` there will be also
                                  a ``Markup`` object in the template
                                  namespace to define partial html fragments.
                                  Note that we do not recommend this feature.
        `default_filters`         list of tuples in the form (``filter_name``,
                                  ``arguments``) where ``filter_name`` is the
                                  name of a registered filter and
                                  ``arguments`` a tuple with the filter
                                  arguments. The filters specified here will
                                  always be applied when printing data to the
                                  template. *new in Jinja 1.1*
        `template_charset`        The charset of the templates. Defaults
                                  to ``'utf-8'``.
        `charset`                 Charset of all string input data. Defaults
                                  to ``'utf-8'``.
        `namespace`               Global namespace for all templates.
        `loader`                  Specify a template loader.
        `filters`                 dict of filters or the default filters if
                                  not defined.
        `tests`                   dict of tests of the default tests if not
                                  defined.
        `context_class`           the context class this template should use.
                                  See the `Context` documentation for more
                                  details.
        `undefined_singleton`     The singleton value that is used for missing
                                  variables. *new in Jinja 1.1*
        `disable_regexps`         Disable support for regular expresssions.
        `friendly_traceback`      Set this to `False` to disable the developer
                                  friendly traceback rewriting. Whenever an
                                  runtime or syntax error occours jinja will
                                  try to make a developer friendly traceback
                                  that shows the error in the template line.
                                  This however can be annoying when debugging
                                  broken functions that are called from the
                                  template. *new in Jinja 1.1*
        `translator_factory`      A callback function that is called with
                                  the context as first argument to get the
                                  translator for the current instance.
                                  *new in Jinja 1.2*
        ========================= ============================================

        All of these variables except those marked with a star (*) are
        modifiable after environment initialization.
        """

        # lexer / parser information
        self.block_start_string = block_start_string
        self.block_end_string = block_end_string
        self.variable_start_string = variable_start_string
        self.variable_end_string = variable_end_string
        self.comment_start_string = comment_start_string
        self.comment_end_string = comment_end_string
        self.trim_blocks = trim_blocks

        # other stuff
        self.template_charset = template_charset
        self.charset = charset
        self.loader = loader
        if filters is None:
            filters = DEFAULT_FILTERS.copy()
        self.filters = filters
        if tests is None:
            tests = DEFAULT_TESTS.copy()
        self.tests = tests
        self.default_filters = default_filters or []
        self.context_class = context_class
        self.undefined_singleton = undefined_singleton
        self.disable_regexps = disable_regexps
        self.friendly_traceback = friendly_traceback

        # global namespace
        if namespace is None:
            namespace = DEFAULT_NAMESPACE.copy()
        self.globals = namespace

        # jinja 1.0 compatibility
        if auto_escape:
            self.default_filters.append(('escape', (True,)))
            self.globals['Markup'] = Markup

        # and here the translator factory
        self.translator_factory = translator_factory

        # create lexer
        self.lexer = Lexer(self)

    def loader(self, value):
        """
        Get or set the template loader.
        """
        self._loader = LoaderWrapper(self, value)
    loader = property(lambda s: s._loader, loader, doc=loader.__doc__)

    def parse(self, source, filename=None):
        """
        Parse the sourcecode and return the abstract syntax tree. This tree
        of nodes is used by the `translators`_ to convert the template into
        executable source- or bytecode.

        .. _translators: translators.txt
        """
        parser = Parser(self, source, filename)
        return parser.parse()

    def lex(self, source, filename=None):
        """
        Lex the given sourcecode and return a generator that yields tokens.
        The stream returned is not usable for Jinja but can be used if
        Jinja templates should be processed by other tools (for example
        syntax highlighting etc)

        The tuples are returned in the form ``(lineno, token, value)``.
        """
        return self.lexer.tokeniter(source, filename)

    def from_string(self, source):
        """
        Load and parse a template source and translate it into eval-able
        Python code. This code is wrapped within a `Template` class that
        allows you to render it.
        """
        from jinja.translators.python import PythonTranslator
        try:
            rv = PythonTranslator.process(self, Parser(self, source).parse(),
                                          source)
        except TemplateSyntaxError, e:
            # on syntax errors rewrite the traceback if wanted
            if not self.friendly_traceback:
                raise
            from jinja.debugger import raise_syntax_error
            if __debug__:
                __traceback_hide__ = True
            raise_syntax_error(e, self, source)
        else:
            return rv

    def get_template(self, filename):
        """
        Load a template from a loader. If the template does not exist, you
        will get a `TemplateNotFound` exception.
        """
        return self._loader.load(filename)

    def to_unicode(self, value):
        """
        Convert a value to unicode with the rules defined on the environment.
        """
        # undefined and None expand to ""
        if value in (None, self.undefined_singleton):
            return u''
        # things that are already unicode can pass. As long as nobody
        # does ugly things with the class it works for jinja too
        elif isinstance(value, unicode):
            return value
        # otherwise try to use __unicode__ or decode __str__
        try:
            return unicode(value)
        except UnicodeError:
            return str(value).decode(self.charset, 'ignore')

    def get_translator(self, context):
        """
        Return the translator for i18n.

        A translator is an object that provides the two functions
        ``gettext(string)`` and ``ngettext(singular, plural, n)``. Note
        that both of them have to return unicode!
        """
        if self.translator_factory is not None:
            return self.translator_factory(context)
        return FakeTranslator()

    def get_translations(self, name):
        """
        Load template `name` and return all translatable strings (note that
        that it really just returns the strings form this template, not from
        the parent or any included templates!)
        """
        return collect_translations(self.loader.parse(name))

    def get_translations_for_string(self, string):
        """
        Like `get_translations`, but the translations are loaded from a
        normal string that represents the template.
        """
        return collect_translations(self.parse(string))

    def apply_filters(self, value, context, filters):
        """
        Apply a list of filters on the variable.
        """
        # some traceback systems allow to skip frames. but allow
        # disabling that via -O to not make things slow
        if __debug__:
            __traceback_hide__ = True

        cache = context.cache
        for key in filters:
            if key in cache:
                func = cache[key]
            else:
                filtername, args = key
                if filtername not in self.filters:
                    raise FilterNotFound(filtername)
                cache[key] = func = self.filters[filtername](*args)
            value = func(self, context, value)
        return value

    def perform_test(self, context, testname, args, value):
        """
        Perform a test on a variable.
        """
        # some traceback systems allow to skip frames. but allow
        # disabling that via -O to not make things slow
        if __debug__:
            __traceback_hide__ = True

        key = (testname, args)
        if key in context.cache:
            func = context.cache[key]
        else:
            if testname not in self.tests:
                raise TestNotFound(testname)
            context.cache[key] = func = self.tests[testname](*args)
        return not not func(self, context, value)

    def get_attribute(self, obj, name):
        """
        Get one attribute from an object.
        """
        # some traceback systems allow to skip frames. but allow
        # disabling that via -O to not make things slow
        if __debug__:
            __traceback_hide__ = True

        try:
            return obj[name]
        except (TypeError, KeyError, IndexError, AttributeError):
            try:
                return get_attribute(obj, name)
            except (AttributeError, SecurityException):
                pass
        if obj is self.undefined_singleton:
            return _getattr(obj, name)
        return self.undefined_singleton

    def get_attributes(self, obj, attributes):
        """
        Get some attributes from an object. If attributes is an
        empty sequence the object is returned as it.
        """
        get = self.get_attribute
        for name in attributes:
            obj = get(obj, name)
        return obj

    def call_function(self, f, context, args, kwargs, dyn_args, dyn_kwargs):
        """
        Function call helper. Called for all functions that are passed
        any arguments.
        """
        # some traceback systems allow to skip frames. but allow
        # disabling that via -O to not make things slow
        if __debug__:
            __traceback_hide__ = True

        if dyn_args is not None:
            args += tuple(dyn_args)
        if dyn_kwargs is not None:
            kwargs.update(dyn_kwargs)
        if _getattr(f, 'jinja_unsafe_call', False) or \
           _getattr(f, 'alters_data', False):
            return self.undefined_singleton
        if _getattr(f, 'jinja_context_callable', False):
            args = (self, context) + args
        return f(*args, **kwargs)

    def call_function_simple(self, f, context):
        """
        Function call without arguments. Because of the smaller signature and
        fewer logic here we have a bit of redundant code.
        """
        # some traceback systems allow to skip frames. but allow
        # disabling that via -O to not make things slow
        if __debug__:
            __traceback_hide__ = True

        if _getattr(f, 'jinja_unsafe_call', False) or \
           _getattr(f, 'alters_data', False):
            return self.undefined_singleton
        if _getattr(f, 'jinja_context_callable', False):
            return f(self, context)
        return f()

    def finish_var(self, value, ctx):
        """
        As long as no write_var function is passed to the template
        evaluator the source generated by the python translator will
        call this function for all variables.
        """
        # some traceback systems allow to skip frames. but allow
        # disabling that via -O to not make things slow
        if __debug__:
            __traceback_hide__ = True

        if value is None:
            return u''
        elif value is self.undefined_singleton:
            return unicode(value)
        elif _getattr(value, 'jinja_no_finalization', False):
            return value
        val = self.to_unicode(value)
        if self.default_filters:
            val = self.apply_filters(val, ctx, self.default_filters)
        return val
