<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Andi Albrecht</title>
	<atom:link href="http://andialbrecht.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://andialbrecht.wordpress.com</link>
	<description>My personal blog, mostly about Python &#38; Programming</description>
	<lastBuildDate>Wed, 04 Nov 2009 12:19:49 +0000</lastBuildDate>
	<generator>http://wordpress.com/</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<cloud domain='andialbrecht.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://www.gravatar.com/blavatar/875722285aa444f2b9b3aa006df5b47c?s=96&#038;d=http://s.wordpress.com/i/buttonw-com.png</url>
		<title>Andi Albrecht</title>
		<link>http://andialbrecht.wordpress.com</link>
	</image>
			<item>
		<title>Pluggable App Engine e-mail backends for Django</title>
		<link>http://andialbrecht.wordpress.com/2009/11/04/pluggable-app-engine-e-mail-backends-for-django/</link>
		<comments>http://andialbrecht.wordpress.com/2009/11/04/pluggable-app-engine-e-mail-backends-for-django/#comments</comments>
		<pubDate>Wed, 04 Nov 2009 08:16:30 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[App Engine]]></category>
		<category><![CDATA[Django]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=335</guid>
		<description><![CDATA[Yesterday support for pluggable e-mail backends has landed in Django&#8217;s trunk and today I&#8217;m happy to announce two e-mail backend implementations to be used with Django-based App Engine applications.
The e-mail backends allow you to use native Django functions for sending e-mails like django.core.mail.send_mail() on App Engine.
To use pluggable e-mail backends you&#8217;ll have to use a [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=335&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>Yesterday support for <a href="http://code.djangoproject.com/ticket/10355">pluggable e-mail backends</a> has <a href="http://code.djangoproject.com/changeset/11709">landed</a> in Django&#8217;s trunk and today I&#8217;m happy to announce two e-mail backend implementations to be used with Django-based App Engine applications.</p>
<p>The e-mail backends allow you to use native Django functions for sending e-mails like <tt>django.core.mail.send_mail()</tt> on App Engine.</p>
<p>To use pluggable e-mail backends you&#8217;ll have to use a recent version of Django. Support for pluggable e-mail backends was introduced in <a href="http://code.djangoproject.com/changeset/11709">rev11709</a> and will be released in Django 1.2.</p>
<h2>Using the e-mail backend</h2>
<p>The e-mail backends are available as a single package on bitbucket. To check out the most recent sources run:</p>
<p>
<pre>$ hg clone http://bitbucket.org/andialbrecht/appengine_emailbackends/</pre>
</p>
<p>or got to the <a href="http://bitbucket.org/andialbrecht/appengine_emailbackends/downloads/">downloads page</a> and grab a <a href="http://bitbucket.org/andialbrecht/appengine_emailbackends/get/tip.zip">zip</a> file.</p>
<p>To use the e-mail backend for App Engine copy the <tt>appengine_emailbackend</tt> directory to the top-level directory of you App Engine application and add the following line to your settings.py:</p>
<p>
<pre>EMAIL_BACKEND = 'appengine_emailbackend'</pre>
</p>
<p>If you prefer asynchronous e-mail delivery use this line in your settings.py instead:</p>
<p>
<pre>EMAIL_BACKEND = 'appengine_emailbackend.async'</pre>
</p>
<p>When using the async backend e-mails will not be sent immediately but delivered via a taskqueue.</p>
<h2>Running the demo application</h2>
<p>There&#8217;s also a demo application included in this repository. Run this application using</p>
<p>
<pre>$ dev_appserver.py .</pre>
</p>
<p>and open <a href="http://localhost:8080">http://localhost:8080</a> in your web browser. Remember to include a current checkout of the Django SVN repository in the top-level directory of the demo application.</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/335/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/335/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/335/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/335/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/335/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/335/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/335/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/335/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/335/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/335/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=335&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/11/04/pluggable-app-engine-e-mail-backends-for-django/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>
	</item>
		<item>
		<title>Dominion Set Designer</title>
		<link>http://andialbrecht.wordpress.com/2009/10/11/dominion-set-designer/</link>
		<comments>http://andialbrecht.wordpress.com/2009/10/11/dominion-set-designer/#comments</comments>
		<pubDate>Sun, 11 Oct 2009 13:40:31 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[Google App Engine]]></category>
		<category><![CDATA[Swing Low]]></category>
		<category><![CDATA[App Engine]]></category>
		<category><![CDATA[Dominion]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=331</guid>
		<description><![CDATA[If you&#8217;re a regular Dominion player like me, you&#8217;re always in search for new card sets. Fortunately I&#8217;ve had a little time this weekend to write a simple App Engine application that gives me a random card set on demand.
You can see it in action at http://dominionsets.appspot.com
That&#8217;s it, just a short post on this, now [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=331&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>If you&#8217;re a regular Dominion player like me, you&#8217;re always in search for new card sets. Fortunately I&#8217;ve had a little time this weekend to write a simple App Engine application that gives me a random card set on demand.</p>
<p><a href="http://andialbrecht.files.wordpress.com/2009/10/screenshot.jpg"><img class="aligncenter size-medium wp-image-332" title="Screenshot" src="http://andialbrecht.files.wordpress.com/2009/10/screenshot.jpg?w=300&#038;h=206" alt="Screenshot" width="300" height="206" /></a>You can see it in action at <a href="http://dominionsets.appspot.com">http://dominionsets.appspot.com</a></p>
<p>That&#8217;s it, just a short post on this, now back to the next match :)</p>
<p><span style="background-color:#ffffff;">P.s.: Some technical stuff&#8230; There&#8217;s Django with it&#8217;s i18n support under the hood. The UI is powered by JQuery.</span></p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/331/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/331/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/331/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/331/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/331/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/331/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/331/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/331/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/331/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/331/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=331&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/10/11/dominion-set-designer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>

		<media:content url="http://andialbrecht.files.wordpress.com/2009/10/screenshot.jpg?w=300" medium="image">
			<media:title type="html">Screenshot</media:title>
		</media:content>
	</item>
		<item>
		<title>CrunchyFrog 0.4.1 released</title>
		<link>http://andialbrecht.wordpress.com/2009/10/01/crunchyfrog-0-4-1-released/</link>
		<comments>http://andialbrecht.wordpress.com/2009/10/01/crunchyfrog-0-4-1-released/#comments</comments>
		<pubDate>Thu, 01 Oct 2009 08:01:36 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[CrunchyFrog]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[PyGTK]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=326</guid>
		<description><![CDATA[A new bugfix release for CrunchyFrog is out in the wild. There are not much visible changes, but a few bug fixes under the hood. One visible change is the option to give the SQL editor much more space by hiding all other UI elements in the main window. Here&#8217;s a screenshot:
The second screenshot shows [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=326&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p><span style="background-color:#ffffff;">A new bugfix release for <a title="CrunchyFrog Homepage" href="http://crunchyfrog.googlecode.com">CrunchyFrog</a> is out in the wild. There are not much visible changes, but a few bug fixes under the hood. One visible change is the option to give the SQL editor much more space by hiding all other UI elements in the main window. Here&#8217;s a screenshot:</span></p>
<div id="attachment_327" class="wp-caption aligncenter" style="width: 310px"><a href="http://andialbrecht.files.wordpress.com/2009/10/cf_full_editor.png"><img class="size-medium wp-image-327" title="cf_full_editor" src="http://andialbrecht.files.wordpress.com/2009/10/cf_full_editor.png?w=300&#038;h=200" alt="SQL editor eating up all space" width="300" height="200" /></a><p class="wp-caption-text">SQL editor eating up all space</p></div>
<p>The second screenshot shows the new highlighting of errors in the editor. In this case there&#8217;s no table called &#8220;actor&#8221;. When the execution of a statement results in a SQL error, CrunchyFrog now tries to find the position of the error in the SQL editor, highlights the corresponding part in your SQL statement and moves the cursor to the right place. I found it very handy to have the cursor in place to correct the error and run the statement again without searching the error before.</p>
<div id="attachment_328" class="wp-caption aligncenter" style="width: 310px"><a href="http://andialbrecht.files.wordpress.com/2009/10/cf_errors.png"><img class="size-medium wp-image-328" title="cf_errors" src="http://andialbrecht.files.wordpress.com/2009/10/cf_errors.png?w=300&#038;h=200" alt="ups... an error occurred" width="300" height="200" /></a><p class="wp-caption-text">ups... an error occurred</p></div>
<p>If you&#8217;re interested in the other, non-visible changes, please read the <a title="Change Log" href="http://packages.python.org/crunchyfrog/changes.html#release-0-4-1-oct-1-2009">full list of changes</a> or just go straight to the<a href="http://code.google.com/p/crunchyfrog/"> project page</a> and give it a try :)</p>
<p>Thanks to everyone who contributed to this release by sending patches and submitting issues on the <a title="Issue Tracker" href="http://code.google.com/p/crunchyfrog/issues/list">tracker</a>!</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/326/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/326/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/326/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/326/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/326/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/326/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/326/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/326/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/326/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/326/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=326&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/10/01/crunchyfrog-0-4-1-released/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>

		<media:content url="http://andialbrecht.files.wordpress.com/2009/10/cf_full_editor.png?w=300" medium="image">
			<media:title type="html">cf_full_editor</media:title>
		</media:content>

		<media:content url="http://andialbrecht.files.wordpress.com/2009/10/cf_errors.png?w=300" medium="image">
			<media:title type="html">cf_errors</media:title>
		</media:content>
	</item>
		<item>
		<title>&#8220;Thank you, Mark&#8221;</title>
		<link>http://andialbrecht.wordpress.com/2009/07/03/thank-you/</link>
		<comments>http://andialbrecht.wordpress.com/2009/07/03/thank-you/#comments</comments>
		<pubDate>Fri, 03 Jul 2009 06:18:25 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[Swing Low]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=307</guid>
		<description><![CDATA[The quote in the title is something that Dana Colley said after giving credits to the members of Orchestra Morphine on some show circulating through the internet about nine years ago. I think this show is still available on the HI-N-DRY website and I strongly recommend to buy it, it&#8217;s a fascinating recording.
I&#8217;ve choosen this [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=307&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>The quote in the title is something that Dana Colley said after giving credits to the members of Orchestra Morphine on some show circulating through the internet about nine years ago. I think this show is still available on the HI-N-DRY website and I strongly recommend to buy it, it&#8217;s a fascinating recording.</p>
<p>I&#8217;ve choosen this quote because it expresses what I&#8217;m thinking today, the 10th anniversary of <a href="http://www.bostonphoenix.com/archive/music/99/07/08/MARK_SANDMAN.html">Mark Sandman&#8217;s passing</a>. His music shaped me more than any other music I&#8217;m listening too. He had that idea of a unique sound, not to mention the great lyrics.  It&#8217;s not easy to explain to someone who never listened to Morphine what this idea is about. It&#8217;s not even easy to explain it to people that had to hear quite a lot of Morphine the past ten (and more) years (many, many thanks to my family for their patience with my musical taste they call &#8220;a bit strange&#8221; from time to time :)&#8230;<span id="more-307"></span></p>
<p>There&#8217;s always something good about unique and innovative ideas: They make their way through time. Without a doubt, Mark died way too early. But his musical concept is still present today. Just have a look at Twinemen, A.K.A.C.O.D., Orchestra Morphine and many other artists and bands doing covers and claiming Marks musical ideas as a major influence for their own ideas.</p>
<p>I discoverd Morphine through a good friend of mine. He visited me after a travel to Canada somewhere in the late 90&#8217;s and had those records in his hand, just saying &#8220;Listen to this.&#8221; That fundamentally <a title="Listen yourself" href="http://www.youtube.com/watch?v=M34iZH4-qkI">changed the way</a> I was listening to music up to today. It was this mixture of blues, jazz, rock and poetry carried through a suprising soundscape of a 2-string slide bass, baritone saxophone and a straight and driving drum.  Unfortunately I&#8217;ve never had the chance to see them live. We were about to see them in Brugge in July &#8216;99, but that show never happened.</p>
<p>I&#8217;ve met a few people since that who found the Morphine sound and lyrics &#8220;so charmingly depressive&#8221;. But, hey, I&#8217;m not sure if they really listened&#8230; :) One thing I really like about the lyrics in Sandman songs is that there&#8217;s always something that spontaneously comes to your mind in certain situations. Here, it is to just say &#8220;Yes&#8221; (in terms of &#8220;get in your go-cart and go, little sister&#8221;).</p>
<p>A funny side-note regarding the lyrics: A few years ago I wrote my thesis about fortune in medieval Arthurian literature and after a busy day reading manuscripts, making notes und trying to understand the different concepts of fortune that can be found, I&#8217;ve put the B-Sides into my player and found myself laughing out loud, Mark summarizes them all in &#8220;Lucky Day&#8221;:</p>
<blockquote><p>Now I&#8217;m down a little in fact I&#8217;m down a lot<br />
I&#8217;m on a roller coaster ride that I can&#8217;t stop<br />
My luck has changed but she&#8217;ll come back<br />
That&#8217;s the beauty of a game of chance<br />
I can&#8217;t loose forever but I&#8217;m doomed to try<br />
Keep on hearing a voice inside<br />
Players win and winners play, have a lucky day</p></blockquote>
<p>There&#8217;s both the concept of being at almighty Fortune&#8217;s mercy and the concept of getting rid of her by using your own virtue condensed in a few lines while I was reading verse over verse to find a subtle phrase for it.</p>
<p>BTW, the version of &#8220;Lucky Day&#8221; found on the B-Sides is one of my favorites.  The instrumental part preceeding these lines is incredibly powerfull and one of the most astonishing bits of music I&#8217;ve ever heard.</p>
<p>So, let me close with saying thanks too. Thanks to the folks at <a href="http://www.hindry.com">HI-N-DRY</a> and the <a href="http://www.firstgiving.com/sandmanmusicproject">Mark Sandman Music Project</a> for spreading a musical idea over years now, keeping up the memory of Mark Sandman and of course for the honest and good music they make. And thanks to Mark for giving his potion of uniqueness and sharing his thoughts and insights in an fascinating musical way. I&#8217;m looking forward to the first downloads on HI-N-DRY today featuring covers of Sandman songs, poetry and unreleased songs from the depths of the HI-N-DRY archive!</p>
<blockquote><p>With mistakes yea mistakes and sudden inspirations<br />
Edges corners explosions convections<br />
All fast through a slow motion landscape</p></blockquote>
<p>P.S.: The lyrics are copied from <a href="http://lukin.com/tos/">The Other Side</a>, kudos to Ian Hadfield for keeping this site up!</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/307/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/307/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/307/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/307/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/307/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/307/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/307/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/307/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/307/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/307/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=307&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/07/03/thank-you/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>
	</item>
		<item>
		<title>Working on hgsvn</title>
		<link>http://andialbrecht.wordpress.com/2009/06/20/working-on-hgsvn/</link>
		<comments>http://andialbrecht.wordpress.com/2009/06/20/working-on-hgsvn/#comments</comments>
		<pubDate>Sat, 20 Jun 2009 19:55:18 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[hgsvn]]></category>
		<category><![CDATA[Mercurial]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=278</guid>
		<description><![CDATA[Yesterday I&#8217;ve taken over maintenance of hgsvn from it&#8217;s original author Antoine Pitrou. Many thanks to Antoine at this point for doing a great job on this tool!
In case you don&#8217;t know, hgsvn is &#8220;a set of scripts to work locally on Subversion checkouts using Mercurial.&#8221;
My aim is not to make these scripts a full-blown [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=278&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>Yesterday I&#8217;ve taken over maintenance of <a title="hgsvn on PyPI" href="http://pypi.python.org/pypi/hgsvn/">hgsvn</a> from it&#8217;s original author Antoine Pitrou. Many thanks to Antoine at this point for doing a great job on this tool!</p>
<p>In case you don&#8217;t know, hgsvn is &#8220;a set of scripts to work locally on Subversion checkouts using Mercurial.&#8221;</p>
<p>My aim is not to make these scripts a full-blown Subversion extensions for Mercurial. <a title="hgsubversion on bitbucket.org" href="http://bitbucket.org/durin42/hgsubversion/wiki/Home">hgsubversion</a> or the <a title="Using the convert extension for SVN+hg" href="http://www.selenic.com/mercurial/wiki/WorkingWithSubversion#With_Convert_extension">convert extension </a>will probably do a much better job with tighter integration as it&#8217;s in the scope of hgsvn. So the main focus is on fixing bugs and to finish and release the hgpushsvn script that commits changes in your Mercurial repository back to Subversion.</p>
<p>The reason why I decided to work on this project is pretty simple: I&#8217;ve made good experiences with it and I still want to keep using it.</p>
<p>With hgpushsvn the tool provides three scripts for the most basic tasks:</p>
<p><code>hgimportsvn</code> initializes a hg repository and fetches the sources from svn, optionally starting at a specific revision.</p>
<p><code>hgpullsvn</code> pulls new change sets from svn to your local hg repository.</p>
<p><code>hgpushsvn</code> pushes back your local commits to the svn repository.</p>
<p>Using hgsvn for a while now, I&#8217;m feeling very comfortable with these scripts and they fit very well into my workflow.</p>
<p>The next things I&#8217;m about to do are:</p>
<ul>
<li>fix the unittests, at least one is broken ATM</li>
<li>setup a buildbot to have some automated tests</li>
<li>finish hgpullsvn</li>
</ul>
<p>If you&#8217;re using hgsvn, please file bug reports or submit patches on the <a title="hgsvn's issue tracker" href="http://bitbucket.org/andialbrecht/hgsvn/issues/">issue tracker</a>.</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/278/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/278/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/278/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/278/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/278/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/278/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/278/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/278/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/278/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/278/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=278&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/06/20/working-on-hgsvn/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>
	</item>
		<item>
		<title>How Do You Look When Merging Fails ;-)</title>
		<link>http://andialbrecht.wordpress.com/2009/05/09/when-merging-fails/</link>
		<comments>http://andialbrecht.wordpress.com/2009/05/09/when-merging-fails/#comments</comments>
		<pubDate>Sat, 09 May 2009 06:15:59 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[/dev/null]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Mercurial]]></category>
		<category><![CDATA[Twitter]]></category>
		<category><![CDATA[webcam]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=257</guid>
		<description><![CDATA[There was a Simpsons episode, I can&#8217;t recall correctly, but I think Bart recorded Lisa when her heart breaked and he watched it in slow motion to stop exactly at that point.
I thought of this episode yesterday while playing around with my laptop&#8217;s webcam and a Python shell. Finally I wrote a little fun script [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=257&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>There was a Simpsons episode, I can&#8217;t recall correctly, but I think Bart recorded Lisa when her heart breaked and he watched it in slow motion to stop exactly at that point.</p>
<p>I thought of this episode yesterday while playing around with my laptop&#8217;s webcam and a Python shell. Finally I wrote a little fun script that does almost the same: Just register it as a hg hook and it takes a picture of you exactly at the unique moment when merging fails and it sends it directly and without any further questions to <a href="http://twitpic.com/">Twitpic</a> and <a href="http://twitter.com">Twitter</a>:</p>
<pre class="brush: python;">
#!/usr/bin/env python

import os
import sys
import tempfile
import time

from CVtypes import cv
from twitpic import TwitPicAPI

DEVICE = 0
TWITTER_USER = 'xxx'  # CHANGE THIS!
TWITTER_PWD = 'xxx'   # CHANGE THIS!
# This is the time in seconds you need to realize that the merge has
# failed. When setting this consider that it already takes about a second
# for the camera to take the picture. &quot;0&quot; means no delay ;-)
EMOTIONAL_SLUGGISHNESS_RATE = 0.0

def grab_image(fname):
  camera = cv.CreateCameraCapture(DEVICE)
  frame = cv.QueryFrame(camera)
  cv.SaveImage(fname, frame)

def how_do_you_look():
  failed = bool(os.environ.get('HG_ERROR', 0))
  if not failed:
    return  # hmpf, maybe next time...
  fd, fname = tempfile.mkstemp('.jpg')
  if EMOTIONAL_SLUGGISHNESS_RATE &gt; 0:
    time.sleep(EMOTIONAL_SLUGGISHNESS_RATE)
  grab_image(fname)
  twit = TwitPicAPI(TWITTER_USER, TWITTER_PWD)
  retcode = twit.upload(fname, post_to_twitter=True,
                        message='Another merge failed.')
  os.remove(fname)

if __name__ == '__main__':
  how_do_you_look()
</pre>
<p>You&#8217;ll need the <a title="Download CVTypes from Sourceforge" href="http://sourceforge.net/project/showfiles.php?group_id=82407&amp;package_id=232299">CVtypes</a> OpenCV wrapper and this <a title="python-twitpic Project Page" href="http://code.google.com/p/python-twitpic/">Twitpic Python module</a>. I&#8217;ve patched the twitpic module to support messages. Have a look at <a href="http://code.google.com/p/python-twitpic/issues/detail?id=2">this issue</a> if it&#8217;s already supported, otherwise a diff that adds the message keyword is attached to the issue. To use it as a Mercurial hook just add to .hg/hgrc:</p>
<pre>[hooks]
update =  /path/to/the/above/script.py</pre>
<p>and make the script executable.</p>
<p>The results are pretty good :)</p>
<p style="text-align:center;"><a title="Another merge failed. on Twitpic" href="http://twitpic.com/4txls"><img class="aligncenter" src="http://twitpic.com/show/thumb/4txls.jpg" alt="Another merge failed. on Twitpic" width="150" height="150" /></a></p>
<p>Have fun!</p>
<p>BTW, the way how to access the camera is inspired by <a title="More camera fun" href="http://blog.jozilla.net/2008/06/27/fun-with-python-opencv-and-face-detection/">this nice blog post about face recognition</a> using OpenCV.</p>
<p><strong>Edit (2009-05-12):</strong> It was Ralph, not Lisa. Thanks Florian!</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/257/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/257/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/257/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/257/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/257/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/257/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/257/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/257/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/257/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/257/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=257&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/05/09/when-merging-fails/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>

		<media:content url="http://twitpic.com/show/thumb/4txls.jpg" medium="image">
			<media:title type="html">Another merge failed. on Twitpic</media:title>
		</media:content>
	</item>
		<item>
		<title>App Engine Tracebacks via E-Mail</title>
		<link>http://andialbrecht.wordpress.com/2009/04/30/app-engine-tracebacks-via-email/</link>
		<comments>http://andialbrecht.wordpress.com/2009/04/30/app-engine-tracebacks-via-email/#comments</comments>
		<pubDate>Thu, 30 Apr 2009 18:51:14 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[App Engine]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=241</guid>
		<description><![CDATA[Edit 2009-09-04: Starting with App Engine SDK 1.2.5 released today there&#8217;s now a much smarter approach. See the docstring of the ereporter module for details!
Django&#8217;s error reporting via e-mail is something I&#8217;ve missed for my App Engine applications. If you&#8217;ve followed the instructions on how to use Django on App Engine errors are already logged. [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=241&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p><strong>Edit 2009-09-04:</strong> <em>Starting with App Engine SDK 1.2.5 </em><a href="http://googleappengine.blogspot.com/2009/09/app-engine-sdk-125-released-for-python.html"><em>released today</em></a><em> there&#8217;s now a much smarter approach. See the </em><a href="http://code.google.com/p/googleappengine/source/browse/trunk/python/google/appengine/ext/ereporter/ereporter.py"><em>docstring of the ereporter module</em></a><em> for details!</em></p>
<p>Django&#8217;s <a href="http://docs.djangoproject.com/en/dev/howto/error-reporting/#howto-error-reporting">error reporting via e-mail</a> is something I&#8217;ve missed for my App Engine applications. If you&#8217;ve followed the instructions on <a title="Using Django 1.0 on App Engine with Zipimport" href="http://code.google.com/appengine/articles/django10_zipimport.html">how to use Django on App Engine</a> errors are already logged. But honestly, I don&#8217;t check the logs very often and sometimes it&#8217;s just a silly typo that&#8217;s fixed in a minute ;-)</p>
<p>So if you&#8217;ve followed the instructions you can simply extend the <tt>log_exception</tt> function to send an email after the exception is logged:</p>
<pre class="brush: python;">
def log_exception(*args, **kwds):
  &quot;&quot;&quot;Django signal handler to log an exception.&quot;&quot;&quot;
  excinfo = sys.exc_info()
  cls, err = excinfo[:2]
  subject = 'Exception in request: %s: %s' % (cls.__name__, err)
  logging.exception(subject)

  # Send an email to the admins
  if 'request' in kwds:
    try:
      repr_request = repr(kwds['request'])
    except:
      repr_request = 'Request repr() not available.'
  else:
    repr_request = 'Request not available.'
  msg = ('Application: %s\nVersion: %s\n\n%s\n\n%s'
         % (os.getenv('APPLICATION_ID'), os.getenv('CURRENT_VERSION_ID'),
            ''.join(traceback.format_exception(*excinfo)),
            repr_request))
  subject = '[%s] %s' % (os.getenv('APPLICATION_ID'), subject)
  mail.send_mail_to_admins('APP_ADMIN@EXAMPLE.COM',
                           subject,
                           msg)
</pre>
<p>Make sure to replace <em>&#8220;APP_ADMIN@EXAMPLE.COM&#8221;</em> with the email address of an admin for this application and to add these two imports somewhere at the top of your <tt>main.py</tt>:</p>
<pre class="brush: python;">
import traceback
from google.appengine.api import mail
</pre>
<p>You don&#8217;t need to comment this when developing your app to avoid lots of useless mails. The dev server doesn&#8217;t fully implement <tt>send_mail_to_admins()</tt> most likely as there&#8217;s no real concept of an designated admin user in the SDK. It just writes a short message to the log, even if the sendmail option is enabled.</p>
<p>P.s.: If you use webapp instead of Django have a look at the <a title="Best Practices - Building a Production Quality Application on Google App Engine" href="http://sites.google.com/site/io/best-practices---building-a-production-quality-application-on-google-app-engine">seventh slide of this presentation</a> by Ken Ashcraft. He shows a similar implementation for webapp based applications.</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/241/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/241/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/241/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/241/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/241/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/241/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/241/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/241/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/241/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/241/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=241&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/04/30/app-engine-tracebacks-via-email/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>
	</item>
		<item>
		<title>SQL Parsing with Python, Pt. II</title>
		<link>http://andialbrecht.wordpress.com/2009/03/29/sql-parsing-with-python-pt-ii/</link>
		<comments>http://andialbrecht.wordpress.com/2009/03/29/sql-parsing-with-python-pt-ii/#comments</comments>
		<pubDate>Sun, 29 Mar 2009 15:39:49 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[lexer]]></category>
		<category><![CDATA[parser]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=189</guid>
		<description><![CDATA[After I&#8217;ve described some basics in part I let&#8217;s have a closer look to the actual Python module called sqlparse. First off, have a look at the project page on how to download and install this module in case you&#8217;re interested. But now, let&#8217;s have some fun&#8230;
The API of the module is pretty simple. It [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=189&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>After I&#8217;ve described some basics in <a href="http://andialbrecht.wordpress.com/2009/03/29/sql-parsing-with-python-pt-i/">part I</a> let&#8217;s have a closer look to the actual Python module called <code>sqlparse</code>. First off, have a look at the <a href="http://code.google.com/p/python-sqlparse/">project page</a> on how to download and install this module in case you&#8217;re interested. But now, let&#8217;s have some fun&#8230;</p>
<p>The API of the module is pretty simple. It provides three top-level functions on module level: <tt>sqlparse.split(sql)</tt> splits <em>sql</em> into separate statements, <tt>sqlparse.parse(sql)</tt> parses <em>sql</em> and returns a tree-like structure and <tt>sqlparse.format(sql, **kwds)</tt> returns a beautified version of <em>sql</em> according to <em>kwds</em>.</p>
<p>As mentioned in my previous post, what effort needs to be done to build the return values depends on what lexing and parsing work is needed to find the result. For example <tt>sqlparse.split()</tt> does the following:</p>
<ul>
<li>Generate a token-type/value stream basically with a copy of Pygments lexer</li>
<li>Apply a filter (in terms of a Pygments stream filter) to find statements</li>
<li>Serialize the token stream back to unicode</li>
</ul>
<p><tt>sqlparse.parse()</tt> does all the grouping work and <tt>sqlparse.format()</tt> runs through those groups, modifies them according to the given formatting rules and finally converts it back to unicode.</p>
<p>Here&#8217;s an example session in a Python shell:</p>
<pre class="brush: python;">
&gt;&gt;&gt; import sqlparse
&gt;&gt;&gt; # Splitting statements:
&gt;&gt;&gt; sql = 'select * from foo; select * from bar;'
&gt;&gt;&gt; sqlparse.split(sql)
&lt;&gt; # Formatting statemtents:
&gt;&gt;&gt; sql = 'select * from foo where id in (select id from bar);'
&gt;&gt;&gt; print sqlparse.format(sql, reindent=True, keyword_case='upper')
SELECT *
FROM foo
WHERE id IN
  (SELECT id
   FROM bar);

&gt;&gt;&gt; # Parsing
&gt;&gt;&gt; sql = 'select * from &quot;someschema&quot;.&quot;mytable&quot; where id = 1'
&gt;&gt;&gt; res = sqlparse.parse(sql)
&gt;&gt;&gt; res
&lt;&lt;&gt;&gt; stmt = res[0]
&gt;&gt;&gt; stmt.to_unicode()  # converting it back to unicode
&lt;&lt;&lt;u&gt;&gt;&gt; # This is how the internal representation looks like:
&gt;&gt;&gt; stmt.tokens
&lt;&lt;&gt;&gt;
</pre>
<p>Now, how does the grouping work? Grouping is done with a set of simple functions. Each function searches for a simple pattern and if it finds one a new group is built. Let&#8217;s have a look at the function that finds the WHERE clauses.</p>
<pre class="brush: python;">
def group_where(tlist):
    [group_where(sgroup) for sgroup in tlist.get_sublists()
     if not isinstance(sgroup, Where)]
    idx = 0
    token = tlist.token_next_match(idx, T.Keyword, 'WHERE')
    stopwords = ('ORDER', 'GROUP', 'LIMIT', 'UNION')
    while token:
        tidx = tlist.token_index(token)
        end = tlist.token_next_match(tidx+1, T.Keyword, stopwords)
        if end is None:  # WHERE is at the end of the statement
            end = tlist.tokens[-1]
        else:
            end = tlist.tokens[tlist.token_index(end)-1]
        group = tlist.group_tokens(Where, tlist.tokens_between(token, end))
        idx = tlist.token_index(group)
        token = tlist.token_next_match(idx, T.Keyword, 'WHERE')
</pre>
<p><em>tlist</em> is a list of tokens, possible subgroups are handled first (bottom-up approach). Then it grabs the first occuring &#8220;WHERE&#8221; and looks for the next matching stop word. I&#8217;m pretty unsure if the stop words approach is right here, but at least it works for now&#8230; If it finds a stop word, a group using the class <tt>Where</tt> is created, the tokens between WHERE and the stop word are attached to it and &#8211; still within the while loop &#8211; the next WHERE keyword is used as the next starting point.</p>
<p>So why not use a grammar here? At first, this piece of code is pretty simple and easy to maintain. But it can also handle grammatically incorrect statements more lazily, e.g. it&#8217;s no problem to add an if clause that &#8211; when for example an unexpected token occurs &#8211; the function just jumps to the next occurance of WHERE without changing anything or even raising an exception. To achieve this the token classes provide helper functions to inspect the surroundings of an occurrence (in fact, just simple list operations). There&#8217;s no limitation what a grouping function can do with the given token list, so you could even &#8220;guess&#8221; a proper group with some nasty algorithm.</p>
<p>The current problem with this approach is performance. Here are some numbers:</p>
<table style="text-align:center;" border="0" cellspacing="5" cellpadding="0">
<tbody>
<tr>
<th>Size</th>
<th>split()</th>
<th>parse()</th>
</tr>
<tr>
<td>100kb (20600 tokens)</td>
<td>0.3 secs.</td>
<td>1.8 secs.</td>
</tr>
<tr>
<td>1.9MB (412500 tokens)</td>
<td>5.53 secs.</td>
<td>37 secs.</td>
</tr>
</tbody>
</table>
<p>Most of the performance is lost when giving up the stream-oriented approach in the parsing phase. The numbers are based on the first unrevised working version. I expect performance improvents especially in the way how token lists are handled behind the scenes with upcoming versions. For real life statements the parser behaves quite well. BTW, the Pygments lexer takes about 6 seconds (compared to 5.5 secs. for splitting) for the 1.9MB of SQL.</p>
<p>The non-validating approach is a disadvantage too. You&#8217;ll never know if a statement is valid. You can even parse <a href="http://books.google.com/books?id=fRaLfuvzem8C&amp;dq=hartmann+iwein&amp;printsec=frontcover&amp;source=bl&amp;ots=8xNAgOe5DW&amp;sig=jOXtB-oEB7GZHBfRqqLHlqG_GCM&amp;hl=en&amp;ei=EJXPSY6_N4OP-Abhy_DUBw&amp;sa=X&amp;oi=book_result&amp;resnum=1&amp;ct=result#PPA3,M1">middle high german phrases</a> and receive a result:</p>
<pre class="brush: python;">
&gt;&gt;&gt; sqlparse.parse('swer an rehte güete wendet sîn gemüete')[0].tokens
&lt;&lt;&gt;&gt;
</pre>
<p>It&#8217;s up to the user of this module to provide suitable input and to interpret the output. Furthermore the parser only supports not every nifty edge of an SQL dialect. Currently it&#8217;s mostly ANSI-SQL with some PostgreSQL specific stuff. But it should be easy to implement further grouping functions to provide more SQL varieties.</p>
<p>The splitting feature is currently used by <a href="http://crunchyfrog.googlecode.com/svn/tags/0.3.4/utils/command/build_manpage.py">CrunchyFrog</a> and does a pretty good job there. I assume that SQL splitting works stable and reliable in most cases. Beautifiying and parsing is very new in the module and full functionality needs to be proven with time. Luckily the top-level API with it&#8217;s three functions is damn simple and keeps the doors open for significant changes behind the scenes if they&#8217;re needed.</p>
<p><span style="text-decoration:line-through;">The sources of the sqlparse module are currently hosted on <a href="http://github.com/andialbrecht/python-sqlparse">github.com</a> but may move to Google Code anytime soon.</span> Refer to the <a href="http://code.google.com/p/python-sqlparse/">project page</a> on Google Code for downloads and how to access the sources.</p>
<p>In addition there&#8217;s a <a href="http://sqlformat.appspot.com/">simple AppEngine application</a> that exposes the formatting features as an online service.</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/189/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/189/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/189/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/189/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/189/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/189/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/189/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/189/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/189/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/189/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=189&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/03/29/sql-parsing-with-python-pt-ii/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>
	</item>
		<item>
		<title>SQL Parsing with Python, Pt. I</title>
		<link>http://andialbrecht.wordpress.com/2009/03/29/sql-parsing-with-python-pt-i/</link>
		<comments>http://andialbrecht.wordpress.com/2009/03/29/sql-parsing-with-python-pt-i/#comments</comments>
		<pubDate>Sun, 29 Mar 2009 10:53:07 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[lexer]]></category>
		<category><![CDATA[parser]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=145</guid>
		<description><![CDATA[Some time ago I was in search for some kind of SQL parser module for Python. As you can guess, my search wasn&#8217;t really successfull. pyparsing needs a grammar, but I&#8217;m not really interested in writing a full-blown grammar for SQL with it&#8217;s various dialects. AFAICT Gadfly implements a customized SQL parser, but I was [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=145&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>Some time ago <a href="http://andialbrecht.wordpress.com/2008/09/11/on-crunchyfrog-03/">I was in search</a> for some kind of SQL parser module for Python. As you can guess, my search wasn&#8217;t really successfull. <a href="http://pyparsing.wikispaces.com/">pyparsing</a> needs a grammar, but I&#8217;m not really interested in writing a full-blown grammar for SQL with it&#8217;s various dialects. AFAICT <a href="http://cvs.zope.org/*checkout*/Packages/GadflyDA/gadfly/gadfly.html">Gadfly</a> implements a customized SQL parser, but I was not able to figure out how to use it. And there&#8217;s <a href="http://pypi.python.org/pypi/sqlliterals/">sqlliterals</a>, but as the name suggests it&#8217;s just for identifying literals within statements.</p>
<p>I expect such a module to do the following:</p>
<ul>
<li>It should be fast ;-)</li>
<li>scalable &#8211; what the parser needs to know about a string containing SQL statements depends on what I want to do with it. If I just want to split that string in separate statements I don&#8217;t need to know as much as when I want to know what identifiers occur in it.</li>
<li>non-validating -  Parsing shouldn&#8217;t fail if the statement is syntactically incorrect as I want to use it for an SQL editor where the statements are (naturally) incorrect most of the time.</li>
<li>some beautifying is a nice-to-have &#8211; We all know SQL generated by a script and copied from a logging output to a database front-end &#8211; it always looks ugly and is pretty unreadable.</li>
</ul>
<p>The only thing I&#8217;ve found that comes close to my needs is <a href="http://pygments.org/">Pygments</a> (yes, the syntax highlighter). It does a really good job highlighting SQL, so it &#8220;understands&#8221; at least something. To be concrete, it has a rock-solid and fast lexer. There&#8217;s a <a href="http://pygments.org/faq/#understand-code">FAQ</a> entry if it&#8217;s possible to use Pygments for progamming language processing. The answer is:</p>
<blockquote><p>The Pygments lexing machinery is quite powerful can be used to build <strong><em>lexers</em></strong> for basically all languages. However, <strong><em>parsing</em></strong> them is not possible, though some      lexers go some steps in this direction in order to e.g. highlight function names differently.</p>
<p>Also, error reporting is not the scope of Pygments. It focuses on correctly highlighting      syntactically valid documents, not finding and compensating errors.</p></blockquote>
<p>There&#8217;s a nice distinction between lexers and parser in this answer. The former does lexical and the latter syntactical analysis of a given stream of characters. For my needs this led to a two step process: first to do the lexing using Pygments mechanism (BTW, read &#8220;focuses on [...] syntactically valid documents, not finding [...] errors&#8221; as &#8220;non-validating&#8221;) and then to add parsing on top of the token stream.</p>
<p>So I stripped the lexing mechanism for SQL out of Pygments to get rid of some overhead not needed for my purposes (e.g. loading of extensions). The only changes to the Pygments tokenizer was to replace a huge regex for finding keywords by a dictionary-based lookup and to add a few new tokens to make live easier in the second processing stage. The achieved performance improvement by replacing the regex doesn&#8217;t play a significant role for Pygments, it just speeds up the lexing a bit.</p>
<p>In the second step the very efficient token-type/value stream generated in step 1 is grouped together in nested lists of simple classes. Instantiating a bunch of classes results in a performance loss, but with the benefit of some helpful methods for analyzing the now classified tokens. As the classes are pretty simple most of the performance loss was recaptured by using <a href="http://docs.python.org/glossary.html#term-slots">slots</a>.</p>
<p>The grouping mechanism is again non-validating. It tries to find patterns like identifiers and their aliases or WHERE clauses and their conditions in the token stream, but it just leaves the tokens unchanged if it doesn&#8217;t find what it&#8217;s looking for. So it actually defines some sort of &#8220;grammar&#8221;, but very unrestrictive. That means that the quality of answers to questions like &#8220;Is there a WHERE clause?&#8221; or &#8220;Which tables are affected?&#8221; heavily depends on the syntactical clearness of the given statement at the time when it&#8217;s processed and it&#8217;s up to the user or application using the module to intrepet the answers right. As the module is seen as a helper during the develoment process of SQL statements it doesn&#8217;t insist on syntactical correctness as the targeted database system would do when the statement is executed.</p>
<p>All in all these are the two (simple) processing steps:</p>
<ol>
<li>Lexing a given string using regular expressions and generate a token-type/value stream (in fact, this is the Pygments lexer).</li>
<li>Parsing
<ol>
<li>Instantiate a simple token class for each item in the stream to ease syntactical analysis.</li>
<li>Group those tokens in nested lists by using simple functions by finding patterns.</li>
</ol>
</li>
</ol>
<p>Depending on what should be done with the statements, only the first step is required. For example, splitting statements or simple formatting rules don&#8217;t require parsing.</p>
<p><a href="http://andialbrecht.wordpress.com/2009/03/29/sql-parsing-with-python-pt-ii/">Pt. II</a> of this blog post will cover the two remaining points: scalibality and the beautifiying goodie. And it will give a closer look at the  module I came up with, including some performance stats. For the impatient: the source code of this module is available <a href="http://code.google.com/p/python-sqlparse/">here</a>.</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/145/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/145/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/145/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/145/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/145/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/145/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/145/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/145/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/145/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/145/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=145&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/03/29/sql-parsing-with-python-pt-i/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>
	</item>
		<item>
		<title>Creating Man Pages Using optparse and distutils</title>
		<link>http://andialbrecht.wordpress.com/2009/03/17/creating-a-man-page-with-distutils-and-optparse/</link>
		<comments>http://andialbrecht.wordpress.com/2009/03/17/creating-a-man-page-with-distutils-and-optparse/#comments</comments>
		<pubDate>Tue, 17 Mar 2009 08:55:09 +0000</pubDate>
		<dc:creator>Andi Albrecht</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[distutils]]></category>
		<category><![CDATA[man page]]></category>
		<category><![CDATA[optparse]]></category>

		<guid isPermaLink="false">http://andialbrecht.wordpress.com/?p=54</guid>
		<description><![CDATA[This blog post describes how to generate a simple man page during build time for Python applications using distutils and optparse. Technically this post describes how to write a custom HelpFormatter and a custom build command.<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=54&subd=andialbrecht&ref=&feed=1" />]]></description>
			<content:encoded><![CDATA[<div class='snap_preview'><br /><p>This blog post describes how to generate a simple man page during build time for Python applications using <tt>distutils</tt> and <tt>optparse</tt>. Technically this post describes how to write a custom HelpFormatter and a custom build command.</p>
<p>For GUI applications command line options usually bother me not much. Just in some rare cases, mainly when debugging or feeling unlucky with the startup behavior, I&#8217;m interested in these options. Even then I prefer to use &#8220;&#8211;help&#8221;. BTW, for the &#8220;real&#8221; command line tools like <tt>sed</tt>, <tt>ls</tt> or <tt>grep</tt> this is totally different.</p>
<p>So, due to my lack of interest in man pages for GUI applications I&#8217;ve shipped the previous release of <a title="CrunchyFrog's Homepage" href="http://crunchyfrog.googlecode.com">CrunchyFrog</a> along with a totally out-dated man page. Moreover I really don&#8217;t like writing man pages or keeping them in sync with the actual command line options already documented by OptionParser&#8217;s help features&#8230;<br />
<span id="more-54"></span></p>
<p>Did I say <cite>&#8220;already documented&#8221;</cite>? I&#8217;m not a friend of doing things twice, reusing that &#8220;already documented&#8221; should be very pleasant to go to get rid of my problem.</p>
<h2>What To Put On A Man Page?</h2>
<p>The short answer is: It should mention the application&#8217;s name, it&#8217;s purpose, how to start it, who wrote it, where to find additional information and what command line options it provides. Or, in terms of man page sections: &#8220;NAME&#8221;, &#8220;SYNOPSIS&#8221;, &#8220;DESCRIPTION&#8221;, &#8220;OPTIONS&#8221;, &#8220;SEE ALSO&#8221; and &#8220;AUTHOR&#8221; (as you may already have noticed, I&#8217;m focussing just on man page (1) in this post). Of course, there are more possible sections, but after inspecting some randomly choosen man pages for GUI applications on my system, it seems that those sections are the most common ones.</p>
<p>(If you are interested in the full answer, read the <a title="Linux Man Page Howto" href="http://tldp.org/HOWTO/Man-Page/q3.html">Linux Man Page Howto</a>.)</p>
<p>Now back to our Python application that uses <tt>optparse</tt> to provide command line options.</p>
<p><tt>optparse</tt> comes with a pretty good out-of-the-box <a href="http://docs.python.org/library/optparse.html#generating-help">help feature</a>. As stated in the docs the primary purpose is to assist in <cite>creating user-friendly command-line interfaces</cite> and not to generate help documents (like man pages). But the ability to write a custom <tt>HelpFormatter</tt> makes it easy to generate such documents.</p>
<h2>Writing a Custom HelpFormatter</h2>
<p>Let&#8217;s have a look at the custom formatter:</p>
<pre class="brush: python;">
class ManPageFormatter(optparse.HelpFormatter):

    def __init__(self, indent_increment=2, max_help_position=24,
	         width=None, short_first=1):
        &quot;&quot;&quot;Constructor. Unfortunately HelpFormatter is no new-style class.&quot;&quot;&quot;
        optparse.HelpFormatter.__init__(self, indent_increment,
                                        max_help_position, width, short_first)

    def _markup(self, txt):
        &quot;&quot;&quot;Prepares txt to be used in man pages.&quot;&quot;&quot;
        return txt.replace('-', '\\-')

    def format_usage(self, usage):
        &quot;&quot;&quot;Formate the usage/synopsis line.&quot;&quot;&quot;
        return self._markup(usage)

    def format_heading(self, heading):
        &quot;&quot;&quot;&quot;Format a heading.
        If level is 0 return an empty string. This usually is the string &quot;Options&quot;.
        &quot;&quot;&quot;&quot;
        if self.level == 0:
            return ''
        return '.TP\n%s\n' % self._markup(heading.upper())

    def format_option(self, option):
        &quot;&quot;&quot;Format a single option.
        The base class takes care to replace custom optparse values.
        &quot;&quot;&quot;&quot;
        result = []
        opts = self.option_strings[option]
        result.append('.TP\n.B %s\n' % self._markup(opts))
        if option.help:
            help_text = '%s\n' % self._markup(self.expand_default(option))
            result.append(help_text)
        return ''.join(result)</pre>
<p>The three methods starting with <tt>format_*</tt> do all the formatting for us. The base class has additional formatting methods, but there&#8217;s no need to overwrite them as they do nothing special except wrapping lines to the desired width.</p>
<p>To use a custom formatter either give it as an parameter to the <tt>OptionParser</tt> constructor or use</p>
<pre>parser.formatter = ManPageFormatter()
parser.formatter.set_parser(parser)</pre>
<p>if your option parser is already instantiated.</p>
<p>Now, when you call <tt>parser.format_option_help()</tt> you&#8217;ll see a man page fragment with all options (including nicely formatted option groups). Maybe you wonder why not use <tt>parser.print_help()</tt> here, which normally adds a nice usage line and the description if given to the output. The reason for that is quite simple: Many of the documentation features of <tt>optparse</tt> are optional. If there&#8217;s no usage defined, no usage line will be formatted. So using it for the SYNOPSIS section and eventually the header of the man page isn&#8217;t a good idea. Everything surrounding the OPTIONS section of the man page should be generated by something else, that takes care to include a proper header and footer. We are using the custom formatter just for the options themself.</p>
<h2>Adding a Custom Build Command</h2>
<p>To write the rest of the man page we&#8217;re using a custom build command for <tt>distutils</tt>. This command is executed when you run <code>python setup.py build</code> or <tt>install</tt> that depends on <tt>build</tt>. There&#8217;s a nice side-effect when generating the man page on build time: You can simply skip this step when the <tt>setup.py</tt> commands are run on an OS that doesn&#8217;t know what a man pages is ,-)</p>
<p>The skeleton of a <tt>distutils</tt> command looks like this:</p>
<pre class="brush: python;">
# -*- coding: utf-8 -*-

&quot;&quot;&quot;build_manpage command -- Generate man page from optparse/dist metadata.&quot;&quot;&quot;

from distutils.command.build import build
from distutils.core import Command

class build_manpage(Command):

    description = 'Generate man page.'

    user_options = []

    def initialize_options(self):
	pass

    def finalize_options(self):
	pass

    def run(self):
	# Do something useful!

build.sub_commands.append(('build_manpage', None))
</pre>
<p>As you can see, <em>&#8220;build_manpage&#8221;</em> is added as a sub-command to the build command and the build_manpage class has some methods for option handling and a run() method.</p>
<p>To use this command add the following to your setup.py:</p>
<pre>from distutils.core import setup
from build_manpage import build_manpage
setup(
    [...]
    cmdclass={'build_manpage': build_manpage}
)</pre>
<h3>Adding Command Options</h3>
<p>Generating the man page needs some options. The build_manpage commands needs to know where to write the generated man page and which option parser it should use. To achieve this we have to modify the <em>user_options</em> attribute and the two corresponding methods in our skeleton class:</p>
<pre class="brush: python;">
    user_options = [
        ('output=', 'O', 'output file'),
        ('parser=', None, 'module path to optparser (e.g. mymod:func'),
        ]                                                                       

    def initialize_options(self):
        self.output = None
        self.parser = None                                                      

    def finalize_options(self):
        if self.output is None:
            raise DistutilsOptionError('\'output\' option is required')
        if self.parser is None:
            raise DistutilsOptionError('\'parser\' option is required')
        mod_name, func_name = self.parser.split(':')
        fromlist = mod_name.split('.')
        try:
            mod = __import__(mod_name, fromlist=fromlist)
            self._parser = getattr(mod, func_name)()
        except ImportError, err:
            raise
        self._parser.formatter = ManPageFormatter()
        self._parser.formatter.set_parser(self._parser)
        self.announce('Writing man page %s' % self.output)
        self._today = datetime.date.today()
</pre>
<p><em>user_options</em> is a list of 3-tuples (long option, short option, help),  <em>initialize_options()</em> sets default values and <em>finalize_options()</em> makes sure that the given options are sane (nb, the Command class has some validation functions, unfortunately you&#8217;ll have to read the source to find them&#8230;). Run <code>setup.py build_manpage --help</code> to see how to use your options on the command line. Besides setting these options on the command line you can set them in a file called <tt>setup.cfg</tt> too:</p>
<pre>[build_manpage]
output=data/mymanpage.1
parser=myapp.somemod:get_parser</pre>
<p>Both options are required. <em>output</em> tells the command to which file it should write the generated man page, <em>parser</em> is a string pointing to a function that returns an <tt>OptionParser</tt> instance (a dotted module path with a function name separated by a single colon).</p>
<h2>Putting It All Together</h2>
<p>This is done by the run() method, of course. In finalize_options() the option parser was imported and our ManPageFormatter was attached to it. The run() method now does the following:</p>
<pre class="brush: python;">
    def run(self):                                                             
        manpage = []                                                           
        manpage.append(self._write_header())                                   
        manpage.append(self._write_options())                                  
        manpage.append(self._write_footer())                                   
        stream = open(self.output, 'w')                                        
        stream.write(''.join(manpage))                                         
        stream.close()
</pre>
<p>Let&#8217;s have a look at the _write_header() helper function (_write_options() is basically a call to the formatter and _write_footer() just adds the author and a link to the homepage):</p>
<pre class="brush: python;">
    def _write_header(self):                                                   
        appname = self.distribution.get_name()                                 
        ret = []                                                               
        ret.append('.TH %s 1 %s\n' % (self._markup(appname),                   
                                      self._today.strftime('%Y\\-%m\\-%d')))   
        description = self.distribution.get_description()                      
        if description:                                                        
            name = self._markup('%s - %s' % (self._markup(appname),            
                                             description.splitlines()[0]))     
        else:                                                                  
            name = self._markup(appname)                                       
        ret.append('.SH NAME\n%s\n' % name)                                    
        synopsis = self._parser.get_usage()                                    
        if synopsis:                                                           
            synopsis = synopsis.replace('%s ' % appname, '')                   
            ret.append('.SH SYNOPSIS\n.B %s\n%s\n' % (self._markup(appname),   
                                                      synopsis))               
        long_desc = self.distribution.get_long_description()                   
        if long_desc:                                                          
            ret.append('.SH DESCRIPTION\n%s\n' % self._markup(long_desc))      
        return ''.join(ret)
</pre>
<p>As you can see, this method makes use of another advantage of using a distutils command. It fetches some information not covered by optparse from distutils&#8217; meta data and in addition takes care of optparse&#8217;s optional <em>usage</em> and <em>description</em> attributes.</p>
<p>The complete implementation of the build_manpage command is available <a href="http://crunchyfrog.googlecode.com/svn/tags/0.3.4/utils/command/build_manpage.py">here</a>.</p>
<p>This is how the resulting man page for CrunchyFrog looks like:</p>
<p style="text-align:center;">
<div id="attachment_67" class="wp-caption aligncenter" style="width: 218px"><a href="http://andialbrecht.files.wordpress.com/2009/03/cfmanpage.jpg"><img class="size-medium wp-image-67" title="Generated Man Page for CrunchyFrog" src="http://andialbrecht.files.wordpress.com/2009/03/cfmanpage.jpg?w=208&#038;h=300" alt="Generated Man Page for CrunchyFrog" width="208" height="300" /></a><p class="wp-caption-text">Generated Man Page for CrunchyFrog</p></div>
<p>I&#8217;ll have to admit, it&#8217;s a very simple approach to have a man page without writing it and &#8211; even worse &#8211; maintaining it. But at least it solved my problem to distribute a GUI application with an up-to-date man page. This approach has no extra requirements as everything needed can be found in Python&#8217;s stdlib and the man page is generated when needed. Luckily the build command is extensible, so it&#8217;s no problem to add additional sections (what about connection the script to a bug tracker and add a hopefully not too long BUGS section?).</p>
<p>P.S.: In case your interested in another formatter example, I&#8217;ve recently written another HelpFormatter that creates a Google Code Wiki page for <a href="http://codereview.appspot.com">Rietveld</a>&#8217;s upload.py. The source are available <a href="http://crunchyfrog.googlecode.com/svn/tags/0.3.4/utils/command/build_manpage.py">here</a>.</p>
<p><strong>Edit (2009-03-25):</strong> There&#8217;s a nice <a href="http://bazaar.launchpad.net/~necoro/portato/trunk/revision/387">patch</a> written by René Neumann that adds a SEE ALSO option to the distutils command.</p>
  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/andialbrecht.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/andialbrecht.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/andialbrecht.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/andialbrecht.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/andialbrecht.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/andialbrecht.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/andialbrecht.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/andialbrecht.wordpress.com/54/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/andialbrecht.wordpress.com/54/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/andialbrecht.wordpress.com/54/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=andialbrecht.wordpress.com&blog=4169425&post=54&subd=andialbrecht&ref=&feed=1" /></div>]]></content:encoded>
			<wfw:commentRss>http://andialbrecht.wordpress.com/2009/03/17/creating-a-man-page-with-distutils-and-optparse/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/19ac36b7f91b333375f70f75c40ba1a7?s=96&#38;d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96" medium="image">
			<media:title type="html">andialbrecht</media:title>
		</media:content>

		<media:content url="http://andialbrecht.files.wordpress.com/2009/03/cfmanpage.jpg?w=208" medium="image">
			<media:title type="html">Generated Man Page for CrunchyFrog</media:title>
		</media:content>
	</item>
	</channel>
</rss>