Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional attributes #172

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions hamlpy/attributes_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from pyparsing import *


class AttributesParser:
def __init__(self, string):
self.string = string

def parse(self):
def evalCode(input, position, tokens):
return eval(u''.join(tokens))

STRING = quotedString.copy().setParseAction(evalCode)

NUMBER = Combine(
Optional('-') + ('0' | Word('123456789', nums)) +
Optional('.' + Word(nums)) +
Optional(Word('eE', exact=1) + Word(nums + '+-', nums))
).setParseAction(evalCode)

NONE = Keyword('None').setParseAction(replaceWith(None))

DELIMITED_LIST = (quotedString ^ NUMBER) + ZeroOrMore(',' + (quotedString ^ NUMBER))
TUPLE = (Suppress('(') + DELIMITED_LIST + Suppress(')')).setParseAction(evalCode)
LIST = (Suppress('[') + DELIMITED_LIST + Suppress(']')).setParseAction(evalCode)

NAME = Word(alphanums + '_')
ATOM = Combine(NAME ^ quotedString ^ NUMBER ^ NONE)
TRAILER = '.' + NAME
FACTOR = Forward()
POWER = Combine(ATOM + ZeroOrMore(TRAILER)) + Optional(Keyword('**') + FACTOR)
FACTOR = ((Keyword('+') ^ Keyword('-') ^ Keyword('~')) + FACTOR) ^ POWER
TERM = FACTOR + ZeroOrMore((Keyword('*') ^ Keyword('/') ^ Keyword('%') ^ Keyword('//')) + FACTOR)
ARITH_EXPRESSION = TERM + ZeroOrMore((Keyword('+') ^ Keyword('-')) + TERM)
SHIFT_EXPRESSION = ARITH_EXPRESSION + ZeroOrMore((Keyword('<<') ^ Keyword('>>')) + ARITH_EXPRESSION)
AND_EXPRESSION = SHIFT_EXPRESSION + ZeroOrMore(Keyword('&') + SHIFT_EXPRESSION)
XOR_EXPRESSION = AND_EXPRESSION + ZeroOrMore(Keyword('^') + AND_EXPRESSION)
EXPRESSION = XOR_EXPRESSION + ZeroOrMore(Keyword('|') + XOR_EXPRESSION)
COMPARISON_OP = Keyword('<') ^ Keyword('>') ^ Keyword('==') ^ Keyword('>=') ^ Keyword('<=') ^ Keyword('<>') ^ Keyword('!=') ^ Keyword('in') ^ Keyword('not in') ^ Keyword('is') ^ Keyword('is not')
COMPARISON = Combine(EXPRESSION + ZeroOrMore(COMPARISON_OP + EXPRESSION), adjacent=False, joinString=' ')
NOT_TEST = Forward()
NOT_TEST << Combine((Keyword('not') + NOT_TEST) ^ COMPARISON, adjacent=False, joinString=' ')
AND_TEST = Combine(NOT_TEST + ZeroOrMore(Keyword('and') + NOT_TEST), adjacent=False, joinString=' ')
TEST = Combine(AND_TEST + ZeroOrMore(Keyword('or') + AND_TEST), adjacent=False, joinString=' ')

VALUE = STRING ^ NUMBER ^ TUPLE ^ LIST ^ NONE
ATTRIBUTE = Group(STRING + Suppress(':') + VALUE + Optional(Keyword('if').suppress() + TEST + Optional(Keyword('else').suppress() + VALUE)))
ATTRIBUTES = delimitedList(ATTRIBUTE)

parsed = {}

for group in ATTRIBUTES.parseString(self.string[1:-1]):
key = group[0]
value = group[1]
condition = group[2] if len(group) > 2 else None
alt_value = group[3] if len(group) > 3 else None

if key not in parsed:
parsed[key] = []
parsed[key].append((value, condition, alt_value))

return parsed
92 changes: 60 additions & 32 deletions hamlpy/elements.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import re
import sys
from types import NoneType
from attributes_parser import AttributesParser


def _conditional_string(value, condition, alt_value=None):
if alt_value is None:
return '{%% if %(condition)s %%}%(value)s{%% endif %%}' % {
'value': value,
'condition': condition
}

return '{%% if %(condition)s %%}%(value)s{%% else %%}%(alt_value)s{%% endif %%}' % {
'value': value,
'condition': condition,
'alt_value': alt_value,
}


class Element(object):
"""contains the pieces of an element and can populate itself from haml element text"""
Expand Down Expand Up @@ -33,7 +47,6 @@ class Element(object):
ATTRIBUTE_REGEX = re.compile(r'(?P<pre>\{\s*|,\s*)%s\s*:\s*%s' % (_ATTRIBUTE_KEY_REGEX, _ATTRIBUTE_VALUE_REGEX), re.UNICODE)
DJANGO_VARIABLE_REGEX = re.compile(r'^\s*=\s(?P<variable>[a-zA-Z_][a-zA-Z0-9._-]*)\s*$')


