Skip to content

Instantly share code, notes, and snippets.

@mikelikespie
Created August 28, 2010 10:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikelikespie/554994 to your computer and use it in GitHub Desktop.
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
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">
# 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()
@mikelikespie
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment