Source code for pyfcstm.render.env

"""
Jinja2 environment configuration for template rendering.

This module provides a single convenience function, :func:`create_env`, which
builds a :class:`jinja2.Environment` configured for pyfcstm template rendering.
It augments a new environment with helper filters and globals via
:func:`pyfcstm.utils.jinja2.add_settings_for_env`, and then injects state machine
constants into the global namespace for template usage.

The module exposes the following public component:

* :func:`create_env` - Create a configured Jinja2 environment with state globals

Example::

    >>> from pyfcstm.render.env import create_env
    >>> env = create_env()
    >>> template = env.from_string("Initial state: {{ INIT_STATE }}")
    >>> template.render()
    'Initial state: ...'

.. note::
   The ``INIT_STATE`` and ``EXIT_STATE`` globals are imported from
   :mod:`pyfcstm.dsl` and made available to Jinja2 templates.
"""

from typing import Iterable

import jinja2

from ..dsl import INIT_STATE, EXIT_STATE
from ..dsl import node as dsl_nodes
from ..model.model import OperationStatement
from .statement import (
    create_stmt_render_template, fn_stmt_render, fn_stmts_render,
    _KNOWN_STMT_STYLES, _normalize_stmt_style,
)
from ..utils import add_settings_for_env


def _render_operation_statement(node) -> str:
    """
    Render one operation statement to DSL text.

    :param node: AST or model operation statement to render.
    :type node: Any
    :return: DSL rendering of the statement.
    :rtype: str
    :raises TypeError: If ``node`` is not a supported operation statement.
    """
    if isinstance(node, OperationStatement):
        return str(node.to_ast_node())
    if isinstance(node, dsl_nodes.OperationalStatement):
        return str(node)

    raise TypeError(f'Unsupported operation statement type: {type(node)!r}')


def _render_operation_statements(nodes: Iterable, sep: str = '\n') -> str:
    """
    Render an iterable of operation statements to DSL text.

    :param nodes: Operation statements to render.
    :type nodes: Iterable
    :param sep: Separator inserted between rendered statements.
    :type sep: str
    :return: Joined DSL rendering.
    :rtype: str
    """
    return sep.join(_render_operation_statement(node) for node in nodes)


def _default_state_vars_from_env(env: jinja2.Environment):
    """
    Read the renderer-provided default state-variable names from the environment.

    :param env: Jinja2 environment.
    :type env: jinja2.Environment
    :return: Default state-variable names, or ``None`` when unavailable.
    :rtype: Any
    """
    return env.globals.get('_stmt_default_state_vars')


def _default_var_types_from_env(env: jinja2.Environment):
    """
    Read the renderer-provided default variable types from the environment.

    :param env: Jinja2 environment.
    :type env: jinja2.Environment
    :return: Default variable types, or ``None`` when unavailable.
    :rtype: Any
    """
    return env.globals.get('_stmt_default_var_types')


[docs] def create_env() -> jinja2.Environment: """ Create and configure a Jinja2 environment for template rendering. This function initializes a :class:`jinja2.Environment`, applies additional filters and globals via :func:`pyfcstm.utils.jinja2.add_settings_for_env`, and registers the ``INIT_STATE`` and ``EXIT_STATE`` values as Jinja2 globals. :return: A configured Jinja2 environment instance. :rtype: jinja2.Environment Example:: >>> env = create_env() >>> template = env.from_string("Initial state: {{ INIT_STATE }}") >>> rendered = template.render() """ env = jinja2.Environment() env = add_settings_for_env(env) env.globals['INIT_STATE'] = INIT_STATE env.globals['EXIT_STATE'] = EXIT_STATE env.globals['operation_stmt_render'] = _render_operation_statement env.filters['operation_stmt_render'] = _render_operation_statement env.globals['operation_stmts_render'] = _render_operation_statements env.filters['operation_stmts_render'] = _render_operation_statements stmt_templates = { style_name: create_stmt_render_template(style_name) for style_name in _KNOWN_STMT_STYLES.keys() } def _stmt_render(node, style: str = 'dsl', state_vars=None, var_types=None, visible_names=None, visible_var_types=None, indent: str = ' ', level: int = 0) -> str: style = _normalize_stmt_style(style) return fn_stmt_render( node=node, templates=stmt_templates[style], env=env, state_vars=_default_state_vars_from_env(env) if state_vars is None else state_vars, var_types=_default_var_types_from_env(env) if var_types is None else var_types, visible_names=visible_names, visible_var_types=visible_var_types, indent=indent, level=level, ) def _stmts_render(nodes, style: str = 'dsl', state_vars=None, var_types=None, visible_names=None, visible_var_types=None, indent: str = ' ', level: int = 0, sep: str = '\n') -> str: style = _normalize_stmt_style(style) return fn_stmts_render( nodes=nodes, templates=stmt_templates[style], env=env, state_vars=_default_state_vars_from_env(env) if state_vars is None else state_vars, var_types=_default_var_types_from_env(env) if var_types is None else var_types, visible_names=visible_names, visible_var_types=visible_var_types, indent=indent, level=level, sep=sep, ) env.globals['stmt_render'] = _stmt_render env.filters['stmt_render'] = _stmt_render env.globals['stmts_render'] = _stmts_render env.filters['stmts_render'] = _stmts_render return env