1
2
3
4 """
5 This file is part of the web2py Web Framework (Copyrighted, 2007-2011).
6 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
7
8 Author: Thadeus Burgess
9
10 Contributors:
11
12 - Thank you to Massimo Di Pierro for creating the original gluon/template.py
13 - Thank you to Jonathan Lundell for extensively testing the regex on Jython.
14 - Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py.
15 """
16
17 import os
18 import re
19 import cStringIO
20 import restricted
21
22
24 """
25 Basic Container Object
26 """
27 - def __init__(self, value = None, pre_extend = False):
28 self.value = value
29 self.pre_extend = pre_extend
30
32 return str(self.value)
33
35 - def __init__(self, name = '', pre_extend = False):
36 self.name = name
37 self.value = None
38 self.pre_extend = pre_extend
39
41 if self.value:
42 return str(self.value)
43 else:
44 raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + \
45 "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." )
46
48 return "%s->%s" % (self.name, self.value)
49
51 """
52 Block Container.
53
54 This Node can contain other Nodes and will render in a hierarchical order
55 of when nodes were added.
56
57 ie::
58
59 {{ block test }}
60 This is default block test
61 {{ end }}
62 """
63 - def __init__(self, name = '', pre_extend = False, delimiters = ('{{','}}')):
64 """
65 name - Name of this Node.
66 """
67 self.nodes = []
68 self.name = name
69 self.pre_extend = pre_extend
70 self.left, self.right = delimiters
71
73 lines = ['%sblock %s%s' % (self.left,self.name,self.right)]
74 for node in self.nodes:
75 lines.append(str(node))
76 lines.append('%send%s' % (self.left, self.right))
77 return ''.join(lines)
78
80 """
81 Get this BlockNodes content, not including child Nodes
82 """
83 lines = []
84 for node in self.nodes:
85 if not isinstance(node, BlockNode):
86 lines.append(str(node))
87 return ''.join(lines)
88
90 """
91 Add an element to the nodes.
92
93 Keyword Arguments
94
95 - node -- Node object or string to append.
96 """
97 if isinstance(node, str) or isinstance(node, Node):
98 self.nodes.append(node)
99 else:
100 raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node)
101
103 """
104 Extend the list of nodes with another BlockNode class.
105
106 Keyword Arguments
107
108 - other -- BlockNode or Content object to extend from.
109 """
110 if isinstance(other, BlockNode):
111 self.nodes.extend(other.nodes)
112 else:
113 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
114
116 """
117 Merges all nodes into a single string.
118
119 blocks -- Dictionary of blocks that are extending
120 from this template.
121 """
122 lines = []
123
124 for node in self.nodes:
125
126 if isinstance(node, BlockNode):
127
128 if node.name in blocks:
129
130 lines.append(blocks[node.name].output(blocks))
131
132 else:
133 lines.append(node.output(blocks))
134
135 else:
136 lines.append(str(node))
137
138 return ''.join(lines)
139
140 -class Content(BlockNode):
141 """
142 Parent Container -- Used as the root level BlockNode.
143
144 Contains functions that operate as such.
145 """
146 - def __init__(self, name = "ContentBlock", pre_extend = False):
147 """
148 Keyword Arguments
149
150 name -- Unique name for this BlockNode
151 """
152 self.name = name
153 self.nodes = []
154 self.blocks = {}
155 self.pre_extend = pre_extend
156
158 lines = []
159
160 for node in self.nodes:
161
162 if isinstance(node, BlockNode):
163
164 if node.name in self.blocks:
165
166 lines.append(self.blocks[node.name].output(self.blocks))
167 else:
168
169 lines.append(node.output(self.blocks))
170 else:
171
172 lines.append(str(node))
173
174 return ''.join(lines)
175
176 - def _insert(self, other, index = 0):
177 """
178 Inserts object at index.
179 """
180 if isinstance(other, str) or isinstance(other, Node):
181 self.nodes.insert(index, other)
182 else:
183 raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.")
184
185 - def insert(self, other, index = 0):
186 """
187 Inserts object at index.
188
189 You may pass a list of objects and have them inserted.
190 """
191 if isinstance(other, (list, tuple)):
192
193 other.reverse()
194 for item in other:
195 self._insert(item, index)
196 else:
197 self._insert(other, index)
198
199 - def append(self, node):
200 """
201 Adds a node to list. If it is a BlockNode then we assign a block for it.
202 """
203 if isinstance(node, str) or isinstance(node, Node):
204 self.nodes.append(node)
205 if isinstance(node, BlockNode):
206 self.blocks[node.name] = node
207 else:
208 raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node)
209
210 - def extend(self, other):
211 """
212 Extends the objects list of nodes with another objects nodes
213 """
214 if isinstance(other, BlockNode):
215 self.nodes.extend(other.nodes)
216 self.blocks.update(other.blocks)
217 else:
218 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
219
220 - def clear_content(self):
222
224
225 r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL)
226
227 r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL)
228
229
230
231 re_block = re.compile('^(elif |else:|except:|except |finally:).*$',
232 re.DOTALL)
233
234 re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL)
235
236 re_pass = re.compile('^pass( .*)?$', re.DOTALL)
237
238 - def __init__(self, text,
239 name = "ParserContainer",
240 context = dict(),
241 path = 'views/',
242 writer = 'response.write',
243 lexers = {},
244 delimiters = ('{{','}}'),
245 _super_nodes = [],
246 ):
247 """
248 text -- text to parse
249 context -- context to parse in
250 path -- folder path to templates
251 writer -- string of writer class to use
252 lexers -- dict of custom lexers to use.
253 delimiters -- for example ('{{','}}')
254 _super_nodes -- a list of nodes to check for inclusion
255 this should only be set by "self.extend"
256 It contains a list of SuperNodes from a child
257 template that need to be handled.
258 """
259
260
261 self.name = name
262
263 self.text = text
264
265
266
267 self.writer = writer
268
269
270 if isinstance(lexers, dict):
271 self.lexers = lexers
272 else:
273 self.lexers = {}
274
275
276 self.path = path
277
278 self.context = context
279
280
281 self.delimiters = delimiters
282 if delimiters!=('{{','}}'):
283 escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1]))
284 self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL)
285
286
287
288 self.content = Content(name=name)
289
290
291
292
293
294 self.stack = [self.content]
295
296
297
298 self.super_nodes = []
299
300
301
302 self.child_super_nodes = _super_nodes
303
304
305
306 self.blocks = {}
307
308
309 self.parse(text)
310
312 """
313 Return the parsed template with correct indentation.
314
315 Used to make it easier to port to python3.
316 """
317 return self.reindent(str(self.content))
318
320 "Make sure str works exactly the same as python 3"
321 return self.to_string()
322
324 "Make sure str works exactly the same as python 3"
325 return self.to_string()
326
328 """
329 Reindents a string of unindented python code.
330 """
331
332
333 lines = text.split('\n')
334
335
336 new_lines = []
337
338
339
340
341 credit = 0
342
343
344 k = 0
345
346
347
348
349
350
351
352
353
354 for raw_line in lines:
355 line = raw_line.strip()
356
357
358 if not line:
359 continue
360
361
362
363
364 if TemplateParser.re_block.match(line):
365 k = k + credit - 1
366
367
368 k = max(k,0)
369
370
371 new_lines.append(' '*(4*k)+line)
372
373
374 credit = 0
375
376
377 if TemplateParser.re_pass.match(line):
378 k -= 1
379
380
381
382
383
384
385 if TemplateParser.re_unblock.match(line):
386 credit = 1
387 k -= 1
388
389
390
391 if line.endswith(':') and not line.startswith('#'):
392 k += 1
393
394
395
396 new_text = '\n'.join(new_lines)
397
398 if k > 0:
399 self._raise_error('missing "pass" in view', new_text)
400 elif k < 0:
401 self._raise_error('too many "pass" in view', new_text)
402
403 return new_text
404
410
411 - def _get_file_text(self, filename):
412 """
413 Attempt to open ``filename`` and retrieve its text.
414
415 This will use self.path to search for the file.
416 """
417
418
419 if not filename.strip():
420 self._raise_error('Invalid template filename')
421
422
423
424 filename = eval(filename, self.context)
425
426
427 filepath = os.path.join(self.path, filename)
428
429
430 try:
431 fileobj = open(filepath, 'rb')
432
433 text = fileobj.read()
434
435 fileobj.close()
436 except IOError:
437 self._raise_error('Unable to open included view file: ' + filepath)
438
439 return text
440
441 - def include(self, content, filename):
442 """
443 Include ``filename`` here.
444 """
445 text = self._get_file_text(filename)
446
447 t = TemplateParser(text,
448 name = filename,
449 context = self.context,
450 path = self.path,
451 writer = self.writer,
452 delimiters = self.delimiters)
453
454 content.append(t.content)
455
457 """
458 Extend ``filename``. Anything not declared in a block defined by the
459 parent will be placed in the parent templates ``{{include}}`` block.
460 """
461 text = self._get_file_text(filename)
462
463
464 super_nodes = []
465
466 super_nodes.extend(self.child_super_nodes)
467
468 super_nodes.extend(self.super_nodes)
469
470 t = TemplateParser(text,
471 name = filename,
472 context = self.context,
473 path = self.path,
474 writer = self.writer,
475 delimiters = self.delimiters,
476 _super_nodes = super_nodes)
477
478
479
480 buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters)
481 pre = []
482
483
484 for node in self.content.nodes:
485
486 if isinstance(node, BlockNode):
487
488 if node.name in t.content.blocks:
489
490 continue
491
492 if isinstance(node, Node):
493
494
495 if node.pre_extend:
496 pre.append(node)
497 continue
498
499
500
501 buf.append(node)
502 else:
503 buf.append(node)
504
505
506
507 self.content.nodes = []
508
509
510 t.content.blocks['__include__' + filename] = buf
511
512
513 t.content.insert(pre)
514
515
516 t.content.extend(self.content)
517
518
519 self.content = t.content
520
522
523
524
525
526
527
528 in_tag = False
529 extend = None
530 pre_extend = True
531
532
533
534
535 ij = self.r_tag.split(text)
536
537
538 for j in range(len(ij)):
539 i = ij[j]
540
541 if i:
542 if len(self.stack) == 0:
543 self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
544
545
546 top = self.stack[-1]
547
548 if in_tag:
549 line = i
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602 line = line[2:-2].strip()
603
604
605 if not line:
606 continue
607
608
609
610 def remove_newline(re_val):
611
612
613 return re_val.group(0).replace('\n', '\\n')
614
615
616
617
618 line = re.sub(TemplateParser.r_multiline,
619 remove_newline,
620 line)
621
622 if line.startswith('='):
623
624 name, value = '=', line[1:].strip()
625 else:
626 v = line.split(' ', 1)
627 if len(v) == 1:
628
629
630
631 name = v[0]
632 value = ''
633 else:
634
635
636
637
638 name = v[0]
639 value = v[1]
640
641
642
643
644
645
646
647 if name in self.lexers:
648
649
650
651
652
653
654 self.lexers[name](parser = self,
655 value = value,
656 top = top,
657 stack = self.stack,)
658
659 elif name == '=':
660
661
662 buf = "\n%s(%s)" % (self.writer, value)
663 top.append(Node(buf, pre_extend = pre_extend))
664
665 elif name == 'block' and not value.startswith('='):
666
667 node = BlockNode(name = value.strip(),
668 pre_extend = pre_extend,
669 delimiters = self.delimiters)
670
671
672 top.append(node)
673
674
675
676
677
678 self.stack.append(node)
679
680 elif name == 'end' and not value.startswith('='):
681
682
683
684 self.blocks[top.name] = top
685
686
687 self.stack.pop()
688
689 elif name == 'super' and not value.startswith('='):
690
691
692
693 if value:
694 target_node = value
695 else:
696 target_node = top.name
697
698
699 node = SuperNode(name = target_node,
700 pre_extend = pre_extend)
701
702
703 self.super_nodes.append(node)
704
705
706 top.append(node)
707
708 elif name == 'include' and not value.startswith('='):
709
710 if value:
711 self.include(top, value)
712
713
714
715 else:
716 include_node = BlockNode(name = '__include__' + self.name,
717 pre_extend = pre_extend,
718 delimiters = self.delimiters)
719 top.append(include_node)
720
721 elif name == 'extend' and not value.startswith('='):
722
723
724 extend = value
725 pre_extend = False
726
727 else:
728
729
730 if line and in_tag:
731
732
733 tokens = line.split('\n')
734
735
736
737
738
739
740 continuation = False
741 len_parsed = 0
742 for k in range(len(tokens)):
743
744 tokens[k] = tokens[k].strip()
745 len_parsed += len(tokens[k])
746
747 if tokens[k].startswith('='):
748 if tokens[k].endswith('\\'):
749 continuation = True
750 tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip())
751 else:
752 tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip())
753 elif continuation:
754 tokens[k] += ')'
755 continuation = False
756
757
758 buf = "\n%s" % '\n'.join(tokens)
759 top.append(Node(buf, pre_extend = pre_extend))
760
761 else:
762
763 buf = "\n%s(%r, escape=False)" % (self.writer, i)
764 top.append(Node(buf, pre_extend = pre_extend))
765
766
767 in_tag = not in_tag
768
769
770 to_rm = []
771
772
773 for node in self.child_super_nodes:
774
775 if node.name in self.blocks:
776
777 node.value = self.blocks[node.name]
778
779
780 to_rm.append(node)
781
782
783 for node in to_rm:
784
785
786 self.child_super_nodes.remove(node)
787
788
789 if extend:
790 self.extend(extend)
791
792
793 -def parse_template(filename,
794 path = 'views/',
795 context = dict(),
796 lexers = {},
797 delimiters = ('{{','}}')
798 ):
799 """
800 filename can be a view filename in the views folder or an input stream
801 path is the path of a views folder
802 context is a dictionary of symbols used to render the template
803 """
804
805
806 if isinstance(filename, str):
807 try:
808 fp = open(os.path.join(path, filename), 'rb')
809 text = fp.read()
810 fp.close()
811 except IOError:
812 raise restricted.RestrictedError(filename, '', 'Unable to find the file')
813 else:
814 text = filename.read()
815
816
817 return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
818
820 """
821 Returns the indented python code of text. Useful for unit testing.
822
823 """
824 return str(TemplateParser(text))
825
826
827
828 -def render(content = "hello world",
829 stream = None,
830 filename = None,
831 path = None,
832 context = {},
833 lexers = {},
834 delimiters = ('{{','}}')
835 ):
836 """
837 >>> render()
838 'hello world'
839 >>> render(content='abc')
840 'abc'
841 >>> render(content='abc\\'')
842 "abc'"
843 >>> render(content='a"\\'bc')
844 'a"\\'bc'
845 >>> render(content='a\\nbc')
846 'a\\nbc'
847 >>> render(content='a"bcd"e')
848 'a"bcd"e'
849 >>> render(content="'''a\\nc'''")
850 "'''a\\nc'''"
851 >>> render(content="'''a\\'c'''")
852 "'''a\'c'''"
853 >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
854 '0<br />1<br />2<br />3<br />4<br />'
855 >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
856 '0<br />1<br />2<br />3<br />4<br />'
857 >>> render(content="{{='''hello\\nworld'''}}")
858 'hello\\nworld'
859 >>> render(content='{{for i in range(3):\\n=i\\npass}}')
860 '012'
861 """
862
863 import globals
864
865
866 if not content and not stream and not filename:
867 raise SyntaxError, "Must specify a stream or filename or content"
868
869
870 close_stream = False
871 if not stream:
872 if filename:
873 stream = open(filename, 'rb')
874 close_stream = True
875 elif content:
876 stream = cStringIO.StringIO(content)
877
878
879 context['response'] = globals.Response()
880
881
882 code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters))
883 try:
884 exec(code) in context
885 except Exception:
886
887 raise
888
889 if close_stream:
890 stream.close()
891
892
893 return context['response'].body.getvalue()
894
895
896 if __name__ == '__main__':
897 import doctest
898 doctest.testmod()
899