# coding: utf-8
"""
jinja2schema.model
~~~~~~~~~~~~~~~~~~
"""
import pprint
from jinja2 import nodes
from . import _compat
[docs]class Variable(object):
"""A base variable class.
.. attribute:: linenos
An ordered list of line numbers on which the variable occurs.
.. attribute:: label
A name of the variable in template.
.. attribute:: constant
Is true if the variable is defined using a ``{% set %}`` tag before it
occurs within any other statement in the template, or inferred from
a constant Jinja2 node.
.. attribute:: may_be_defined
Is true if the variable would be defined (using a ``{% set %}`` expression) if it's missing
from the template context. For example, ``x`` is ``may_be_defined`` in the following template::
{% if x is undefined %} {% set x = 1 %} {% endif %}
.. attribute:: used_with_default
Is true if the variable occurs only within the ``default`` filter.
.. attribute:: checked_as_undefined
Is true if the variable occurs within ``{% if %}`` block which condition checks
if the variable is undefined.
.. attribute:: checked_as_defined
Is true if the variable occurs within ``{% if %}`` block which condition checks
if the variable is defined.
.. attribute:: value
Value of the variable in template. Set by default filter or assignment.
"""
def __init__(self, label=None, linenos=None, constant=False,
may_be_defined=False, used_with_default=False,
checked_as_undefined=False, checked_as_defined=False,
value=None, order_nr=None):
self.label = label
self.linenos = linenos if linenos is not None else []
self.constant = constant
self.may_be_defined = may_be_defined
self.used_with_default = used_with_default
self.checked_as_undefined = checked_as_undefined
self.checked_as_defined = checked_as_defined
self.value = value
self.order_nr = order_nr
def clone(self):
cls = type(self)
return cls(**self.__dict__)
@classmethod
def _get_kwargs_from_ast(cls, ast):
return {
'linenos': [ast.lineno],
'label': ast.name if isinstance(ast, nodes.Name) else None,
'value': ast.value if hasattr(ast, 'value') else None,
}
@classmethod
[docs] def from_ast(cls, ast, **kwargs):
"""Constructs a variable using information from ``ast`` (such as label and line numbers).
:param ast: AST node
:type ast: :class:`jinja2.nodes.Node`
"""
for k, v in list(kwargs.items()):
if v is None:
del kwargs[k]
kwargs = dict(cls._get_kwargs_from_ast(ast), **kwargs)
return cls(**kwargs)
@property
def required(self):
return not any([self.may_be_defined, self.used_with_default,
self.checked_as_defined, self.checked_as_undefined])
def __eq__(self, other):
return (
type(self) is type(other) and
self.linenos == other.linenos and
self.label == other.label and
self.constant == other.constant and
self.used_with_default == other.used_with_default and
self.checked_as_undefined == other.checked_as_undefined and
self.checked_as_defined == other.checked_as_defined and
self.required == other.required and
self.value == other.value
)
def __ne__(self, other):
return not self.__eq__(other)
[docs]class Dictionary(Variable):
"""A dictionary.
Implements some methods of Python :class:`dict`.
.. automethod:: __setitem__
.. automethod:: __getitem__
.. automethod:: __delitem__
.. automethod:: get
.. automethod:: items
.. automethod:: iteritems
.. automethod:: keys
.. automethod:: iterkeys
.. automethod:: pop
"""
def __init__(self, data=None, **kwargs):
self.data = data or {}
super(Dictionary, self).__init__(**kwargs)
def __eq__(self, other):
return super(Dictionary, self).__eq__(other) and self.data == other.data
def __repr__(self):
return pprint.pformat(self.data)
def clone(self):
rv = super(Dictionary, self).clone()
rv.data = {}
for k, v in _compat.iteritems(self.data):
rv.data[k] = v.clone()
return rv
@classmethod
def from_ast(cls, ast, data=None, **kwargs):
kwargs = dict(cls._get_kwargs_from_ast(ast), **kwargs)
return cls(data, **kwargs)
[docs] def __setitem__(self, key, value):
self.data[key] = value
[docs] def __getitem__(self, key):
return self.data[key]
[docs] def __delitem__(self, key):
del self.data[key]
def __contains__(self, key):
return key in self.data
[docs] def get(self, name, default=None):
if name in self:
return self[name]
else:
return default
[docs] def items(self):
return self.data.items()
[docs] def iteritems(self):
return _compat.iteritems(self.data)
[docs] def keys(self):
return self.data.keys()
[docs] def iterkeys(self):
return _compat.iterkeys(self.data)
[docs] def pop(self, key, default=None):
return self.data.pop(key, default)
[docs]class List(Variable):
"""A list which items are of the same type.
.. attribute:: item
A structure of list items, subclass of :class:`Variable`.
"""
def __init__(self, item, **kwargs):
self.item = item
super(List, self).__init__(**kwargs)
def __eq__(self, other):
return super(List, self).__eq__(other) and self.item == other.item
def __repr__(self):
return pprint.pformat([self.item])
def clone(self):
rv = super(List, self).clone()
rv.item = self.item.clone()
return rv
@classmethod
def from_ast(cls, ast, item, **kwargs):
kwargs = dict(cls._get_kwargs_from_ast(ast), **kwargs)
return cls(item, **kwargs)
[docs]class Tuple(Variable):
"""A tuple.
.. attribute:: items
A :class:`tuple` of :class:`Variable` instances or ``None`` if the tuple items are unknown.
.. attribute:: items
Whether new elements can be added to the tuple in the process of merge or not.
"""
def __init__(self, items, **kwargs):
self.items = tuple(items) if items is not None else None
self.may_be_extended = kwargs.pop('may_be_extended', False)
super(Tuple, self).__init__(**kwargs)
def __eq__(self, other):
return super(Tuple, self).__eq__(other) and self.items == other.items
def __repr__(self):
return pprint.pformat(self.items)
def clone(self):
rv = super(Tuple, self).clone()
rv.items = self.items and tuple(s.clone() for s in self.items)
return rv
@classmethod
def from_ast(cls, ast, items, **kwargs):
kwargs = dict(cls._get_kwargs_from_ast(ast), **kwargs)
return cls(items, **kwargs)
[docs]class Scalar(Variable):
"""A scalar. Either string, number, boolean or ``None``."""
def __repr__(self):
return '<scalar>'
[docs]class String(Scalar):
"""A string."""
def __repr__(self):
return '<string>'
[docs]class Number(Scalar):
"""A number."""
def __repr__(self):
return '<number>'
[docs]class Boolean(Scalar):
"""A boolean."""
def __repr__(self):
return '<boolean>'
[docs]class Unknown(Variable):
"""A variable which type is unknown."""
def __repr__(self):
return '<unknown>'