Code

Ticket #5172: numerical-foo-loop.diff

File numerical-foo-loop.diff, 5.6 KB (added by gnuvince, 7 years ago)
Line 
1Index: django/template/defaulttags.py
2===================================================================
3--- django/template/defaulttags.py      (revision 6413)
4+++ django/template/defaulttags.py      (working copy)
5@@ -69,6 +69,59 @@
6                 return smart_unicode(value)
7         return u''
8 
9+class NumForNode(Node):
10+    def __init__(self, loopvar, start, end, step, exclusive, nodelist_loop):
11+        self.loopvar = loopvar
12+        self.start = Variable(start)
13+        self.end = Variable(end)
14+        self.step = Variable(step)
15+        self.exclusive = exclusive
16+        self.nodelist_loop = nodelist_loop
17+
18+    def __repr__(self):
19+        if self.exclusive:
20+            exclusive = ' exclusive'
21+        else:
22+            exclusive = ''
23+
24+        if step != 1:
25+            step_str = ' by %s' % step
26+        else:
27+            step_str = ''
28+
29+        return '<For Node: for %s %s to %s%s%s>' % \
30+                (self.loopvar, self.start, self.end,
31+                 step_str, exclusive)
32+
33+    def __iter__(self):
34+        for node in self.nodelist_loop:
35+            yield node
36+
37+    def make_seq(self, context):
38+        start = int(self.start.resolve(context))
39+        end = int(self.end.resolve(context))
40+        step = int(self.step.resolve(context))
41+   
42+        if step < 0:
43+            end -= int(not self.exclusive)
44+        else:
45+            end += int(not self.exclusive)
46+
47+        return xrange(start, end, step)
48+
49+
50+    def render(self, context):
51+        nodelist = NodeList()
52+        context.push()
53+        seq = self.make_seq(context)
54+        for i in seq:
55+            context[self.loopvar] = i
56+            for node in self.nodelist_loop:
57+                nodelist.append(node.render(context))
58+        context.pop()
59+        return nodelist.render(context)
60+
61+
62 class ForNode(Node):
63     def __init__(self, loopvars, sequence, reversed, nodelist_loop):
64         self.loopvars, self.sequence = loopvars, sequence
65@@ -565,9 +618,13 @@
66 
67     """
68     bits = token.contents.split()
69+
70     if len(bits) < 4:
71         raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents
72 
73+    if bits[-3] != 'in' and bits[-2] != 'in':
74+        return do_numfor(parser, token)
75+
76     reversed = bits[-1] == 'reversed'
77     in_index = reversed and -3 or -2
78     if bits[in_index] != 'in':
79@@ -584,6 +641,35 @@
80     return ForNode(loopvars, sequence, reversed, nodelist_loop)
81 do_for = register.tag("for", do_for)
82 
83+def do_numfor(parser, token):
84+    bits = token.split_contents()
85+
86+    if len(bits) < 5:
87+        raise TemplateSyntaxError("num 'for' tag should have at least 5 arguments: %s" % token.contents)
88+    if bits[3] != 'to':
89+        raise TemplateSyntaxError("num 'for' tag should use the format 'for i 1 to 5': %s" % token.contents)
90+    try:
91+        start, end = bits[2], bits[4]
92+    except ValueError:
93+        raise TemplateSyntaxError("num 'for' tag should use the format 'for i 1 to 5': %s" % token.contents)
94+       
95+    step = '1'
96+    try:
97+        if bits[5] == 'by':
98+            try:
99+                step = bits[6]
100+            except IndexError:
101+                raise TemplateSyntaxError("step value required after 'by' keyword: %s" % token.contents)
102+    except IndexError:
103+        pass
104+    exclusive = bits[-1] == 'exclusive'
105+
106+    loopvar = bits[1]
107+
108+    nodelist_loop = parser.parse(('endfor',))
109+    parser.delete_first_token()
110+    return NumForNode(loopvar, start, end, step, exclusive, nodelist_loop)
111+
112 def do_ifequal(parser, token, negate):
113     bits = list(token.split_contents())
114     if len(bits) != 3:
115Index: tests/regressiontests/templates/tests.py
116===================================================================
117--- tests/regressiontests/templates/tests.py    (revision 6413)
118+++ tests/regressiontests/templates/tests.py    (working copy)
119@@ -365,6 +365,23 @@
120             'for-tag-unpack12': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2))}, ("one:1,carrot/two:2,/", "one:1,carrot/two:2,INVALID/")),
121             'for-tag-unpack13': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'cheese'))}, ("one:1,carrot/two:2,cheese/", "one:1,carrot/two:2,cheese/")),
122 
123+
124+            ### NUMERICAL FOR TAG #####################################################
125+            'numfor-tag01': ("{% for i 1 to 5 %}{{ i }}{% endfor %}", {}, "12345"),
126+            'numfor-tag02': ("{% for i 1 to 5 exclusive %}{{ i }}{% endfor %}", {}, u"1234"),
127+            'numfor-tag03': ("{% for i 1 to 5 by 2 %}{{ i }}{% endfor %}", {}, "135"),
128+            'numfor-tag04': ("{% for i 1 to 5 by 2 exclusive %}{{ i }}{% endfor %}", {}, "13"),
129+
130+            'numfor-tag05': ("{% for i 5 to 1 by -1 %}{{ i }}{% endfor %}", {}, "54321"),
131+            'numfor-tag06': ("{% for i 5 to 1 by -1 exclusive %}{{ i }}{% endfor %}", {}, "5432"),
132+            'numfor-tag07': ("{% for i 5 to 1 by -2 %}{{ i }}{% endfor %}", {}, "531"),
133+            'numfor-tag08': ("{% for i 5 to 1 by -2 exclusive %}{{ i }}{% endfor %}", {}, "53"),
134+
135+            'numfor-tag09': ("{% for i 1 to 5 by -1 %}{{ i }}{% endfor %}", {}, ""),
136+            'numfor-tag10': ("{% for i 5 to 1 by 1 %}{{ i }}{% endfor %}", {}, ""),
137+
138+            'numfor-tag11': ("{% for i x to y by z %}{{ i }}{% endfor %}", {'x': 1, 'y': 5, 'z': 1}, "12345"),
139+
140             ### IF TAG ################################################################
141             'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
142             'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),