def __init__(self, haml, attr_wrapper="'"):
self.haml = haml
self.attr_wrapper = attr_wrapper
Expand Down Expand Up @@ -65,11 +78,11 @@ def _parse_haml(self):
self.inline_content = split_tags.get('inline').strip()

def _parse_class_from_attributes_dict(self):
clazz = self.attributes_dict.get('class', '')
if not isinstance(clazz, str):
clazz = ''
for one_class in self.attributes_dict.get('class'):
clazz += ' ' + one_class
clazz = ''
for classes in self.attributes_dict.get('class', []):
if isinstance(classes, basestring):
classes = [classes]
clazz += ' ' + u' '.join(classes)
return clazz.strip()

def _parse_id(self, id_haml):
Expand All @@ -82,7 +95,9 @@ def _parse_id(self, id_haml):
def _parse_id_dict(self, id_dict):
text = ''
id_dict = self.attributes_dict.get('id')
if isinstance(id_dict, str):
if isinstance(id_dict, list):
id_dict = id_dict[0]
if isinstance(id_dict, basestring):
text = '_' + id_dict
else:
text = ''
Expand Down Expand Up @@ -121,31 +136,44 @@ def _parse_attribute_dictionary(self, attribute_dict_string):
# Put double quotes around key
attribute_dict_string = re.sub(self.ATTRIBUTE_REGEX, '\g<pre>"\g<key>":\g<val>', attribute_dict_string)
# Parse string as dictionary
attributes_dict = eval(attribute_dict_string)
for k, v in attributes_dict.items():
if k != 'id' and k != 'class':
if isinstance(v, NoneType):
self.attributes += "%s " % (k,)
elif isinstance(v, int) or isinstance(v, float):
self.attributes += "%s=%s " % (k, self.attr_wrap(v))

attributes_dict = AttributesParser(attribute_dict_string).parse()
for attribute_name, values in attributes_dict.items():
if len(values) == 1 and attribute_name not in ('id', 'class'):
attributes_dict[attribute_name] = values[0]
value, condition, alt_value = values[0]

if value is None:
attribute = '%s ' % attribute_name
else:
# DEPRECATED: Replace variable in attributes (e.g. "= somevar") with Django version ("{{somevar}}")
v = re.sub(self.DJANGO_VARIABLE_REGEX, '{{\g<variable>}}', attributes_dict[k])
if v != attributes_dict[k]:
sys.stderr.write("\n---------------------\nDEPRECATION WARNING: %s" % self.haml.lstrip() + \
"\nThe Django attribute variable feature is deprecated and may be removed in future versions." +
"\nPlease use inline variables ={...} instead.\n-------------------\n")

attributes_dict[k] = v
v = v.decode('utf-8')
self.attributes += "%s=%s " % (k, self.attr_wrap(self._escape_attribute_quotes(v)))
if isinstance(value, basestring):
value = self._escape_attribute_quotes(value.decode('utf-8'))
attribute = '%s=%s ' % (attribute_name, self.attr_wrap(value))

if condition is None:
self.attributes += attribute
elif alt_value is None:
self.attributes += _conditional_string(attribute.strip(), condition.strip()) + ' '
else:
if isinstance(alt_value, basestring):
alt_value = self._escape_attribute_quotes(alt_value.decode('utf-8'))
alt_attribute = '%s=%s ' % (attribute_name, self.attr_wrap(alt_value))

self.attributes += _conditional_string(attribute.strip(), condition, alt_attribute.strip()) + ' '
else:
for key, (value, condition, alt_value) in enumerate(values):
if condition is None:
attributes_dict[attribute_name][key] = value
else:
attributes_dict[attribute_name][key] = _conditional_string(value, condition, alt_value)

if attribute_name not in ('id', 'class'):
attributes_dict[attribute_name] = u' '.join(attributes_dict[attribute_name])
self.attributes += '%s ' % attributes_dict[attribute_name]

self.attributes = self.attributes.strip()
except Exception, e:
except Exception:
raise
raise Exception('failed to decode: %s' % attribute_dict_string)
#raise Exception('failed to decode: %s. Details: %s'%(attribute_dict_string, e))

return attributes_dict




1 change: 0 additions & 1 deletion hamlpy/hamlpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
VALID_EXTENSIONS=['haml', 'hamlpy']

class Compiler:

def __init__(self, options_dict=None):
options_dict = options_dict or {}
self.debug_tree = options_dict.pop('debug_tree', False)
Expand Down
Loading