Source code for curly.utils

# -*- coding: utf-8 -*-
"""A various utilities which are used by Curly."""


import re
import shlex
import textwrap

from curly import exceptions


[docs]def make_regexp(pattern): """Make regular expression from the given patterns. This is just a trivial wrapper for :py:func:`re.compile` which sets a list of default flags. :param str pattern: A pattern to compile into regular expression. :return: Compiled regular expression """ pattern = textwrap.dedent(pattern) pattern = re.compile(pattern, re.MULTILINE | re.VERBOSE | re.DOTALL) return pattern
[docs]def make_expression(text): """Make template expression from the tag in the pattern. Anatomy of the tag is rather simple: ``{% if something | went | through "named pipe" %}`` is a valid tag. ``if`` here is the function name you are going to apply. And this long line ``something | went | through "named pipe"`` is called expression. Yes, basic reference implementation considers whole expression as a name in the context, but in more advanced implementations, it is a DSL which is used for calculation of function arguments from the expression. This function uses shell lexing to split expression above into the list of words like ``["something", "|", "went", "through", "named pipe"]``. :param text: A text to make expression from :type text: str or None :return: The list of parsed expressions :rtype: list[str] """ text = text or "" text = text.strip() text = shlex.split(text) if not text: text = [""] return text
[docs]def resolve_variable(varname, context): """Resolve value named as varname from the context. In most trivial case, implementation of the method is like that: .. code-block:: python3 def resolve_variable(varname, context): return context[varname] but it works only for the most trivial cases. Even such simple template language as Curly is is required to support dot notation. So: .. code-block:: pycon >>> context = { ... "first_name": "Sergey", ... "last_name": "Arkhipov", ... "roles": { ... "admin": [ ... "view_user", ... "update_user", ... "delete_user" ... ] ... } ... } >>> resolve_variable("roles.admin.1", context) 'update_user' So it is possible to resolve nested structures and also - arrays. But sometimes you may have ambiguous situations. The rule of this function is to try to resolve literally before going deep into the nested structure. .. code-block:: pycon >>> context = { ... "first_name": "Sergey", ... "last_name": "Arkhipov", ... "roles": { ... "admin": [ ... "view_user", ... "update_user", ... "delete_user" ... ] ... }, ... "roles.admin.1": "haha!" ... } >>> resolve_variable("roles.admin.1", context) 'haha!' Also, dot notation supports not only items, but attributes also. :param str varname: Expression to resolve :param dict context: A dictionary with variables to resolve. :return: Resolved value :raises: :py:exc:`curly.exceptions.CurlyEvaluateNoKeyError`: if it is not possible to resolve ``varname`` within a ``context``. """ try: return get_item_or_attr(varname, context) except exceptions.CurlyEvaluateError: pass chunks = varname.split(".", 1) if len(chunks) == 1: raise exceptions.CurlyEvaluateNoKeyError(context, varname) current_name, rest_name = chunks new_context = resolve_variable(current_name, context) resolved = resolve_variable(rest_name, new_context) return resolved
[docs]def get_item_or_attr(varname, context): """Resolve literal varname in context for :py:func:`resolve_variable`. Supports resolving of items and attributes. Also, tries indexes if possible. :param str varname: Expression to resolve :param dict context: A dictionary with variables to resolve. :return: Resolved value :raises: :py:exc:`curly.exceptions.CurlyEvaluateNoKeyError`: if it is not possible to resolve ``varname`` within a ``context``. """ try: return context[varname] except Exception: try: return getattr(context, varname) except Exception as exc: pass if isinstance(varname, str) and varname.isdigit(): return get_item_or_attr(int(varname), context) raise exceptions.CurlyEvaluateNoKeyError(context, varname)