Created
August 28, 2010 10:25
-
-
Save mikelikespie/554994 to your computer and use it in GitHub Desktop.
A DSL for html generation in python (I am a bad person and have been around ruby developers too long). It's just a prototype and the generation can't be controlled too well yet, but it is sweet. I am pretty sure this is violating most of the Zen of pyth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from functools import partial, wraps | |
from contextlib import contextmanager | |
from cStringIO import StringIO | |
############################### | |
# Should output the following | |
# <html> | |
# <head> | |
# <title> | |
# does this work? | |
# </title> | |
# </head> | |
# <body> | |
# <div style="color: green;font_size: 3px;" id="hi" class="there"> | |
# <p class="big small"> | |
# this is some text | |
# <br/> | |
# <div class="content"> | |
# wrapped one | |
# </div> | |
# </p> | |
# </div> | |
# <div style="color: blue;font_size: 3px;" id="hi" class="there"> | |
# <p class="big small"> | |
# this is some text | |
# <br/> | |
# <div class="content"> | |
# wrapped two | |
# </div> | |
# </p> | |
# </div> | |
# </body> | |
# <div id="link_3" class="link"> | |
# <a href="http://google.com" title="google"> | |
# </a> | |
# </div> | |
# <div id="OMG"> | |
# after | |
# </div> | |
# </html> | |
def main(): | |
deferred_var = 'before' | |
@contextmanager | |
def big_wrapper(c, color): | |
with c.div('hi', | |
class_='there', | |
style=dict( | |
font_size='3px', | |
color=color)): | |
with c.p(None, ('big', 'small')): | |
c.text('this is some text').br() | |
with c.div(None, 'content'): | |
yield | |
def tiny_widget(c, link_id, url, title): | |
with c.div('link_%s' % link_id, 'link'): | |
with c.a(url, title=title): | |
c.text(title) | |
c = CheeseContext() | |
with c: | |
with c.html(): | |
with c.head(): | |
with c.title(): | |
c.text('does this work?') | |
with c.body(): | |
with big_wrapper(c, 'green'): | |
c.text('wrapped one') | |
with big_wrapper(c, 'blue'): | |
c.text('wrapped two') | |
tiny_widget(c, 3, 'http://google.com', 'google') | |
deferred_var = 'before' # | |
@c.defer | |
def omg(c): | |
with c.div('OMG'): | |
c.text(deferred_var) | |
deferred_var = 'after' # let's see if thsi shows up | |
################ | |
# Library Code | |
################ | |
def _maketags(*tags): | |
def maketag(tag): | |
def tfunc(self, id=None, class_=None, **attrs): | |
return self.tag(tag, id, class_, **attrs) | |
tfunc.__name__ = tag | |
return tfunc | |
return [maketag(tag) for tag in tags] | |
class CheeseContext(object): | |
indent_str = ' ' | |
quote_str = '"' | |
def __init__(self, stream=sys.stdout, start_depth=0): | |
self.start_depth = start_depth | |
self.pending = None | |
self.tagstack = list() | |
self.lastwastext = False | |
self.streams = [stream] | |
self.deferreds = [] | |
def tag(self, tag, id=None, class_=None, **attrs): | |
if id: attrs['id'] = id | |
if class_: attrs['class'] = class_ | |
self.flush() | |
self.pending = (tag, attrs) | |
return self | |
div, span, br, body, html, p, head, title = _maketags('div', 'span', 'br', 'body', 'html', 'p', 'head', 'title') | |
def a(self, href=None, **attrs): | |
return self.tag('a', href=href, **attrs) | |
@property | |
def stream(self): | |
return self.streams[-1] | |
@property | |
def depth(self): | |
return self.start_depth + len(self.tagstack) | |
def _flush_pending(self, self_closing): | |
self._flushtext() | |
tag, attrs = self.pending | |
self.pending = None | |
self.indent() | |
self.write('<%s' % tag) | |
self._write_attrs(attrs) | |
self.write('/>\n' if self_closing else '>\n') | |
def defer(self, fn): | |
con = CheeseContext(stream=self.stream, start_depth=self.depth) | |
self.streams.append(StringIO()) ## todo propogate settings | |
def _gen(): | |
yield | |
last_stream = self.streams.pop() | |
fn(con) | |
self.write(last_stream.getvalue()) | |
self.deferreds.append(_gen()) | |
return None | |
def flush(self): | |
if self.pending: | |
self._flush_pending(True) | |
def indent(self): | |
self.write(self.indent_str * self.depth) # YYY is it faster to multiply the string? | |
def write(self, obj, *args, **kwargs): | |
self.stream.write(obj, *args, **kwargs) | |
def text(self, text): | |
self.flush() | |
if not self.lastwastext: | |
self.lastwastext = True | |
self.indent() | |
self.write(text) | |
return self | |
def _flushtext(self): | |
if self.lastwastext: | |
self.lastwastext = False | |
self.write('\n') | |
def __enter__(self): | |
if self.pending: | |
tag = self.pending[0] | |
self._flush_pending(False) | |
self.tagstack.append(tag) | |
self._flushtext() | |
return self | |
def __exit__(self, exc_type, exc_value, traceback): | |
self._flushtext() | |
if self.pending: | |
self._flush_pending(True) | |
if self.tagstack: | |
tag = self.tagstack.pop() | |
self.indent() | |
self.write('</%s>\n' % tag) | |
elif self.deferreds: | |
while self.deferreds: | |
for d in self.deferreds.pop(): | |
pass | |
def _write_attrs(self, attrs): | |
for k,v in attrs.iteritems(): | |
if v is None: | |
continue | |
# we'll be nice and replace it with a string | |
self.write(' %s="' % k.replace('_', '-')) | |
# if its a list or something that looks like it | |
# separate it by spaces. Would be used for somethign like a class | |
if isinstance(v, (list, tuple, set)): | |
self.write(' '.join(v)) | |
# if its dict like thing write it out like one would a thing | |
elif isinstance(v, (dict,)): | |
for k_, v_ in v.iteritems(): | |
self.write('%s: %s;' % (k_, v_)) | |
elif v is True: | |
self.write('true') | |
elif v is False: | |
self.write('false') | |
else: # ok, we don't know what it is. just assume its a string | |
self.write(v) | |
self.write('"') | |
if __name__ == '__main__': | |
main() |
Author
mikelikespie
commented
Aug 28, 2010
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment