OLD | NEW |
1 # Copyright 2012 Benjamin Kalman | 1 # Copyright 2012 Benjamin Kalman |
2 # | 2 # |
3 # Licensed under the Apache License, Version 2.0 (the "License"); | 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
4 # you may not use this file except in compliance with the License. | 4 # you may not use this file except in compliance with the License. |
5 # You may obtain a copy of the License at | 5 # You may obtain a copy of the License at |
6 # | 6 # |
7 # http://www.apache.org/licenses/LICENSE-2.0 | 7 # http://www.apache.org/licenses/LICENSE-2.0 |
8 # | 8 # |
9 # Unless required by applicable law or agreed to in writing, software | 9 # Unless required by applicable law or agreed to in writing, software |
10 # distributed under the License is distributed on an "AS IS" BASIS, | 10 # distributed under the License is distributed on an "AS IS" BASIS, |
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 # See the License for the specific language governing permissions and | 12 # See the License for the specific language governing permissions and |
13 # limitations under the License. | 13 # limitations under the License. |
14 | 14 |
15 # TODO: New name, not "handlebar". | |
16 # TODO: Escaping control characters somehow. e.g. \{{, \{{-. | 15 # TODO: Escaping control characters somehow. e.g. \{{, \{{-. |
17 | 16 |
18 import json | 17 import json |
19 import re | 18 import re |
20 | 19 |
21 '''Handlebar templates are data binding templates more-than-loosely inspired by | 20 '''Motemplate templates are data binding templates more-than-loosely inspired by |
22 ctemplate. Use like: | 21 ctemplate. Use like: |
23 | 22 |
24 from handlebar import Handlebar | 23 from motemplate import Motemplate |
25 | 24 |
26 template = Handlebar('hello {{#foo bar/}} world') | 25 template = Motemplate('hello {{#foo bar/}} world') |
27 input = { | 26 input = { |
28 'foo': [ | 27 'foo': [ |
29 { 'bar': 1 }, | 28 { 'bar': 1 }, |
30 { 'bar': 2 }, | 29 { 'bar': 2 }, |
31 { 'bar': 3 } | 30 { 'bar': 3 } |
32 ] | 31 ] |
33 } | 32 } |
34 print(template.render(input).text) | 33 print(template.render(input).text) |
35 | 34 |
36 Handlebar will use get() on contexts to return values, so to create custom | 35 Motemplate will use get() on contexts to return values, so to create custom |
37 getters (for example, something that populates values lazily from keys), just | 36 getters (for example, something that populates values lazily from keys), just |
38 provide an object with a get() method. | 37 provide an object with a get() method. |
39 | 38 |
40 class CustomContext(object): | 39 class CustomContext(object): |
41 def get(self, key): | 40 def get(self, key): |
42 return 10 | 41 return 10 |
43 print(Handlebar('hello {{world}}').render(CustomContext()).text) | 42 print(Motemplate('hello {{world}}').render(CustomContext()).text) |
44 | 43 |
45 will print 'hello 10'. | 44 will print 'hello 10'. |
46 ''' | 45 ''' |
47 | 46 |
48 class ParseException(Exception): | 47 class ParseException(Exception): |
49 '''The exception thrown while parsing a template. | 48 '''The exception thrown while parsing a template. |
50 ''' | 49 ''' |
51 def __init__(self, error): | 50 def __init__(self, error): |
52 Exception.__init__(self, error) | 51 Exception.__init__(self, error) |
53 | 52 |
(...skipping 587 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
641 def Render(self, render_state): | 640 def Render(self, render_state): |
642 value = render_state.contexts.Resolve(self._id.name) | 641 value = render_state.contexts.Resolve(self._id.name) |
643 if value is None: | 642 if value is None: |
644 render_state.AddResolutionError(self._id) | 643 render_state.AddResolutionError(self._id) |
645 return | 644 return |
646 render_state.text.Append(json.dumps(value, separators=(',',':'))) | 645 render_state.text.Append(json.dumps(value, separators=(',',':'))) |
647 | 646 |
648 def __repr__(self): | 647 def __repr__(self): |
649 return '{{*%s}}' % self._id | 648 return '{{*%s}}' % self._id |
650 | 649 |
651 # TODO: Better common model of _PartialNodeWithArguments, _PartialNodeInContext, | |
652 # and _PartialNode. | |
653 class _PartialNodeWithArguments(_DecoratorNode): | 650 class _PartialNodeWithArguments(_DecoratorNode): |
654 def __init__(self, partial, args): | 651 def __init__(self, partial, args): |
655 if isinstance(partial, Handlebar): | 652 if isinstance(partial, Motemplate): |
656 # Preserve any get() method that the caller has added. | 653 # Preserve any get() method that the caller has added. |
657 if hasattr(partial, 'get'): | 654 if hasattr(partial, 'get'): |
658 self.get = partial.get | 655 self.get = partial.get |
659 partial = partial._top_node | 656 partial = partial._top_node |
660 _DecoratorNode.__init__(self, partial) | 657 _DecoratorNode.__init__(self, partial) |
661 self._partial = partial | 658 self._partial = partial |
662 self._args = args | 659 self._args = args |
663 | 660 |
664 def Render(self, render_state): | 661 def Render(self, render_state): |
665 render_state.contexts.Scope(self._args, self._partial.Render, render_state) | 662 render_state.contexts.Scope(self._args, self._partial.Render, render_state) |
666 | 663 |
667 class _PartialNodeInContext(_DecoratorNode): | 664 class _PartialNodeInContext(_DecoratorNode): |
668 def __init__(self, partial, context): | 665 def __init__(self, partial, context): |
669 if isinstance(partial, Handlebar): | 666 if isinstance(partial, Motemplate): |
670 # Preserve any get() method that the caller has added. | 667 # Preserve any get() method that the caller has added. |
671 if hasattr(partial, 'get'): | 668 if hasattr(partial, 'get'): |
672 self.get = partial.get | 669 self.get = partial.get |
673 partial = partial._top_node | 670 partial = partial._top_node |
674 _DecoratorNode.__init__(self, partial) | 671 _DecoratorNode.__init__(self, partial) |
675 self._partial = partial | 672 self._partial = partial |
676 self._context = context | 673 self._context = context |
677 | 674 |
678 def Render(self, render_state): | 675 def Render(self, render_state): |
679 original_contexts = render_state.contexts | 676 original_contexts = render_state.contexts |
(...skipping 20 matching lines...) Expand all Loading... |
700 | 697 |
701 @classmethod | 698 @classmethod |
702 def Inline(cls, id_): | 699 def Inline(cls, id_): |
703 return cls(None, id_, None) | 700 return cls(None, id_, None) |
704 | 701 |
705 def Render(self, render_state): | 702 def Render(self, render_state): |
706 value = render_state.contexts.Resolve(self._id.name) | 703 value = render_state.contexts.Resolve(self._id.name) |
707 if value is None: | 704 if value is None: |
708 render_state.AddResolutionError(self._id) | 705 render_state.AddResolutionError(self._id) |
709 return | 706 return |
710 if not isinstance(value, (Handlebar, _Node)): | 707 if not isinstance(value, (Motemplate, _Node)): |
711 render_state.AddResolutionError(self._id, description='not a partial') | 708 render_state.AddResolutionError(self._id, description='not a partial') |
712 return | 709 return |
713 | 710 |
714 if isinstance(value, Handlebar): | 711 if isinstance(value, Motemplate): |
715 node, name = value._top_node, value._name | 712 node, name = value._top_node, value._name |
716 else: | 713 else: |
717 node, name = value, None | 714 node, name = value, None |
718 | 715 |
719 partial_render_state = render_state.ForkPartial(name, self._id) | 716 partial_render_state = render_state.ForkPartial(name, self._id) |
720 | 717 |
721 arg_context = {} | 718 arg_context = {} |
722 if self._pass_through_id is not None: | 719 if self._pass_through_id is not None: |
723 context = render_state.contexts.Resolve(self._pass_through_id.name) | 720 context = render_state.contexts.Resolve(self._pass_through_id.name) |
724 if context is not None: | 721 if context is not None: |
(...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
905 self.Advance() | 902 self.Advance() |
906 | 903 |
907 def __repr__(self): | 904 def __repr__(self): |
908 return '%s(next_token=%s, remainder=%s)' % (type(self).__name__, | 905 return '%s(next_token=%s, remainder=%s)' % (type(self).__name__, |
909 self.next_token, | 906 self.next_token, |
910 self._string[self._cursor:]) | 907 self._string[self._cursor:]) |
911 | 908 |
912 def __str__(self): | 909 def __str__(self): |
913 return repr(self) | 910 return repr(self) |
914 | 911 |
915 class Handlebar(object): | 912 class Motemplate(object): |
916 '''A handlebar template. | 913 '''A motemplate template. |
917 ''' | 914 ''' |
918 def __init__(self, template, name=None): | 915 def __init__(self, template, name=None): |
919 self.source = template | 916 self.source = template |
920 self._name = name | 917 self._name = name |
921 tokens = _TokenStream(template) | 918 tokens = _TokenStream(template) |
922 self._top_node = self._ParseSection(tokens) | 919 self._top_node = self._ParseSection(tokens) |
923 if not self._top_node: | 920 if not self._top_node: |
924 raise ParseException('Template is empty') | 921 raise ParseException('Template is empty') |
925 if tokens.HasNext(): | 922 if tokens.HasNext(): |
926 raise ParseException('There are still tokens remaining at %s, ' | 923 raise ParseException('There are still tokens remaining at %s, ' |
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1149 return self.source == other.source and self._name == other._name | 1146 return self.source == other.source and self._name == other._name |
1150 | 1147 |
1151 def __ne__(self, other): | 1148 def __ne__(self, other): |
1152 return not (self == other) | 1149 return not (self == other) |
1153 | 1150 |
1154 def __repr__(self): | 1151 def __repr__(self): |
1155 return str('%s(%s)' % (type(self).__name__, self._top_node)) | 1152 return str('%s(%s)' % (type(self).__name__, self._top_node)) |
1156 | 1153 |
1157 def __str__(self): | 1154 def __str__(self): |
1158 return repr(self) | 1155 return repr(self) |
OLD | NEW |