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
413
414 - def _get_file_text(self, filename):
415 """
416 Attempt to open ``filename`` and retrieve its text.
417
418 This will use self.path to search for the file.
419 """
420
421
422 if not filename.strip():
423 self._raise_error('Invalid template filename')
424
425
426
427 filename = eval(filename, self.context)
428
429
430 filepath = os.path.join(self.path, filename)
431
432
433 try:
434 fileobj = open(filepath, 'rb')
435
436 text = fileobj.read()
437
438 fileobj.close()
439 except IOError:
440 self._raise_error('Unable to open included view file: ' + filepath)
441
442 return text
443
444 - def include(self, content, filename):
445 """
446 Include ``filename`` here.
447 """
448 text = self._get_file_text(filename)
449
450 t = TemplateParser(text,
451 name = filename,
452 context = self.context,
453 path = self.path,
454 writer = self.writer,
455 delimiters = self.delimiters)
456
457 content.append(str(t.content))
458
460 """
461 Extend ``filename``. Anything not declared in a block defined by the
462 parent will be placed in the parent templates ``{{include}}`` block.
463 """
464 text = self._get_file_text(filename)
465
466
467 super_nodes = []
468
469 super_nodes.extend(self.child_super_nodes)
470
471 super_nodes.extend(self.super_nodes)
472
473 t = TemplateParser(text,
474 name = filename,
475 context = self.context,
476 path = self.path,
477 writer = self.writer,
478 delimiters = self.delimiters,
479 _super_nodes = super_nodes)
480
481
482
483 buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters)
484 pre = []
485
486
487 for node in self.content.nodes:
488
489 if isinstance(node, BlockNode):
490
491 if node.name in t.content.blocks:
492
493 continue
494
495 if isinstance(node, Node):
496
497
498 if node.pre_extend:
499 pre.append(node)
500 continue
501
502
503
504 buf.append(node)
505 else:
506 buf.append(node)
507
508
509
510 self.content.nodes = []
511
512
513 t.content.blocks['__include__' + filename] = buf
514
515
516 t.content.insert(pre)
517
518
519 t.content.extend(self.content)
520
521
522 self.content = t.content
523
525
526
527
528
529
530
531 in_tag = False
532 extend = None
533 pre_extend = True
534
535
536
537
538 ij = self.r_tag.split(text)
539
540
541 for j in range(len(ij)):
542 i = ij[j]
543
544 if i:
545 if len(self.stack) == 0:
546 self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
547
548
549 top = self.stack[-1]
550
551 if in_tag:
552 line = i
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
603
604
605 line = line[2:-2].strip()
606
607
608 if not line:
609 continue
610
611
612
613 def remove_newline(re_val):
614
615
616 return re_val.group(0).replace('\n', '\\n')
617
618
619
620
621 line = re.sub(TemplateParser.r_multiline,
622 remove_newline,
623 line)
624
625 if line.startswith('='):
626
627 name, value = '=', line[1:].strip()
628 else:
629 v = line.split(' ', 1)
630 if len(v) == 1:
631
632
633
634 name = v[0]
635 value = ''
636 else:
637
638
639
640
641 name = v[0]
642 value = v[1]
643
644
645
646
647
648
649
650 if name in self.lexers:
651
652
653
654
655
656
657 self.lexers[name](parser = self,
658 value = value,
659 top = top,
660 stack = self.stack,)
661
662 elif name == '=':
663
664
665 buf = "\n%s(%s)" % (self.writer, value)
666 top.append(Node(buf, pre_extend = pre_extend))
667
668 elif name == 'block' and not value.startswith('='):
669
670 node = BlockNode(name = value.strip(),
671 pre_extend = pre_extend,
672 delimiters = self.delimiters)
673
674
675 top.append(node)
676
677
678
679
680
681 self.stack.append(node)
682
683 elif name == 'end' and not value.startswith('='):
684
685
686
687 self.blocks[top.name] = top
688
689
690 self.stack.pop()
691
692 elif name == 'super' and not value.startswith('='):
693
694
695
696 if value:
697 target_node = value
698 else:
699 target_node = top.name
700
701
702 node = SuperNode(name = target_node,
703 pre_extend = pre_extend)
704
705
706 self.super_nodes.append(node)
707
708
709 top.append(node)
710
711 elif name == 'include' and not value.startswith('='):
712
713 if value:
714 self.include(top, value)
715
716
717
718 else:
719 include_node = BlockNode(name = '__include__' + self.name,
720 pre_extend = pre_extend,
721 delimiters = self.delimiters)
722 top.append(include_node)
723
724 elif name == 'extend' and not value.startswith('='):
725
726
727 extend = value
728 pre_extend = False
729
730 else:
731
732
733 if line and in_tag:
734
735
736 tokens = line.split('\n')
737
738
739
740
741
742
743 continuation = False
744 len_parsed = 0
745 for k in range(len(tokens)):
746
747 tokens[k] = tokens[k].strip()
748 len_parsed += len(tokens[k])
749
750 if tokens[k].startswith('='):
751 if tokens[k].endswith('\\'):
752 continuation = True
753 tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip())
754 else:
755 tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip())
756 elif continuation:
757 tokens[k] += ')'
758 continuation = False
759
760
761 buf = "\n%s" % '\n'.join(tokens)
762 top.append(Node(buf, pre_extend = pre_extend))
763
764 else:
765
766 buf = "\n%s(%r, escape=False)" % (self.writer, i)
767 top.append(Node(buf, pre_extend = pre_extend))
768
769
770 in_tag = not in_tag
771
772
773 to_rm = []
774
775
776 for node in self.child_super_nodes:
777
778 if node.name in self.blocks:
779
780 node.value = self.blocks[node.name]
781
782
783 to_rm.append(node)
784
785
786 for node in to_rm:
787
788
789 self.child_super_nodes.remove(node)
790
791
792 if extend:
793 self.extend(extend)
794
795
796 -def parse_template(filename,
797 path = 'views/',
798 context = dict(),
799 lexers = {},
800 delimiters = ('{{','}}')
801 ):
802 """
803 filename can be a view filename in the views folder or an input stream
804 path is the path of a views folder
805 context is a dictionary of symbols used to render the template
806 """
807
808
809 if isinstance(filename, str):
810 try:
811 fp = open(os.path.join(path, filename), 'rb')
812 text = fp.read()
813 fp.close()
814 except IOError:
815 raise restricted.RestrictedError(filename, '', 'Unable to find the file')
816 else:
817 text = filename.read()
818
819
820 return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
821
823 """
824 Returns the indented python code of text. Useful for unit testing.
825
826 """
827 return str(TemplateParser(text))
828
829
830
831 -def render(content = "hello world",
832 stream = None,
833 filename = None,
834 path = None,
835 context = {},
836 lexers = {},
837 delimiters = ('{{','}}')
838 ):
839 """
840 >>> render()
841 'hello world'
842 >>> render(content='abc')
843 'abc'
844 >>> render(content='abc\\'')
845 "abc'"
846 >>> render(content='a"\\'bc')
847 'a"\\'bc'
848 >>> render(content='a\\nbc')
849 'a\\nbc'
850 >>> render(content='a"bcd"e')
851 'a"bcd"e'
852 >>> render(content="'''a\\nc'''")
853 "'''a\\nc'''"
854 >>> render(content="'''a\\'c'''")
855 "'''a\'c'''"
856 >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
857 '0<br />1<br />2<br />3<br />4<br />'
858 >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
859 '0<br />1<br />2<br />3<br />4<br />'
860 >>> render(content="{{='''hello\\nworld'''}}")
861 'hello\\nworld'
862 >>> render(content='{{for i in range(3):\\n=i\\npass}}')
863 '012'
864 """
865
866 import globals
867
868
869 if not content and not stream and not filename:
870 raise SyntaxError, "Must specify a stream or filename or content"
871
872
873 close_stream = False
874 if not stream:
875 if filename:
876 stream = open(filename, 'rb')
877 close_stream = True
878 elif content:
879 stream = cStringIO.StringIO(content)
880
881
882 context['response'] = globals.Response()
883
884
885 code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters))
886 try:
887 exec(code) in context
888 except Exception:
889
890 raise
891
892 if close_stream:
893 stream.close()
894
895
896 return context['response'].body.getvalue()
897
898
899 if __name__ == '__main__':
900 import doctest
901 doctest.testmod()
902