1--- /dev/null Thu Jan 01 00:00:00 1970 +0000
2+++ b/constants.py Tue Apr 29 23:31:20 2008 -0400
3@@ -0,0 +1,75 @@
4+#!/usr/bin/env python
5+
6+import time
7+
8+types = {
9+ 'html' : [('Content-type', 'text/html')],
10+ 'redirect' : lambda x: [('Location', x)],
11+}
12+
13+# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
14+_status = {
15+ # informational
16+ 100 : 'CONTINUE',
17+ 101 : 'SWITCHING PROTOCOLS',
18+ # success
19+ 200 : 'OK',
20+ 201 : 'CREATED',
21+ 202 : 'ACCEPTED',
22+ 203 : 'NON-AUTHORATIVE INFORMATION',
23+ 204 : 'NO CONTENT',
24+ 205 : 'RESET CONTENT',
25+ 205 : 'PARTIAL CONTENT',
26+ # redirection
27+ 300 : 'MULTIPLE CHOICES',
28+ 301 : 'MOVED PERMANENTLY',
29+ 302 : 'FOUND',
30+ 303 : 'SEE OTHER',
31+ 304 : 'NOT MODIFIED',
32+ 305 : 'USE PROXY',
33+ 307 : 'TEMPORARY REDIRECT',
34+ # client error
35+ 400 : 'BAD REQUEST',
36+ 401 : 'UNAUTHORIZED',
37+ 403 : 'FORBIDDEN',
38+ 404 : 'NOT FOUND',
39+ 405 : 'METHOD NOT ALLOWED',
40+ 406 : 'NOT ACCEPTABLE',
41+ 407 : 'PROXY AUTHENTICATION REQUIRED',
42+ 408 : 'REQUEST TIMEOUT',
43+ 409 : 'CONFLICT',
44+ 410 : 'GONE',
45+ 411 : 'LENGTH REQUIRED',
46+ 412 : 'PRECONDITION FAILED',
47+ 413 : 'REQUEST ENTITY TOO LARGE',
48+ 414 : 'REQUEST-URI TOO LONG',
49+ 415 : 'UNSUPPORTED MEDIA TYPE',
50+ 416 : 'REQUESTED RANGE NOT SATISFIABLE',
51+ # server error
52+ 500 : 'INTERNAL SERVER ERROR',
53+ 501 : 'NOT IMPLEMENTED',
54+ 502 : 'BAD GATEWAY',
55+ 503 : 'SERVICE UNAVAILABLE',
56+ 504 : 'BAD GATEWAY',
57+ 505 : 'HTTP VERSION NOT SUPPORTED',
58+}
59+
60+months = {
61+ 'Jan' : 1,
62+ 'Feb' : 2,
63+ 'Mar' : 3,
64+ 'Apr' : 4,
65+ 'May' : 5,
66+ 'Jun' : 6,
67+ 'Jul' : 7,
68+ 'Aug' : 8,
69+ 'Sep' : 9,
70+ 'Oct' : 10,
71+ 'Nov' : 11,
72+ 'Dec' : 12,
73+}
74+
75+thisyear = lambda: time.localtime()[0]
76+
77+status = lambda x: '%d %s' % (x, _status[x])
78+
1.1--- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2+++ b/forms.py Tue Apr 29 23:31:20 2008 -0400
1.3@@ -0,0 +1,210 @@
1.4+#!/usr/bin/env python
1.5+
1.6+"""This builds on the concepts of FormBuild (http://formbuild.org), but
1.7+because that library was a bit complex for my needs, I've simplified it
1.8+immensely.
1.9+
1.10+The basic idea is that you create a Form object, populated it with
1.11+FormElements (which represent the form fields) and then call "render".
1.12+
1.13+You can control the way that the form is rendered via the Layout; the
1.14+default layout manager accepts a few different parameters and outputs
1.15+the form as a table.
1.16+"""
1.17+
1.18+def rmkeys(dict, keys):
1.19+ for key in keys:
1.20+ if key in dict:
1.21+ del dict[key]
1.22+
1.23+required = '<p class="smaller"><em>name</em>, <em>email</em>, and <em>comment</em> are <strong>required</strong>.</p>'
1.24+
1.25+markdownHelp = """
1.26+<tr>
1.27+\t<td></td>
1.28+\t<td style="vertical-align: top;"><span class="smaller">links get auto-converted, you may also use <a href="http://daringfireball.net/projects/markdown/syntax">markdown</a></span></td>
1.29+</tr>
1.30+"""
1.31+
1.32+testform = {
1.33+ 'options' : {
1.34+ 'action' : '/comments/postfree/',
1.35+ 'class_' : 'post_comment',
1.36+ },
1.37+ 'elements' : [
1.38+ {'name':'options', 'type':'hidden', 'value':'ip'},
1.39+ {'name':'target', 'type':'hidden', 'value':'4:299',
1.40+ 'postElem':required},
1.41+ {'name':'name', 'label':'name:'},
1.42+ {'name':'email', 'label':'email:<br /><span class="smaller">(not shown)</span>'},
1.43+ {'name':'web_site', 'label':'url:'},
1.44+ {'name':'comment', 'type':'textarea', 'rows':10, 'cols':60,
1.45+ 'postElem':markdownHelp}
1.46+ ],
1.47+}
1.48+
1.49+
1.50+def FormBuilder(form_dict, submit=True):
1.51+ """Takes a dictionary and creates a form from it.
1.52+
1.53+ The dictionary format is the following:
1.54+ {
1.55+ 'options' : {<opts>},
1.56+ 'elements': [{<elem>},],
1.57+ }
1.58+ opts: {'method':<>, ...} (options for the Form
1.59+ elem: {'name':<name>, 'opt':<val>, 'preElem':<pre>, 'postElem':<post>}
1.60+
1.61+ """
1.62+ fd = form_dict
1.63+ elems = fd.get('elements', [])
1.64+ options = fd.get('options', {})
1.65+ f = Form(**options)
1.66+ for elem in elems:
1.67+ name = elem['name']
1.68+ pre = elem.get('preElem', None)
1.69+ post = elem.get('postElem', None)
1.70+ rmkeys(elem, ('name','postElem', 'preElem'))
1.71+ f.elements.append(FormElement(name, **elem))
1.72+ if pre: f.layout.preElement[name] = pre
1.73+ if post: f.layout.postElement[name] = post
1.74+ pre, post = None, None
1.75+ return f
1.76+
1.77+class Form:
1.78+ def __init__(self, **kwargs):
1.79+ self.method = kwargs.get('method', 'post')
1.80+ self.action = kwargs.get('action', None)
1.81+ if not self.action: raise Exception('an action for your form is necessary.')
1.82+ self.elements = kwargs.get('elements', [])
1.83+ self.layout = kwargs.get('layout', Layout())
1.84+ rmkeys(kwargs, ('method', 'action', 'elements', 'layout'))
1.85+ self.args = kwargs
1.86+
1.87+ def addInput(self, formelem):
1.88+ self.elements.append(formelem)
1.89+
1.90+ def checkElements(self):
1.91+ checked = []
1.92+ for elem in elements:
1.93+ if elem.name in [x.name for x in checked]:
1.94+ raise Exception('repeat "name" in elements')
1.95+ checked.append(elem)
1.96+
1.97+ def fill(self, fill_dict):
1.98+ eledict = dict([(x.name, x) for x in self.elements])
1.99+ for k,v in fill_dict.items():
1.100+ if k in eledict:
1.101+ eledict[k]['value'] = v
1.102+ return self.render()
1.103+
1.104+ def render(self):
1.105+ header = '<form method="%s" action="%s"' % (self.method, self.action)
1.106+ for key in self.args:
1.107+ if key == 'class_':
1.108+ header += ' class="%s"' % self.args[key]
1.109+ else:
1.110+ header += ' %s="%s"' % (key, self.args[key])
1.111+ header += '>\n\t<fieldset>\n'
1.112+ body = self.layout(self.elements)
1.113+ footer = '\t</fieldset>\n</form>\n'
1.114+ return header + body + footer
1.115+
1.116+class FormElement:
1.117+ def __init__(self, name, **kwargs):
1.118+ self.type = kwargs.get('type', 'text')
1.119+ self.name = name
1.120+ self.extra = kwargs.get('extra', None)
1.121+ self.label = kwargs.get('label', None)
1.122+ rmkeys(kwargs, ('type', 'extra', 'label'))
1.123+ self.args = kwargs
1.124+
1.125+ def _inputFormatter(self):
1.126+ s = '<input type="%s" name="%s"' % (self.type, self.name)
1.127+ for arg in self.args:
1.128+ s += ' %s="%s"' % (arg, self.args[arg])
1.129+ return s + ' />'
1.130+
1.131+ def _textareaFormatter(self):
1.132+ s = '<textarea name="%s"' % self.name
1.133+ self.value = self.args.get('value', '')
1.134+ rmkeys(self.args, ('value',))
1.135+ for arg in self.args:
1.136+ s += ' %s="%s"' % (arg, self.args[arg])
1.137+ return s + '>%s</textarea>' % self.value
1.138+
1.139+ def __str__(self):
1.140+ if self.type.lower() in ('text', 'hidden',):
1.141+ return self._inputFormatter()
1.142+ elif self.type.lower() in ('textarea'):
1.143+ return self._textareaFormatter()
1.144+
1.145+ def __repr__(self):
1.146+ return '<FormInput %s>'
1.147+
1.148+def ishidden(element):
1.149+ return element.type.lower() == 'hidden'
1.150+
1.151+class Layout:
1.152+ def __init__(self):
1.153+ self.called = False
1.154+ self.preElement = {}
1.155+ self.postElement = {}
1.156+
1.157+ def __call__(self, elements, indent=1):
1.158+ self.indent = indent
1.159+ self.i = '\t' * (indent + 1)
1.160+ i = self.i
1.161+ s = ''
1.162+ hidden = [e for e in elements if ishidden(e)]
1.163+ rest = [e for e in elements if not ishidden(e)]
1.164+ for elem in hidden:
1.165+ s += self.hiddenElementFormatter(elem)
1.166+ s += self.layoutHeader()
1.167+ for elem in rest:
1.168+ s += self.elementFormatter(elem)
1.169+ s += '\n' + self.layoutFooter()
1.170+ return s
1.171+
1.172+ def hiddenElementFormatter(self, element):
1.173+ i = self.i
1.174+ s = ''
1.175+ if element.name in self.preElement:
1.176+ s += self.prePostFormatter(element.name, pre=True)
1.177+ s += i + str(element) + '\n'
1.178+ if element.name in self.postElement:
1.179+ s += self.prePostFormatter(element.name, post=True)
1.180+ return s
1.181+
1.182+ def elementFormatter(self, element):
1.183+ i = self.i
1.184+ s = ''
1.185+ if element.name in self.preElement:
1.186+ s += self.prePostFormatter(element.name, pre=True)
1.187+ if element.label:
1.188+ s += '%s<tr>\n%s\t<th><label for="%s">%s</label></th>\n' % (i, i, element.name, element.label)
1.189+ else:
1.190+ s += '%s<tr>\n%s\t<th></th>\n' % (i, i)
1.191+ s += '%s\t<td>%s</td>\n%s</tr>\n' % (i, str(element), i)
1.192+ if element.name in self.postElement:
1.193+ s += self.prePostFormatter(element.name, post=True)
1.194+ return s
1.195+
1.196+ def prePostFormatter(self, name, pre=False, post=False):
1.197+ if pre: text = self.preElement[name]
1.198+ elif post: text = self.postElement[name]
1.199+ else: return ''
1.200+ i = self.i
1.201+ t = text.strip()
1.202+ joinstr = '\n' + i
1.203+ t = i + joinstr.join(t.split('\n')) + '\n'
1.204+ return t
1.205+
1.206+ def layoutHeader(self):
1.207+ i = '\t' * self.indent
1.208+ return '%s<table>\n%s<tbody>\n' % (i,i)
1.209+
1.210+ def layoutFooter(self):
1.211+ i = '\t' * self.indent
1.212+ return '%s</tbody>\n%s</table>\n' % (i,i)
1.213+
2.1--- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2+++ b/template.py Tue Apr 29 23:31:20 2008 -0400
2.3@@ -0,0 +1,93 @@
2.4+#!/usr/bin/env python
2.5+
2.6+"""Template utilities."""
2.7+
2.8+import time
2.9+
2.10+try: from cStringIO import StringIO
2.11+except: from StringIO import StringIO
2.12+from pprint import pprint as _pprint
2.13+
2.14+from mako.template import Template
2.15+from mako.lookup import TemplateLookup
2.16+
2.17+lookup = TemplateLookup(directories=['./templates'],
2.18+ #module_directory='./templates/modules/',
2.19+ output_encoding='utf-8',
2.20+ encoding_errors='replace',
2.21+ filesystem_checks=True,
2.22+ collection_size=100)
2.23+
2.24+import markdown as _markdown
2.25+
2.26+def url(env):
2.27+ return u'http://' + unicode(env['HTTP_HOST']) + unicode(env['SCRIPT_NAME'])
2.28+
2.29+def render(template_name):
2.30+ """Convenience decorator to be used by controllers.
2.31+
2.32+ Decorate a controller with 'render("template_name")'; Since it will use
2.33+ the lookup object specified here, it will find the templates at
2.34+ compile time. Return a dictionary representing the Context for the
2.35+ template from the controller, and this will return the iterable."""
2.36+ def decorater(func):
2.37+ def decorated(*args, **kwargs):
2.38+ template = lookup.get_template(template_name)
2.39+ environ = args[0]
2.40+ ret = func(*args, **kwargs)
2.41+ if not isinstance(ret, dict): return ret
2.42+ return [template.render(env=environ, **ret)]
2.43+ decorated.__name__ = func.__name__
2.44+ decorated.__doc__ = func.__doc__
2.45+ return decorated
2.46+ return decorater
2.47+
2.48+def pprint(text):
2.49+ """Pretty print filter for debugging."""
2.50+ s = StringIO()
2.51+ try: obj = eval(text)
2.52+ except: return text
2.53+ _pprint(obj, stream=s)
2.54+ s.seek(0)
2.55+ return s.read()
2.56+
2.57+def markdown(text):
2.58+ """Returns the HTML generated by markdown on markdown text."""
2.59+ return unicode(_markdown.markdown(text))
2.60+
2.61+def lgroup(list_):
2.62+ """Groups a list of lists by the sub-list index. Ex.
2.63+ [[1, 1], [1, 2], [2, 1]] -> [(1, [1, 2]), (2, [1])]
2.64+ """
2.65+ d = {}
2.66+ for a,b in list_:
2.67+ if d.has_key(a): d[a].append(b)
2.68+ else: d[a] = [b]
2.69+ l = d.items()
2.70+ l.sort(reverse=True)
2.71+ return l
2.72+
2.73+def longmonth(month, format='%B'):
2.74+ """Returns the month in a given format (default: %B)."""
2.75+ tup = (2000, int(month), 1, 1, 1, 1, 1, 1, 1)
2.76+ return time.strftime(format, tup)
2.77+
2.78+def strftime(timestamp, formatstr):
2.79+ """Strftime for ISO 8601 times; follows time.strftime with the addition
2.80+ of '%D' which returns an ordinal day (1st, 3rd, 13th, 22nd, etc)."""
2.81+ def extraFormat(timeval, formatstring):
2.82+ return formatstring.replace('%D', dayFormatter(timeval))
2.83+ def dayFormatter(timeval):
2.84+ # day is the 2nd in a timeval
2.85+ day = timeval[2]
2.86+ if day in (11, 12, 13): return u'%dth' % day
2.87+ digit = day % 10
2.88+ if digit == 1: return u'%dst' % day
2.89+ elif digit == 2: return u'%dnd' % day
2.90+ elif digit == 3: return u'%drd' % day
2.91+ else: return u'%dth' % day
2.92+ timestamp = timestamp.split('.')[0]
2.93+ timeval = time.strptime(timestamp, '%Y-%m-%dT%H:%M:%S')
2.94+ formatstr = extraFormat(timeval, formatstr)
2.95+ return time.strftime(formatstr, timeval)
2.96+