Skip to content

Commit dcbe916

Browse files
committed
add support for chained conditions
1 parent 054a59f commit dcbe916

File tree

3 files changed

+50
-27
lines changed

3 files changed

+50
-27
lines changed

Diff for: hamlpy/elements.py

+44-27
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ def __init__(self, test, body, orelse=NOTHING):
1313
self.body = body
1414
self.orelse = orelse
1515

16+
def __repr__(self):
17+
if self.orelse is self.NOTHING:
18+
attrs = [self.test, self.body]
19+
else:
20+
attrs = [self.test, self.body, self.orelse]
21+
return "<%s@0X%X %r>" % (self.__class__.__name__, id(self), attrs)
22+
1623
class Element(object):
1724
"""contains the pieces of an element and can populate itself from haml element text"""
1825

@@ -48,17 +55,17 @@ class Element(object):
4855
ATTRKEY_REGEX = re.compile(r"\s*(%s|%s)\s*:\s*" % (
4956
_SINGLE_QUOTE_STRING_LITERAL_REGEX, _DOUBLE_QUOTE_STRING_LITERAL_REGEX),
5057
re.UNICODE)
51-
_VALUE_LIST_REGEX = r"\[\s*(?:(?:%s|%s)\s*,?\s*)*\]" % (
58+
_VALUE_LIST_REGEX = r"\[\s*(?:(?:%s|%s|None(?!\w)|\d+)\s*,?\s*)*\]" % (
5259
_SINGLE_QUOTE_STRING_LITERAL_REGEX, _DOUBLE_QUOTE_STRING_LITERAL_REGEX)
53-
_VALUE_TUPLE_REGEX = r"\(\s*(?:(?:%s|%s)\s*,?\s*)*\)" % (
60+
_VALUE_TUPLE_REGEX = r"\(\s*(?:(?:%s|%s|None(?!\w)|\d+)\s*,?\s*)*\)" % (
5461
_SINGLE_QUOTE_STRING_LITERAL_REGEX, _DOUBLE_QUOTE_STRING_LITERAL_REGEX)
55-
ATTRVAL_REGEX = re.compile(r"\d+|None(?!\w)|%s|%s|%s|%s" % (
62+
ATTRVAL_REGEX = re.compile(r"None(?!\w)|%s|%s|%s|%s|\d+" % (
5663
_SINGLE_QUOTE_STRING_LITERAL_REGEX, _DOUBLE_QUOTE_STRING_LITERAL_REGEX,
5764
_VALUE_LIST_REGEX, _VALUE_TUPLE_REGEX), re.UNICODE)
5865

59-
CONDITION_REGEX = re.compile(r"(.|%s|%s)+?(?=,| else |$)" % (
60-
_SINGLE_QUOTE_STRING_LITERAL_REGEX, _DOUBLE_QUOTE_STRING_LITERAL_REGEX),
61-
re.UNICODE)
66+
CONDITION_REGEX = re.compile(r"(%s|%s|%s|%s|(?!,| else ).)+" % (
67+
_SINGLE_QUOTE_STRING_LITERAL_REGEX, _DOUBLE_QUOTE_STRING_LITERAL_REGEX,
68+
_VALUE_LIST_REGEX, _VALUE_TUPLE_REGEX), re.UNICODE)
6269

6370
NEWLINE_REGEX = re.compile("[\r\n]+")
6471

@@ -156,6 +163,12 @@ def _parse_attribute_dictionary(self, attribute_dict_string):
156163
self.attributes += "{%% %s %%} " % val.test
157164
value = "{%% %s %%}%s" % (val.test,
158165
self.add_attr(key, val.body))
166+
while isinstance(val.orelse, Conditional):
167+
val = val.orelse
168+
if key not in ("id", "class"):
169+
self.attributes += "{%% el%s %%} " % val.test
170+
value += "{%% el%s %%}%s" % (val.test,
171+
self.add_attr(key, val.body))
159172
if val.orelse is not val.NOTHING:
160173
if key not in ("id", "class"):
161174
self.attributes += "{% else %} "
@@ -182,31 +195,35 @@ def parse_attr(self, string):
182195
if not match:
183196
raise SyntaxError("Dictionary key expected at %r" % string)
184197
key = eval(match.group(1))
185-
string = string[match.end():]
186-
match = self.ATTRVAL_REGEX.match(string)
187-
if not match:
188-
raise SyntaxError("Dictionary value expected at %r" % string)
189-
val = eval(match.group(0))
190-
string = string[match.end():].lstrip()
191-
if string.startswith("if "):
192-
match = self.CONDITION_REGEX.match(string)
193-
# Note: cannot fail. At least the "if" word must match.
194-
condition = match.group(0)
195-
string = string[len(condition):].lstrip()
196-
if string.startswith("else "):
197-
string = string[5:].lstrip()
198-
match = self.ATTRVAL_REGEX.match(string)
199-
if not match:
200-
raise SyntaxError(
201-
"Missing \"else\" expression at %r" % string)
202-
val = Conditional(condition, val, eval(match.group(0)))
203-
string = string[match.end():].lstrip()
204-
else:
205-
val = Conditional(condition, val)
198+
(val, string) = self.parse_attribute_value(string[match.end():])
206199
if string.startswith(","):
207200
string = string[1:].lstrip()
208201
yield (key, val)
209202

203+
def parse_attribute_value(self, string):
204+
"""Parse an attribute value from dictionary string
205+
206+
Return a (value, tail) pair where tail is remainder of the string.
207+
208+
"""
209+
match = self.ATTRVAL_REGEX.match(string)
210+
if not match:
211+
raise SyntaxError("Dictionary value expected at %r" % string)
212+
val = eval(match.group(0))
213+
string = string[match.end():].lstrip()
214+
if string.startswith("if "):
215+
match = self.CONDITION_REGEX.match(string)
216+
# Note: cannot fail. At least the "if" word must match.
217+
condition = match.group(0)
218+
string = string[len(condition):].lstrip()
219+
if string.startswith("else "):
220+
(orelse, string) = self.parse_attribute_value(
221+
string[5:].lstrip())
222+
val = Conditional(condition, val, orelse)
223+
else:
224+
val = Conditional(condition, val)
225+
return (val, string)
226+
210227
def add_attr(self, key, value):
211228
"""Add attribute definition to self.attributes
212229

Diff for: hamlpy/test/hamlpy_test.py

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ def test_dictionaries_allow_conditionals(self):
6363
"<img {% if coming %} src='hello' {% endif %} />"),
6464
("%img{'src': 'hello' if coming else 'goodbye' }",
6565
"<img {% if coming %} src='hello' {% else %} src='goodbye' {% endif %} />"),
66+
("%item{'a': 'one' if b == 1 else 'two' if b == [1, 2] else None}",
67+
"<item {% if b == 1 %} a='one' {% elif b == [1, 2] %} a='two' {% else %} a {% endif %}></item>"),
6668
# For id and class attributes, conditions work on individual parts
6769
# of the value (more parts can be added from HAML tag).
6870
("%div{'id': 'No1' if tree is TheLarch, 'class': 'quite-a-long-way-away'}",

Diff for: reference.md

+4
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ The 'else' part may be omitted, for example:
135135

136136
%div{'id': 'No1' if tree is TheLarch}
137137

138+
The 'else' part also may contain conditional expression:
139+
140+
'score': '29.9' if name == 'St Stephan' else '29.3' if name == 'Richard III'
141+
138142
For the 'class' and 'id' attributes conditional expressions are processed in a different way: condition tags are placed inside the value rather than around the whole attribute. That is done so because these attributes may get additional value parts from [HAML syntax](#class-and-id--and-). The downside is that conditional expression cannot remove 'class' or 'id' attribute altogether, as it happens with common attributes. Example:
139143

140144
%div{'id': 'dog_kennel' if assisant.name == 'Mr Lambert' else 'mattress',

0 commit comments

Comments
 (0)