76 __unicode__ = render_unicode |
76 __unicode__ = render_unicode |
77 |
77 |
78 # default .render method |
78 # default .render method |
79 render = render_unicode |
79 render = render_unicode |
80 |
80 |
81 class Tag (IRenderable) : |
81 class Container (IRenderable) : |
|
82 """ |
|
83 A container holds a sequence of other renderable items. |
|
84 """ |
|
85 |
|
86 @classmethod |
|
87 def process_contents (cls, contents) : |
|
88 """ |
|
89 Postprocess contents iterable to return new list. |
|
90 |
|
91 Items that are None will be omitted from the return value. |
|
92 |
|
93 >>> list(Container.process_contents([])) |
|
94 [] |
|
95 >>> list(Container.process_contents([None])) |
|
96 [] |
|
97 >>> list(Container.process_contents([u'foo'])) |
|
98 [u'foo'] |
|
99 """ |
|
100 |
|
101 for content in contents : |
|
102 if content is None : |
|
103 continue |
|
104 |
|
105 else : |
|
106 # normal, handle as unicode data |
|
107 yield content |
|
108 |
|
109 def __init__ (self, *contents) : |
|
110 """ |
|
111 Construct this container with the given sub-items |
|
112 """ |
|
113 |
|
114 # store postprocessed |
|
115 self.contents = list(self.process_contents(contents)) |
|
116 |
|
117 def render_raw_lines (self, **render_opts) : |
|
118 """ |
|
119 Render our contents as a series of non-indented lines, with the contents handling indentation themselves. |
|
120 |
|
121 >>> list(Container(5).render_raw_lines()) |
|
122 [u'5'] |
|
123 >>> list(Container('line1', 'line2').render_raw_lines()) |
|
124 [u'line1', u'line2'] |
|
125 >>> list(Container('a', Tag('b', 'bb'), 'c').render_raw_lines()) |
|
126 [u'a', u'<b>', u'\\tbb', u'</b>', u'c'] |
|
127 >>> list(Container(Tag('hr'), Tag('foo')('bar')).render_raw_lines()) |
|
128 [u'<hr />', u'<foo>', u'\\tbar', u'</foo>'] |
|
129 """ |
|
130 |
|
131 for content in self.contents : |
|
132 if isinstance(content, IRenderable) : |
|
133 # sub-items |
|
134 for line in content.render_raw_lines(**render_opts) : |
|
135 yield line |
|
136 |
|
137 else : |
|
138 # escape raw values |
|
139 yield escape(unicode(content)) |
|
140 |
|
141 def __repr__ (self) : |
|
142 return 'Container(%s)' % ', '.join(repr(c) for c in self.contents) |
|
143 |
|
144 class Tag (Container) : |
82 """ |
145 """ |
83 A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags). |
146 A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags). |
84 |
147 |
85 Provides various kinds of rendering output |
148 Provides various kinds of rendering output |
86 """ |
149 """ |
87 |
|
88 @staticmethod |
|
89 def process_contents (contents) : |
|
90 """ |
|
91 Postprocess contents iterable to return new list. |
|
92 |
|
93 Items that are None will be omitted from the return value. |
|
94 |
|
95 XXX: flatten |
|
96 |
|
97 >>> Tag.process_contents([]) |
|
98 [] |
|
99 >>> Tag.process_contents([None]) |
|
100 [] |
|
101 >>> Tag.process_contents([u'foo']) |
|
102 [u'foo'] |
|
103 """ |
|
104 |
|
105 return [c for c in contents if c is not None] |
|
106 |
150 |
107 @staticmethod |
151 @staticmethod |
108 def process_attrs (attrs) : |
152 def process_attrs (attrs) : |
109 """ |
153 """ |
110 Postprocess attributes. |
154 Postprocess attributes. |
111 |
155 |
112 Key-value pairs where the value is None will be ommitted, and any trailing underscores in the key omitted. |
156 Key-value pairs where the value is None will be ommitted, and any trailing underscores in the key omitted. |
113 |
157 |
114 TODO: only remove one underscore |
158 TODO: only remove one underscore |
115 |
159 |
116 >>> Tag.process_attrs(dict()) |
160 >>> dict(Tag.process_attrs(dict())) |
117 {} |
161 {} |
118 >>> Tag.process_attrs(dict(foo='bar')) |
162 >>> dict(Tag.process_attrs(dict(foo='bar'))) |
119 {'foo': 'bar'} |
163 {'foo': 'bar'} |
120 >>> Tag.process_attrs(dict(class_='bar', frob=None)) |
164 >>> dict(Tag.process_attrs(dict(class_='bar', frob=None))) |
121 {'class': 'bar'} |
165 {'class': 'bar'} |
122 """ |
166 """ |
123 |
167 |
124 return dict((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None) |
168 return ((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None) |
125 |
169 |
126 def __init__ (self, name, *contents, **attrs) : |
170 def __init__ (self, name, *contents, **attrs) : |
127 """ |
171 """ |
128 Construct tag with given name/attributes or contents. |
172 Construct tag with given name/attributes or contents. |
129 |
173 |
136 >>> Tag('foo', 'quux', bar=5) |
180 >>> Tag('foo', 'quux', bar=5) |
137 Tag('foo', 'quux', bar=5) |
181 Tag('foo', 'quux', bar=5) |
138 >>> Tag('foo', class_='ten') |
182 >>> Tag('foo', class_='ten') |
139 Tag('foo', class='ten') |
183 Tag('foo', class='ten') |
140 """ |
184 """ |
141 |
185 |
|
186 # store contents as container |
|
187 super(Tag, self).__init__(*contents) |
|
188 |
|
189 # store postprocessed stuff |
142 self.name = name |
190 self.name = name |
143 |
191 self.attrs = dict(self.process_attrs(attrs)) |
144 # store filtered/processed versions |
|
145 self.contents = self.process_contents(contents) |
|
146 self.attrs = self.process_attrs(attrs) |
|
147 |
192 |
148 def __call__ (self, *contents, **attrs) : |
193 def __call__ (self, *contents, **attrs) : |
149 """ |
194 """ |
150 Return a new Tag with this tag's attributes and contents, as well as the given attributes/contents. |
195 Return a new Tag with this tag's attributes and contents, as well as the given attributes/contents. |
151 |
196 |
194 u'foo="5" bar="<"' |
239 u'foo="5" bar="<"' |
195 """ |
240 """ |
196 |
241 |
197 return " ".join(self.format_attr(n, v) for n, v in self.attrs.iteritems()) |
242 return " ".join(self.format_attr(n, v) for n, v in self.attrs.iteritems()) |
198 |
243 |
199 def render_contents (self, **render_opts) : |
|
200 """ |
|
201 Render the contents of the tag as a series of indented lines, with given render_lines options for subtags |
|
202 |
|
203 >>> list(Tag('x', 5).render_contents()) |
|
204 [u'5'] |
|
205 >>> list(Tag('x', 'line1', 'line2').render_contents()) |
|
206 [u'line1', u'line2'] |
|
207 >>> list(Tag('x', 'a', Tag('b', 'bb'), 'c').render_contents()) |
|
208 [u'a', u'<b>', u'\\tbb', u'</b>', u'c'] |
|
209 """ |
|
210 |
|
211 for content in self.contents : |
|
212 if isinstance(content, IRenderable) : |
|
213 # sub-tags |
|
214 for line in content.render_raw_lines(**render_opts) : |
|
215 yield line |
|
216 |
|
217 else : |
|
218 # escape raw values |
|
219 yield escape(unicode(content)) |
|
220 |
|
221 def render_raw_lines (self, indent=u'\t') : |
244 def render_raw_lines (self, indent=u'\t') : |
222 """ |
245 """ |
223 Render the tag and indented content |
246 Render the tag and indented content |
224 |
247 |
225 >>> list(Tag('xx', 'yy', zz='foo').render_raw_lines(indent=' ')) |
248 >>> list(Tag('xx', 'yy', zz='foo').render_raw_lines(indent=' ')) |