482e54cfce
Signed-off-by: Corentin Chary <corentincj@iksaif.net>
241 lines
7.1 KiB
Python
241 lines
7.1 KiB
Python
from django import template
|
|
|
|
__author__ = "SmileyChris"
|
|
|
|
#==============================================================================
|
|
# Calculation objects
|
|
#==============================================================================
|
|
|
|
class BaseCalc(object):
|
|
def __init__(self, var1, var2=None, negate=False):
|
|
self.var1 = var1
|
|
self.var2 = var2
|
|
self.negate = negate
|
|
|
|
def resolve(self, context):
|
|
try:
|
|
var1, var2 = self.resolve_vars(context)
|
|
outcome = self.calculate(var1, var2)
|
|
except:
|
|
outcome = False
|
|
if self.negate:
|
|
return not outcome
|
|
return outcome
|
|
|
|
def resolve_vars(self, context):
|
|
var2 = self.var2 and self.var2.resolve(context)
|
|
return self.var1.resolve(context), var2
|
|
|
|
def calculate(self, var1, var2):
|
|
raise NotImplementedError()
|
|
|
|
|
|
class Or(BaseCalc):
|
|
def calculate(self, var1, var2):
|
|
return var1 or var2
|
|
|
|
|
|
class And(BaseCalc):
|
|
def calculate(self, var1, var2):
|
|
return var1 and var2
|
|
|
|
|
|
class Equals(BaseCalc):
|
|
def calculate(self, var1, var2):
|
|
return var1 == var2
|
|
|
|
|
|
class Greater(BaseCalc):
|
|
def calculate(self, var1, var2):
|
|
return var1 > var2
|
|
|
|
|
|
class GreaterOrEqual(BaseCalc):
|
|
def calculate(self, var1, var2):
|
|
return var1 >= var2
|
|
|
|
|
|
class In(BaseCalc):
|
|
def calculate(self, var1, var2):
|
|
return var1 in var2
|
|
|
|
|
|
OPERATORS = {
|
|
'=': (Equals, True),
|
|
'==': (Equals, True),
|
|
'!=': (Equals, False),
|
|
'>': (Greater, True),
|
|
'>=': (GreaterOrEqual, True),
|
|
'<=': (Greater, False),
|
|
'<': (GreaterOrEqual, False),
|
|
'or': (Or, True),
|
|
'and': (And, True),
|
|
'in': (In, True),
|
|
}
|
|
BOOL_OPERATORS = ('or', 'and')
|
|
|
|
|
|
class IfParser(object):
|
|
error_class = ValueError
|
|
|
|
def __init__(self, tokens):
|
|
self.tokens = tokens
|
|
|
|
def _get_tokens(self):
|
|
return self._tokens
|
|
|
|
def _set_tokens(self, tokens):
|
|
self._tokens = tokens
|
|
self.len = len(tokens)
|
|
self.pos = 0
|
|
|
|
tokens = property(_get_tokens, _set_tokens)
|
|
|
|
def parse(self):
|
|
if self.at_end():
|
|
raise self.error_class('No variables provided.')
|
|
var1 = self.get_bool_var()
|
|
while not self.at_end():
|
|
op, negate = self.get_operator()
|
|
var2 = self.get_bool_var()
|
|
var1 = op(var1, var2, negate=negate)
|
|
return var1
|
|
|
|
def get_token(self, eof_message=None, lookahead=False):
|
|
negate = True
|
|
token = None
|
|
pos = self.pos
|
|
while token is None or token == 'not':
|
|
if pos >= self.len:
|
|
if eof_message is None:
|
|
raise self.error_class()
|
|
raise self.error_class(eof_message)
|
|
token = self.tokens[pos]
|
|
negate = not negate
|
|
pos += 1
|
|
if not lookahead:
|
|
self.pos = pos
|
|
return token, negate
|
|
|
|
def at_end(self):
|
|
return self.pos >= self.len
|
|
|
|
def create_var(self, value):
|
|
return TestVar(value)
|
|
|
|
def get_bool_var(self):
|
|
"""
|
|
Returns either a variable by itself or a non-boolean operation (such as
|
|
``x == 0`` or ``x < 0``).
|
|
|
|
This is needed to keep correct precedence for boolean operations (i.e.
|
|
``x or x == 0`` should be ``x or (x == 0)``, not ``(x or x) == 0``).
|
|
"""
|
|
var = self.get_var()
|
|
if not self.at_end():
|
|
op_token = self.get_token(lookahead=True)[0]
|
|
if isinstance(op_token, basestring) and (op_token not in
|
|
BOOL_OPERATORS):
|
|
op, negate = self.get_operator()
|
|
return op(var, self.get_var(), negate=negate)
|
|
return var
|
|
|
|
def get_var(self):
|
|
token, negate = self.get_token('Reached end of statement, still '
|
|
'expecting a variable.')
|
|
if isinstance(token, basestring) and token in OPERATORS:
|
|
raise self.error_class('Expected variable, got operator (%s).' %
|
|
token)
|
|
var = self.create_var(token)
|
|
if negate:
|
|
return Or(var, negate=True)
|
|
return var
|
|
|
|
def get_operator(self):
|
|
token, negate = self.get_token('Reached end of statement, still '
|
|
'expecting an operator.')
|
|
if not isinstance(token, basestring) or token not in OPERATORS:
|
|
raise self.error_class('%s is not a valid operator.' % token)
|
|
if self.at_end():
|
|
raise self.error_class('No variable provided after "%s".' % token)
|
|
op, true = OPERATORS[token]
|
|
if not true:
|
|
negate = not negate
|
|
return op, negate
|
|
|
|
|
|
#==============================================================================
|
|
# Actual templatetag code.
|
|
#==============================================================================
|
|
|
|
class TemplateIfParser(IfParser):
|
|
error_class = template.TemplateSyntaxError
|
|
|
|
def __init__(self, parser, *args, **kwargs):
|
|
self.template_parser = parser
|
|
return super(TemplateIfParser, self).__init__(*args, **kwargs)
|
|
|
|
def create_var(self, value):
|
|
return self.template_parser.compile_filter(value)
|
|
|
|
|
|
class SmartIfNode(template.Node):
|
|
def __init__(self, var, nodelist_true, nodelist_false=None):
|
|
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
|
|
self.var = var
|
|
|
|
def render(self, context):
|
|
if self.var.resolve(context):
|
|
return self.nodelist_true.render(context)
|
|
if self.nodelist_false:
|
|
return self.nodelist_false.render(context)
|
|
return ''
|
|
|
|
def __repr__(self):
|
|
return "<Smart If node>"
|
|
|
|
def __iter__(self):
|
|
for node in self.nodelist_true:
|
|
yield node
|
|
if self.nodelist_false:
|
|
for node in self.nodelist_false:
|
|
yield node
|
|
|
|
def get_nodes_by_type(self, nodetype):
|
|
nodes = []
|
|
if isinstance(self, nodetype):
|
|
nodes.append(self)
|
|
nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
|
|
if self.nodelist_false:
|
|
nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
|
|
return nodes
|
|
|
|
|
|
def smart_if(parser, token):
|
|
"""
|
|
A smarter {% if %} tag for django templates.
|
|
|
|
While retaining current Django functionality, it also handles equality,
|
|
greater than and less than operators. Some common case examples::
|
|
|
|
{% if articles|length >= 5 %}...{% endif %}
|
|
{% if "ifnotequal tag" != "beautiful" %}...{% endif %}
|
|
|
|
Arguments and operators _must_ have a space between them, so
|
|
``{% if 1>2 %}`` is not a valid smart if tag.
|
|
|
|
All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``),
|
|
``!=``, ``>``, ``>=``, ``<`` and ``<=``.
|
|
"""
|
|
bits = token.split_contents()[1:]
|
|
var = TemplateIfParser(parser, bits).parse()
|
|
nodelist_true = parser.parse(('else', 'endif'))
|
|
token = parser.next_token()
|
|
if token.contents == 'else':
|
|
nodelist_false = parser.parse(('endif',))
|
|
parser.delete_first_token()
|
|
else:
|
|
nodelist_false = None
|
|
return SmartIfNode(var, nodelist_true, nodelist_false)
|
|
|