<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7254386064439146371</id><updated>2011-10-02T08:03:36.059-07:00</updated><category term='ruby'/><category term='gsoc'/><category term='ctags'/><category term='lxml'/><category term='jQuery'/><category term='javascript'/><category term='python'/><category term='web'/><category term='binary search'/><category term='rails'/><category term='summer of code'/><category term='forms'/><category term='windows'/><category term='pygame'/><category term='sqaencode'/><category term='testing'/><category term='hell'/><category term='sublime'/><category term='bisect'/><title type='text'>akalias</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.akalias.net/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>40</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-3579328776989630972</id><published>2011-01-04T19:33:00.000-08:00</published><updated>2011-01-04T22:47:23.785-08:00</updated><title type='text'>Cartesian</title><content type='html'>&lt;h3&gt;I see your hacky method overloading ... &lt;/h3&gt;&lt;p&gt; Luckily with Python you can implement your own hacky little overloads. &lt;/p&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='None'&gt;&lt;span class='keyword'&gt;from&lt;/span&gt; itertools &lt;span class='keyword'&gt;import&lt;/span&gt; product&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;argslist&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;args&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='None'&gt;&lt;span class='None'&gt;[&lt;/span&gt; &lt;span class='None'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;tuple&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;t&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class='keyword'&gt;for&lt;/span&gt; t &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;product&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='keyword'&gt;*&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;a.split&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;|&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class='keyword'&gt;for&lt;/span&gt; a  &lt;span class='keyword'&gt;in&lt;/span&gt; args&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class='None'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;Signatures&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='superclass'&gt;defaultdict&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;&lt;span class='support'&gt;__init__&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.seen &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;defaultdict&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;set&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class='metaFunctionCallPython'&gt;defaultdict.&lt;span class='support'&gt;__init__&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='variable'&gt;self&lt;/span&gt;, &lt;span class='support'&gt;list&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;&lt;span class='support'&gt;__setitem__&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;key&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;item&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;isinstance&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;key, &lt;span class='support'&gt;tuple&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;            m, args &lt;span class='keyword'&gt;=&lt;/span&gt; key&lt;br /&gt;            &lt;span class='keyword'&gt;for&lt;/span&gt; k &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;argslist&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;args&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;:&lt;span class='None'&gt;&lt;span class='variable'&gt;self&lt;/span&gt;.seen&lt;span class='None'&gt;[&lt;/span&gt;&lt;span class='None'&gt;m&lt;/span&gt;&lt;span class='None'&gt;]&lt;/span&gt;&lt;/span&gt;.&lt;span class='metaFunctionCallPython'&gt;add&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;k&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;br /&gt;            &lt;span class='None'&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='None'&gt;[&lt;/span&gt;&lt;span class='None'&gt;m&lt;/span&gt;&lt;span class='None'&gt;]&lt;/span&gt;&lt;/span&gt;.&lt;span class='metaFunctionCallPython'&gt;append&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;item&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class='keyword'&gt;else&lt;/span&gt;:&lt;br /&gt;            &lt;span class='metaFunctionCallPython'&gt;defaultdict.&lt;span class='support'&gt;__setitem__&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='variable'&gt;self&lt;/span&gt;, key, item&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;&lt;span class='support'&gt;__contains__&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; (&lt;span class='variable'&gt;m&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;args&lt;/span&gt;)&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class='keyword'&gt;for&lt;/span&gt; k &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;argslist&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;args&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;            &lt;span class='keyword'&gt;if&lt;/span&gt; k &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='None'&gt;&lt;span class='variable'&gt;self&lt;/span&gt;.seen&lt;span class='None'&gt;[&lt;/span&gt;&lt;span class='None'&gt;m&lt;/span&gt;&lt;span class='None'&gt;]&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;                &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='constant'&gt;True&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;parse_sigs&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    api &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;PyQuery&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;filename&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;api.xml&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    sigs &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;Signatures&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;for&lt;/span&gt; sig &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;api&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;entries entry[type=method] signature&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;        entry &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;sig.getparent&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        name &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;entry.get&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;name&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class='keyword'&gt;for&lt;/span&gt; args &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;arg_combinations&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;PyQuery&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;sig&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;argument&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;            k, v &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;parse_sig&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;name, args&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class='metaFunctionCallPython'&gt;v.update&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;sig&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;:&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;PyQuery&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;sig&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class='keyword'&gt;if&lt;/span&gt; k:&lt;br /&gt;                key &lt;span class='keyword'&gt;=&lt;/span&gt; (k, &lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;tuple&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;v&lt;span class='metaFunctionCallPython'&gt;[&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;types&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;                &lt;span class='keyword'&gt;if&lt;/span&gt; key &lt;span class='keyword'&gt;not&lt;/span&gt; &lt;span class='keyword'&gt;in&lt;/span&gt; sigs:&lt;br /&gt;                    &lt;span class='None'&gt;sigs&lt;span class='None'&gt;[&lt;/span&gt;&lt;span class='None'&gt;key&lt;/span&gt;&lt;span class='None'&gt;]&lt;/span&gt;&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; v&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;dict&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;sigs&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&lt;br /&gt; 'toggle': [{'args': ['handler(eventObject)', 'handler(eventObject)'],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle(handler(eventObject), handler(eventObject))',&lt;br /&gt;             'types': ['Function', 'Function']},&lt;br /&gt;            {'args': ['handler(eventObject)',&lt;br /&gt;                      'handler(eventObject)',&lt;br /&gt;                      'handler(eventObject)'],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle(handler(eventObject), handler(eventObject), handler(eventObject))',&lt;br /&gt;             'types': ['Function', 'Function', 'Function']},&lt;br /&gt;            {'args': [],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle()',&lt;br /&gt;             'types': []},&lt;br /&gt;            {'args': ['duration'],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle(duration)',&lt;br /&gt;             'types': ['String']},&lt;br /&gt;            {'args': ['callback'],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle(callback)',&lt;br /&gt;             'types': ['Callback']},&lt;br /&gt;            {'args': ['duration', 'callback'],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle(duration, callback)',&lt;br /&gt;             'types': ['String', 'Callback']},&lt;br /&gt;            {'args': ['duration', 'easing'],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle(duration, easing)',&lt;br /&gt;             'types': ['String', 'String']},&lt;br /&gt;            {'args': ['duration', 'easing', 'callback'],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle(duration, easing, callback)',&lt;br /&gt;             'types': ['String', 'String', 'Callback']},&lt;br /&gt;            {'args': ['showOrHide'],&lt;br /&gt;             'name': 'toggle',&lt;br /&gt;             'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;             'sig_str': 'toggle(showOrHide)',&lt;br /&gt;             'types': ['Boolean']}],&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt; I haven't written tests (in javascript) but those sigs look fairly legit &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-3579328776989630972?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/3579328776989630972/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=3579328776989630972' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/3579328776989630972'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/3579328776989630972'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2011/01/cartesian.html' title='Cartesian'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-8534957941336986068</id><published>2011-01-04T17:03:00.000-08:00</published><updated>2011-01-04T21:35:16.565-08:00</updated><title type='text'>Method (Overload) Madness</title><content type='html'>&lt;pre class='blackboard'&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='constant'&gt;&lt;span class='string'&gt;_toggle&lt;/span&gt;&lt;span class='constant'&gt;:&lt;/span&gt;&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;fn&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;toggle&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;toggle&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt; &lt;span class='storage'&gt;function&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt; fn, fn2, callback &lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='storage'&gt;var&lt;/span&gt; &lt;span class='variable'&gt;bool&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='keyword'&gt;typeof&lt;/span&gt; &lt;span class='variable'&gt;fn&lt;/span&gt; &lt;span class='keyword'&gt;===&lt;/span&gt; &lt;span class='string'&gt;&lt;span class='string'&gt;"&lt;/span&gt;boolean&lt;span class='string'&gt;"&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;isFunction&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;fn&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='keyword'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;isFunction&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;fn2&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;_toggle&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='support'&gt;apply&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;arguments&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt; &lt;span class='keyword'&gt;else&lt;/span&gt; &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;fn&lt;/span&gt; &lt;span class='keyword'&gt;==&lt;/span&gt; &lt;span class='constant'&gt;null&lt;/span&gt; &lt;span class='keyword'&gt;||&lt;/span&gt; &lt;span class='variable'&gt;bool&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;each&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='storage'&gt;function&lt;/span&gt;&lt;span class='None'&gt;()&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='storage'&gt;var&lt;/span&gt; &lt;span class='variable'&gt;state&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='variable'&gt;bool&lt;/span&gt; ? &lt;span class='variable'&gt;fn&lt;/span&gt; &lt;span class='None'&gt;:&lt;/span&gt; &lt;span class='None'&gt;&lt;span class='entity'&gt;jQuery&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;is&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;"&lt;/span&gt;:hidden&lt;span class='string'&gt;"&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;jQuery&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;[&lt;/span&gt; &lt;span class='variable'&gt;state&lt;/span&gt; ? &lt;span class='string'&gt;&lt;span class='string'&gt;"&lt;/span&gt;show&lt;span class='string'&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class='None'&gt;:&lt;/span&gt; &lt;span class='string'&gt;&lt;span class='string'&gt;"&lt;/span&gt;hide&lt;span class='string'&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class='None'&gt;]&lt;/span&gt;&lt;span class='None'&gt;()&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt; &lt;span class='keyword'&gt;else&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;animate&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;genFx&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;"&lt;/span&gt;toggle&lt;span class='string'&gt;"&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='constant'&gt;3&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;fn&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;fn2&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;callback&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class="lineNumber"&gt;&lt;br /&gt;&lt;br /&gt;....&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;animate&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt; &lt;span class='storage'&gt;function&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt; prop, speed, easing, callback &lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='storage'&gt;var&lt;/span&gt; &lt;span class='variable'&gt;optall&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;speed&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;speed&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;easing&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;callback&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;isEmptyObject&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;prop&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;each&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;optall&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='support'&gt;complete&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class="lineNumber"&gt;&lt;br /&gt;&lt;br /&gt;....&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;extend&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;speed&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt; &lt;span class='storage'&gt;function&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt; speed, easing, fn &lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='storage'&gt;var&lt;/span&gt; &lt;span class='variable'&gt;opt&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='variable'&gt;speed&lt;/span&gt; &lt;span class='keyword'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='keyword'&gt;typeof&lt;/span&gt; &lt;span class='variable'&gt;speed&lt;/span&gt; &lt;span class='keyword'&gt;===&lt;/span&gt; &lt;span class='string'&gt;&lt;span class='string'&gt;"&lt;/span&gt;object&lt;span class='string'&gt;"&lt;/span&gt;&lt;/span&gt; ? &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;extend&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;{}&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt; &lt;span class='variable'&gt;speed&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;:&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='constant'&gt;&lt;span class='string'&gt;complete&lt;/span&gt;&lt;span class='constant'&gt;:&lt;/span&gt;&lt;/span&gt; &lt;span class='variable'&gt;fn&lt;/span&gt; &lt;span class='keyword'&gt;||&lt;/span&gt; &lt;span class='keyword'&gt;!&lt;/span&gt;&lt;span class='variable'&gt;fn&lt;/span&gt; &lt;span class='keyword'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='variable'&gt;easing&lt;/span&gt; &lt;span class='keyword'&gt;||&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;isFunction&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;speed&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='keyword'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='variable'&gt;speed&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='constant'&gt;&lt;span class='string'&gt;duration&lt;/span&gt;&lt;span class='constant'&gt;:&lt;/span&gt;&lt;/span&gt; &lt;span class='variable'&gt;speed&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='constant'&gt;&lt;span class='string'&gt;easing&lt;/span&gt;&lt;span class='constant'&gt;:&lt;/span&gt;&lt;/span&gt; &lt;span class='variable'&gt;fn&lt;/span&gt; &lt;span class='keyword'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='variable'&gt;easing&lt;/span&gt; &lt;span class='keyword'&gt;||&lt;/span&gt; &lt;span class='variable'&gt;easing&lt;/span&gt; &lt;span class='keyword'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='keyword'&gt;!&lt;/span&gt;&lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;isFunction&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;easing&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='keyword'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='variable'&gt;easing&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;duration&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;fx&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;off&lt;/span&gt; ? &lt;span class='constant'&gt;0&lt;/span&gt; &lt;span class='None'&gt;:&lt;/span&gt; &lt;span class='keyword'&gt;typeof&lt;/span&gt; &lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;duration&lt;/span&gt; &lt;span class='keyword'&gt;===&lt;/span&gt; &lt;span class='string'&gt;&lt;span class='string'&gt;"&lt;/span&gt;number&lt;span class='string'&gt;"&lt;/span&gt;&lt;/span&gt; ? &lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='constant'&gt;&lt;span class='string'&gt;duration&lt;/span&gt; &lt;span class='constant'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;duration&lt;/span&gt; &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;fx&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;speeds&lt;/span&gt; ? &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;fx&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;speeds&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;[&lt;/span&gt;&lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;duration&lt;/span&gt;&lt;span class='None'&gt;]&lt;/span&gt; &lt;span class='None'&gt;:&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;fx&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;speeds&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;_default&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='comment'&gt;&lt;span class='comment'&gt;//&lt;/span&gt; Queueing&lt;br /&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;old&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='support'&gt;complete&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='support'&gt;opt&lt;/span&gt;.&lt;span class='entity'&gt;complete&lt;/span&gt; = &lt;span class='storage'&gt;function&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;queue&lt;/span&gt; &lt;span class='keyword'&gt;!==&lt;/span&gt; &lt;span class='constant'&gt;false&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;jQuery&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;this&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;dequeue&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;()&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;jQuery&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='entity'&gt;isFunction&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='variable'&gt;old&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt; &lt;span class='None'&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;old&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;.&lt;/span&gt;&lt;span class='support'&gt;call&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt; &lt;span class='variable'&gt;this&lt;/span&gt; &lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='variable'&gt;opt&lt;/span&gt;&lt;span class='None'&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;}&lt;/span&gt;&lt;span class='None'&gt;,&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://blogdata.akalias.net/madness.png" /&gt;&lt;br /&gt;&lt;img src="http://blogdata.akalias.net/madness2.png" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt; It's interesting. I thought they would have a more sophisticated and abstract dispatch method. Something declarative and usable by plugins. &lt;/p&gt;&lt;p&gt; I was hoping to be able enumerate the different valid signature types but I have a feeling there isn't going to be some 'easily' exploitable pattern.  The args aren't optional in a left to right manner such that for all optional args all args to the left have been optioned. You can call toggle with a callback function as the sole argument. Complicating things somewhat more is the fact that some arguments are described as accepting multiple types. Duration can be a number or string. &lt;/p&gt;&lt;h3&gt;fadeTo Shenanigans&lt;/h3&gt;&lt;br /&gt;&lt;img src="http://blogdata.akalias.net/madness3.png"&gt;&lt;p&gt;Yet the following works perfectly&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;$(this).fadeTo(function(){ console.log('weird')}, Math.random());&lt;/pre&gt;&lt;p&gt; Not that it would be a good idea to rely upon implementation quirks&lt;/p&gt;&lt;br /&gt;&lt;p&gt;I'm just interested in automatically computing the different valid combinations of working signatures. Should be doable. Working left to right, taking the first combination of each signature. Cheating for a second and altering my routines to swap out things like "String|Number" to "String" I can see I'm on the right path&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; 'toggle()': {'args': [], 'name': 'toggle', 'sig': [&lt;signature&gt;], 'types': []},&lt;br /&gt;&lt;br /&gt; 'toggle(Boolean)': {'args': ['showOrHide'],&lt;br /&gt;                     'name': 'toggle',&lt;br /&gt;                     'sig': [&lt;signature&gt;],&lt;br /&gt;                     'types': ['Boolean']},&lt;br /&gt; 'toggle(Callback)': {'args': ['callback'],&lt;br /&gt;                      'name': 'toggle',&lt;br /&gt;                      'sig': [&lt;signature&gt;],&lt;br /&gt;                      'types': ['Callback']},&lt;br /&gt; 'toggle(Function, Function)': {'args': ['handler(eventObject)',&lt;br /&gt;                                         'handler(eventObject)'],&lt;br /&gt;                                'name': 'toggle',&lt;br /&gt;                                'sig': [&lt;signature&gt;],&lt;br /&gt;                                'types': ['Function', 'Function']},&lt;br /&gt; 'toggle(Function, Function, Function)': {'args': ['handler(eventObject)',&lt;br /&gt;                                                   'handler(eventObject)',&lt;br /&gt;                                                   'handler(eventObject)'],&lt;br /&gt;                                          'name': 'toggle',&lt;br /&gt;                                          'sig': [&lt;signature&gt;],&lt;br /&gt;                                          'types': ['Function',&lt;br /&gt;                                                    'Function',&lt;br /&gt;                                                    'Function']},&lt;br /&gt; 'toggle(String)': {'args': ['duration'],&lt;br /&gt;                    'name': 'toggle',&lt;br /&gt;                    'sig': [&lt;signature&gt;],&lt;br /&gt;                    'types': ['String']},&lt;br /&gt; 'toggle(String, Callback)': {'args': ['duration', 'callback'],&lt;br /&gt;                              'name': 'toggle',&lt;br /&gt;                              'sig': [&lt;signature&gt;],&lt;br /&gt;                              'types': ['String', 'Callback']},&lt;br /&gt; 'toggle(String, String)': {'args': ['duration', 'easing'],&lt;br /&gt;                            'name': 'toggle',&lt;br /&gt;                            'sig': [&lt;signature&gt;],&lt;br /&gt;                            'types': ['String', 'String']},&lt;br /&gt; 'toggle(String, String, Callback)': {'args': ['duration',&lt;br /&gt;                                               'easing',&lt;br /&gt;                                               'callback'],&lt;br /&gt;                                      'name': 'toggle',&lt;br /&gt;                                      'sig': [&lt;signature&gt;],&lt;br /&gt;                                      'types': ['String',&lt;br /&gt;                                                'String',&lt;br /&gt;                                                'Callback']},&lt;br /&gt;&lt;/pre&gt;&lt;p&gt; I could make a special class for the the types currently represented by a string sourced directly from the xml.  This could compare "String|Number" == "Number" as true.  Then tuples could be compared &lt;code&gt;('String|Number', 'Callback') == ('Number', 'Callback') &lt;/code&gt; &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-8534957941336986068?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/8534957941336986068/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=8534957941336986068' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8534957941336986068'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8534957941336986068'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2011/01/method-overload-madness.html' title='Method (Overload) Madness'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-5123450230250849724</id><published>2011-01-04T05:10:00.000-08:00</published><updated>2011-01-04T05:17:36.389-08:00</updated><title type='text'>5 minutes of fame</title><content type='html'>Don't really know how much you can really read into an online test, but seeing I did well I'll not criticize it too much :) Not bad after such a big break.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.odesk.com/trends/Python"&gt;&lt;img src="http://ninja.akalias.net/images/alltime5th.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;p&gt;The pay on oDesk is pretty lousy but it's all relative. If you earned 10 dollars an hour and lived in Cambodia? You can live in a resort on the beach for 500 a month there and it gets a lot cheaper than that ... &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-5123450230250849724?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/5123450230250849724/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=5123450230250849724' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/5123450230250849724'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/5123450230250849724'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2011/01/5-minutes-of-fame.html' title='5 minutes of fame'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-1944878194341580711</id><published>2011-01-04T05:04:00.000-08:00</published><updated>2011-01-04T16:22:20.114-08:00</updated><title type='text'>Brushing Up</title><content type='html'>&lt;form&gt;&lt;h3&gt;Some eBooks I have been, am currently, and plan to be, reading&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;jQuery in Action &lt;input type="checkbox" checked="checked" /&gt;&lt;/li&gt;&lt;li&gt;JavaScript - The Definitive Guide - 5th Edition &lt;input type="checkbox" checked="checked" /&gt;&lt;/li&gt;&lt;li&gt;PacktPub.Firebug.1.5.Apr.2010 &lt;input type="checkbox" checked="checked" /&gt;&lt;/li&gt;&lt;li&gt;Rapid GUI Programming with Python and Qt - The Definitive Guide to PyQt Programming &lt;input type="checkbox" checked="checked" /&gt;&lt;/li&gt;&lt;li&gt;OReilly.CSS.The.Missing.Manual.2nd.Edition.Sep.2009 &lt;input type="checkbox" checked="checked" /&gt;&lt;/li&gt;&lt;li&gt;Selenium 1.0 Testing Tools Beginner’s Guide &lt;input type="checkbox" /&gt;&lt;/li&gt;&lt;li&gt;JavaScript Testing Beginner's Guide 2010 Packt &lt;input type="checkbox" /&gt;&lt;/li&gt;&lt;li&gt;SQL Pocket Guide, Third Edition &lt;input type="checkbox"/&gt;&lt;/li&gt;&lt;li&gt;Pro JavaScript with MooTools - Learning Advanced JavaScript Programming &lt;input type="checkbox" /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/form&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-1944878194341580711?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/1944878194341580711/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=1944878194341580711' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/1944878194341580711'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/1944878194341580711'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2011/01/brushing-up.html' title='Brushing Up'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-7864685184147934105</id><published>2011-01-04T02:16:00.000-08:00</published><updated>2011-01-04T16:39:23.872-08:00</updated><title type='text'>SRS, jQuery</title><content type='html'>&lt;h1&gt;Back&lt;/h1&gt;&lt;p&gt;My dear readers will be glad to know that I'm back.  Where the hell have I been I imagine them asking ( I must have a great imagination, considering noone reads this blog! ) &lt;/p&gt;&lt;p&gt;For the last year or so I've been studying music, one of the great unrequited loves in my life. I realised, or shall I say, I came to an even deeper understanding of, how profoundly untalented a musician I am. This is not to say that I don't believe I have potential but I'm positive it will take a Bruce Lee like zeal to unleash and heretofore unseen training methods. I haven't given up. I'm just taking a timeout to develop more programming skills to apply to the problem. &lt;/p&gt;&lt;p&gt;Throughout the year struggling away at ear training, using the available software I found myself yearning for something better. Being one of those arrogant "I could do this better" programmer types cursed also with a low pain threshold ("I have to use a mouse?? kill me!") ideas started to form in my mind. I didn't have the time or inclination to work on any of them however as I was busy studying and pretty much burned out on anything IT related. &lt;/p&gt;&lt;p&gt;But that's never here nor there. The central topic of this blogpost is 'scheduled repetition software'. Why lead in with your mediocre musicians lament? &lt;/p&gt;&lt;p&gt;Well, if you ever *completely stopped* something for any length of time you'll know the danger of gaining rust and losing insights. In the Skydiving world they call this state 'uncurrent' and you are considered a danger to yourself and others if you go for longer than a few months without jumping. We've all heard the "Use it or lose it" maxim. &lt;/p&gt;&lt;p&gt;Having finished up the year of study, I realised I needed some work and decided I should brush up on some of my old programming skills. Ouch! I've been somewhat blessed with a pretty good long term memory and retention. However, there's so much I just plain forgot. &lt;/p&gt;&lt;p&gt;Little details that before were readily available were now lost leaving me no recourse but to use references. "Where do I import that module from again?" "How do you get hg to automatically update server side?" "What was the keybinding for that command?" "The trigger for that snippet?" "What was the difference between left outer joins and ... " &lt;/p&gt;&lt;p&gt;And so on. &lt;/p&gt;&lt;p&gt;At one point during the year, I actually memorized 72 ascending musical intervals over four or five days. You think I could recall them a month later? No. Cramming doesn't work. You need to review. I can recall the intervals in the key of C and some in G/D/E but most I've forgotten. &lt;/p&gt;&lt;p&gt;Now I was considering studying this year also. It occured to me that it would be hard to maintain a decent skill/knowledge level to be able to work efficiently enough that I wouldn't be better off just digging ditches. &lt;/p&gt;&lt;p&gt;I knew it would be futile to try and cram in a heap of study over the holidays. &lt;/p&gt;&lt;br /&gt;&lt;pre&gt;  &lt;br /&gt;  TODO:&lt;br /&gt;  Brushup on WebDev&lt;br /&gt;&lt;br /&gt;       jQuery in action.&lt;br /&gt;       CSS, The Missing Manual.&lt;br /&gt;       Firebug, Beginners Guide&lt;br /&gt;       SQLAlchemy tutorial&lt;br /&gt;       Genshi templating&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;I've done cramming in the past with things like Django (for an ill fated project ...), and you invariably forget it unless you use it day to day until it's burned into your long term memory. &lt;/p&gt;&lt;p&gt;How to bypass the need for a long period of 'day to day'? How to get something burned into your memory 'unnaturally'? &lt;/p&gt;&lt;p&gt;One thing you learn as a musician, is that you really need to apply 'deliberate practice' to get better. I'm not sure it's something many programmers do. At least I know I never did. In fact even when 'current' there were somethings I routinely used a reference for, even as little as a week between, just because my mind was habitually forgetful. &lt;/p&gt;&lt;p&gt;I left school at 12 years of age, obviously never attending university. Needless to say my study skills aren't very sophisticated. I actually find this thought rather encouraging as I know I'm miles from having plateued. There's lots of "low hanging fruit" ready to be picked. &lt;/p&gt;&lt;p&gt;So I had 6 weeks until school started again (at this point I planned on returning to study). I realised I should probably read into some study tips. &lt;/p&gt;&lt;p&gt;I downloaded a bootleg copy of the Memletics manual, which goes into quite some depth about the most efficent methods for learning. &lt;/p&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Nutrition. &lt;/li&gt;&lt;li&gt;Exercise.&lt;/li&gt;&lt;li&gt;Attitude.&lt;/li&gt;&lt;li&gt;SRS.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;p&gt;I've still yet to apply most of what I read in there. If you think turning short term memory into long term memory is hard try developing new habits! &lt;/p&gt;&lt;p&gt;The biggest change in my learning habits is the use of SRS software. In fact I even got in some daily revisions on XMAS day. &lt;/p&gt;&lt;br /&gt;&lt;h3&gt;Enter Anki&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;Anki is an implementation ( PyQt ) of software based flashcards. What makes this different to normal flashcards is that it intelligently schedules your cards for repetition at a time when it thinks you'll forget. I guess by now you have gleaned that SRS is an acronym for Scheduled Repetition Software.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The cards are html with all that entails: you can embed sounds/images and hyperlink. It has some other cool features like 'download shared deck'.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;In fact, I found that someone had created a jQuery deck with roughly 180 cards. &lt;/p&gt;&lt;p&gt;Now, as I've learned there's something of an art to creating flashcards. You really need to reduce the amount of information to one or two discrete chunks. &lt;/p&gt;&lt;p&gt;This of course violates your prejudices against redundancy. IIRC, and I'm pretty sure I do (at least I remember this!), there was a meme floating about by the name of DRY, "Don't repeat yourself". With flashcards, scheduled REPETITION ones at that, the whole point is for repetition so that's OK. &lt;/p&gt;&lt;p&gt;As Anki is implemented in Python/PyQt and is extensible via plugins, one of the first things I did was to create a PYRO based bridge between it and my python extensible editor. &lt;/p&gt;&lt;p&gt;I'd copy/paste text from pdfs websites into a text buffer, manipulating the information into Q/A form then push 10 - 20 cards at a time using the multiple selection capabilites of my editor. &lt;/p&gt;&lt;p&gt;This information reorganizing process itself is invaluable as it really forces you to go beyond habitual skimming. &lt;/p&gt;&lt;p&gt;This requires some time and discipline and feels like a lot of work so I'd imagine it's not something everyone will have the stomach for. However, 'slow is fast'. What's the alternative? If you spend a few hours skimming and within a few weeks can recall nothing of value you've wasted your time. You have to think long term. &lt;/p&gt;&lt;p&gt;If it's something you think you'll eventually want/need to know then it's a good idea to spend the time upfront. How many times have you wasted countless hours fucking about blind, cause of artificial deadline pressures? I know people who have been developing for ages who always 'too busy' to RTFM. &lt;/p&gt;&lt;p&gt;Software development is complex. How can what is basically glorified rote memorisation really help? &lt;/p&gt;&lt;p&gt;How can you make critical implementation decisions if you don't know the frameworks you are using? How many times have you reinvented the wheel, because of lack of knowing your options? How much more creative can you be if you really know your 'material' and can think clearly about possibilities? It's what makes an expert and expert. &lt;/p&gt;&lt;p&gt;I applied Anki to brushing up on CSS, jQuery, Firebug and am learning the PyQt framework. At the rate I crammed some of it in, I doubt I'd have retained much without Anki. &lt;/p&gt;&lt;br /&gt;&lt;h3&gt;jQuery Examples&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;One of my projects I've been tinkering on lately is a flash card export of the jQuery API raw xml dump. I wish to create flash cards going from {short_description : method_name} and its inverse {method_name, short_description} &lt;/p&gt;&lt;p&gt;Not only that but I want each html card to contain links which open up the example code in my editor. Luckily my editor can run commands argumented via the command line. I created a custom sblm:// protocol which allows commands to be sent in the query string. &lt;/p&gt;&lt;p&gt;I created a routine to determine all valid combinations of arguments so I can memorise the signatures of routines and create snippets.  ( I'll have to make it a little smarter to filter the conflicting variations somehow. There's only so many ways a function can interpret a single boolean for instance )  &lt;/p&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;arg_combinations&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;args&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        args_n &lt;span class='keyword'&gt;=&lt;/span&gt; (&lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;int&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;bool&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;e.get&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;optional&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class='constant'&gt;False&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class='keyword'&gt;for&lt;/span&gt; e &lt;span class='keyword'&gt;in&lt;/span&gt; args)&lt;br /&gt;    &lt;br /&gt;        &lt;span class='keyword'&gt;for&lt;/span&gt; n &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;xrange&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='constant'&gt;2&lt;/span&gt; &lt;span class='keyword'&gt;**&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;sum&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;args_n&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;            opt_args &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='None'&gt;&lt;span class='None'&gt;[&lt;/span&gt;&lt;span class='None'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            n_optional &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='keyword'&gt;-&lt;/span&gt;&lt;span class='constant'&gt;1&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;            &lt;span class='keyword'&gt;for&lt;/span&gt; arg &lt;span class='keyword'&gt;in&lt;/span&gt; args:&lt;br /&gt;                optional &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;arg.get&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;optional&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;                &lt;span class='keyword'&gt;if&lt;/span&gt; optional: n_optional &lt;span class='keyword'&gt;+=&lt;/span&gt; &lt;span class='constant'&gt;1&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;                &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='keyword'&gt;not&lt;/span&gt; optional &lt;span class='keyword'&gt;or&lt;/span&gt; optional &lt;span class='keyword'&gt;and&lt;/span&gt; n &lt;span class='keyword'&gt;&amp;amp;&lt;/span&gt; (&lt;span class='constant'&gt;2&lt;/span&gt; &lt;span class='keyword'&gt;**&lt;/span&gt; n_optional):&lt;br /&gt;                    &lt;span class='metaFunctionCallPython'&gt;opt_args.append&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;arg&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;            &lt;span class='keyword'&gt;yield&lt;/span&gt; opt_args&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='None'&gt; &lt;span class='None'&gt;'toggle()': {'args': [], 'name': 'toggle', 'sig': [&amp;lt;signature&amp;gt;], 'types': []},&lt;br /&gt;&lt;/span&gt;&lt;br /&gt; &lt;span class='None'&gt;'toggle(callback)': {'args': ['callback'],&lt;br /&gt;&lt;/span&gt;                      &lt;span class='None'&gt;'name': 'toggle',&lt;br /&gt;                      'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;                      'types': ['Callback']},&lt;br /&gt;&lt;/span&gt; &lt;span class='None'&gt;'toggle(duration)': {'args': ['duration'],&lt;br /&gt;&lt;/span&gt;                      &lt;span class='None'&gt;'name': 'toggle',&lt;br /&gt;                      'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;                      'types': ['String|Number']},&lt;br /&gt;&lt;/span&gt; &lt;span class='None'&gt;'toggle(duration, callback)': {'args': ['duration', 'callback'],&lt;br /&gt;&lt;/span&gt;                                &lt;span class='None'&gt;'name': 'toggle',&lt;br /&gt;                                'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;                                'types': ['String|Number', 'Callback']},&lt;br /&gt;&lt;/span&gt; &lt;span class='None'&gt;'toggle(duration, easing)': {'args': ['duration', 'easing'],&lt;br /&gt;&lt;/span&gt;                              &lt;span class='None'&gt;'name': 'toggle',&lt;br /&gt;                              'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;                              'types': ['String|Number', 'String']},&lt;br /&gt;&lt;/span&gt; &lt;span class='None'&gt;'toggle(duration, easing, callback)': {'args': ['duration',&lt;br /&gt;&lt;/span&gt;                                                 &lt;span class='None'&gt;'easing',&lt;br /&gt;                                                 'callback'],&lt;br /&gt;&lt;/span&gt;                                        &lt;span class='None'&gt;'name': 'toggle',&lt;br /&gt;                                        'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;                                        'types': ['String|Number',&lt;br /&gt;&lt;/span&gt;                                                  &lt;span class='None'&gt;'String',&lt;br /&gt;                                                  'Callback']},&lt;br /&gt;&lt;/span&gt; &lt;span class='None'&gt;'toggle(easing)': {'args': ['easing'],&lt;br /&gt;&lt;/span&gt;                    &lt;span class='None'&gt;'name': 'toggle',&lt;br /&gt;                    'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;                    'types': ['String']},&lt;br /&gt;&lt;/span&gt; &lt;span class='None'&gt;'toggle(easing, callback)': {'args': ['easing', 'callback'],&lt;br /&gt;&lt;/span&gt;                              &lt;span class='None'&gt;'name': 'toggle',&lt;br /&gt;                              'sig': [&amp;lt;signature&amp;gt;],&lt;br /&gt;                              'types': ['String', 'Callback']},&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;I wonder if you really can pass a single string as an arg and have the toggle function determine whether you meant duration or easing? I'll have to try it our or better yet, look at the jQuery soruce&lt;/p&gt;&lt;p&gt;For purposes of automatically creating snippets I guess you really only want to look at the different signature types. jQuery uses anonymous functions a lot and &lt;em&gt;literal&lt;/em&gt; string based arguments so creating snippets with placeholder args already quoted and &lt;q&gt;function(){}&lt;/q&gt; inline seems like a decent idea&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;Web Snippets&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;There's a reason iterative development is usurping upfront design methodologies. We are fallible and mostly lacking detailed imagination. Our most glaring and atypically ungrudging admission: the fact we are so obsessed with testing. &lt;/p&gt;&lt;p&gt;I'm a great believer in efficient text editing. This shortens your feedback loop. If you can input text twice as fast you get through, I dunno, probably not twice as many, but a lot more 'fuckups'. Iterations! &lt;/p&gt;&lt;p&gt;The thing about snippets I've found is that unless I can recall instantly the abbreviation, I'll tend to just type in the whole words. &lt;/p&gt;&lt;p&gt;Now this really ties back to what I was saying before about 'deliberate practice' &lt;/p&gt;&lt;p&gt;In theory, if you practiced enough you could use completely arbitrary 2 letter combinations to compress an API worth of words down. However, memorising, even with SRS, is easier with some mnemonics so... &lt;/p&gt;&lt;p&gt;I've actually written some routines which will take a list of words and attempt to create ideal abbreviations and ensure unique. &lt;/p&gt;&lt;p&gt;One of the routines uses the Carnegie Mellon University Pronouncing Dictionary, cmudict for short, found in the nltk toolkit. &lt;/p&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='None'&gt;&lt;span class='None'&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;rank_letters&lt;/span&gt;&lt;span class='None'&gt;(&lt;/span&gt;&lt;span class='None'&gt;&lt;span class='variable'&gt;wd&lt;/span&gt;&lt;/span&gt;&lt;span class='None'&gt;)&lt;/span&gt;&lt;span class='None'&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='keyword'&gt;not&lt;/span&gt; wd &lt;span class='keyword'&gt;in&lt;/span&gt; CMU: &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='None'&gt;&lt;span class='None'&gt;[&lt;/span&gt;&lt;span class='None'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;        cmu &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.&lt;span class='metaFunctionCallPython'&gt;join&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;_&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;ch&lt;span class='metaFunctionCallPython'&gt;[&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='keyword'&gt;-&lt;/span&gt;&lt;span class='constant'&gt;1&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;]&lt;/span&gt;&lt;/span&gt;.&lt;span class='metaFunctionCallPython'&gt;isdigit&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class='keyword'&gt;else&lt;/span&gt; ch &lt;span class='keyword'&gt;for&lt;/span&gt; ch &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;CMU&lt;span class='metaFunctionCallPython'&gt;[&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;wd&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='constant'&gt;0&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        cmus &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;defaultdict&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;int&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;        mapping &lt;span class='keyword'&gt;=&lt;/span&gt;  &lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;^(&lt;span class='constant'&gt;\\&lt;/span&gt;w)_|_(&lt;span class='constant'&gt;\\&lt;/span&gt;w)_|(&lt;span class='constant'&gt;\\&lt;/span&gt;w)_|_(&lt;span class='constant'&gt;\\&lt;/span&gt;w)$|_(&lt;span class='constant'&gt;\\&lt;/span&gt;w)&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;        &lt;span class='keyword'&gt;for&lt;/span&gt; i, w &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;enumerate&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;mapping.split&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='string'&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='string'&gt;|&lt;span class='string'&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;            &lt;span class='keyword'&gt;for&lt;/span&gt; m &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;re.finditer&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;w, cmu&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;:&lt;br /&gt;                ch &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;m.group&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='constant'&gt;1&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;span class='metaFunctionCallPython'&gt;lower&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;                &lt;span class='keyword'&gt;if&lt;/span&gt; ch &lt;span class='keyword'&gt;in&lt;/span&gt; wd: &lt;span class='None'&gt;cmus&lt;span class='None'&gt;[&lt;/span&gt;&lt;span class='None'&gt;ch&lt;/span&gt;&lt;span class='None'&gt;]&lt;/span&gt;&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;max&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;i&lt;span class='keyword'&gt;+&lt;/span&gt;&lt;span class='constant'&gt;1&lt;/span&gt;, &lt;span class='metaFunctionCallPython'&gt;cmus&lt;span class='metaFunctionCallPython'&gt;[&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;ch&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;        &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='support'&gt;sorted&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;cmus.keys&lt;span class='metaFunctionCallPython'&gt;(&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;, &lt;/span&gt;&lt;span class='variable'&gt;key&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='metaFunctionCallPython'&gt;&lt;span class='storage'&gt;lambda&lt;/span&gt; &lt;span class='metaFunctionCallPython'&gt;&lt;span class='variable'&gt;k&lt;/span&gt;&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;:&lt;/span&gt;&lt;/span&gt; (&lt;span class='metaFunctionCallPython'&gt;cmus&lt;span class='metaFunctionCallPython'&gt;[&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;k&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;]&lt;/span&gt;&lt;/span&gt;)&lt;/span&gt;&lt;span class='metaFunctionCallPython'&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Intuitively I sensed that words surrounded by vowel sounds are the 'strongest' letters in a word, followed by those adjacent to only one. It seems to work reasonably well for such a crude method. &lt;/p&gt;&lt;pre&gt;&lt;br /&gt;eg&lt;br /&gt;    document - DocuMeNt - dm&lt;br /&gt;    border   - BorDer   - bd&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;I also used the words corpus to create routines to decompose composite words.&lt;/p&gt;&lt;pre&gt;&lt;br /&gt;    mouseup    -  mouse, up     -  mu&lt;br /&gt;    mousedown  -  mouse, down   -  md&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;I'm going to create desc/entity flashcards and snippets also for html elements/attributes and css property/values. &lt;/p&gt;&lt;p&gt;With my editor at least, the bindings are smart enough to know when the cursor is inside a tag. eg. &amp;lt;|div&amp;gt; Why the hell type in annoying long character sequences when the set of identifiers compresses down to one, two, maybe three (ouch) character ids? &lt;/p&gt;&lt;p&gt;Over a course of weeks, you could, with SRS, systematically memorise the entire html set of elements and their attributes along with related snippet abbreviations. Shld I sy abrv instd? &lt;/p&gt;&lt;p&gt;It would be analogous to touchtyping. A lifetime of benefit for a short period of practice. &lt;/p&gt;&lt;p&gt;On second thought ... A lifetime of entering html/css? There's a horrid thought... &lt;/p&gt;&lt;br /&gt;&lt;h3&gt;Statistics&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;Ideally I'd like to give the most frequently used entities the most suitable and short abbreviations, one character ones at times. &lt;/p&gt;&lt;p&gt;Google apparently did a &lt;a href="http://code.google.com/webstats/"&gt;massive study&lt;/a&gt; looking at web authoring statistics. They have the data as to which elements should receive the honorary 1 keyers. &lt;/p&gt;&lt;br /&gt;&lt;h3&gt;Namespaces&lt;/h3&gt;&lt;br /&gt;&lt;p&gt;You mean there's other javascript snippet candidates other than jQuery? Poses some problems. Where there's a problem, there's a solution. &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-7864685184147934105?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/7864685184147934105/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=7864685184147934105' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7864685184147934105'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7864685184147934105'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2011/01/srs-jquery.html' title='SRS, jQuery'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-9087910129547077665</id><published>2009-07-28T22:25:00.000-07:00</published><updated>2009-07-29T00:18:15.598-07:00</updated><title type='text'>Consolation:  It's not you, it's the other guy.</title><content type='html'>I have been doing some freelancing work but it hasn't really been buttering the bread on a consistent basis. I avoid PHP which doesn't really leave much else job wise. Normally I value my time and flexible working hours far more than dollars so a full time job isn't something I'd really look for. Once you have been poor long enough however you start resorting to desperate measures and thinking of all sorts of crazy ideas. Boredom sets in and you even do things like offer your services practically free. Fail with consolation: &lt;blockquote&gt;it is also clear to me that you possess attributes that would make an excellent employee - talent, enthusiasm and dedication. I would have no issues with offering you work if I were able to get XXXX to that stage... I am more than happy for you to put me down as a reference for any job opportunities, as I would highly recommend you to any employer. &lt;/blockquote&gt; Now whether the guy was just politely fobbing me off or he actually meant that I'm not entirely sure half the time. I've never particularly thought of myself as `talented` when it comes to code. `Persistent` like a retarded dog who doesn't know when to stop, sure.&lt;br /&gt;&lt;br /&gt;When you can't practically give yourself away and you again decide you really want stable cash what do you then? 'I know! I'll look for a *well paying* job. What a shrewd and cunning plan!'&lt;br /&gt;&lt;br /&gt;As I'm starting to get reasonably fluent with python, not needing references every 10 seconds, I naturally looked for jobs using that. There were however none available that didn't require `3 years experience` and `knowledge of finance` etc. The `python` keyword did however turn up another interesting advertisement.&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;We're a Richmond-based software development studio specialising in web technologies such as Flex and Ruby on Rails. We are looking for a passionate and energetic developer to join our team of three.&lt;br /&gt;&lt;br /&gt;We're more interested in what you can do than what you say you can do. So prove yourself with your attitude and with your code.&lt;br /&gt;&lt;br /&gt;We're looking for someone who's spent their evenings and weekends working on their own projects, and has something to show for it. We have a particular love of open source collaboration, and you should too. Tell us about the projects you watch, and the projects you contribute to: do you have accounts with Github, SourceForge, or RubyForge?&lt;br /&gt;&lt;br /&gt;We want to be floored by your enthusiasm to work with new products, programs, and languages. Dynamic or functional languages like Python, Ruby, Actionscript, Erlang, and Haskell are looked upon favorably. We're not interested in your academic transcript, your 2-day ScrumMaster black-belt, or your Microsoft certificates, but we are interested in real talent, sharp skills, and unmatched motivation.&lt;br /&gt;&lt;br /&gt;You must send through a cover letter describing your pet projects or open source projects, example source code from the project, and your resume to recruitment@silverpond.com.au. We won't consider any applications which don't have all three.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Normally I wouldn't have even bothered applying for something like that but I thought, if that guy will give me a reference then I may as well try it. I didn't really have much time to create an application or research the position all that much as I was away on a trip helping my father with a building project. Being an unfit person who would normally spend way too much time on a computer, after a days work I was quite tired in my poor fitness. I mostly reused an old application for another Job at a game development firm.&lt;br /&gt;&lt;br /&gt;I was somewhat suprised when I got a positive sounding response from the recruiter/director of the company "I'm free to chat late this week or early next week, so I'm looking forward to hearing from you"&lt;br /&gt;&lt;br /&gt;We eventually had a chat a few days later, which I wasn't exactly well prepared for. There was some initial awkardness with some nerves causing to me blurt out "good thanks and yourself" completely out of phase. It then smoothened out somewhat and I was happy to find the team was a group of 20 somethings and that it wasn't an issue I didn't have any experience with the technologies they used. I didn't really have many decent questions prepared ( it was actually my first job interview! ) but regardless at the end I was left with the impression it went well enough. The recruiter wanted me to come in a few days later for some facetime with the rest of the team.&lt;br /&gt;&lt;br /&gt;As it turns out I never got that opportunity. Someone else had done their homework and really impressed them so well with their phone interview they got the job solely on that. I found this out as after as I asked the recruiter for any particular reasons the other candidate was chosen (over me). I was very appreciative with the email he replied with:&lt;blockquote&gt;&lt;br /&gt;1) The candidate we chose asked questions about the projects we're doing and suggested improvements to them&lt;br /&gt;&lt;br /&gt;2) The candidate also outlined what he could contribute to the team, and offered a timeframe to do them in.&lt;br /&gt;&lt;br /&gt;Of the 80 of so applicants it was between the candidate, one other person and yourself. Your energy and enthusiasm is highly commendable. I'll touch base with you later in the year, say November, and see where you're at.&lt;/blockquote&gt;Overall, I have mixed feelings. I'm somewhat disappointed that I didn't have greater time to research the company more and also that I didn't make better use of the time I did have. The biggest disappointment is the fact that I missed out on a job where they didn't care that I lacked a degree or even any real experience with their tools of choice. "I don't really know ruby.." I had said. "It won't be an issue" was the reply. Similar conversations re: git / rails / flex. However it's somewhat encouraging to have made the short list from 80 candidates. That I feel pretty good about.&lt;br /&gt;&lt;br /&gt;Will I continue investigating ruby / rails? My only concern is that it seems somewhat wasteful to walk away from my python experience ( It's the libraries stupid ) for a language so similiar. My `next language` to learn has always been planned to be C then possibly assembly.&lt;br /&gt;&lt;br /&gt;I wonder if I would learn much from ruby about programming in general coming from python. Perhaps the similarities would be actually a boon to recognizing core concepts and distilling an understanding, compared to something radically different where you have no reference? I'm not really sure and I doubt I could really know for myself unless I tried. However, at the end of the day; there *does* seem to be a lot more work available for ruby/rails as opposed to python.&lt;br /&gt;&lt;br /&gt;I can't shake the nagging suspicion I have just been "It's not you it's XXXX"d twice.&lt;br /&gt;&lt;br /&gt;I posted on facebook, "didn't get the job and wonders why he marginalizes himself learning obscure tool-sets". A friend commented: &lt;blockquote&gt;And don't focus on tool-sets - focus on your general problem solving abilities, There are a million different ways to solve any programming problem these days. Key thing is to be adaptable.&lt;/blockquote&gt;I agree with the notion that general problem solving ability is more important than any particular toolset. However, if you had said ability *and* a proficiency with a particular toolset it would make sense to use them where possible. Using the `right tool for the job` is part of solving problems.&lt;br /&gt;&lt;br /&gt;Python isn't exactly an `obscure` toolset, however if you are unwilling to work with PHP you definitely are marginalized. I have a very low pain threshold in general. PHP is painful. I don't like pain. Nuff said.&lt;br /&gt;&lt;br /&gt;Of interest to me was that the recruiter during his `wish you all the best` phonecall mentioned that the ruby community met once a month (in my home city) Perhaps learning some ruby/rails and getting involved would at the least yield some freelancing contracts. Never really been to any geek meetups. Surely beer is involved?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-9087910129547077665?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/9087910129547077665/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=9087910129547077665' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/9087910129547077665'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/9087910129547077665'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/07/consolation-its-not-you-its-other-guy.html' title='Consolation:  It&apos;s not you, it&apos;s the other guy.'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-4547104394937749441</id><published>2009-07-27T23:18:00.000-07:00</published><updated>2009-07-27T23:32:17.103-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><title type='text'>Ruby Tuesday</title><content type='html'>IPython is a huge part of my workflow. How to replace it?&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;(1:24:57 PM) akalias: Does ruby have an equivalent to python's IPython ? ie  a hugely enhanced shell with better completion / syntax coloring etc?&lt;br /&gt;(1:25:32 PM) dominikh: irb with auto_indent and the "wirble" gem&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;I have sourced a few ruby/git ebooks&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/Beginning-Ruby-Novice-Professional-Second/dp/1430223634/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1248762313&amp;amp;sr=8-1"&gt; Beginning Ruby: From Novice to Professional - July 2009 &lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/Ruby-Best-Practices-Gregory-Brown/dp/0596523009/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1248762375&amp;amp;sr=8-1"&gt; Ruby Best Practices - June 2009 &lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/Programming-Ruby-1-9-Pragmatic-Programmers/dp/1934356085/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1248762422&amp;amp;sr=8-1"&gt; Programming Ruby 1.9 (PickAxe) - April 2009 &lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.amazon.com/Version-Control-Git-collaborative-development/dp/0596520123/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1248762494&amp;amp;sr=8-1"&gt; Version Control with Git &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-4547104394937749441?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/4547104394937749441/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=4547104394937749441' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4547104394937749441'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4547104394937749441'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/07/ruby-tuesday.html' title='Ruby Tuesday'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-336565113552710846</id><published>2009-07-27T20:39:00.000-07:00</published><updated>2009-07-28T02:33:14.577-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><title type='text'>Windows Ruby Solutions</title><content type='html'>&lt;quote&gt;&lt;br /&gt;(1:26:08 PM) akalias: what is the best option for ruby on windows?  a vm with linux ;) ?&lt;br /&gt;(1:26:25 PM) dominikh: in my opinion actually yes...&lt;br /&gt;(1:26:39 PM) dominikh: don't know if you can even get a decent terminal in window&lt;br /&gt;&lt;/quote&gt;&lt;br /&gt;The solution is simple: remove windows from the ruby mix&lt;br /&gt;&lt;br /&gt;I have VMWare installed and preconfigured 256MB `headless` Ubuntu webserver appliances are readily available. If I set up one of these with SAMBA file-sharing I should be able to use my existing toolset quite easily and use SSH to log in.&lt;br /&gt;&lt;br /&gt;Now if only the internet wasn't so damn slow where I live...&lt;br /&gt;&lt;br /&gt;UPDATE:&lt;br /&gt;&lt;br /&gt;Net is super slow - however I discovered that I had an old Gutsy Gibbon image laying about on one of my portables. It already had colemak keymap installed. The problem was Gutsy Gibbon has reached the EOL. apt-get was no longer working&lt;br /&gt;&lt;blockquote&gt;(5:00:13 PM) ubottu: &lt;ActionParsnip&gt; wants you to know: For upgrading, see the instructions at https://help.ubuntu.com/community/UpgradeNotes - see also http://www.ubuntu.com/getubuntu/upgrading&lt;/blockquote&gt;&lt;br /&gt;&lt;img src="http://blogdata.akalias.net/virtual-machine-apt-get.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;No problem.&lt;br /&gt;&lt;br /&gt;UPDATE:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.ruby-forum.com/topic/188125#821365"&gt;Compiling Ruby 1.9.1 from source &lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-336565113552710846?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/336565113552710846/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=336565113552710846' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/336565113552710846'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/336565113552710846'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/07/windows-ruby-solutions.html' title='&lt;strike&gt;Windows&lt;/strike&gt; Ruby Solutions'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-602633956224736815</id><published>2009-07-27T19:22:00.000-07:00</published><updated>2009-07-28T17:36:09.786-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><category scheme='http://www.blogger.com/atom/ns#' term='hell'/><title type='text'>Windows Ruby Options</title><content type='html'>I'm looking at learning ruby/rails/git for a full-time job I'm a candidate for. Windows seems to be very much a second class citizen in all of these worlds. Git at least has MSYS git readily available and well maintained. Ruby is another story.&lt;br /&gt;&lt;br /&gt;The 1.9 version of ruby, I'm assuming is &lt;a href="http://blog.silverpond.com.au/?p=11"&gt;used by the company&lt;/a&gt;, was released in late 07 and now, midway through 09, there is no `one click installer` available.&lt;br /&gt;&lt;br /&gt;You can download a 1.9 binary zip at patch level 0 ( the latest is in the high two hundreds ) but it is missing crucial dlls such as zlib and readline.&lt;br /&gt;&lt;br /&gt;What's a windoze fool to do? I'm familiar with linux, mostly ubuntu/debian but a lot more productive in a patched up windows. I use replacement windows managers, independent virtual desktop managers for each monitor, a replacement for explorer etc&lt;br /&gt;&lt;br /&gt;I have tried setting up a similar environment using linux before but my crappy video card wouldn't allow virtual desktop sets per monitor. Changing environments *and* languages at the same time wouldn't be that smart.&lt;br /&gt;&lt;br /&gt;Probably the best would be to try ( in order ):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://blog.orangecabin.com/2009/05/install-ruby-1-9-on-windows-using-zip-binary/"&gt;Find native windows versions of 1.9&lt;/a&gt;&lt;/li&gt;&lt;li&gt; Set up a VM running ubuntu and file-sharing/ssh &lt;/li&gt;&lt;li&gt; Run linux natively &lt;/li&gt;&lt;li&gt; Run a mac &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I have played with ruby under cygwin in the past and it was retarded slow so that is not really an option.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-602633956224736815?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/602633956224736815/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=602633956224736815' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/602633956224736815'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/602633956224736815'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/07/windows-ruby-options.html' title='Windows Ruby Options'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-9141001626632981778</id><published>2009-07-27T18:29:00.000-07:00</published><updated>2009-07-27T23:15:29.187-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Delving Into Ruby</title><content type='html'>A lot of python users seem to hold a fair amount of contempt for ruby. They don't like the use of `perlish` punctuation and operators like `@`, `=~` and `:` and its `more than one way` expressiveness.&lt;br /&gt;&lt;br /&gt;I have always looked at ruby code and thought the syntax seemed quite readable. Having explicit repeated sections like `self.` as instance variable in python seems just noisy and I prefer the more compact @ruby way.&lt;br /&gt;&lt;br /&gt;Rails seems to be architected with a controller class `instance` per request, the view context sourced from the controllers instance variables.&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='keyword'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;method&lt;/span&gt;&lt;br /&gt;    &lt;span class='variable'&gt;@a&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;'hello'&lt;/span&gt;&lt;br /&gt;    &lt;span class='variable'&gt;@b&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; @a &lt;span class='keyword'&gt;*&lt;/span&gt; &lt;span class='constant'&gt;4&lt;/span&gt;&lt;br /&gt;    &lt;span class='variable'&gt;@c&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; @b &lt;span class='keyword'&gt;*&lt;/span&gt; &lt;span class='constant'&gt;3&lt;/span&gt;&lt;br /&gt;&lt;span class='keyword'&gt;end &lt;/span&gt; &lt;/pre&gt;&lt;br /&gt;Compare this with the typical python web paradigm.  Instance variables (self.xxx) are unused as controller instances are shared between requests. Locals are built up first and then redeclared, returned as a context dict.&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;method&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;    a &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;'hello'&lt;/span&gt;&lt;br /&gt;    b &lt;span class='keyword'&gt;=&lt;/span&gt; a &lt;span class='keyword'&gt;*&lt;/span&gt; &lt;span class='constant'&gt;4&lt;/span&gt;&lt;br /&gt;    c &lt;span class='keyword'&gt;=&lt;/span&gt; b &lt;span class='keyword'&gt;*&lt;/span&gt; &lt;span class='constant'&gt;3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='support'&gt;dict&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;( &lt;/span&gt;&lt;span class='variable'&gt;a&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;a, &lt;/span&gt;&lt;span class='variable'&gt;b&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;b, &lt;/span&gt;&lt;span class='variable'&gt;c&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;c )&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;The example is very basic but note in python you couldn't even validly do the following&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;method&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='support'&gt;dict&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; (&lt;br /&gt;        &lt;/span&gt;&lt;span class='variable'&gt;a&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='string'&gt;'hello'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;,&lt;br /&gt;        &lt;/span&gt;&lt;span class='variable'&gt;b&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; a &lt;/span&gt;&lt;span class='keyword'&gt;*&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='constant'&gt;4&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;,&lt;br /&gt;        &lt;/span&gt;&lt;span class='variable'&gt;c&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; b &lt;/span&gt;&lt;span class='keyword'&gt;*&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='constant'&gt;3&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;,&lt;br /&gt;    )&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;`a` hasn't been declared until the dict structure has been parsed therefore you can't reference it to build `b`. At best you can do&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;method&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;    a &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;'hello'&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='support'&gt;dict&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; (&lt;br /&gt;        &lt;/span&gt;&lt;span class='variable'&gt;a&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; a,&lt;br /&gt;        &lt;/span&gt;&lt;span class='variable'&gt;b&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; a &lt;/span&gt;&lt;span class='keyword'&gt;*&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='constant'&gt;4&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;,&lt;br /&gt;        &lt;/span&gt;&lt;span class='variable'&gt;c&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; b &lt;/span&gt;&lt;span class='keyword'&gt;*&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='constant'&gt;3&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;,&lt;br /&gt;    )&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;I have always hated that pattern (as do some other pythonistas) and would generally return locals()&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;method&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;    a &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;'hello'&lt;/span&gt;&lt;br /&gt;    b &lt;span class='keyword'&gt;=&lt;/span&gt; a &lt;span class='keyword'&gt;*&lt;/span&gt; &lt;span class='constant'&gt;4&lt;/span&gt;&lt;br /&gt;    c &lt;span class='keyword'&gt;=&lt;/span&gt; b &lt;span class='keyword'&gt;*&lt;/span&gt; &lt;span class='constant'&gt;3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='support'&gt;locals&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;()&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;But what if you want to convert your context to JSON?  At the very least you have the `self` controller instance in your context. This doesn't convert very well to JSON literals. You then have to filter keys from your context dict. It's supposed to be the Python Way to be explicit ( ie declaring your context vars rather than using locals() or horrible _getframe hacks to try and remove some repetition )&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;Beautiful is better than ugly.&lt;br /&gt;Explicit is better than implicit.&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;To me, being explicit in Python (in this case) breaks the first commandment; it is fugly. Contrastingly, the compactness of the @ruby way, a trifling detail, allows a quite sweet pattern. Just prefix any variables you want in the context with a @. I appreciate it not just from a purely visual sense of aesthetics; practically it would be easier to work with.&lt;br /&gt;&lt;br /&gt;How on earth is using @ here less readable or uglier on a whole? Could it be better? &lt;a href="http://cobra-language.com/"&gt; maybe &lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Anyway, `It's all subjective`, `Beauty is in the eye of the beholder` and `Ugliness is to the bone` and other cliches.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-9141001626632981778?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/9141001626632981778/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=9141001626632981778' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/9141001626632981778'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/9141001626632981778'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/07/delving-into-ruby.html' title='Delving Into Ruby'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-3185405698774136044</id><published>2009-07-19T00:43:00.000-07:00</published><updated>2009-07-19T02:26:38.964-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sublime'/><category scheme='http://www.blogger.com/atom/ns#' term='lxml'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>NodeSelect (or  Hi SilverPond!)</title><content type='html'>I haven't blogged in a month or so as generally I prefer actually coding to blogging about the coding I am doing. So many ideas! So little time! I have noticed quick screen-casts illustrate concepts much more effectively and efficiently. Therefore when communicating about projects with fellow programmers I'll generally just whip up a quick cast.&lt;br /&gt;&lt;br /&gt;So what's been up? Amongst other fun projects, lately I have been working on creating some xml/xhtml helpers for my editor. It's still in prototype stage but the basic concept is proving to be quite promising. As I'm away on a trip I'll just list the commands and link to a few screencasts recorded earlier for the moment.&lt;br /&gt;&lt;br /&gt;This quick blog post expressly for the purpose of showing a potential employer some of my `pet projects`.&lt;br /&gt;&lt;br /&gt;&lt;div id="media"&gt;&lt;object id="csSWF" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="800" height="618" codebase="http://active.macromedia.com/flash7/cabs/ swflash.cab#version=9,0,28,0"&gt;&lt;param name="src" value="http://blogdata.akalias.net/css-select-search-in-regions/css-select-search-in-regions.swf"/&gt;&lt;param name="bgcolor" value="#1a1a1a"/&gt;&lt;param name="quality" value="best"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="scale" value="showall"/&gt;&lt;param name="flashVars" value="autostart=false"/&gt;&lt;embed name="csSWF" src="http://blogdata.akalias.net/css-select-search-in-regions/css-select-search-in-regions.swf" width="800" height="618" bgcolor="#1a1a1a" quality="best" allowscriptaccess="always" allowfullscreen="true" scale="showall" flashvars="autostart=false" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div id="media"&gt;&lt;object id="csSWF" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="800" height="618" codebase="http://active.macromedia.com/flash7/cabs/ swflash.cab#version=9,0,28,0"&gt;&lt;param name="src" value="http://blogdata.akalias.net/css-select-text-nodes/css-select-text-nodes.swf"/&gt;&lt;param name="bgcolor" value="#1a1a1a"/&gt;&lt;param name="quality" value="best"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="scale" value="showall"/&gt;&lt;param name="flashVars" value="autostart=false"/&gt;&lt;embed name="csSWF" src="http://blogdata.akalias.net/css-select-text-nodes/css-select-text-nodes.swf" width="800" height="618" bgcolor="#1a1a1a" quality="best" allowscriptaccess="always" allowfullscreen="true" scale="showall" flashvars="autostart=false" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;&lt;h3&gt; Commands &lt;/h3&gt;&lt;ul&gt;&lt;li&gt;pathSelect CSS&lt;/li&gt;&lt;li&gt;pathSelect XPath&lt;/li&gt;&lt;li&gt;collapseNode&lt;/li&gt;&lt;li&gt;prettyPrintNode&lt;/li&gt;&lt;li&gt;commentNode&lt;/li&gt;&lt;li&gt;tidySelection&lt;/li&gt;&lt;li&gt;selectInsideTag&lt;/li&gt;&lt;li&gt;moveToNodeEnd&lt;/li&gt;&lt;li&gt;selectElementName&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt; ScreenCasts &lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://blogdata.akalias.net/css-select-prototype/css-select-prototype.htm"&gt;css-select-prototype/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blogdata.akalias.net/css-select-elementname/css-select-elementname.htm"&gt;css-select-elementname/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blogdata.akalias.net/css-select-fullselections/css-select-fullselections.htm"&gt;css-select-fullselections/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blogdata.akalias.net/css-select-nodeselect/css-select-nodeselect.htm"&gt;css-select-nodeselect/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blogdata.akalias.net/css-select-search-in-regions/css-select-search-in-regions.htm"&gt;css-select-search-in-regions/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blogdata.akalias.net/css-select-text-nodes/css-select-text-nodes.htm"&gt;css-select-text-nodes/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blogdata.akalias.net/selectnodes-pretty-print-collapse/selectnodes-pretty-print-collapse.htm"&gt;selectnodes-pretty-print-collapse/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blogdata.akalias.net/selectnodes-relativeindentation-snippets/selectnodes-relativeindentation-snippets.htm"&gt;selectnodes-relativeindentation-snippets/&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Links&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.sublimetext.com/forum/viewtopic.php?f=5&amp;t=547"&gt;Forum Thread&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://bitbucket.org/sublimator/webdevelopment/"&gt;BitBucket Repo&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-3185405698774136044?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/3185405698774136044/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=3185405698774136044' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/3185405698774136044'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/3185405698774136044'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/07/nodeselect.html' title='NodeSelect (or  Hi SilverPond!)'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-8792828320284141566</id><published>2009-05-29T06:25:00.000-07:00</published><updated>2009-07-19T01:33:25.687-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='forms'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Sprox</title><content type='html'>An interesting CRUD library based on sqlalchemy / formencode / toscawidgets. It actually generates the html. Looks quite handy if you wanted fully generated forms.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://sprox.org/#form-generation"&gt; Sprox &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I'm not sure if it can do nested model forms or if it can that the functionality is decoupled from the html generation and available for use. It seems like the author has put a lot of &lt;a href="http://sprox.org/class_diagram.html"&gt;thought&lt;/a&gt; into it. I will definitely investigate it and ToscaWidgets further. I haven't really had much experience with auto-generated forms. I get the feeling that they are hard to customize but it's likely worth auto-generating when possible.&lt;br /&gt;&lt;br /&gt;There doesn't seem to be m?any projects focused on (semi or fully) automatically creating list/pagination filter forms. This is quite commonly required in admin sections of a site. &lt;br /&gt;&lt;br /&gt;I can't even remember if Django does.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-8792828320284141566?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/8792828320284141566/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=8792828320284141566' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8792828320284141566'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8792828320284141566'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/05/sprox.html' title='Sprox'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-4961556305655138993</id><published>2009-05-29T01:55:00.000-07:00</published><updated>2009-07-19T01:33:56.786-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='forms'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='sqaencode'/><title type='text'>sqaencode 0.1</title><content type='html'>I have been messing around a bit and now pretty much have a working lib for encoding nested models into forms and back and automatically generating formencode Schema from sqlalchemy models.&lt;br /&gt;&lt;br /&gt;&lt;pre class="blackboard"&gt;E:\python_repos\sqaencode&amp;gt;nosetests --with-coverage --cover-package=sqaencode&lt;br /&gt;................&lt;br /&gt;Name                   Stmts   Exec  Cover   Missing&lt;br /&gt;----------------------------------------------------&lt;br /&gt;sqaencode                  6      6   100%&lt;br /&gt;sqaencode.constants       10     10   100%&lt;br /&gt;sqaencode.decode          47     45    95%   126, 161&lt;br /&gt;sqaencode.encode          28     26    92%   90-91&lt;br /&gt;sqaencode.util            12     12   100%&lt;br /&gt;sqaencode.validators      46     42    91%   30, 64, 66, 129&lt;br /&gt;----------------------------------------------------&lt;br /&gt;TOTAL                    149    141    94%&lt;br /&gt;----------------------------------------------------------------------&lt;br /&gt;Ran 16 tests in 0.962s&lt;br /&gt;&lt;br /&gt;OK&lt;/pre&gt;&lt;br /&gt;I created a ModelSchema class, subclassing formencode.Schema and giving it an inline __metaclass__ inheriting from formencode.declarative.DeclarativeMeta. I over-rode the __repr__ to output an (almost) eval()able representation.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;ModelSchema&lt;/span&gt;(&lt;span class='superclass'&gt;Schema&lt;/span&gt;):&lt;br /&gt;    &lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;__metaclass__&lt;/span&gt;(&lt;span class='superclass'&gt;DeclarativeMeta&lt;/span&gt;):&lt;br /&gt;        &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__repr__&lt;/span&gt;(&lt;span class='variable'&gt;cls&lt;/span&gt;):&lt;br /&gt;            model &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='variable'&gt;cls&lt;/span&gt;.__model__.&lt;span class='variable'&gt;__name__&lt;/span&gt;&lt;br /&gt;            base &lt;span class='keyword'&gt;=&lt;/span&gt; [ &lt;span class='string'&gt;"class &lt;/span&gt;&lt;span class='stringInterpolation'&gt;%(model)s&lt;/span&gt;&lt;span class='string'&gt;Schema(model_schema(&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%(model)s&lt;/span&gt;&lt;span class='string'&gt;)):"&lt;/span&gt; &lt;span class='keyword'&gt;%&lt;/span&gt; &lt;span class='support'&gt;dict&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; (&lt;br /&gt;                              &lt;/span&gt;&lt;span class='variable'&gt;model&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; model)&lt;/span&gt;  ]&lt;br /&gt;&lt;br /&gt;            &lt;span class='keyword'&gt;for&lt;/span&gt; arg &lt;span class='keyword'&gt;in&lt;/span&gt; SCHEMA_ARGS:&lt;br /&gt;                &lt;span class='metaFunctionCallPy'&gt;base.append(&lt;/span&gt;&lt;span class='string'&gt;'    &lt;/span&gt;&lt;span class='stringInterpolation'&gt;%-20s&lt;/span&gt;&lt;span class='string'&gt; = &lt;/span&gt;&lt;span class='stringInterpolation'&gt;%r&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;%&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; (arg, &lt;/span&gt;&lt;span class='support'&gt;getattr&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;cls&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, arg)))&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class='metaFunctionCallPy'&gt;base.append(&lt;/span&gt;&lt;span class='string'&gt;''&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class='keyword'&gt;for&lt;/span&gt; key, validator &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='support'&gt;sorted&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;cls&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.fields.items())&lt;/span&gt;:&lt;br /&gt;                &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;key.startswith(&lt;/span&gt;&lt;span class='string'&gt;'_'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;: &lt;span class='keyword'&gt;continue&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;                args &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;non_default_validator_args(validator)&lt;/span&gt;&lt;br /&gt;                &lt;span class='metaFunctionCallPy'&gt;base.append(&lt;/span&gt;&lt;span class='string'&gt;'    &lt;/span&gt;&lt;span class='stringInterpolation'&gt;%-20s&lt;/span&gt;&lt;span class='string'&gt; = &lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;(&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;)'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;%&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; (&lt;br /&gt;                    key, &lt;/span&gt;&lt;span class='support'&gt;type&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(validator).__name__, args) )&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='constant'&gt;\n&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;.&lt;span class='metaFunctionCallPy'&gt;join(base)&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;Using the model_schema factory to create a ModelSchema and then getting a repr:&lt;br /&gt;&lt;pre class='blackboard'&gt;In [&lt;span class='constant'&gt;1&lt;/span&gt;]: &lt;span class='keyword'&gt;from&lt;/span&gt; sqaencode &lt;span class='keyword'&gt;import&lt;/span&gt; model_schema&lt;br /&gt;&lt;br /&gt;In [&lt;span class='constant'&gt;2&lt;/span&gt;]: &lt;span class='metaFunctionCallPy'&gt;model_schema(Product)&lt;/span&gt;&lt;br /&gt;Out[&lt;span class='constant'&gt;2&lt;/span&gt;]:&lt;br /&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;ProductSchema&lt;/span&gt;(&lt;span class='metaFunctionCallPy'&gt;model_schema(Product)&lt;/span&gt;):&lt;br /&gt;    ignore_key_missing   &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='constant'&gt;True&lt;/span&gt;&lt;br /&gt;    allow_extra_fields   &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='constant'&gt;True&lt;/span&gt;&lt;br /&gt;    pre_validators       &lt;span class='keyword'&gt;=&lt;/span&gt; []&lt;br /&gt;&lt;br /&gt;    active               &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Bool()&lt;/span&gt;&lt;br /&gt;    amount               &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    colour               &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    description          &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    featured             &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Bool()&lt;/span&gt;&lt;br /&gt;    &lt;span class='support'&gt;id&lt;/span&gt;                   &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Int()&lt;/span&gt;&lt;br /&gt;    image                &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    image_thumb          &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    image_zoom           &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    keywords             &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    material             &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    name                 &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    ordernum             &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Int()&lt;/span&gt;&lt;br /&gt;    sku                  &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UnicodeString()&lt;/span&gt;&lt;br /&gt;    views                &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Int()&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The __metaclass__ __repr__ hack serves a dual purpose. a) for debugging and b) as templating. By printing the repr you can use that as a template for customizing a model schema. You actually inherit from a dynamically generated class ( a function call taking a model and optional arguments). I wasn't even too sure you could do that in python. It's nice I don't have to create a new meta class mechanism and can stick with existing formencode semantics.&lt;br /&gt;&lt;br /&gt;Note the `ignore_key_missing` flag that is by default set to True. I think when using this I will just define which fields to validate purely by virtue of what is included in the html. eg If there is no `sku` field in the form then it will not be validated.&lt;br /&gt;&lt;br /&gt;What if I *didn't* want to globally ignore missing keys and wanted to manually declare which to ignore? Formencode has an in-built sub-classing mechanism whereby if you declare `some_key = None` then some_key will not be validated at all.&lt;br /&gt;&lt;br /&gt;To declare a Product model_schema with plural Colors inline:&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;ProductSchema&lt;/span&gt;(&lt;span class='metaFunctionCallPy'&gt;model_schema(Product, &lt;/span&gt;&lt;span class='variable'&gt;nested&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='constant'&gt;True&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;):&lt;br /&gt;    colors &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;model_schema(Color, &lt;/span&gt;&lt;span class='variable'&gt;plural&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='constant'&gt;True&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With Python 2.6 you can do inline customisable declarations of relations:&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;ProductSchema&lt;/span&gt;(&lt;span class='metaFunctionCallPy'&gt;model_schema(Product, &lt;/span&gt;&lt;span class='variable'&gt;nested&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='constant'&gt;True&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;):&lt;br /&gt;    &lt;span class='entity'&gt;@sqaencode.plural&lt;/span&gt;&lt;br /&gt;    &lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;colors&lt;/span&gt;(&lt;span class='metaFunctionCallPy'&gt;model_schema(Color)&lt;/span&gt;):&lt;br /&gt;        some_field &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;NonDefault()&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I'm thinking about creating a mechanism whereby I subclass sqlalchemy.types.* for metadata purposes to further drive automatic schema generation. A cool thing about Django's tight integration is the high level data types. Url, Email etc You declare higher level properties to what are essentially stored as VARCHAR types in the database. It's not *just* a string.&lt;br /&gt;&lt;br /&gt;SqlAlchemy, while really great, (rightly) doesn't try and abstract beyond basic SQL. There is nothing stopping an end user however doing something like this:&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class="lineNumber"&gt;1  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;Url&lt;/span&gt;(&lt;span class='superclass'&gt;Unicode&lt;/span&gt;):   &lt;span class='keyword'&gt;pass&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;2  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;Email&lt;/span&gt;(&lt;span class='superclass'&gt;Unicode&lt;/span&gt;): &lt;span class='keyword'&gt;pass&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;3  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;4  &lt;/span&gt;higher_level_table &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Table ( &lt;/span&gt;&lt;span class='string'&gt;'higher_level'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, metadata,&lt;br /&gt;&lt;span class="lineNumber"&gt;5  &lt;/span&gt;    Column(&lt;/span&gt;&lt;span class='string'&gt;'url'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, Url(&lt;/span&gt;&lt;span class='constant'&gt;32&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)),&lt;br /&gt;&lt;span class="lineNumber"&gt;6  &lt;/span&gt;    Column(&lt;/span&gt;&lt;span class='string'&gt;'email'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, Email(&lt;/span&gt;&lt;span class='constant'&gt;32&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)),&lt;br /&gt;&lt;span class="lineNumber"&gt;7  &lt;/span&gt;    Column(&lt;/span&gt;&lt;span class='string'&gt;'id'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, Integer(), &lt;/span&gt;&lt;span class='variable'&gt;primary_key&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='constant'&gt;True&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, &lt;/span&gt;&lt;span class='variable'&gt;autoincrement&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='constant'&gt;True&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, &lt;/span&gt;&lt;span class='variable'&gt;nullable&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='constant'&gt;False&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;),&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;8  &lt;/span&gt;)&lt;br /&gt;&lt;span class="lineNumber"&gt;9  &lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;If Url and Email were imported column types from sqaencode.types then you could add them to the sqalchemy =&gt; formencode type mapping and they would be picked up by model_schema()&lt;br /&gt;&lt;br /&gt;What's on the todo? &lt;br /&gt;&lt;ul&gt;&lt;li&gt; Options for automatically generating child Schemas. &lt;/li&gt; &lt;li&gt; Setting the length on String/UnicodeString validators automatically to the max length of the corresponding column. &lt;/li&gt; &lt;li&gt; Automatically create unique column validators &lt;/li&gt; &lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-4961556305655138993?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/4961556305655138993/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=4961556305655138993' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4961556305655138993'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4961556305655138993'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/05/01.html' title='sqaencode 0.1'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-4010449110100374552</id><published>2009-05-27T22:20:00.000-07:00</published><updated>2009-07-19T01:37:08.616-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Reading</title><content type='html'>A few days ago I scribbled an entry into my TODO.txt:&lt;blockquote&gt; Learn about Python packaging, namespaces etc, best practices. setup.py etc etc &lt;/blockquote&gt; I started reading a lot and watching whatever screen casts I could. One useful book was &lt;a href="http://www.packtpub.com/expert-python-programming/book"&gt;Expert Python Programming&lt;/a&gt;. It is light on actual python and heavy on the soft skills in using open source python tools to refine your work flow. That is exactly what I was looking for. It is only about 300 pages so I skimmed through most of it in a day.&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://showmedo.com/videotutorials/series?name=mcfckfJ4w"&gt;Agile Development Tools in Python&lt;/a&gt; series by &lt;a href="http://percious.com/blog/"&gt;Christopher Perkins&lt;/a&gt;, while pretty lighton gave some nice quick overviews of some very useful tools.&lt;br /&gt;&lt;br /&gt;Between these two sources I got a good overview of:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;setuptools&lt;/li&gt;&lt;li&gt;distutils&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;virtualenv&lt;/li&gt;&lt;li&gt;paster&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;nosetest&lt;/li&gt;&lt;li&gt;     coverage&lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;docutils&lt;/li&gt;&lt;li&gt;reST&lt;/li&gt;&lt;li&gt;sphinx&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;virtualenv&lt;/h3&gt;&lt;br /&gt;virtualenv will set up an isolated version of python, where applications can remain blissfuly ignorant of the rapid change in the outside world. If you have an application that needs a particular version of a lib and another requiring a different one then this is the tool for the job.&lt;br /&gt;&lt;br /&gt;It will set up a folder structure with (std) Lib and site-packages folders and a scripts directory. One of the scripts is of course the virtual python interpreter. It also has a tremendously useful `activate` script. This will put the Scripts (bin on *nix) folder on PATH and the lib folders on PYTHONPATH. It also modifies your console prompt to display the name of the current virtualenv.&lt;br /&gt;&lt;pre&gt;(myenv) C:\myenv&lt;/pre&gt;&lt;br /&gt;I haven't used it much as of yet but I can imagine that it could get pretty confusing once you had a tonne of them so that is a nice and thoughtful addition.&lt;br /&gt;&lt;br /&gt;The net result being that if you `activate` an environment and then run `python some_python.py` from an arbitrary directory it will run it using the virtualenv. Likewise for any of the scripts in your (*nix: bin, win: Scripts) folder. There is no need for explicitly referencing them. When you are done you just `deactivate` the environment with corresponding script.&lt;br /&gt;&lt;br /&gt;setuptools is installed by default into each new virtualenv and easy_install is included in the scripts folder. You use easy_install to, funnily enough, easily install the packages you require.&lt;br /&gt;&lt;br /&gt;virtualenv seems of great utility, however I imagine it could get pretty wasteful in terms of network usage. You don't really want to be downloading 10 packages EVERY time you start some new project. It would be handy if all of your local virtualenv environments used some sort of global local cache when using easy_install.&lt;br /&gt;&lt;br /&gt;If you needed for example cherrypy and you already had the latest version on your hide drive some where it would just pull the egg from there. Otherwise it would first pull the package into the cache from pypi.&lt;br /&gt;&lt;br /&gt;It would be good if this all happened transparently and each virtualenv's easy_install respected some global distutils.cfg setting for cache location. Where do eggs lay about usually? In a Nest. I'll probably happily find that this has been sorted out.&lt;br /&gt;&lt;br /&gt;Even if you had a cache, saving yourself some bandwidth, if you just copied an egg into each environment that would still be disk wastage. This seems like a good use for symbolic linking. It would be silly to reinvent that in python. Although, I'm not too sure how well Windows supports something like that. *nix is definitely superior in that respect.&lt;br /&gt;&lt;h3&gt;paver&lt;/h3&gt;&lt;br /&gt;Another interesting util is one called paver that sort of ties together virtualenv and distutils/setuptools to allow a more `pythonic` zc.buildout. Buildout uses declarative ini files for everything and is apparently harder to hack if you want to do anything out of the ordinary. (I wouldn't know for sure if these are valid criticisms )&lt;br /&gt;&lt;br /&gt;From what I have read of buildout vs paver/virtualenv I think I will invest in the latter. It just seems to appeal more to my personal sense of aesthetics.&lt;br /&gt;&lt;h3&gt;paster&lt;/h3&gt;&lt;br /&gt;Paster is a hodgepodge macgyver swiss knife created by Ian Bicking. It can launch WSGI applications, launch missiles, sink ships and god knows what else. This utility, `eclectic` in the words of Ian, is basically the kitchen sink.&lt;br /&gt;&lt;br /&gt;Of interest to me is the project template creation commands. It will create a folder with setup.py, README.txt etc and run you through a command line based wizard to set it all up, asking version number, author name and the like.  These project templates can give a bit of an insight into how other people structure their projects.&lt;br /&gt;&lt;br /&gt;Where do the tests go? Inside the distribution proper? Housed inside the same folder as the setup.py? I'm somewhat leaning towards having the tests included in the public package. ie package.tests.fixtures I think it makes it easy then to import fixtures in doctested examples documentation.&lt;br /&gt;&lt;h3&gt;distutils / setuptools&lt;/h3&gt;&lt;br /&gt;With regard to structuring projects, if there is one thing I can hope to take away from all this, then it is the `setup.py develop` command.&lt;br /&gt;&lt;br /&gt;This distutils (or is it setuptools?) command will put your under development package on sys.path so you don't have to run `setup.py install` every time you make changes. Quite handy. Of course you could have a virtualenv where you have a package in `develop` mode and another where you have ran a full `setup.py install` at a certain version.&lt;br /&gt;&lt;h3&gt;nosetests&lt;/h3&gt;&lt;br /&gt;Nosetests has a really great coverage plugin. It will run your tests, and you can specify which package/module you want it to dump a list of line ranges that haven't been `covered` by your test code. This makes it super easy to tell which code paths still needs testing( or culling! ). I'm assuming this is only really of use for pure python code. If so, that is another tick next to dynamic `interpreted` languages.&lt;br /&gt;&lt;h3&gt;sphinx&lt;/h3&gt;&lt;br /&gt;I also experimented with docutils/ reST / Sphinx for documentation. Sphinx is the spiffy new reST based documentation system developed originally for Python proper. It has a built-in indexer and javascript search client and seemingly many other great features. For this reason it seems a LOT of python projects are using it now. I imagine the fact that it's quite easy on the eye out of the box doesn't hurt take-up much either.&lt;br /&gt;&lt;h3&gt; A new project&lt;/h3&gt;&lt;br /&gt;I started on creating a setup.py enabled, nose/doc tested, sphinx documented project. It *really* slows down the whole process. I suppose it's a lot to try and learn all at once.&lt;br /&gt;&lt;br /&gt;I'm still not really sold on test first prototyping. TDD of something you have a little bit of experience with, sure. TDD/documenting something when you are in new terrain and you KNOW that you are going to mess up and have to redux anyway, just seems wasteful.&lt;br /&gt;&lt;br /&gt;I probably just don't `get it` yet. `Slow is fast` has generally held true in a lot of other areas of my experience.&lt;br /&gt;&lt;br /&gt;What else to read up on? I need to learn more about the standard library logging package.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-4010449110100374552?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/4010449110100374552/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=4010449110100374552' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4010449110100374552'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4010449110100374552'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/05/reading.html' title='Reading'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-186795601580710828</id><published>2009-05-22T22:39:00.001-07:00</published><updated>2009-05-22T22:40:30.709-07:00</updated><title type='text'>Souvenir</title><content type='html'>&lt;blockquote&gt;&lt;br /&gt;prison color blue&lt;br /&gt;it's a uniform of choice&lt;br /&gt;count yourself lucky &lt;br /&gt;that you don't write the software&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Some lines from the great Neil Finn's song `Souvenir`.  Count yourself lucky indeed.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-186795601580710828?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/186795601580710828/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=186795601580710828' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/186795601580710828'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/186795601580710828'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/05/oh-dear.html' title='Souvenir'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-833755097788154937</id><published>2009-05-22T20:13:00.000-07:00</published><updated>2009-07-19T01:34:11.023-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='forms'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='sqaencode'/><title type='text'>FormEncode + SqlAlchemy = SQAEncode</title><content type='html'>The way I do admin model CRUD at the moment is have a models.py file containing all my &lt;a href="http://www.sqlalchemy.org/"&gt;sqlalchemy&lt;/a&gt; tables/classes and their respective &lt;a href="http://formencode.org/Design.html#basic-metaphor"&gt;formencode&lt;/a&gt;.Schema/s in a forms.py file. The problem is that I end up having to repeat all the fields and to me that just stinks.&lt;br /&gt;&lt;br /&gt;It's not a big deal as far as typing them out goes as I will typically declare a table / model in sqlalchemy and then use a homebrewed scaffolding function to generate derived code for the Schema and html for the actual form. When actually editing a model form I use &lt;a href="http://genshi.edgewall.org/"&gt;genshi&lt;/a&gt;'s &lt;a href="http://genshi.edgewall.org/wiki/Documentation/filters.html#html-form-filler"&gt;HTMLFormFiller&lt;/a&gt; filter to set the blank forms values.&lt;br /&gt;&lt;br /&gt;This approach really is keeping in the spirit of the&lt;a href="http://formencode.org/"&gt; formencode &lt;/a&gt;library. The author's &lt;a href="http://formencode.org/Design.html#presentation"&gt;philosophy&lt;/a&gt; is that after a while configuring an `autogenerated` form from a model by declaring options in code is just as much work as declaring in html.&lt;br /&gt;&lt;br /&gt;Configuration includes black/white listing certain fields, whether they are allowed to be empty (null/None) and also logical/presentational ordering and grouping of individual fields.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://docs.formalchemy.org/forms.html"&gt;formalchemy&lt;/a&gt; on the other hand will take a model and automatically generate a form with the values already set. It can not though do nested models. ie A parent model with an arbitrary amount of inline children to CRUD in the same form. The author considers this `almost always bad design` It only allows what I term `relating` the parent/root model to existing models.&lt;br /&gt;&lt;br /&gt;While it *may* sometimes save you from doing any `&lt;a href="http://docs.formalchemy.org/forms.html#configuring-and-rendering-forms"&gt;configuration&lt;/a&gt;` whatsoever in the case where the form validation schema reflects perfectly the model, in my (admittedly meagre) experience this would be fairly rare.&lt;br /&gt;&lt;br /&gt;The authors of &lt;a href="http://docs.formalchemy.org/forms.html"&gt;formalchemy&lt;/a&gt; recommend the use of CSS to customize the appearance of the auto generated forms. For more extensive customization you can override each fields renderer (&amp;lt;input type = 'text'&amp;gt; vs &amp;lt;textarea&amp;gt; etc) or the global form generation function.&lt;br /&gt;&lt;br /&gt;Having not really used &lt;a href="http://docs.formalchemy.org/forms.html"&gt;formalchemy&lt;/a&gt; that much I can't say with certainty just how unweildy it gets trying to customize a forms options / looks. I'd guess that you would end up having to do just as much `configuration`, ie repeating of fields / `work`&lt;br /&gt;&lt;br /&gt;Looking at a lot of my formencode Schemas in relation to their underlying model, it seems they are declared in a `white list` manner. The schemas (and html) simply don't declare fields that aren't `public`. The underlying type map is reasonably consistent. A sqlalchemy Bool becomes a formencode Bool, a Unicode maps to a UnicodeString etc. It's typically only the options that are changed.&lt;br /&gt;&lt;br /&gt;I have an emybronic idea to auto generate&lt;a href="http://formencode.org/"&gt; formencode &lt;/a&gt;validators from an sqlalchemy model, mapping validators to column/relation types. I imagine `configuring` this would look something like the following.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class="lineNumber"&gt;1  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;ProductSchema&lt;/span&gt;(&lt;span class='superclass'&gt;ModelSchema&lt;/span&gt;): &lt;br /&gt;&lt;span class="lineNumber"&gt;2  &lt;/span&gt;    _model &lt;span class='keyword'&gt;=&lt;/span&gt; Product&lt;br /&gt;&lt;span class="lineNumber"&gt;3  &lt;/span&gt;    &lt;br /&gt;&lt;span class="lineNumber"&gt;4  &lt;/span&gt;    long_description &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;options(&lt;/span&gt;&lt;span class='variable'&gt;not_empty&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='constant'&gt;True&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;5  &lt;/span&gt;    date_created &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='constant'&gt;None&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;6  &lt;/span&gt;    name &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;UniqueName()&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The ModelSchema would use some type of __metaclass__. Any `private` field declared as None would be blacklisted from the underlying derived schema. Above `options` would just be an alias for dict. A dict would be used to configure the auto mapped validator. `not_empty = True` would override the not_empty argument mapped from the respective columns nullable property. UniqueName above would completely override from the UnicodeString. (In fact any column with unique = True could probably have an auto generated `unique field` validator attached)&lt;br /&gt;&lt;br /&gt;I'm also currently working on some sqlalchemy formencode integration functions that will take basic one table models and their relations and encode them into nested dictionaries / primary key lists. Also, the other way round, taking a nested dict (as from a formencode.NestedVariables pre validated Schema) and creating/updating/deleting an object tree/graph. &lt;br /&gt;&lt;br /&gt;SqlAlchemy mappers makes it quite easy to introspect classes for relations so the object_graph func signature just looks like:&lt;br /&gt;&lt;br /&gt;&lt;pre class="blackboard"&gt;&lt;span class="lineNumber"&gt;1  &lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;object_graph(nd_dict, root_model)&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This all works fine in basic unit test land AND for one model forms. The problem I'm having is ONETOMANY child objects in a form. Imagine an Invoice form with an inline InvoiceItems table with each row representing an individual item. On a new invoice there would be 3 `blank` rows. How would you know which ones to ignore? Which ones to delete?&lt;br /&gt;&lt;br /&gt;I want to set a `_keep` flag for each item.  The _keep  flag  will be reflected as a checkbox in the form and if unchecked will mean that the child item should be ignored in the validation process.&lt;br /&gt;&lt;br /&gt;I could leave the child tables ( invoice items in the concrete sense) empty and use javascript to add a [+] button to add new children. That to me seems like a `bent over` concession.&lt;br /&gt;&lt;br /&gt;The `object_graph` function which CRUDS model[s] from a nestable dict is currently decoupled from the validation process and knows nothing of form errors. This might make it hard. Before that I was successfully using `child_crud` hooks in my CRUD controllers for this purpose.&lt;br /&gt;&lt;br /&gt;I'll have to sit down and work out which cases I'll need to accommodate. I want to be able to update/delete existing models, create new models and ignore cases where the child CRUD form partials have not been edited.&lt;br /&gt;&lt;br /&gt;How to get the formencode.ForEach validator to ignore items without messing up the errors index? These problems were all solved using a child_crud hook in the controllers but trying to abstract and decouple things is making it a lot harder.&lt;br /&gt;&lt;br /&gt;It will be a nut worth cracking anyway.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-833755097788154937?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/833755097788154937/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=833755097788154937' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/833755097788154937'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/833755097788154937'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/05/formencode-sqlalchemy-sqaencode.html' title='FormEncode + SqlAlchemy = SQAEncode'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-4586848010332947945</id><published>2009-05-11T06:55:00.001-07:00</published><updated>2009-07-19T01:39:04.095-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>PyParsing + SqlAlchemy =  Basic Search Engine</title><content type='html'>Today I learned about writing recursive descent parsers using the PyParsing library.  I managed to cobble together an sqlalchemy expression builder for a basic search engine. &lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class="lineNumber"&gt; 1  &lt;/span&gt;&lt;span class='comment'&gt;#################################### IMPORTS ###################################&lt;br /&gt;&lt;span class="lineNumber"&gt; 2  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 3  &lt;/span&gt;&lt;span class='comment'&gt;# PyParsing&lt;br /&gt;&lt;span class="lineNumber"&gt; 4  &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;from&lt;/span&gt; pyparsing &lt;span class='keyword'&gt;import&lt;/span&gt; ( CaselessLiteral, Literal, Word, alphas, quotedString,&lt;br /&gt;&lt;span class="lineNumber"&gt; 5  &lt;/span&gt;                        removeQuotes, operatorPrecedence, ParseException,&lt;br /&gt;&lt;span class="lineNumber"&gt; 6  &lt;/span&gt;                        stringEnd, opAssoc ) &lt;br /&gt;&lt;span class="lineNumber"&gt; 7  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 8  &lt;/span&gt;&lt;span class='comment'&gt;# SqlAlchemy&lt;br /&gt;&lt;span class="lineNumber"&gt; 9  &lt;/span&gt;&lt;/span&gt;&lt;span class='keyword'&gt;from&lt;/span&gt; sqlalchemy &lt;span class='keyword'&gt;import&lt;/span&gt; and_, not_, or_&lt;br /&gt;&lt;span class="lineNumber"&gt;10  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;11  &lt;/span&gt;&lt;span class='comment'&gt;################################## LIKE ESCAPE #################################&lt;br /&gt;&lt;span class="lineNumber"&gt;12  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;13  &lt;/span&gt;LIKE_ESCAPE &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;r'&lt;/span&gt;&lt;span class='constant'&gt;\\&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;14  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;15  &lt;/span&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;like_escape&lt;/span&gt;(&lt;span class='variable'&gt;s&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;16  &lt;/span&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='string'&gt;'%'&lt;/span&gt; &lt;span class='keyword'&gt;+&lt;/span&gt; ( &lt;span class='metaFunctionCallPy'&gt;s.replace(&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='constant'&gt;\\&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, &lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='constant'&gt;\\\\&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;17  &lt;/span&gt;                    .&lt;span class='metaFunctionCallPy'&gt;replace(&lt;/span&gt;&lt;span class='string'&gt;'%'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, &lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='constant'&gt;\\&lt;/span&gt;&lt;span class='string'&gt;%'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;18  &lt;/span&gt;                    .&lt;span class='metaFunctionCallPy'&gt;replace(&lt;/span&gt;&lt;span class='string'&gt;'_'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, &lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='constant'&gt;\\&lt;/span&gt;&lt;span class='string'&gt;_'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt; ) &lt;span class='keyword'&gt;+&lt;/span&gt; &lt;span class='string'&gt;'%'&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;19  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;20  &lt;/span&gt;&lt;span class='comment'&gt;############################### REUSABLE ACTIONS ###############################&lt;br /&gt;&lt;span class="lineNumber"&gt;21  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;22  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;UnaryOperation&lt;/span&gt;(&lt;span class='support'&gt;object&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;23  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__init__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;, &lt;span class='variable'&gt;t&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;24  &lt;/span&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.op, &lt;span class='variable'&gt;self&lt;/span&gt;.a &lt;span class='keyword'&gt;=&lt;/span&gt; t[&lt;span class='constant'&gt;0&lt;/span&gt;]&lt;br /&gt;&lt;span class="lineNumber"&gt;25  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;26  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__repr__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;27  &lt;/span&gt;        &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='string'&gt;"&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;:(&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;)"&lt;/span&gt; &lt;span class='keyword'&gt;%&lt;/span&gt; (&lt;span class='variable'&gt;self&lt;/span&gt;.name, &lt;span class='support'&gt;str&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.a)&lt;/span&gt;)    &lt;br /&gt;&lt;span class="lineNumber"&gt;28  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;29  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;express&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;30  &lt;/span&gt;        &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='variable'&gt;self&lt;/span&gt;.operator[&lt;span class='constant'&gt;0&lt;/span&gt;]&lt;span class='metaFunctionCallPy'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.a.express())&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;31  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;32  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;BinaryOperation&lt;/span&gt;(&lt;span class='support'&gt;object&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;33  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__init__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;, &lt;span class='variable'&gt;t&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;34  &lt;/span&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.op &lt;span class='keyword'&gt;=&lt;/span&gt; t[&lt;span class='constant'&gt;0&lt;/span&gt;][&lt;span class='constant'&gt;1&lt;/span&gt;]&lt;br /&gt;&lt;span class="lineNumber"&gt;35  &lt;/span&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.operands &lt;span class='keyword'&gt;=&lt;/span&gt; t[&lt;span class='constant'&gt;0&lt;/span&gt;][&lt;span class='constant'&gt;0&lt;/span&gt;::&lt;span class='constant'&gt;2&lt;/span&gt;]&lt;br /&gt;&lt;span class="lineNumber"&gt;36  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;37  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__repr__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;38  &lt;/span&gt;        &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='string'&gt;"&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;:(&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;)"&lt;/span&gt; &lt;span class='keyword'&gt;%&lt;/span&gt; ( &lt;span class='variable'&gt;self&lt;/span&gt;.name,  &lt;br /&gt;&lt;span class="lineNumber"&gt;39  &lt;/span&gt;                             &lt;span class='string'&gt;","&lt;/span&gt;.&lt;span class='metaFunctionCallPy'&gt;join(&lt;/span&gt;&lt;span class='support'&gt;str&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(oper) &lt;/span&gt;&lt;span class='keyword'&gt;for&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; oper &lt;/span&gt;&lt;span class='keyword'&gt;in&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.operands)&lt;/span&gt; )    &lt;br /&gt;&lt;span class="lineNumber"&gt;40  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;41  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;express&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;42  &lt;/span&gt;        &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='variable'&gt;self&lt;/span&gt;.operator[&lt;span class='constant'&gt;0&lt;/span&gt;]&lt;span class='metaFunctionCallPy'&gt;(&lt;/span&gt;&lt;span class='keyword'&gt;*&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;( oper.express() &lt;/span&gt;&lt;span class='keyword'&gt;for&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; oper &lt;/span&gt;&lt;span class='keyword'&gt;in&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.operands ))&lt;/span&gt;    &lt;br /&gt;&lt;span class="lineNumber"&gt;43  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;44  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;SearchAnd&lt;/span&gt;(&lt;span class='superclass'&gt;BinaryOperation&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;45  &lt;/span&gt;    name &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;'AND'&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;46  &lt;/span&gt;    operator &lt;span class='keyword'&gt;=&lt;/span&gt; [and_]&lt;br /&gt;&lt;span class="lineNumber"&gt;47  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;48  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;SearchOr&lt;/span&gt;(&lt;span class='superclass'&gt;BinaryOperation&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;49  &lt;/span&gt;    name &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;'OR'&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;50  &lt;/span&gt;    operator &lt;span class='keyword'&gt;=&lt;/span&gt; [or_]&lt;br /&gt;&lt;span class="lineNumber"&gt;51  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;52  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;SearchNot&lt;/span&gt;(&lt;span class='superclass'&gt;UnaryOperation&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;53  &lt;/span&gt;    name &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;'NOT'&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;54  &lt;/span&gt;    operator &lt;span class='keyword'&gt;=&lt;/span&gt; [not_]&lt;br /&gt;&lt;span class="lineNumber"&gt;55  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;56  &lt;/span&gt;&lt;span class='comment'&gt;############################### REUSABLE GRAMMARS ##############################&lt;br /&gt;&lt;span class="lineNumber"&gt;57  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;58  &lt;/span&gt;AND &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;CaselessLiteral(&lt;/span&gt;&lt;span class='string'&gt;"and"&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt; &lt;span class='keyword'&gt;|&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Literal(&lt;/span&gt;&lt;span class='string'&gt;'+'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;59  &lt;/span&gt;OR  &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;CaselessLiteral(&lt;/span&gt;&lt;span class='string'&gt;"or"&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;  &lt;span class='keyword'&gt;|&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Literal(&lt;/span&gt;&lt;span class='string'&gt;'|'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;60  &lt;/span&gt;NOT &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;CaselessLiteral(&lt;/span&gt;&lt;span class='string'&gt;"not"&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt; &lt;span class='keyword'&gt;|&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;Literal(&lt;/span&gt;&lt;span class='string'&gt;'!'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;61  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;62  &lt;/span&gt;searchTermMaster &lt;span class='keyword'&gt;=&lt;/span&gt;  (&lt;br /&gt;&lt;span class="lineNumber"&gt;63  &lt;/span&gt;    &lt;span class='metaFunctionCallPy'&gt;Word(alphas)&lt;/span&gt; &lt;span class='keyword'&gt;|&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;quotedString.copy()&lt;/span&gt;.&lt;span class='metaFunctionCallPy'&gt;setParseAction( removeQuotes )&lt;/span&gt; )&lt;br /&gt;&lt;span class="lineNumber"&gt;64  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;65  &lt;/span&gt;&lt;span class='comment'&gt;########################## THREAD SAFE PARSER FACTORY ##########################&lt;br /&gt;&lt;span class="lineNumber"&gt;66  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;67  &lt;/span&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;like_parser&lt;/span&gt;(&lt;span class='variable'&gt;model&lt;/span&gt;, &lt;span class='variable'&gt;fields&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;[]):&lt;br /&gt;&lt;span class="lineNumber"&gt;68  &lt;/span&gt;    &lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;SearchTerm&lt;/span&gt;(&lt;span class='support'&gt;object&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;69  &lt;/span&gt;        &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__init__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;, &lt;span class='variable'&gt;tokens&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;70  &lt;/span&gt;            &lt;span class='variable'&gt;self&lt;/span&gt;.term &lt;span class='keyword'&gt;=&lt;/span&gt; tokens[&lt;span class='constant'&gt;0&lt;/span&gt;]&lt;br /&gt;&lt;span class="lineNumber"&gt;71  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;72  &lt;/span&gt;        &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;express&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;73  &lt;/span&gt;            &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;or_ (&lt;br /&gt;&lt;span class="lineNumber"&gt;74  &lt;/span&gt;                &lt;/span&gt;&lt;span class='keyword'&gt;*&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;( &lt;/span&gt;&lt;span class='support'&gt;getattr&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(model, field).like( like_escape(&lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.term),&lt;br /&gt;&lt;span class="lineNumber"&gt;75  &lt;/span&gt;                                               &lt;/span&gt;&lt;span class='variable'&gt;escape&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; &lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; LIKE_ESCAPE) &lt;br /&gt;&lt;span class="lineNumber"&gt;76  &lt;/span&gt;                   &lt;/span&gt;&lt;span class='keyword'&gt;for&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; field &lt;/span&gt;&lt;span class='keyword'&gt;in&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt; fields )&lt;br /&gt;&lt;span class="lineNumber"&gt;77  &lt;/span&gt;            )&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;78  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;79  &lt;/span&gt;        &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__repr__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;80  &lt;/span&gt;            &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='variable'&gt;self&lt;/span&gt;.term&lt;br /&gt;&lt;span class="lineNumber"&gt;81  &lt;/span&gt;    &lt;br /&gt;&lt;span class="lineNumber"&gt;82  &lt;/span&gt;    searchTerm &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;searchTermMaster.copy()&lt;/span&gt;.&lt;span class='metaFunctionCallPy'&gt;setParseAction(SearchTerm)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;83  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;84  &lt;/span&gt;    searchExpr &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;operatorPrecedence( searchTerm,&lt;br /&gt;&lt;span class="lineNumber"&gt;85  &lt;/span&gt;           [ (NOT, &lt;/span&gt;&lt;span class='constant'&gt;1&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, opAssoc.RIGHT, SearchNot),&lt;br /&gt;&lt;span class="lineNumber"&gt;86  &lt;/span&gt;             (AND, &lt;/span&gt;&lt;span class='constant'&gt;2&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, opAssoc.LEFT,  SearchAnd),&lt;br /&gt;&lt;span class="lineNumber"&gt;87  &lt;/span&gt;             (OR,  &lt;/span&gt;&lt;span class='constant'&gt;2&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, opAssoc.LEFT,  SearchOr) ] )&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;88  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;89  &lt;/span&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; searchExpr &lt;span class='keyword'&gt;+&lt;/span&gt; stringEnd&lt;br /&gt;&lt;span class="lineNumber"&gt;90  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;91  &lt;/span&gt;&lt;span class='comment'&gt;########################### SEARCH FIELDS LIKE HELPER ##########################&lt;br /&gt;&lt;span class="lineNumber"&gt;92  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;93  &lt;/span&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;search_fields_like&lt;/span&gt;(&lt;span class='variable'&gt;s&lt;/span&gt;, &lt;span class='variable'&gt;model&lt;/span&gt;, &lt;span class='variable'&gt;fields&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;94  &lt;/span&gt;    &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='support'&gt;isinstance&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(fields, &lt;/span&gt;&lt;span class='support'&gt;basestring&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;: fields &lt;span class='keyword'&gt;=&lt;/span&gt; [fields]&lt;br /&gt;&lt;span class="lineNumber"&gt;95  &lt;/span&gt;    parser &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;like_parser(model, fields)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;96  &lt;/span&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;parser.parseString(s)&lt;/span&gt;[&lt;span class='constant'&gt;0&lt;/span&gt;].&lt;span class='metaFunctionCallPy'&gt;express()&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;97  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;98  &lt;/span&gt;&lt;span class='comment'&gt;################################################################################&lt;br /&gt;&lt;span class="lineNumber"&gt;99  &lt;/span&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-4586848010332947945?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/4586848010332947945/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=4586848010332947945' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4586848010332947945'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4586848010332947945'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/05/pyparsing-sqlalchemy-basic-search.html' title='PyParsing + SqlAlchemy =  Basic Search Engine'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-7179476263349847139</id><published>2009-04-03T03:59:00.000-07:00</published><updated>2009-07-19T01:35:27.240-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sublime'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>ListFilter Prototype</title><content type='html'>&lt;div id="media"&gt; &lt;object id="csSWF" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="768" height="554" codebase="http://active.macromedia.com/flash7/cabs/ swflash.cab#version=9,0,28,0"&gt; &lt;param name="src" value="http://blogdata.akalias.net/FilterPrototype/FilterPrototype.flv"/&gt; &lt;param name="bgcolor" value="#1a1a1a"/&gt; &lt;param name="quality" value="best"/&gt; &lt;param name="allowScriptAccess" value="always"/&gt; &lt;param name="allowFullScreen" value="true"/&gt; &lt;param name="scale" value="showall"/&gt; &lt;param name="flashVars" value="autostart=false"/&gt; &lt;embed name="csSWF" src="http://blogdata.akalias.net/FilterPrototype/FilterPrototype_controller.swf" width="768" height="554" bgcolor="#1a1a1a" quality="best" allowScriptAccess="always" allowFullScreen="true" scale="showall" flashVars="autostart=false" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash"&gt;&lt;/embed&gt; &lt;/object&gt; &lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Multiple space terminated regular expressions for list filtering.&lt;br /&gt;&lt;br /&gt;The examples presented are somewhat hare-brained. a) Cause I'm hare-brained b) I'm really tired at the moment. Note the dramatic pauses :) Can.....you.....see....what....I'm.........do....ing  You can imagine the usefulness though.&lt;br /&gt;&lt;br /&gt;The filter is a prototype implementation of a filtering syntax for a QuickOpen Panel specifically designed for `quick` (try not to laugh) *multiple item* selection. The idea is to allow you to just keep typing in to refine your search. NOT this OR that. NOT that. `Open All In New Window` etc. &lt;br /&gt;&lt;br /&gt;It is implemented using the editors actual text buffer API.&lt;br /&gt;&lt;br /&gt;My editors current QuickOpen panel (while it does support multiple selection using ctrl-enter) works a lot like the FireFox url history search; space terminated tokens that each list item must match else will be filtered from the list. This works great for selecting one item but what if you want to open up multiple items at a time? &lt;br /&gt;&lt;br /&gt;Plain regex searches are too unwieldy and lack the speed of entry desired.&lt;br /&gt;&lt;br /&gt;The filtering is a regex extension of the current `all space terminated word chunks in any order`. It maintains most of the benefits and generally works as before for non regex characters. If you just need to type in some alphanumerics then it should be just as quick. It's essentially multiple regexes instead of multiple substring matches.&lt;br /&gt;&lt;br /&gt;OPERATORS:&lt;br /&gt;&lt;br /&gt;' ' AND operator&lt;br /&gt;! NOT operator&lt;br /&gt;| OR operator&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-7179476263349847139?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/7179476263349847139/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=7179476263349847139' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7179476263349847139'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7179476263349847139'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/04/listfilter-prototype.html' title='ListFilter Prototype'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-7772425399074874101</id><published>2009-04-02T18:32:00.000-07:00</published><updated>2009-04-03T04:22:04.559-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='jQuery'/><title type='text'>jQuery = $;</title><content type='html'>&lt;img src="http://blogdata.akalias.net/hash_events.jpg"/&gt;&lt;br /&gt;&lt;br /&gt;I have been making a foray into JavaScript recently for work and having had good experiences with `jQuery Lightbox` decided to use it for the basis of a job I was doing.&lt;br /&gt;&lt;br /&gt;It is essentially a carpet gallery website with collections of colors (1:M).  The designer wanted it to be `AJAX` ( a term that seems to have been hijacked to mean any page updated without slow browser refresh )&lt;br /&gt;&lt;br /&gt;In the middle of the page is a large image. Above it are next/prev collection links and tiny `swatches` (thumbnails) containing links to each color for the current selection. &lt;br /&gt;&lt;br /&gt;To either side of the image are next/prev color in collection links and upon mouseover a window will appear showing a magnified area following the cursor which is changed to a crosshair.  The cursor will change via css styling to `cursor:wait` whilst waiting for the zoom image to load.&lt;br /&gt;&lt;br /&gt;Upon changing color (via swatch, next/prev collection/color ) an animated gif will show while the chosen color's image is loading.&lt;br /&gt;&lt;br /&gt;At first I was keeping a counter of the current colors position in the collections array of colors (clicking on the little thumbnails would take the title attribute, slug it and use that for the color, updating the current position index by with an $.inArray(color, colors) )&lt;br /&gt;&lt;br /&gt;The filenames to load was, and are, a function of current collection and color.&lt;br /&gt;&lt;br /&gt;The problem was having the state in an internal counter didn't really work for `open in new tab` or for sending links. "Hey check out this carpet... no the red one... Did you type in that url properly? Just paste it in."&lt;br /&gt;&lt;br /&gt;I did some googling for `ajax urls` and stumbled upon a technique that sounded useful. That being polling the hash location for changes and then setting page state as a function of the hash. They said the ideal `polling` rate was 100ms. Sounded pretty hacky but at least the urls worked.&lt;br /&gt;&lt;br /&gt;I searched for and found a &lt;a href="http://plugins.jquery.com/files/jquery.hashhistory.js.txt"&gt; plugin &lt;/a&gt; for jQuery that allows you to set event handlers for when the window hash changes. It uses polling but is responsive (42 ms) and works on IE 6.&lt;br /&gt;&lt;br /&gt;I therefore just set a callback to update all the links and the image upon hash changing.  I split the hash on '--' to find the current collection and color&lt;br /&gt;&lt;br /&gt;The great thing about it is that the event is fired on page load so it goes through the `change color` routine. Updates all the links, shows the loading animated gif etc.&lt;br /&gt;&lt;br /&gt;You can send links to people, open in next tab from any of the links etc&lt;br /&gt;&lt;br /&gt;jQuery and its plugins made everything really straight forward. The only head scratching was getting the magnifying glass to work. None of the plugins worked out of the box for images that changed src. They worked mainly for a `static` gallery.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-7772425399074874101?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/7772425399074874101/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=7772425399074874101' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7772425399074874101'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7772425399074874101'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/04/jquery.html' title='jQuery = $;'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-1969850719368768092</id><published>2009-04-02T17:55:00.000-07:00</published><updated>2009-05-03T21:52:49.390-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ctags'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='bisect'/><category scheme='http://www.blogger.com/atom/ns#' term='binary search'/><title type='text'>CTags 2: TDD</title><content type='html'>This is part two of CTags. See &lt;a href="http://akalias.blogspot.com/2009/03/captain-bisect-and-his-rag-ctag-crew.html"&gt; part 1 &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Test Driven Development&lt;br /&gt;&lt;br /&gt;Adherents religiously write tests first for *every* function they write.&lt;br /&gt;&lt;br /&gt;Personally, I think a tonne of unit tests while prototyping is a waste of time for `simple` stuff with only one programmer working.  It tends to feel like walking in mud. I prefer higher level tests that while they may not pinpoint the exact cause of failure won't double refactoring efforts.  Especially when prototyping. Spend ages testing that `The Wrong Way` works? No thanks... Prototype, then rewrite with tests.&lt;br /&gt;&lt;br /&gt;Sometimes though, `TDD` really is indispensable while exploring. Especially when working on stuff that is pushing the limits of your understanding.  If I start encountering bugs I usually see it as a sign I need to start writing tests.  Rather than using transient print statements to debug I'll write some unit tests.&lt;br /&gt;&lt;br /&gt;The TagFile class below (now commented) is an example of when I found testing while prototyping invaluable.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class="lineNumber"&gt;  1  &lt;/span&gt;&lt;span class='comment'&gt;#################################### IMPORTS ###################################&lt;br /&gt;&lt;span class="lineNumber"&gt;  2  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;  3  &lt;/span&gt;&lt;span class='keyword'&gt;from&lt;/span&gt; __future__ &lt;span class='keyword'&gt;import&lt;/span&gt; with_statement&lt;br /&gt;&lt;span class="lineNumber"&gt;  4  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;  5  &lt;/span&gt;&lt;span class='keyword'&gt;import&lt;/span&gt; os&lt;br /&gt;&lt;span class="lineNumber"&gt;  6  &lt;/span&gt;&lt;span class='keyword'&gt;import&lt;/span&gt; bisect&lt;br /&gt;&lt;span class="lineNumber"&gt;  7  &lt;/span&gt;&lt;span class='keyword'&gt;import&lt;/span&gt; mmap&lt;br /&gt;&lt;span class="lineNumber"&gt;  8  &lt;/span&gt;&lt;span class='keyword'&gt;import&lt;/span&gt; unittest&lt;br /&gt;&lt;span class="lineNumber"&gt;  9  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 10  &lt;/span&gt;&lt;span class='comment'&gt;################################### CONSTANTS ##################################&lt;br /&gt;&lt;span class="lineNumber"&gt; 11  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 12  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 13  &lt;/span&gt;&lt;span class='string'&gt;"""&lt;br /&gt;&lt;span class="lineNumber"&gt; 14  &lt;/span&gt;The tags in a `tags` file are listed one per line formatted as so:&lt;br /&gt;&lt;span class="lineNumber"&gt; 15  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 16  &lt;/span&gt;    tag_name&amp;lt;TAB&amp;gt;file_name&amp;lt;TAB&amp;gt;ex_cmd;"&amp;lt;TAB&amp;gt;extension_fields&lt;br /&gt;&lt;span class="lineNumber"&gt; 17  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 18  &lt;/span&gt;"""&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 19  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 20  &lt;/span&gt;&lt;span class='comment'&gt;# symbolic constants for column indexes&lt;br /&gt;&lt;span class="lineNumber"&gt; 21  &lt;/span&gt;&lt;/span&gt;SYMBOL &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='constant'&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 22  &lt;/span&gt;FILENAME &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='constant'&gt;1&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 23  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 24  &lt;/span&gt;&lt;span class='comment'&gt;################################################################################&lt;br /&gt;&lt;span class="lineNumber"&gt; 25  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 26  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;TagFile&lt;/span&gt;(&lt;span class='support'&gt;object&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt; 27  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__init__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;, &lt;span class='variable'&gt;p&lt;/span&gt;, &lt;span class='variable'&gt;column&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt; 28  &lt;/span&gt;        &lt;span class='string'&gt;""" &lt;br /&gt;&lt;span class="lineNumber"&gt; 29  &lt;/span&gt;        &lt;br /&gt;&lt;span class="lineNumber"&gt; 30  &lt;/span&gt;        Instantiate a new TagFile&lt;br /&gt;&lt;span class="lineNumber"&gt; 31  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 32  &lt;/span&gt;        @p            path to `tags` file&lt;br /&gt;&lt;span class="lineNumber"&gt; 33  &lt;/span&gt;        @column       which column to read&lt;br /&gt;&lt;span class="lineNumber"&gt; 34  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 35  &lt;/span&gt;        """&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 36  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 37  &lt;/span&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.p &lt;span class='keyword'&gt;=&lt;/span&gt; p&lt;br /&gt;&lt;span class="lineNumber"&gt; 38  &lt;/span&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.column &lt;span class='keyword'&gt;=&lt;/span&gt; column&lt;br /&gt;&lt;span class="lineNumber"&gt; 39  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 40  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__getitem__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;, &lt;span class='variable'&gt;index&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt; 41  &lt;/span&gt;        &lt;span class='string'&gt;"self.fh is the mmap opened by get"&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 42  &lt;/span&gt;        &lt;span class='comment'&gt;# Seek to a certain byte index&lt;br /&gt;&lt;span class="lineNumber"&gt; 43  &lt;/span&gt;&lt;/span&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.fh.seek(index)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 44  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 45  &lt;/span&gt;        &lt;span class='comment'&gt;# The position is likely to be halfway through a line so read up to&lt;br /&gt;&lt;span class="lineNumber"&gt; 46  &lt;/span&gt;&lt;/span&gt;        &lt;span class='comment'&gt;# the first new line and throw away the `junk`&lt;br /&gt;&lt;span class="lineNumber"&gt; 47  &lt;/span&gt;&lt;/span&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.fh.readline()&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 48  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 49  &lt;/span&gt;        &lt;span class='comment'&gt;# Note that it's actually returning the column from the line *after* the&lt;br /&gt;&lt;span class="lineNumber"&gt; 50  &lt;/span&gt;&lt;/span&gt;        &lt;span class='comment'&gt;# line region containing the index.&lt;br /&gt;&lt;span class="lineNumber"&gt; 51  &lt;/span&gt;&lt;/span&gt;        &lt;br /&gt;&lt;span class="lineNumber"&gt; 52  &lt;/span&gt;        &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.fh.readline()&lt;/span&gt;.&lt;span class='metaFunctionCallPy'&gt;split(&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='constant'&gt;\t&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;[&lt;span class='variable'&gt;self&lt;/span&gt;.column]&lt;br /&gt;&lt;span class="lineNumber"&gt; 53  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 54  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='support'&gt;__len__&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt; 55  &lt;/span&gt;        &lt;span class='comment'&gt;# bisect.bisect_left search must know how large the file is&lt;br /&gt;&lt;span class="lineNumber"&gt; 56  &lt;/span&gt;&lt;/span&gt;        &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;os.stat(&lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.p)&lt;/span&gt;.st_size&lt;br /&gt;&lt;span class="lineNumber"&gt; 57  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 58  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;get&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;, *&lt;span class='variable'&gt;tags&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt; 59  &lt;/span&gt;        &lt;span class='string'&gt;"""&lt;br /&gt;&lt;span class="lineNumber"&gt; 60  &lt;/span&gt;        &lt;br /&gt;&lt;span class="lineNumber"&gt; 61  &lt;/span&gt;        Get all lines for one or more tags&lt;br /&gt;&lt;span class="lineNumber"&gt; 62  &lt;/span&gt;        &lt;br /&gt;&lt;span class="lineNumber"&gt; 63  &lt;/span&gt;        """&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 64  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 65  &lt;/span&gt;        &lt;span class='keyword'&gt;with&lt;/span&gt; &lt;span class='support'&gt;open&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(&lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.p, &lt;/span&gt;&lt;span class='string'&gt;'r+'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt; &lt;span class='keyword'&gt;as&lt;/span&gt; fh:&lt;br /&gt;&lt;span class="lineNumber"&gt; 66  &lt;/span&gt;            &lt;span class='comment'&gt;# mmap is not needed but delivers performance increase&lt;br /&gt;&lt;span class="lineNumber"&gt; 67  &lt;/span&gt;&lt;/span&gt;            &lt;span class='variable'&gt;self&lt;/span&gt;.fh &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;mmap.mmap(fh.fileno(), &lt;/span&gt;&lt;span class='constant'&gt;0&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 68  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 69  &lt;/span&gt;            &lt;span class='keyword'&gt;for&lt;/span&gt; tag &lt;span class='keyword'&gt;in&lt;/span&gt; tags:&lt;br /&gt;&lt;span class="lineNumber"&gt; 70  &lt;/span&gt;                &lt;span class='comment'&gt;# As __getitem__ returns the colum from the line region *after*&lt;br /&gt;&lt;span class="lineNumber"&gt; 71  &lt;/span&gt;&lt;/span&gt;                &lt;span class='comment'&gt;# that containing the index pt then bisect( alias of&lt;br /&gt;&lt;span class="lineNumber"&gt; 72  &lt;/span&gt;&lt;/span&gt;                &lt;span class='comment'&gt;# bisect_right ) will give the wrong index.&lt;br /&gt;&lt;span class="lineNumber"&gt; 73  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 74  &lt;/span&gt;                b4 &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;bisect.bisect_left(&lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;, tag)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 75  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 76  &lt;/span&gt;                &lt;span class='comment'&gt;# Move the file to the position found&lt;br /&gt;&lt;span class="lineNumber"&gt; 77  &lt;/span&gt;&lt;/span&gt;                &lt;span class='metaFunctionCallPy'&gt;fh.seek(b4)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 78  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 79  &lt;/span&gt;                &lt;span class='comment'&gt;# Iterate over file. There may be more than one tag line to get&lt;br /&gt;&lt;span class="lineNumber"&gt; 80  &lt;/span&gt;&lt;/span&gt;                &lt;span class='comment'&gt;# per symbol/filename&lt;br /&gt;&lt;span class="lineNumber"&gt; 81  &lt;/span&gt;&lt;/span&gt;                &lt;span class='keyword'&gt;for&lt;/span&gt; l &lt;span class='keyword'&gt;in&lt;/span&gt; fh:&lt;br /&gt;&lt;span class="lineNumber"&gt; 82  &lt;/span&gt;                    &lt;span class='comment'&gt;# Compare search vs line at column&lt;br /&gt;&lt;span class="lineNumber"&gt; 83  &lt;/span&gt;&lt;/span&gt;                    comp &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='support'&gt;cmp&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(l.split(&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='constant'&gt;\t&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)[&lt;/span&gt;&lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.column], tag)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 84  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 85  &lt;/span&gt;                    &lt;span class='comment'&gt;# This handles the case of being `left of left` due to &lt;br /&gt;&lt;span class="lineNumber"&gt; 86  &lt;/span&gt;&lt;/span&gt;                    &lt;span class='comment'&gt;# __getitem__ index being left of symbol it returns&lt;br /&gt;&lt;span class="lineNumber"&gt; 87  &lt;/span&gt;&lt;/span&gt;                    &lt;span class='comment'&gt;# ie wait until catch up&lt;br /&gt;&lt;span class="lineNumber"&gt; 88  &lt;/span&gt;&lt;/span&gt;                    &lt;span class='keyword'&gt;if&lt;/span&gt;    comp &lt;span class='keyword'&gt;==&lt;/span&gt; &lt;span class='keyword'&gt;-&lt;/span&gt;&lt;span class='constant'&gt;1&lt;/span&gt;:  &lt;span class='keyword'&gt;continue&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 89  &lt;/span&gt;                    &lt;span class='comment'&gt;# If line is greater then have moved on to next symbol&lt;br /&gt;&lt;span class="lineNumber"&gt; 90  &lt;/span&gt;&lt;/span&gt;                    &lt;span class='keyword'&gt;elif&lt;/span&gt;  comp:  &lt;span class='keyword'&gt;break&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 91  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 92  &lt;/span&gt;                    &lt;span class='comment'&gt;# Found tag!&lt;br /&gt;&lt;span class="lineNumber"&gt; 93  &lt;/span&gt;&lt;/span&gt;                    &lt;span class='keyword'&gt;yield&lt;/span&gt; l&lt;br /&gt;&lt;span class="lineNumber"&gt; 94  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 95  &lt;/span&gt;            &lt;span class='comment'&gt;# close mmap&lt;br /&gt;&lt;span class="lineNumber"&gt; 96  &lt;/span&gt;&lt;/span&gt;            &lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.fh.close()&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 97  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 98  &lt;/span&gt;&lt;span class='comment'&gt;##################################### TESTS ####################################&lt;br /&gt;&lt;span class="lineNumber"&gt; 99  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;100  &lt;/span&gt;&lt;span class='storage'&gt;class&lt;/span&gt; &lt;span class='entity'&gt;CTagsTest&lt;/span&gt;(&lt;span class='superclass'&gt;unittest.TestCase&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;101  &lt;/span&gt;    &lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;test_tags_files&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;102  &lt;/span&gt;        &lt;span class='string'&gt;"""&lt;br /&gt;&lt;span class="lineNumber"&gt;103  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;104  &lt;/span&gt;        This test basically iterates over each line in the tags file creating&lt;br /&gt;&lt;span class="lineNumber"&gt;105  &lt;/span&gt;        a list of lines for each unique symbol it finds. It then compares this&lt;br /&gt;&lt;span class="lineNumber"&gt;106  &lt;/span&gt;        list to that returned by the TagFile binary search. &lt;br /&gt;&lt;span class="lineNumber"&gt;107  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;108  &lt;/span&gt;        """&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;109  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;110  &lt;/span&gt;        &lt;span class='comment'&gt;# Successfully passed test on 10MB+ tags file&lt;br /&gt;&lt;span class="lineNumber"&gt;111  &lt;/span&gt;&lt;/span&gt;        &lt;span class='comment'&gt;# tags = r"C:\Python25\Lib\tags"&lt;br /&gt;&lt;span class="lineNumber"&gt;112  &lt;/span&gt;&lt;/span&gt;        &lt;br /&gt;&lt;span class="lineNumber"&gt;113  &lt;/span&gt;        tags &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;'tags'&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;114  &lt;/span&gt;        tag_file &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;TagFile(tags, SYMBOL)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;115  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;116  &lt;/span&gt;        &lt;span class='keyword'&gt;with&lt;/span&gt; &lt;span class='support'&gt;open&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(tags, &lt;/span&gt;&lt;span class='string'&gt;'r'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt; &lt;span class='keyword'&gt;as&lt;/span&gt; fh:&lt;br /&gt;&lt;span class="lineNumber"&gt;117  &lt;/span&gt;            latest &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;''&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;118  &lt;/span&gt;            lines  &lt;span class='keyword'&gt;=&lt;/span&gt; []&lt;br /&gt;&lt;span class="lineNumber"&gt;119  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;120  &lt;/span&gt;            &lt;span class='keyword'&gt;for&lt;/span&gt; l &lt;span class='keyword'&gt;in&lt;/span&gt; fh:&lt;br /&gt;&lt;span class="lineNumber"&gt;121  &lt;/span&gt;                symbol &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='metaFunctionCallPy'&gt;l.split(&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='constant'&gt;\t&lt;/span&gt;&lt;span class='string'&gt;'&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;)&lt;/span&gt;[SYMBOL]&lt;br /&gt;&lt;span class="lineNumber"&gt;122  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;123  &lt;/span&gt;                &lt;span class='keyword'&gt;if&lt;/span&gt; symbol !&lt;span class='keyword'&gt;=&lt;/span&gt; latest:&lt;br /&gt;&lt;span class="lineNumber"&gt;124  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;125  &lt;/span&gt;                    &lt;span class='keyword'&gt;if&lt;/span&gt; latest:&lt;br /&gt;&lt;span class="lineNumber"&gt;126  &lt;/span&gt;                        tags &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='support'&gt;list&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;(tag_file.get(latest))&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;127  &lt;/span&gt;                        &lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.assertEqual(lines, tags)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;128  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;129  &lt;/span&gt;                        lines &lt;span class='keyword'&gt;=&lt;/span&gt; []&lt;br /&gt;&lt;span class="lineNumber"&gt;130  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;131  &lt;/span&gt;                    latest &lt;span class='keyword'&gt;=&lt;/span&gt; symbol&lt;br /&gt;&lt;span class="lineNumber"&gt;132  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;133  &lt;/span&gt;                lines &lt;span class='keyword'&gt;+=&lt;/span&gt; [l]&lt;br /&gt;&lt;span class="lineNumber"&gt;134  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;135  &lt;/span&gt;&lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='support'&gt;__name__&lt;/span&gt; &lt;span class='keyword'&gt;==&lt;/span&gt; &lt;span class='string'&gt;'__main__'&lt;/span&gt;:&lt;br /&gt;&lt;span class="lineNumber"&gt;136  &lt;/span&gt;    &lt;span class='metaFunctionCallPy'&gt;unittest.main()&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;137  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;138  &lt;/span&gt;&lt;span class='comment'&gt;################################################################################&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-1969850719368768092?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/1969850719368768092/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=1969850719368768092' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/1969850719368768092'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/1969850719368768092'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/04/tdd.html' title='CTags 2: TDD'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-8944039198987836395</id><published>2009-03-29T20:21:00.000-07:00</published><updated>2009-07-19T02:06:39.932-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ctags'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='bisect'/><category scheme='http://www.blogger.com/atom/ns#' term='binary search'/><title type='text'>CTags 1: Captain Bisect And His Rag (c)Tag Crew</title><content type='html'>&lt;h2&gt; CTags&lt;/h2&gt;&lt;blockquote&gt;    Ctags generates an index (or tag) file of language objects found in source files&lt;br /&gt;that allows these items to be quickly and easily located by a text editor or&lt;br /&gt;other utility. A tag signifies a language object for which an index entry is&lt;br /&gt;available (or, alternatively, the index entry created for that object).&lt;br /&gt;&lt;/blockquote&gt;&lt;p&gt;Python has a philosophy of `Batteries Included` (and `Designer Straight Jacket`)&lt;br /&gt;and its standard library has many useful modules to allow you to zoom out and&lt;br /&gt;fly. Say what? What pixie dust have you have been snorting!?&lt;br /&gt;&lt;/p&gt;&lt;p&gt;The higher level you are the higher level you can go. If your stuck in the&lt;br /&gt;trenches of detail it's hard to see emergent patterns to exploit which can help&lt;br /&gt;you simplify code. Refactoring and simplifying is generally an iterative process&lt;br /&gt;of simplifying, getting a higher level view and simplifying again.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Taking a bottom up approach in this helps as you know what building blocks you&lt;br /&gt;need to create along the way. Don't want to be *too* simplistic. How can you do&lt;br /&gt;things `top down` if you aren't `on top`?&lt;br /&gt;Maybe you have flown over many times already and are just implementing an old&lt;br /&gt;innovation from a time honoured map. That's exactly the spirit of prototyping.&lt;br /&gt;Sending in the scouts to survey the terrain and report back.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;However, those troops get mighty mutinous really quick if you don't take the&lt;br /&gt;time to at least make a spot before you send em out like a swarm of jellyfish&lt;br /&gt;(especially when they are being fired at)&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Strategy? Fly over, send in the scouts and maintain communication.&lt;/p&gt;&lt;p&gt;What the hell is all that got to do with CTags? Not much I admit. But we does&lt;br /&gt;love to babble.&lt;/p&gt;&lt;p&gt;Python std libraries are your `A Team` of special operatives that can do the&lt;br /&gt;work you tell em without constant supervision and no need to worry about the&lt;br /&gt;messy details. Go nuke PHP! "Yes Sir!"&lt;br /&gt;&lt;/p&gt;&lt;p&gt;One of these crackshot ninjas is Captain `bisect` with his special move&lt;br /&gt;`bisect_left`.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;He's a great leader because he's absolutely fastidious in keeping his company of&lt;br /&gt;troops in perfect sorted order at all times.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Some of his duties involves training new recruits and he'll do a trick where&lt;br /&gt;upon meeting them all for the first time he will get them to silently line up in&lt;br /&gt;alphabetical order while he has his back turned. (we'll say alphabetical by&lt;br /&gt;name, but well, these guys *are* macho army men so..)&lt;br /&gt;&lt;/p&gt;&lt;pre class="blackboard"&gt;&lt;span class="keyword"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; scum &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="string"&gt;"omewEDjyFapAdxslfhbgBcnCtkqzuvri"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="keyword"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="support"&gt;len&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;(scum)&lt;/span&gt; &lt;span class="keyword"&gt;==&lt;/span&gt; &lt;span class="constant"&gt;32&lt;/span&gt;&lt;br /&gt;&lt;span class="constant"&gt;True&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="keyword"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; men &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="string"&gt;''&lt;/span&gt;.&lt;span class="metaFunctionCallPy"&gt;join(&lt;/span&gt;&lt;span class="support"&gt;sorted&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;(scum))&lt;/span&gt;&lt;br /&gt;&lt;span class="keyword"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; men&lt;br /&gt;&lt;span class="string"&gt;'ABCDEFabcdefghijklmnopqrstuvwxyz'&lt;/span&gt;&lt;/pre&gt;( Isn't it funny how the ones that try and make them selves seem big are in fact the smallest? )&lt;br /&gt;&lt;br /&gt;&lt;div style="padding: 1em; background-color: black; color: white;"&gt;" I bet I can find the position in the line of any one of you maggots after at most 5 guesses "&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;A, Yeah right, he thinks. "Find q sir" &lt;/p&gt;&lt;p&gt;bisect, "man 16 are you a lesser man than q?"     k, "Yes"&lt;br /&gt;bisect, "man 24 are you a lesser man than q?"     s, "No"&lt;br /&gt;bisect, "man 20 are you a lesser man than q?"     o, "Yes"&lt;br /&gt;bisect, "man 22 are you a lesser man than q?"     q, "No"  (smiles)&lt;br /&gt;bisect, "man 21 are you a lesser man than q?"     p, "Yes"&lt;br /&gt;&lt;/p&gt;&lt;p&gt;bisect,  "man 22 you are q!"&lt;br /&gt;&lt;br /&gt;troops, "Bravo Sir!"&lt;/p&gt;&lt;/div&gt;&lt;p&gt;( bisect is a a bit of a kookoo and uses 0 based indexing. Lucky the scum neverseem to get confused )&lt;br /&gt;&lt;/p&gt;&lt;p&gt;How does he do it!? &lt;/p&gt;&lt;p&gt;He starts in the middle (32 / 2 = 16) and compares what he was searching for with what he finds there. What he finds, k, is less than q so he knows he can rule out all other men before k as they too would be less than q.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;This only worked for him because all the men are in order.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;He then subdivides again. The man at position 16, k,  is less than q so his area will start at&lt;br /&gt;17 and extend in the opposite direction ending (as before) at 32 (the greatest man).  He then looks for his next midpoint with an eye to ruling out another half of the remaining men.&lt;/p&gt;&lt;p&gt;( He repeats this simple process until his start point is no longer less than the&lt;br /&gt;end point )&lt;br /&gt;&lt;/p&gt;&lt;pre class="blackboard"&gt;((17 + 32) // 2 = 24)&lt;br /&gt;men[24] (s) is not lt q so his end point becomes 24&lt;br /&gt;&lt;br /&gt;((17 + 24) // 2 = 20)&lt;br /&gt;men[20] (o) is less than q so his start point becomes 21&lt;br /&gt;&lt;br /&gt;((21 + 24) // 2 = 22)&lt;br /&gt;men[22] (q) is not less than q :) so his end point becomes 22&lt;br /&gt;&lt;br /&gt;((21 + 22 // 2 = 21))&lt;br /&gt;men[21] (p) is less than q so his start point becomes 22&lt;br /&gt;&lt;/pre&gt;Start is 22 and end is 22 so he has a match!&lt;p&gt;With 64 men (double 32) he would only take 1 more guess; As each guess rules&lt;br /&gt;out half and half is the inversion of double.&lt;br /&gt;&lt;br /&gt;What about 128 (or some arbitrary number?) How many times can you half 128&lt;br /&gt;before you get one (the right `one`).&lt;br /&gt;&lt;br /&gt;Or in other words what to the power of two makes 128?&lt;br /&gt;&lt;br /&gt;32 log 2 = 5&lt;br /&gt;64 log 2 = 6&lt;br /&gt;128 log 2 = 7&lt;br /&gt;&lt;/p&gt;&lt;pre class="blackboard"&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; math&lt;br /&gt;&lt;span class="metaFunctionCallPy"&gt;math.log(&lt;/span&gt;&lt;span class="constant"&gt;128&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;, &lt;/span&gt;&lt;span class="constant"&gt;2&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Captain bisect's talent scales exceedingly well and in fact he's ready to put&lt;br /&gt;forth his talent to whatever use come up with.&lt;br /&gt;&lt;pre class="blackboard"&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; bisect&lt;br /&gt;&lt;span class="metaFunctionCallPy"&gt;bisect.bisect_left(some_sequence, search)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;WOAH What a verbose explanation! Too much detail!  Couldn't you just have said&lt;br /&gt;use `bisect.bisect_left` to index left most occurence of any item in a sequence.&lt;br /&gt;&lt;br /&gt;Yeah, that's kind of the point. Python is chock full of handy high level&lt;br /&gt;abstractions you can trust to do the job without worrying about the details.&lt;br /&gt;You are already zooming.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;"Ok you admit your are babbling but what the heck is this got to do with&lt;br /&gt;CTags?" &lt;/p&gt;&lt;p&gt;To paraphrase,  Ctags generates an index (in sorted order) of symbols to&lt;br /&gt;be quickly and easily located. Sounds like a job for Captain bisect. He is&lt;br /&gt;actually made of this stern stuff called `C` that makes him faster than anything&lt;br /&gt;you could genetically engineer in your laboratories. &lt;/p&gt;&lt;p&gt;Python also has this useful thing going called duck typing. bisect will work&lt;br /&gt;on any class that exposes a __getitem__ method.&lt;br /&gt;&lt;br /&gt;But what if you wanted Captain bisect to search a 50 MB ctags file? You can't&lt;br /&gt;sub class a file object... can you? In any case each index would just return&lt;br /&gt;a character wouldn't it?&lt;/p&gt;&lt;p&gt;A sneak preview of how to use bisect and mmap to binary search CTags `tags` files. Explanation to &lt;a href="http://akalias.blogspot.com/2009/04/tdd.html"&gt; follow.&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;pre class="blackboard"&gt;&lt;span class="lineNumber"&gt; 1  &lt;/span&gt;&lt;span class="comment"&gt;#################################### IMPORTS ###################################&lt;br /&gt;&lt;span class="lineNumber"&gt; 2  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 3  &lt;/span&gt;&lt;span class="keyword"&gt;from&lt;/span&gt; __future__ &lt;span class="keyword"&gt;import&lt;/span&gt; with_statement&lt;br /&gt;&lt;span class="lineNumber"&gt; 4  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt; 5  &lt;/span&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; os&lt;br /&gt;&lt;span class="lineNumber"&gt; 6  &lt;/span&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; bisect&lt;br /&gt;&lt;span class="lineNumber"&gt; 7  &lt;/span&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; mmap&lt;br /&gt;&lt;span class="lineNumber"&gt; 8  &lt;/span&gt;&lt;span class="keyword"&gt;import&lt;/span&gt; unittest&lt;br /&gt;&lt;span class="lineNumber"&gt; 9  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;10  &lt;/span&gt;&lt;span class="comment"&gt;################################### CONSTANTS ##################################&lt;br /&gt;&lt;span class="lineNumber"&gt;11  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;12  &lt;/span&gt;&lt;span class="comment"&gt;# CSV Column in tag file&lt;br /&gt;&lt;span class="lineNumber"&gt;13  &lt;/span&gt;&lt;/span&gt;SYMBOL &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;14  &lt;/span&gt;FILENAME &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;1&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;15  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;16  &lt;/span&gt;&lt;span class="comment"&gt;################################################################################&lt;br /&gt;&lt;span class="lineNumber"&gt;17  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;18  &lt;/span&gt;&lt;span class="storage"&gt;class&lt;/span&gt; &lt;span class="entity"&gt;TagFile&lt;/span&gt;(&lt;span class="support"&gt;object&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;19  &lt;/span&gt;    &lt;span class="storage"&gt;def&lt;/span&gt; &lt;span class="support"&gt;__init__&lt;/span&gt;(&lt;span class="variable"&gt;self&lt;/span&gt;, &lt;span class="variable"&gt;p&lt;/span&gt;, &lt;span class="variable"&gt;column&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;20  &lt;/span&gt;        &lt;span class="variable"&gt;self&lt;/span&gt;.p &lt;span class="keyword"&gt;=&lt;/span&gt; p&lt;br /&gt;&lt;span class="lineNumber"&gt;21  &lt;/span&gt;        &lt;span class="variable"&gt;self&lt;/span&gt;.column &lt;span class="keyword"&gt;=&lt;/span&gt; column&lt;br /&gt;&lt;span class="lineNumber"&gt;22  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;23  &lt;/span&gt;    &lt;span class="storage"&gt;def&lt;/span&gt; &lt;span class="support"&gt;__getitem__&lt;/span&gt;(&lt;span class="variable"&gt;self&lt;/span&gt;, &lt;span class="variable"&gt;index&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;24  &lt;/span&gt;        &lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;.fh.seek(index)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;25  &lt;/span&gt;        &lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;.fh.readline()&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;26  &lt;/span&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;.fh.readline()&lt;/span&gt;.&lt;span class="metaFunctionCallPy"&gt;split(&lt;/span&gt;&lt;span class="string"&gt;'&lt;/span&gt;&lt;span class="constant"&gt;\t&lt;/span&gt;&lt;span class="string"&gt;'&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;)&lt;/span&gt;[&lt;span class="variable"&gt;self&lt;/span&gt;.column]&lt;br /&gt;&lt;span class="lineNumber"&gt;27  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;28  &lt;/span&gt;    &lt;span class="storage"&gt;def&lt;/span&gt; &lt;span class="support"&gt;__len__&lt;/span&gt;(&lt;span class="variable"&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;29  &lt;/span&gt;        &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="metaFunctionCallPy"&gt;os.stat(&lt;/span&gt;&lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;.p)&lt;/span&gt;.st_size&lt;br /&gt;&lt;span class="lineNumber"&gt;30  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;31  &lt;/span&gt;    &lt;span class="storage"&gt;def&lt;/span&gt; &lt;span class="entity"&gt;get&lt;/span&gt;(&lt;span class="variable"&gt;self&lt;/span&gt;, *&lt;span class="variable"&gt;tags&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;32  &lt;/span&gt;        &lt;span class="keyword"&gt;with&lt;/span&gt; &lt;span class="support"&gt;open&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;(&lt;/span&gt;&lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;.p, &lt;/span&gt;&lt;span class="string"&gt;'r+'&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;as&lt;/span&gt; fh:&lt;br /&gt;&lt;span class="lineNumber"&gt;33  &lt;/span&gt;            &lt;span class="variable"&gt;self&lt;/span&gt;.fh &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="metaFunctionCallPy"&gt;mmap.mmap(fh.fileno(), &lt;/span&gt;&lt;span class="constant"&gt;0&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;34  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;35  &lt;/span&gt;            &lt;span class="keyword"&gt;for&lt;/span&gt; tag &lt;span class="keyword"&gt;in&lt;/span&gt; tags:&lt;br /&gt;&lt;span class="lineNumber"&gt;36  &lt;/span&gt;                b4 &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="metaFunctionCallPy"&gt;bisect.bisect_left(&lt;/span&gt;&lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;, tag)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;37  &lt;/span&gt;                &lt;span class="metaFunctionCallPy"&gt;fh.seek(b4)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;38  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;39  &lt;/span&gt;                &lt;span class="keyword"&gt;for&lt;/span&gt; l &lt;span class="keyword"&gt;in&lt;/span&gt; fh:&lt;br /&gt;&lt;span class="lineNumber"&gt;40  &lt;/span&gt;                    comp &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="support"&gt;cmp&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;(l.split(&lt;/span&gt;&lt;span class="string"&gt;'&lt;/span&gt;&lt;span class="constant"&gt;\t&lt;/span&gt;&lt;span class="string"&gt;'&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;)[&lt;/span&gt;&lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;.column], tag)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;41  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;42  &lt;/span&gt;                    &lt;span class="keyword"&gt;if&lt;/span&gt;    comp &lt;span class="keyword"&gt;==&lt;/span&gt; &lt;span class="keyword"&gt;-&lt;/span&gt;&lt;span class="constant"&gt;1&lt;/span&gt;:    &lt;span class="keyword"&gt;continue&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;43  &lt;/span&gt;                    &lt;span class="keyword"&gt;elif&lt;/span&gt;  comp:          &lt;span class="keyword"&gt;break&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;44  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;45  &lt;/span&gt;                    &lt;span class="keyword"&gt;yield&lt;/span&gt; l&lt;br /&gt;&lt;span class="lineNumber"&gt;46  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;47  &lt;/span&gt;            &lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;.fh.close()&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;48  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;49  &lt;/span&gt;&lt;span class="comment"&gt;##################################### TESTS ####################################&lt;br /&gt;&lt;span class="lineNumber"&gt;50  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;51  &lt;/span&gt;&lt;span class="storage"&gt;class&lt;/span&gt; &lt;span class="entity"&gt;CTagsTest&lt;/span&gt;(&lt;span class="superclass"&gt;unittest.TestCase&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;52  &lt;/span&gt;    &lt;span class="storage"&gt;def&lt;/span&gt; &lt;span class="entity"&gt;test_tags_files&lt;/span&gt;(&lt;span class="variable"&gt;self&lt;/span&gt;):&lt;br /&gt;&lt;span class="lineNumber"&gt;53  &lt;/span&gt;        tags &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="string"&gt;r"tags"&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;54  &lt;/span&gt;        tag_file &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="metaFunctionCallPy"&gt;TagFile(tags, SYMBOL)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;55  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;56  &lt;/span&gt;        &lt;span class="keyword"&gt;with&lt;/span&gt; &lt;span class="support"&gt;open&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;(tags, &lt;/span&gt;&lt;span class="string"&gt;'r'&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;as&lt;/span&gt; fh:&lt;br /&gt;&lt;span class="lineNumber"&gt;57  &lt;/span&gt;            latest &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="string"&gt;''&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;58  &lt;/span&gt;            lines  &lt;span class="keyword"&gt;=&lt;/span&gt; []&lt;br /&gt;&lt;span class="lineNumber"&gt;59  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;60  &lt;/span&gt;            &lt;span class="keyword"&gt;for&lt;/span&gt; l &lt;span class="keyword"&gt;in&lt;/span&gt; fh:&lt;br /&gt;&lt;span class="lineNumber"&gt;61  &lt;/span&gt;                symbol &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="metaFunctionCallPy"&gt;l.split(&lt;/span&gt;&lt;span class="string"&gt;'&lt;/span&gt;&lt;span class="constant"&gt;\t&lt;/span&gt;&lt;span class="string"&gt;'&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;)&lt;/span&gt;[SYMBOL]&lt;br /&gt;&lt;span class="lineNumber"&gt;62  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;63  &lt;/span&gt;                &lt;span class="keyword"&gt;if&lt;/span&gt; symbol !&lt;span class="keyword"&gt;=&lt;/span&gt; latest:&lt;br /&gt;&lt;span class="lineNumber"&gt;64  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;65  &lt;/span&gt;                    &lt;span class="keyword"&gt;if&lt;/span&gt; latest:&lt;br /&gt;&lt;span class="lineNumber"&gt;66  &lt;/span&gt;                        tags &lt;span class="keyword"&gt;=&lt;/span&gt; &lt;span class="support"&gt;list&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;(tag_file.get(latest))&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;67  &lt;/span&gt;                        &lt;span class="variable"&gt;self&lt;/span&gt;&lt;span class="metaFunctionCallPy"&gt;.assertEqual(lines, tags)&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;68  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;69  &lt;/span&gt;                        lines &lt;span class="keyword"&gt;=&lt;/span&gt; []&lt;br /&gt;&lt;span class="lineNumber"&gt;70  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;71  &lt;/span&gt;                    latest &lt;span class="keyword"&gt;=&lt;/span&gt; symbol&lt;br /&gt;&lt;span class="lineNumber"&gt;72  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;73  &lt;/span&gt;                lines &lt;span class="keyword"&gt;+=&lt;/span&gt; [l]&lt;br /&gt;&lt;span class="lineNumber"&gt;74  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;75  &lt;/span&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; &lt;span class="support"&gt;__name__&lt;/span&gt; &lt;span class="keyword"&gt;==&lt;/span&gt; &lt;span class="string"&gt;'__main__'&lt;/span&gt;:&lt;br /&gt;&lt;span class="lineNumber"&gt;76  &lt;/span&gt;    &lt;span class="metaFunctionCallPy"&gt;unittest.main()&lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;77  &lt;/span&gt;&lt;br /&gt;&lt;span class="lineNumber"&gt;78  &lt;/span&gt;&lt;span class="comment"&gt;################################################################################&lt;/span&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-8944039198987836395?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/8944039198987836395/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=8944039198987836395' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8944039198987836395'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8944039198987836395'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2009/03/captain-bisect-and-his-rag-ctag-crew.html' title='CTags 1: Captain Bisect And His Rag (c)Tag Crew'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-2158421290595866734</id><published>2008-08-28T18:41:00.001-07:00</published><updated>2009-07-19T01:36:15.732-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>GSOC OverView</title><content type='html'>My GSOC project was all about testing for PyGame;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;    &lt;li&gt; I wrote lots of tests; Almost every module in PyGame now has at least one test &lt;/li&gt;&lt;br /&gt;    &lt;li&gt; Test modules can now be isolated in subprocesses; one segfault no longer brings down the whole test suite &lt;/li&gt;&lt;br /&gt;    &lt;li&gt; Can now test for speed regressions; important for real time software such as games &lt;/li&gt;&lt;br /&gt;    &lt;li&gt; PyGame Automated Build Page extended&lt;br /&gt;        &lt;ul&gt; &lt;li&gt; Shows / Collects more info &lt;/li&gt; &lt;li&gt; Runs tests in subprocesses &lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;    &lt;/li&gt;&lt;br /&gt;    &lt;li&gt; Test Stubbing Utility: A Testing "Todo List" &lt;/li&gt;&lt;br /&gt;    &lt;li&gt; Optional Interactive Tests / Test Tagging &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;For writing the tests I wrote a small utility that inspects the PyGame package and finds all the untested callables (functions, properties) and creates test stubs, including documentation for each so you don't have to leave the editor. The stubber knows which functions have already been tested by using a naming scheme for all of the tests. Essentially, "test_$callable__$comment", namespaced by having TestCase[s] per Class and a test module per module.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;In this way I could create stubs for each module, essentially a TODO list, and cycle through all the modules looking for tests that were easy to write. The functions in PyGame are many and greatly varied, each requiring somewhat specialised knowledge to test. I wasn't able to write tests for all them but hopefully the test stubbing utility will help enable some testing sprints. I intend to develop a testing website where people can submit bugs/tests in the form of a unittest.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;PyGame has a somewhat unique set of requirements compared to most python libraries in that most of the framework is actually written in C. C code when it goes awry can do some very strange things. We had a test runner running all of the tests in one single process so if one failed hard it would bring down the whole suite. This can be a bit of a pain so I developed a test runner that isolates each module in a subprocess.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Some of the tests in PyGame have requirements that make them unsuitable for running as part of the main test suite. For example some require a CDRom, a JoyStick, take way too long or need interaction with a human. With the test runner script I extended unittest with the ability to exclude certain tests by tags. The tags can be module, class or individual test level and are inheritable/ over-ridable.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Another extension to the test runner was the ability to randomize the run ordering of tests, so along with the test results the seed is printed out. If there are failures you can seed the randomizer with the failure inducing seed. We also wanted to be able to record the timings of each individual test so we could make comparisons between revisions / platforms. I again extended the test runner with that ability.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;I worked with Brian Fisher to extend the PyGame automated build page to record the test results in a ZODB and utilize the new test runner to run tests in subprocesses. We will be able to use this information for detecting speed regressions amongst other things.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-2158421290595866734?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/2158421290595866734/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=2158421290595866734' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/2158421290595866734'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/2158421290595866734'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/08/gsoc-overview.html' title='GSOC OverView'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-2530254058885750197</id><published>2008-08-23T08:25:00.000-07:00</published><updated>2009-07-19T01:36:15.732-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Johnny, Kick A Hole Right In The Sky</title><content type='html'>Johnny, Kick a hole right in the sky! Won't some body testify? Poke a lion in it's eye!&lt;br /&gt;&lt;br /&gt;I bought pygame-testify.net today, and set up a python/cgi based form that takes a zip and enumerates the results + adds the (safe evaled) test results dict to a ZODB. &lt;br /&gt;&lt;br /&gt;I found a multi-part python snippet for POST[ing] of test results. &lt;br /&gt;&lt;br /&gt;The test/build page is starting to come together. &lt;br /&gt;&lt;br /&gt;I am using htpasswd for security.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-2530254058885750197?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/2530254058885750197/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=2530254058885750197' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/2530254058885750197'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/2530254058885750197'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/08/johnny-kick-hole-right-in-sky.html' title='Johnny, Kick A Hole Right In The Sky'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-9024973610084232494</id><published>2008-08-02T16:22:00.000-07:00</published><updated>2009-07-19T01:36:15.732-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>todo_xxxxxxx</title><content type='html'>I recently altered the "fail incomplete tests" mechanism we use in the pygame test runner. Before we were doing assertions on test_utils.test_not_implemented(). This would check a module level variable test_utils.fail_incomplete_tests, which we would set as desired depending on whether we wanted to fail incomplete tests.&lt;br /&gt;&lt;br /&gt;This was a fairly non-invasive technique but as I was already hijacking the test loading mechanism for filtering tests by tags, I realized I could alter the TestLoader class to pick up tests starting with the prefix "todo_" as well as "test_". I would call TestCase.fail directly which would only run if picking up todo_ tests.&lt;br /&gt;&lt;br /&gt;This of course meant altering all the stubs. I pondered briefly doing a mass search and replace, completely automating it but I don't really trust that for tests.&lt;br /&gt;&lt;br /&gt;For the test stubs I have been including the documentation so it's really easy to walk through a test file writing tests without having to leave the editor. I was just using inspect.getdoc to get the __doc__ string.&lt;br /&gt;&lt;br /&gt;It seems the documentation included in the .doc files is different to that contained in the __doc__ for each function. The __doc__ seems to be the function signature and a very brief, usually one sentence description. The .doc files contains a lot more detailed descriptions that can be very useful when writing tests. &lt;br /&gt;&lt;br /&gt;I quickly added a docs_as_dict() function to makeref.py, then added it to the stub generator. The stub generator will add both the __doc__ and the .doc file documentation to each stub.&lt;br /&gt;&lt;br /&gt;I went through semi-manually updating all the unfilled out stubs for each test file with the more complete docs and the new todo_xxxxx test naming. It took about an hour but I feel more confident than if I had just grep'd it.&lt;br /&gt;&lt;br /&gt;Everything is pretty much now in place for the test site I wanted to create.&lt;br /&gt;&lt;br /&gt;Test Timing&lt;br /&gt;Test Tagging&lt;br /&gt;Isolated Tests&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-9024973610084232494?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/9024973610084232494/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=9024973610084232494' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/9024973610084232494'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/9024973610084232494'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/08/todoxxxxxxx.html' title='todo_xxxxxxx'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-7262991378946556461</id><published>2008-07-18T21:14:00.000-07:00</published><updated>2009-07-19T01:36:15.732-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>import test.unittest as unittest</title><content type='html'>I split the test runner further, now into three files, with all the monkey business in unittest_patch. &lt;br /&gt;&lt;br /&gt;The patching is done by a patch() function taking an optparse options object as the solo argument, which drives the decisions behind which parts of unittest are patched.&lt;br /&gt;&lt;br /&gt;With the features we wanted I had to override some methods in a quite drastic way. I even needed to override TestCase.run, a many many line method. The only way I could do this was to basically copy/paste, alter and monkey-patch in. This meant sometimes calling private members.&lt;br /&gt;&lt;br /&gt;Unfortunately, the author of unittest had decided somewhere between python 2.4 and 2.5 that he would rename all the private members from the double underscore preceding __name_mangling convention to a single underscore _caution. &lt;br /&gt;&lt;br /&gt;As my mentor said (or something like it), "using an underscore is a warning, that said member is an implementation detail not an interface".&lt;br /&gt;&lt;br /&gt;What to do? We now include a 2.5 version of unittest in the test directory. Apparenly pygame has come full circle; it was included way back in the day before PyUnit was part of the standard library.&lt;br /&gt;&lt;br /&gt;All of our individual test files, typically $module_test.py, all import an unpatched unittest and run unittest.main() to make the module "conveniently executable". Only when running the complete suite is unittest enhanced with extra functionality.&lt;br /&gt;&lt;br /&gt;While I was in there tinkering with the internals, recording timings of individual tests I moved the redirect std(err|out) per module to per test. I then patched the TextTestRunner to dump stderr/stdout on error.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;printErrorList&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;, &lt;span class='variable'&gt;flavour&lt;/span&gt;, &lt;span class='variable'&gt;errors&lt;/span&gt;):&lt;br /&gt;    &lt;span class='keyword'&gt;for&lt;/span&gt; test, err &lt;span class='keyword'&gt;in&lt;/span&gt; ((e[&lt;span class='constant'&gt;0&lt;/span&gt;], e[&lt;span class='constant'&gt;1&lt;/span&gt;]) &lt;span class='keyword'&gt;for&lt;/span&gt; e &lt;span class='keyword'&gt;in&lt;/span&gt; errors):&lt;br /&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.stream.writeln(&lt;span class='variable'&gt;self&lt;/span&gt;.separator1)&lt;br /&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.stream.writeln(&lt;span class='string'&gt;"&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;: &lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;"&lt;/span&gt; &lt;span class='keyword'&gt;%&lt;/span&gt; (flavour, test))&lt;br /&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.stream.writeln(&lt;span class='variable'&gt;self&lt;/span&gt;.separator2)&lt;br /&gt;        &lt;span class='variable'&gt;self&lt;/span&gt;.stream.writeln(&lt;span class='string'&gt;"&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;"&lt;/span&gt; &lt;span class='keyword'&gt;%&lt;/span&gt; err)&lt;br /&gt;&lt;br /&gt;        &lt;span class='comment'&gt;# DUMP REDIRECTED STDERR / STDOUT ON ERROR / FAILURE&lt;br /&gt;&lt;/span&gt;        &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='variable'&gt;self&lt;/span&gt;.show_redirected_on_errors:&lt;br /&gt;            stderr, stdout &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='support'&gt;map&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;.tests[test].get, (&lt;span class='string'&gt;'stderr'&lt;/span&gt;,&lt;span class='string'&gt;'stdout'&lt;/span&gt;))&lt;br /&gt;            &lt;span class='keyword'&gt;if&lt;/span&gt; stderr: &lt;span class='variable'&gt;self&lt;/span&gt;.stream.writeln(&lt;span class='string'&gt;"STDERR:&lt;/span&gt;&lt;span class='constant'&gt;\n&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;"&lt;/span&gt; &lt;span class='keyword'&gt;%&lt;/span&gt; stderr)&lt;br /&gt;            &lt;span class='keyword'&gt;if&lt;/span&gt; stdout: &lt;span class='variable'&gt;self&lt;/span&gt;.stream.writeln(&lt;span class='string'&gt;"STDOUT:&lt;/span&gt;&lt;span class='constant'&gt;\n&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;"&lt;/span&gt; &lt;span class='keyword'&gt;%&lt;/span&gt; stdout)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It would be relatively easy to add in support for show locals() etc.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-7262991378946556461?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/7262991378946556461/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=7262991378946556461' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7262991378946556461'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7262991378946556461'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/07/import-testunittest-as-unittest.html' title='import test.unittest as unittest'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-6929538516157335756</id><published>2008-07-15T18:12:00.000-07:00</published><updated>2009-07-19T01:36:15.733-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Redesign</title><content type='html'>I decided to (had to) redesign the test runner, this time cutting more directly to the root of matters, overriding select methods of unittest classes.&lt;br /&gt;&lt;br /&gt;Before, in subprocess mode, I was calling the individual test modules, which would in turn run unittest.main() with all the attendant pains of cmd line options conflicting and output parsing. (we have to add profiling, exclusion by tags etc).  One major design change I made was to unify the single / subprocess modes to use one test runner, (test_runner.py). &lt;br /&gt;&lt;br /&gt;In it, along with a lot of utility functions, is defined a run_test() function. It takes a list of modules and an options object as arguments. It compiles a dictionary of the test results and on completion either returns the dict or in subprocess mode pretty prints it to stdout. (This is then eval'd for an all_results.update(result))&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;RESULTS_TEMPLATE = {&lt;br /&gt;    'output'     :  '',         # unittest.TextTestRunner output&lt;br /&gt;    'stderr'     :  '',         # stderr outpout &lt;br /&gt;    'stdout'     :  '',         # stdout output&lt;br /&gt;    'num_tests'  :   0,         # taken directly from the unittest results object&lt;br /&gt;    'failures'   :  [],         # ditto&lt;br /&gt;    'errors'     :  [],         # ditto&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In single process mode run_tests.py just imports from test_runner.py run_test() function and passes it the optparse options object and list of modules to search for tests. &lt;br /&gt;&lt;br /&gt;Both run_tests.py and test_runner.py, share the same optparse cmd line parser options. In subprocess mode, run_tests.py calls test_runner.py with essentialy the same sys.argv it was initiated with. if __main__ it runs the run_test() function on a list of [args[0]]. Now all the extra functionality and cmd line parsing is all in one place.&lt;br /&gt;&lt;br /&gt;There were quite a few extra little changes that have made it not perfect but a lot better.  Adding exclution by tagging functionality took 10 minutes, most of the time being spent on picking a format. &lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;|Tags:display|&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Adding profiling decorators or whatever other functionality is desired will also be a lot easier now.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-6929538516157335756?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/6929538516157335756/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=6929538516157335756' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/6929538516157335756'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/6929538516157335756'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/07/redesign.html' title='Redesign'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-2568390533286232890</id><published>2008-07-10T18:09:00.000-07:00</published><updated>2009-07-19T01:36:15.733-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Comedy Of Errors</title><content type='html'>** Build Page / Testing **&lt;br /&gt;==========================&lt;br /&gt;&lt;br /&gt;As reported earlier, in reaction to the crashing tests rendering the build page ineffective, I have been working on creating a script to isolate test modules in subprocesses. The approach I took, was to compile the results of each isolated test into the same form as the old test runner. A quick hack, or so I hoped.&lt;br /&gt;&lt;br /&gt;I realised that subprocess out of the box has no cross platform non-blocking calls, so you can't timeout on hung tests. I had to find a recipe for this which unfortunately required win32 extensions. Not really a big deal but still time spent and dependencies.&lt;br /&gt;&lt;br /&gt;So what we have is a test runner parsing the results of a unittest text report, meant for human consumption, which is then in turn parsed by the automated build pages regexes. This seems pretty ridiculous, especially as the form is not exactly machine friendly. I could have (should have?) hacked into the build page code and modularised the test parsing code there, sharing between the test runner and build page.&lt;br /&gt;&lt;br /&gt;But then if you are going to do that why not just replace the TextTestRunner class with something completely customised for the job? Replace unittest bit by bit in an adhoc as-needed fashion? Slowly building a framework? I didn't want to. I'm not really supposed to be and that was the psychology in play.&lt;br /&gt;&lt;br /&gt;Another? foolish design decision I made, based on a shallow visual aesthetic of less LOC, was to parse unittest results in a way that only worked when there was no "test noise". What do I mean by test noise? print statments left in source code. C extensions that don't respect sys.stderr, sys.stdout redirection/supression.&lt;br /&gt;&lt;br /&gt;See below exhibit A, a specimen from a sunny day of testing.&lt;br /&gt;&lt;br /&gt;...............&lt;br /&gt;---------------------------------------------------------------------&lt;br /&gt;Ran 15 tests in 1.234s&lt;br /&gt;&lt;br /&gt;As the tests are running unittest prints to a stream, by default stderr, but it can be any file-like object of choice, either a dot an E or an F, mapping to pass, error or fail. I used a simple regex ^[.EF]*$ to find any "dots" in the return output. If there were any, I would take a slice from the length of the dots. From there I would take the first of a split at the "Ran xxx tests" boundary, defined as '%s\nRan' % (70 * '-'). In between the DOTS and the RAN_TEST_DIV (thus named) would lay the failures.&lt;br /&gt;&lt;br /&gt;To piece it all together as if it was the output of one run I would "join the dots" and join the failures. Then at the end count the total length of DOTS (., E, F combined), E)rrors and F)ailures. Voila. Worked a charm.&lt;br /&gt;&lt;br /&gt;What the hell was I thinking? The whole point of the exercise was to create a reliable test runner. I suppose I thought I was. I wrote a few tests for some spectacularly unimaginative cases. I compared output of single process mode and subprocess mode running some fake test suites, zero assertions, all passing, some failures, some errors. The subprocess mode was character for character perfect in its mime artistry. In fact it was for this easy, pull apart, bind together, compare automated testing that I did it in the first place. &lt;br /&gt;&lt;br /&gt;All was simple and peaceful, until I finally got a linux test box working again. (my laptop fan died) I used ssh to log in and run the tests from my friends windows machine. Of course one of the tests that required initiating the display failed.&lt;br /&gt;&lt;br /&gt;single process mode: 504 tests, FAIL (failures=1)&lt;br /&gt;subprocess mode: 495 tests OK&lt;br /&gt;&lt;br /&gt;What the hell was going on? With horror I realized what I had done. Something was wreaking havoc with the fragile little regex. On failure a huge amount of debugging output was put out by one of the SDL functions interupting the DOTS. I thought about rewriting it using some more substantive regular expressions. I tossed up between doing that and redirecting sys.(stdout|stderr) and passing a StringIO to unittest for test results. I figured by doing that I would be able to keep the comparison tests I had in place, and for that matter the same degree of mimicry. I opted for redirecting std(err|out). I imagined other uses for this at the same time, none all that compelling upon reflection and only useful if implemented in another manner. (only show stdout/stderr on failure of test, can leave print statement debugging in there, I did global redirection)&lt;br /&gt;&lt;br /&gt;Of course to do that I needed to create a command line option for each individual test module to call from the "master" script in subprocess mode. Because unittest.main() is running with it's own getopt parsing, you can't just add an option and check sys.argv or use optparse. You have to do either and then clear those options from sys.argv which would otherwise cause unittest to error. So more fun hamfisting around with unittest. I realised that I would need to do that at some stage for profiling cmd line options so there was another push in that direction. All the time wondering whether I should just completely override the parseArgs method.&lt;br /&gt;&lt;br /&gt;I replaced the test_utils.get_fail_incomplete_option();unittest.main() in each module with a test_utils.get_command_line_options(). unittest.main() always calls sys.exit() on completion of tests so I had to subclass it, overriding one of it's methods. I did this because after catching the unittest result stream to a StringIO, I would restore stderr and write the results to it.&lt;br /&gt;&lt;br /&gt;I added in some test cases, print_stdout and print_stderr, comparing the results (I of course had to put a redirect mode onto single process mode for purposes of testing). Everything was OK again, until I ran it again on the the linux box through ssh.&lt;br /&gt;&lt;br /&gt;495 tests OK. (should have been 504 with one failure)&lt;br /&gt;&lt;br /&gt;Damn it! So it seems that some stderr, stdout is not redirected. I imagine it's mostly C extensions (or system calls) and the like that would do this but then that is pygame all over. Briefly I pondered printing results back out on stdout, and just PrayingTM that any such noise would always be stderr.&lt;br /&gt;&lt;br /&gt;So what did I do? What any fool, already invested would do. I decided to markup the results, with lines like.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&amp;lt;!-- UNITTEST_RESULTS_START_HERE --!&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt; I created 3 sections using 2 divisors. The first is all the noise output, anything not respecting redirection. The second is the unittest results and the last is the multiplexed results, what you would see if running the script in a shell. I overrode the write method on a StringIO collecting unittest results and made it also write to (a previously redirected) stdout. using subprocess.Popen(...., stdout=subprocess.PIPE, stderr = subprocess.STDOUT) everything is muxed together. I then wrote a function that regex splits the 3, keeping the results for compiling DOTS. It's a long way from Kansas though isn't it Toto.&lt;br /&gt;&lt;br /&gt;What a PITA? That's not even the half of it. I ended up having to rewrite all the command lines I was passing to subprocess.Popen from string template to lists so it would work cross platform. Also, the way subprocess multiplexes stderr and stdout when you use the same file object for both is inconsistent cross platform. What you would see is not neccessarily what you get. On windows it would suffice to just "print compiled_test_results", but on linux had there was need to print &gt;&gt; sys.stderr.&lt;br /&gt;&lt;br /&gt;All in all, a lot of tipsy toeing around unittest. I really made a complete tangled webby mess of the whole job. A black comedy of errors. I'm not sure whether to remove the stderr/stdout redirection and replace the regexes with something less fragile. It's already been too much of a hole, sucking in time. I would have to update the run_tests__tests also.&lt;br /&gt;&lt;br /&gt;What would I do differently looking back? What would I do if I had no constraints? Unfortunately, probably two very different questions.&lt;br /&gt;&lt;br /&gt;** What I would do differently? **&lt;br /&gt;==================================&lt;br /&gt;&lt;br /&gt;This much I do know, the build page and the test runner script require intersecting functionality. They both parse the results of a unittest TextTestRunner output to gather statistics on test results. I could have modularised this parsing functionality, sharing between the two of them. This really begs the question though, why parse something designed for human consumption at all? Why not pass a customised test runner class into unittest?&lt;br /&gt;&lt;br /&gt;Still there is the problem of communication across process boundaries, solved by using an asynchronous extension class of subprocess.Popen. Would you log the result of each processes output to a file using something like xml? Or maybe, pickling the results and then joining them back together? You could even have a client / server architecture, using sockets to transfer pickled test results as native python objects back to the server to piece together.&lt;br /&gt;&lt;br /&gt;As well as the requirement for isolation of tests, we are wanting to add profiling functionality and tagging to split tests into different groups.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-2568390533286232890?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/2568390533286232890/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=2568390533286232890' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/2568390533286232890'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/2568390533286232890'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/07/comedy-of-errors.html' title='Comedy Of Errors'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-6127252675280988605</id><published>2008-07-08T19:58:00.000-07:00</published><updated>2009-07-19T01:36:15.733-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>killer redux</title><content type='html'>I was laboring under the bastard conception that when using subprocess.Popen(), shell=True is required for a subprocess executable to have access to the environment variables. Where the hell did I get that idea? Stupid unquestioned assumption that almost gave birth to a lasting bug. &lt;br /&gt;&lt;br /&gt;For the test runner I was using system calls to taskkill or pskill for process controll under windows. The idea was to try executing each and if one was on the %PATH% the return code would not be one of err. If this was the case then the search was over and a Popen wrapper of (taskkill|pskill) would suffice as an os.kill().&lt;br /&gt;&lt;br /&gt;This worked fine and dandy except that on windows98, there would be no error code if either of the task killers weren't on the path. It would define a useless os.kill.&lt;br /&gt;&lt;br /&gt;Lenard, the windows maintainer of PyGame questioned why use a hacky wrapper of pskill or one of it's ilk, when if there was already a reliance on pywin32, why not use win32api.TerminateProcess?&lt;br /&gt;&lt;br /&gt;That works fine but does not kill process trees, something I thought was a requirement due to using shell = True as a Popen constructor argument. Using shell = 1 calls cmd.exe etc which in turn calls the subprocess of choice.&lt;br /&gt;&lt;br /&gt;Realizing that there was only need to kill one process, and that it would also avoid problems with differing return codes on older versions of windows, TerminateProcess was given the job.&lt;br /&gt;&lt;br /&gt;Long live TerminateProcess.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-6127252675280988605?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/6127252675280988605/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=6127252675280988605' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/6127252675280988605'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/6127252675280988605'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/07/killer-redux.html' title='killer redux'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-4504280455811508274</id><published>2008-07-04T23:28:00.000-07:00</published><updated>2009-07-19T01:36:15.733-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>dot points on build page extensions</title><content type='html'>I have been thinking about making some extensions to the build page.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Raw Data&lt;br /&gt;&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Keep raw_data to process at any time. No need to discount old data collected from buggy analysis.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Profiling&lt;br /&gt;&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Use function wrappers, that log profiling of each test and multiple calls. &lt;/li&gt;&lt;br /&gt;&lt;li&gt; -p|--profile command line mode&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Tests&lt;br /&gt;&lt;/h3&gt;&lt;ul&gt;&lt;li&gt; Use subprocess mode by default for run_tests.py &lt;/li&gt;&lt;br /&gt;&lt;li&gt; Web interface for ticketing off tests &lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;h3&gt;Build information&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Post compiler version&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Post complete Setup file&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Post complete build output&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Post complete test output&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Python sys.path&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Environment variables&lt;/li&gt;&lt;br /&gt;&lt;li&gt;As much as possible, unprocessed for archives&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Machine information&lt;br /&gt;&lt;/h3&gt;&lt;ul&gt;&lt;li&gt; Processor speed&lt;/li&gt;&lt;br /&gt;&lt;li&gt;CDRom availability&lt;/li&gt;&lt;br /&gt;&lt;li&gt;etc, etc.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;h3&gt;Breaking up tests &lt;/h3&gt;Should the tests fail if a machine doesn't have a CD drive (assuming stubs were filled out) for example?&lt;br /&gt;&lt;br /&gt;Should tests that require Numeric or NumPy fail if neither available?&lt;br /&gt;&lt;br /&gt;There are some classes of tests that it seems to make sense to split apart from the main "base" group of tests. What should be the "base" group of tests to automate with the run_tests.py test runner?&lt;br /&gt;&lt;br /&gt;What about tests that require human verification? For the build page a "base" group of tests should be specified.&lt;br /&gt;&lt;br /&gt;What should be the requirements for machines sending results to the build page? Numeric, Numpy? win32 extensions on windows? A CD rom drive? 32 bit color display?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-4504280455811508274?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/4504280455811508274/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=4504280455811508274' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4504280455811508274'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4504280455811508274'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/07/dot-points-on-build-page-extensions.html' title='dot points on build page extensions'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-4204365357691616759</id><published>2008-07-03T21:10:00.000-07:00</published><updated>2009-07-19T01:36:15.734-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>test_not_implemented()</title><content type='html'>&lt;pre class='blackboard'&gt;def test_get_arraytypes(self):&lt;br /&gt;&lt;br /&gt;    # __doc__ (as of 2008-06-25) for pygame.sndarray.get_arraytypes:&lt;br /&gt;&lt;br /&gt;      # pygame.sndarray.get_arraytypes (): return tuple&lt;br /&gt;      # &lt;br /&gt;      # Gets the array system types currently supported.&lt;br /&gt;      # &lt;br /&gt;      # Checks, which array system types are available and returns them as a&lt;br /&gt;      # tuple of strings. The values of the tuple can be used directly in&lt;br /&gt;      # the use_arraytype () method.&lt;br /&gt;      # &lt;br /&gt;      # If no supported array system could be found, None will be returned.&lt;br /&gt;&lt;br /&gt;    self.assert_(test_not_implemented()) &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;test_not_implemented() will fail if any test suite is run with a "(-i|--incomplete)" command line option.&lt;br /&gt;&lt;br /&gt;As mentioned in previous posts, I developed a unittest stub generator that will output stubs for any untested units. It is supported by a naming scheme for the tests. The stubber will inspect the xxxx_test.py modules and based upon the names of the unittest.TestCase's and their children test_xxxx methods will determine what is already tested.&lt;br /&gt;&lt;br /&gt;For each public callable there is a corresponding test named test_$callable_name. Comments or descriptions will be appended to this separated by a double underscore.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;test_quit__returns_None_if_not_already_init&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;What if there is a module.quit and a module.class.quit ? Each class has it's own TestCase (and thus namespace) named $classTypeTest. This is typically the case anyway with setUp()'s specific to the class tested.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;get_callables&lt;/span&gt;(&lt;span class='variable'&gt;obj&lt;/span&gt;, &lt;span class='variable'&gt;if_of&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='constant'&gt;None&lt;/span&gt;, &lt;span class='variable'&gt;check_where_defined&lt;/span&gt;&lt;span class='keyword'&gt;=&lt;/span&gt;&lt;span class='constant'&gt;False&lt;/span&gt;):&lt;br /&gt;    publics &lt;span class='keyword'&gt;=&lt;/span&gt; (&lt;span class='support'&gt;getattr&lt;/span&gt;(obj, x) &lt;span class='keyword'&gt;for&lt;/span&gt; x &lt;span class='keyword'&gt;in&lt;/span&gt; &lt;span class='support'&gt;dir&lt;/span&gt;(obj) &lt;span class='keyword'&gt;if&lt;/span&gt; is_public(x))&lt;br /&gt;    callables &lt;span class='keyword'&gt;=&lt;/span&gt; (x &lt;span class='keyword'&gt;for&lt;/span&gt; x &lt;span class='keyword'&gt;in&lt;/span&gt; publics &lt;span class='keyword'&gt;if&lt;/span&gt; &lt;span class='support'&gt;callable&lt;/span&gt;(x) &lt;span class='keyword'&gt;or&lt;/span&gt; isgetsetdescriptor(x))&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;if&lt;/span&gt; check_where_defined:&lt;br /&gt;        callables &lt;span class='keyword'&gt;=&lt;/span&gt; (c &lt;span class='keyword'&gt;for&lt;/span&gt; c &lt;span class='keyword'&gt;in&lt;/span&gt; callables &lt;span class='keyword'&gt;if&lt;/span&gt; ( &lt;span class='string'&gt;'pygame'&lt;/span&gt; &lt;span class='keyword'&gt;in&lt;/span&gt; c.__module__ &lt;span class='keyword'&gt;or&lt;/span&gt;&lt;br /&gt;                    (&lt;span class='string'&gt;'__builtin__'&lt;/span&gt; &lt;span class='keyword'&gt;==&lt;/span&gt; c.__module__ &lt;span class='keyword'&gt;and&lt;/span&gt; isclass(c)) )&lt;br /&gt;                    &lt;span class='keyword'&gt;and&lt;/span&gt; REAL_HOMES.get(c, &lt;span class='constant'&gt;0&lt;/span&gt;) &lt;span class='keyword'&gt;in&lt;/span&gt; (&lt;span class='constant'&gt;0&lt;/span&gt;, obj))&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;if&lt;/span&gt; if_of:&lt;br /&gt;        callables &lt;span class='keyword'&gt;=&lt;/span&gt; (x &lt;span class='keyword'&gt;for&lt;/span&gt; x &lt;span class='keyword'&gt;in&lt;/span&gt; callables &lt;span class='keyword'&gt;if&lt;/span&gt; if_of(x)) &lt;span class='comment'&gt;# isclass, ismethod etc&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; &lt;span class='support'&gt;set&lt;/span&gt;(callables)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The script uses inspection to find all testables in pygame but there were a few complications, for example getter/setter properties and the fact that some objects need to be instantiated before inspection reveals their innards. Also, filtering out non-pygame callables and after that callables that appeared in more than one module.&lt;br /&gt;&lt;br /&gt;eg pygame.rect.Rect led a double life as pygame.sprite.Rect. Just check the __module__ attribute ?&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;In [&lt;span class='constant'&gt;4&lt;/span&gt;]: pygame.sprite.Rect.__module__&lt;br /&gt;Out[&lt;span class='constant'&gt;4&lt;/span&gt;]: &lt;span class='string'&gt;'pygame'&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The workaround was to make a mapping of object to the place where it was defined. There were only 9 of these.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;REAL_HOMES &lt;span class='keyword'&gt;=&lt;/span&gt; {&lt;br /&gt;    pygame.rect.Rect         : pygame.rect,&lt;br /&gt;    pygame.mask.from_surface : pygame.mask,&lt;br /&gt;    pygame.time.get_ticks    : pygame.time,&lt;br /&gt;    .....&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;On some of the classes the __module__ attribute was __builtin__ so I needed put an exception for them in the filtering out of non pygame callables.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;In [&lt;span class='constant'&gt;7&lt;/span&gt;]: pygame.cdrom.CDType.__module__&lt;br /&gt;Out[&lt;span class='constant'&gt;7&lt;/span&gt;]: &lt;span class='string'&gt;'__builtin__'&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;module_stubs&lt;/span&gt;(&lt;span class='variable'&gt;module&lt;/span&gt;):&lt;br /&gt;    stubs &lt;span class='keyword'&gt;=&lt;/span&gt; {}&lt;br /&gt;    all_callables &lt;span class='keyword'&gt;=&lt;/span&gt; get_callables(module, &lt;span class='variable'&gt;check_where_defined&lt;/span&gt; &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='constant'&gt;True&lt;/span&gt;) &lt;span class='keyword'&gt;-&lt;/span&gt; IGNORES&lt;br /&gt;    classes &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='support'&gt;set&lt;/span&gt; (&lt;br /&gt;        c &lt;span class='keyword'&gt;for&lt;/span&gt; c &lt;span class='keyword'&gt;in&lt;/span&gt; all_callables &lt;span class='keyword'&gt;if&lt;/span&gt; isclass(c) &lt;span class='keyword'&gt;or&lt;/span&gt; c &lt;span class='keyword'&gt;in&lt;/span&gt; MUST_INSTANTIATE&lt;br /&gt;    )&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;for&lt;/span&gt; class_ &lt;span class='keyword'&gt;in&lt;/span&gt; classes:&lt;br /&gt;        base_type &lt;span class='keyword'&gt;=&lt;/span&gt; class_&lt;br /&gt;&lt;br /&gt;        &lt;span class='keyword'&gt;if&lt;/span&gt; class_ &lt;span class='keyword'&gt;in&lt;/span&gt; MUST_INSTANTIATE:&lt;br /&gt;            class_ &lt;span class='keyword'&gt;=&lt;/span&gt; get_instance(class_)&lt;br /&gt;&lt;br /&gt;        stubs.update (&lt;br /&gt;            make_stubs(get_callables(class_) &lt;span class='keyword'&gt;-&lt;/span&gt; IGNORES, module, base_type)&lt;br /&gt;        )&lt;br /&gt;&lt;br /&gt;    stubs.update(make_stubs(all_callables &lt;span class='keyword'&gt;-&lt;/span&gt; classes, module))&lt;br /&gt;&lt;br /&gt;    &lt;span class='keyword'&gt;return&lt;/span&gt; stubs&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The stubber finds all modules in the pygame package. For each module it uses inspection to create a set of all the callables minus those set in the IGNORE setting. This is here for any exceptions to the filtering and also for tests that have been grouped under one test name. These objects will not be stubbed.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;IGNORES &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='support'&gt;set&lt;/span&gt;([&lt;br /&gt;&lt;br /&gt;    pygame.rect.Rect.h,           pygame.rect.Rect.w,&lt;br /&gt;    pygame.rect.Rect.x,           pygame.rect.Rect.y,&lt;br /&gt;&lt;br /&gt;    pygame.color.Color.a,         pygame.color.Color.b,&lt;br /&gt;    pygame.color.Color.g,         pygame.color.Color.r,&lt;br /&gt;&lt;br /&gt;......&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;From that it creates a subset of "classes", the criteria being that for each element "inspect.isclass(element)" or that the element is in the manually set MUST_INSTANTIATE dict. This is a mapping of class to helper function, and instantiation args required to return an instance.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;MUST_INSTANTIATE &lt;span class='keyword'&gt;=&lt;/span&gt; {&lt;br /&gt;    &lt;br /&gt;    &lt;span class='comment'&gt;# BaseType / Helper               # (Instantiator / Args) / Callable&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;    pygame.cdrom.CDType            :  (pygame.cdrom.CD, (&lt;span class='constant'&gt;0&lt;/span&gt;,)),&lt;br /&gt;    pygame.mixer.ChannelType       :  (pygame.mixer.Channel, (&lt;span class='constant'&gt;0&lt;/span&gt;,)),&lt;br /&gt;    pygame.time.Clock              :  (pygame.time.Clock, ()),&lt;br /&gt;&lt;span class="lineNumber"&gt;&lt;br /&gt;&lt;br /&gt;..&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Inspecting the xxxxType would reveal no methods, and they needed to be instantiated, but then the object returned contained no other attributes; one example being __name__ needed later for determing the test name. Therefore the xxxxType was sent to the stub generation function as the "parent class" for each callable that was gathered by inspecting the instantiation.&lt;br /&gt;&lt;br /&gt;Any callables not in the "classes" set are assumed module level functions and a stub is created for each.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The test stubber is used from the command line:&lt;br /&gt; &lt;br /&gt;&lt;pre class='blackboard'&gt;$ gen_stubs.py --help&lt;br /&gt;Usage:&lt;br /&gt;$ gen_stubs.py ROOT&lt;br /&gt;&lt;br /&gt;eg.&lt;br /&gt;&lt;br /&gt;$ gen_stubs.py sprite.Sprite&lt;br /&gt;&lt;br /&gt;def test_add(self):&lt;br /&gt;&lt;br /&gt;    # Doc string for pygame.sprite.Sprite:&lt;br /&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Options:&lt;br /&gt;  -h, --help        show this help message and exit&lt;br /&gt;  -l, --list        list callable names not stubs&lt;br /&gt;  -t, --test_names  list test names not stubs&lt;/pre&gt;&lt;br /&gt;    &lt;br /&gt;&lt;pre class='blackboard'&gt;$ gen_stubs.py pygame -l&lt;br /&gt;pygame.base.error.args,&lt;br /&gt;pygame.bufferproxy.BufferProxy.length,&lt;br /&gt;pygame.bufferproxy.BufferProxy.raw,&lt;br /&gt;pygame.event.Event,&lt;br /&gt;pygame.image.tostring,&lt;br /&gt;pygame.joystick.Joystick,&lt;br /&gt;pygame.key.get_repeat,&lt;br /&gt;pygame.mask.Mask,&lt;br /&gt;pygame.mixer.Channel,&lt;br /&gt;pygame.movie.Movie,&lt;br /&gt;pygame.overlay.overlay.display,&lt;br /&gt;pygame.overlay.overlay.get_hardware,&lt;br /&gt;pygame.overlay.overlay.set_location,&lt;br /&gt;pygame.pixelarray.PixelArray.surface,&lt;br /&gt;pygame.sprite.AbstractGroup.add,&lt;br /&gt;pygame.sprite.AbstractGroup.add_internal,&lt;br /&gt;pygame.sprite.AbstractGroup.clear,&lt;br /&gt;pygame.sprite.AbstractGroup.copy,&lt;br /&gt;pygame.sprite.AbstractGroup.draw,&lt;br /&gt;pygame.sprite.AbstractGroup.empty,&lt;br /&gt;pygame.sprite.AbstractGroup.has_internal,&lt;br /&gt;pygame.sprite.AbstractGroup.remove,&lt;br /&gt;pygame.sprite.AbstractGroup.remove_internal,&lt;br /&gt;pygame.sprite.AbstractGroup.sprites,&lt;br /&gt;pygame.sprite.AbstractGroup.update,&lt;br /&gt;pygame.sprite.collide_rect,&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Commas are appended for easy copy/paste into IGNORE list.&lt;br /&gt;&lt;br /&gt;gen_stubs.py is an integral part of the plan to make it extremely easy for people to contribute to unittests. One man can only do so much.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-4204365357691616759?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/4204365357691616759/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=4204365357691616759' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4204365357691616759'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/4204365357691616759'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/07/stubby.html' title='test_not_implemented()'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-531199877844470969</id><published>2008-06-30T20:05:00.000-07:00</published><updated>2008-07-07T00:14:26.642-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>subprocessed</title><content type='html'>PyGame tests are structured in such a way that for each module in the pygame package (eg pygame.sprite, pygame.color) there is a test/xxxx_test.py file containing corresponding unittests. PyGame has an automated build page that shows build and test results for the latest svn version of PyGame on a variety of platforms and versions of python. It uses regular expressions to parse the results of the test runner script.&lt;br /&gt;&lt;br /&gt;The test runner script compiles tests from each of the xxxx_test.py files and runs them in a single process. Advantage: speed, disadvantage: instability. PyGame uses a lot of c code, and where there is c code there is potential for strange errors.&lt;br /&gt;As one example, there was a test for the ability to save OpenGl surfaces which would segfault on windows. This would stop the test runner half way through, leaving it's output in a form the automated build page could not decipher. &lt;br /&gt;&lt;br /&gt;"Build Successful, Invalid Test Results"&lt;br /&gt;&lt;br /&gt;Other issues with running all tests in one process is the need to restore a "fresh" state for tests that rely on it. Conflicts can cause the test runner script to crash completely. On the other hand, some obscure bugs have been uncovered due to them.&lt;br /&gt;&lt;br /&gt;Besides writing tests for individual units I have recently been working on adding a subprocess mode to the python test runner script. It processes the output of each module's test script and outputs the results in the same form as the single process mode.&lt;br /&gt;&lt;br /&gt;There is a library called subunit that uses os.fork() to run unittest suites in subprocesses, that seemed like it would have been a perfect candidate for the job. Unfortunately windows doesn't have the fork system call so it was not an option. Windows python does not even provide os.kill().&lt;br /&gt;&lt;br /&gt;What good is running all tests in subprocesses if one of them hangs and python is using a blocking call to retrieve it's output?&lt;br /&gt;As I was going to the trouble of making a subprocess mode, I realized I should deal with this possibility. Unfortunately the python subprocess module doesn't ship with async calls but I found a recipe on the ActiveState Python CookBook site.&lt;br /&gt;&lt;br /&gt;On windows it relies on win32pipe and win32file from the pywin32 package. I worked around the lack of os.kill on windows by using sytem calls to "taskkill" or "pskill". If a wayward test suite running in a subprocess doesn't finish up in a specified allowance of time then it will be os.kill'd. &lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;COMPLETE_FAILURE_TEMPLATE &lt;span class='keyword'&gt;=&lt;/span&gt; &lt;span class='string'&gt;"""&lt;br /&gt;======================================================================&lt;br /&gt;ERROR: all_tests_for (&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;.AllTestCases)&lt;br /&gt;----------------------------------------------------------------------&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "test\&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;.py", line 1, in all_tests_for&lt;br /&gt;&lt;br /&gt;subprocess completely failed with return code of &lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;&lt;br /&gt;&lt;br /&gt;cmd: &lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;&lt;br /&gt;&lt;br /&gt;return (abbrv):&lt;br /&gt;&lt;/span&gt;&lt;span class='stringInterpolation'&gt;%s&lt;/span&gt;&lt;span class='string'&gt;&lt;br /&gt;&lt;br /&gt;"""&lt;/span&gt;  &lt;span class='comment'&gt;# Leave that last empty line else build page regex won't match&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Running each test suite in a subprocess is a huge performance hit. I think for the automated build page the performance hit won't really effect the experience as it's all running headlessly from cron jobs. Nevertheless, I added the ability to run subprocessed tests simultaneously in multiple threads. Also, the single process mode is still available as is running module specific tests suites.&lt;br /&gt;&lt;br /&gt;I wrote some tests comparing the output of (single|sub)process modes running a group of fake test suites, some all OK, some with errors and failures. &lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;$ run_tests__test.py&lt;br /&gt;all_ok suite OK&lt;br /&gt;failures1 suite OK&lt;br /&gt;&lt;br /&gt;2/2 passes&lt;br /&gt;&lt;br /&gt;-h for help&lt;br /&gt;&lt;br /&gt;$ run_tests__test.py -h&lt;br /&gt;&lt;br /&gt;-v, to output diffs even on success&lt;br /&gt;-u, to output diffs of unnormalized tests&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The standard library module difflib is very good, and extremely well documented.&lt;br /&gt;&lt;br /&gt;Other than the obvious differences such as timing which are normalized before comparison, all is OK :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-531199877844470969?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/531199877844470969/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=531199877844470969' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/531199877844470969'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/531199877844470969'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/07/subprocessed.html' title='subprocessed'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-1274931787902960058</id><published>2008-06-25T18:48:00.000-07:00</published><updated>2009-07-19T01:36:15.734-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='gsoc'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Testing</title><content type='html'>Just a quick note,&lt;br /&gt;&lt;br /&gt;Still waiting on my fan, it's getting shipped in from Amurricah.&lt;br /&gt;&lt;br /&gt;I wrote a run_tests_sub.py the other day that uses subprocess to run each xxxx_test.py in the trunk/test directory.&lt;br /&gt;&lt;br /&gt;It will run with an optional threads paramater:  -t num_threads&lt;br /&gt;&lt;br /&gt;$ run_tests_sub.py -t 4&lt;br /&gt;&lt;br /&gt;Apparently this runs faster on mult-core.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;It should output results similar to run_tests.py though may need tweaking to get it run transparently in place of run_tests.py for the build page.&lt;br /&gt;&lt;br /&gt;Speaking of  the build page,  Rene and I have had a few ideas for a combined build / test web app that collected builds and test statistics ( profiling / passes etc).  Also, a means to distribute the writing of tests. Many hands make light work.&lt;br /&gt;&lt;br /&gt;If it was possible to be assigned a stub of a test to fill out and then post it back painlessly we could quite quickly increase the coverage of our tests. If twenty people filled out 1 test a week, then over a month that would be 80 extra unit tests.&lt;br /&gt;&lt;br /&gt;ATM there are "FAILED (failures=232)", unimplemented tests and possibly that many again that haven't been stubbed out waiting to be written.&lt;br /&gt;&lt;br /&gt;$ run_tests.py -i&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Will show tests that need fleshing out.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-1274931787902960058?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/1274931787902960058/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=1274931787902960058' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/1274931787902960058'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/1274931787902960058'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/06/testing.html' title='Testing'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-8693870793839153937</id><published>2008-06-20T22:41:00.000-07:00</published><updated>2009-07-19T01:40:04.764-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Aha!</title><content type='html'>I realised why the change from CONSTANT = (expr) to CONSTANT = [expr] fixed the bug in the color_test.py&lt;br /&gt;&lt;br /&gt;A generator expression is only good for one iteration and after that it will act as an empty sequence. I thought it would be a reusable lazily evaluated simily of a list comp. Turns out I was dead wrong.&lt;br /&gt;&lt;br /&gt;I went over the stub generator recently and it's pretty much in it's finalized form as far as the naming scheme is concerned.&lt;br /&gt;&lt;br /&gt;Can't wait to get my own computer back in action.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-8693870793839153937?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/8693870793839153937/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=8693870793839153937' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8693870793839153937'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8693870793839153937'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/06/aha.html' title='Aha!'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-2671256794420213290</id><published>2008-06-18T03:26:00.000-07:00</published><updated>2009-07-19T01:40:04.764-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Fan</title><content type='html'>Damn fan on my laptop packed it in. I will have to convince my friend to let me install linux on his windows box while I wait for a replacement. Can't get a windows build of development pygame at the moment due to failing tests... or can I? Temporarily disable the failing tests and let the build farm run?&lt;br /&gt;&lt;br /&gt;What a pain in the arse.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-2671256794420213290?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/2671256794420213290/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=2671256794420213290' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/2671256794420213290'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/2671256794420213290'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/06/fan.html' title='Fan'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-8255675554884786682</id><published>2008-06-12T21:05:00.000-07:00</published><updated>2009-07-19T01:40:04.764-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Happenings</title><content type='html'>I have been writing unittests using the naming scheme (see below) keeping to it as much as possible.&lt;br /&gt;&lt;br /&gt;There have been a few modifications but that's fine as long as I am consistent. I haven't yet written the part of the test stub generator that filters from the generated tests any tests for units that have already been written. I am letting the writing of tests dictate the naming schemes evolution.&lt;br /&gt;&lt;br /&gt;Will post some more thoughts on the naming scheme in days to come. Also thoughts on one to one test names.&lt;br /&gt;&lt;br /&gt;Thoughts on speed of test suites, isolating "dangerous" tests that can crash the whole test suite.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-8255675554884786682?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/8255675554884786682/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=8255675554884786682' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8255675554884786682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/8255675554884786682'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/06/happenings.html' title='Happenings'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-3261592275288398102</id><published>2008-06-12T21:00:00.000-07:00</published><updated>2009-07-19T01:40:18.994-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Thumb Rules: Testing Generalities</title><content type='html'>It's been quite an illuminating experience spending most of my time of late  writing tests. A lot of the rules of thumb I have read about have proven to make good sense.&lt;br /&gt;&lt;br /&gt;With the unittest framework especially, it seems to pay to keep to a minimum the number of assertions per test. Sometimes this increases the verbosity of your tests, something that python programmers seem to almost despise. People tend to optimize for elegance of code and to some extent performance. Test code should probably should be optimized to other priorities&lt;br /&gt;&lt;br /&gt;Why does it pay though? The unittest framework stops any test dead in it's tracks on any error or assertion failure. If you have a whole batallion of assertions grouped under one test, on a failure you will only get information about one failure, and you miss out on a lot of context that would otherwise have been reported. Is there anything common to all these failures?&lt;br /&gt;&lt;br /&gt;For example, there was a bug in some of the sprite collision tests (that intriguingly was not apparent on windows). The problem eluded me, but my brother discovered that it was due to testing the equivalence of lists of sprites, one of which was sourced from a dict, the order of which can not be guaranteed.&lt;br /&gt;&lt;br /&gt;At the time there was a squadron of tests under one test, test_spritecollide.&lt;br /&gt;( I restructured the assertions while renaming tests to fit the test stubber naming scheme.  )&lt;br /&gt;&lt;br /&gt;Running the test on linux, it would only report one failure when in fact the bug was repeated through about 4 - 5 assertions. Fix one and then the next assertion would fail.&lt;br /&gt;&lt;br /&gt;Had they been structured in a way with less assertions per test, showing all the failures, it's possible a programmer of less ability like myself would have been able to solve the problem. Other programmers instantly.&lt;br /&gt;&lt;br /&gt;This context makes things easier to hone in on the real problem. This brings me to another thought. unittests, what are they?&lt;br /&gt;&lt;br /&gt;Unit tests; tests of units. Leaving aside the definition of unit, tests for what? One could say that they are testing for defined behaviour. Essentially then, you are testing for bugs, as if the unit is not working within defined behaviour, the unit is buggy. &lt;br /&gt;&lt;br /&gt;What do you do when something is buggy? You debug of course. Tests then could or even should be debugging aids, especially useful when the tests are written before the actual units.&lt;br /&gt;&lt;br /&gt;If you are not using tests for debugging then what? Some temporary scaffolding that with a tiny bit more energy could have been a test?&lt;br /&gt;&lt;br /&gt;How to make the tests help in debugging without spending too much extra energy?&lt;br /&gt;&lt;br /&gt;I'm not really sure on this, other than trying to keep assertions per test to a minimum. Another thing that may help is to not use anonymous expressions in the tests. If everything has been named, it's easier to use a debugger and get a glimpes of what's going on.&lt;br /&gt;&lt;br /&gt;Tests should probably sacrifice compactness and abstraction for explicitness. There is probably a line to draw somewhere near repeating yourself too much (copy paste programming).&lt;br /&gt;&lt;br /&gt;Sometimes it is easy to miss some bugs in your tests that give a false sense of everything being "OK".&lt;br /&gt;&lt;br /&gt;An example of that is some tests for the Color type properties. I was looping over a generator expression that I defined for some test fixtures. All the tests were passing a OK. It turns out that for some reason ( I still don't know why ) in the scope of the tests, the expression was behaving as an empty sequence. &lt;br /&gt;&lt;br /&gt;eg &lt;br /&gt;&lt;br /&gt;for fixture in []:&lt;br /&gt;    assert something_about(fixture) == something&lt;br /&gt;&lt;br /&gt;Nothing was ever asserted, the tests passed. I since changed the generator expression to a list comp and the tests are asserting themselves. ( expr ) =&gt; [ expr ]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-3261592275288398102?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/3261592275288398102/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=3261592275288398102' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/3261592275288398102'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/3261592275288398102'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/06/thumb-rules-testing-generalities.html' title='Thumb Rules: Testing Generalities'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-7199645463611816206</id><published>2008-05-27T02:18:00.000-07:00</published><updated>2009-07-19T01:40:04.765-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>blog.update(recent_events)</title><content type='html'>I have resolved the failing test issues I had on Ubuntu. &lt;br /&gt;&lt;br /&gt;It was a bug in testing the content equality of two lists; one was the return value of a function, the order of which could not be guaranteed. Odd that it was only an issue on Ubuntu, and presumably the linux platform in general. I commited a patch that fixed it.&lt;br /&gt;&lt;br /&gt;At the moment I am working on creating a script, and supporting test structuring, to automate test stubbing for all units that are not tested.&lt;br /&gt;&lt;br /&gt;This is so I can pick off the tests more methodically. Also, if it's possible to get a large group of people writing tests, just one at a time each week, a lot could very quickly get done. How to reduce the overhead so that is worthwhile and easy to be assigned one unit and write some tests?&lt;br /&gt;&lt;br /&gt;The basic idea is to have a naming convention (possibly renaming existing tests) for tests that makes it easy to see if units are tested. A one 2 one mapping of callable to test.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;test_$Class__$callable__$description&lt;br /&gt;&lt;br /&gt;$Class             if a method&lt;br /&gt;$callable          &lt;br /&gt;$description       if any / optional&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;At the moment the script just creates test stubs for all "public" callables in the pygame package with that naming convention. It appends the callable's doc string as a comment.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class='storage'&gt;def&lt;/span&gt; &lt;span class='entity'&gt;test_Surface__get_colorkey&lt;/span&gt;(&lt;span class='variable'&gt;self&lt;/span&gt;):&lt;br /&gt;    &lt;span class='string'&gt;"""&lt;br /&gt;    TODO: Test for unit, get_colorkey&lt;br /&gt;&lt;br /&gt;    """&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class='comment'&gt;# Docstring:&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class='comment'&gt;# Surface.get_colorkey(): return RGB or None&lt;br /&gt;&lt;/span&gt;    &lt;span class='comment'&gt;# Get the current transparent colorkey&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class='variable'&gt;self&lt;/span&gt;&lt;span class='metaFunctionCallPy'&gt;.assert_(not_completed())&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt; I am still fleshing out the idea&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-7199645463611816206?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/7199645463611816206/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=7199645463611816206' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7199645463611816206'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/7199645463611816206'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/05/blogupdaterecentevents.html' title='blog.update(recent_events)'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-3245815174371803548</id><published>2008-05-06T22:58:00.000-07:00</published><updated>2009-07-19T01:40:04.765-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Compilation of Blues</title><content type='html'>I was advised that I should get some experience compiling pygame. I use windows and thanks to Brian/Lenard it's a one click process to install the latest version of pygame. I knew that in the future I would be needing to run tests on Linux so I thought I may as well set it up now and compile pygame on that. I toyed briefly with the idea of setting up a dual boot. I have ran that setup in the past and it's a PITA so I opted for a virtual machine running Ubuntu 8.04 running on one of my virtual desktops. Virtual insanity.&lt;br /&gt;&lt;br /&gt;After setting up the VM and installing Ubuntu I decided to have a crack at compiling everything from source and not using apt-get. This both for the experience and because I was under the mistaken impression that the svn HEAD version of pygame required newer versions of it dependencies than available through apt-get packaging.&lt;br /&gt;&lt;br /&gt;(The wiki has compilation steps for Mac and Windows with links to source archives of pygame dependencies. A note on the wiki says "We should have a download with everything included. As well as patches for each one that we need". Sounds like a great idea to me. Maybe an svn repo? Is there much difference between the platforms?)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I downloaded the packages and began the quite tedious process of compiling everything.&lt;br /&gt;&lt;br /&gt;............&lt;br /&gt;&lt;br /&gt;I eventually had all the dependencies compiled and was glad to be finally able to begin compiling pygame&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;$&lt;br /&gt;sudo python setup.py install&lt;br /&gt;&lt;br /&gt;............&lt;br /&gt;&lt;br /&gt;building 'pygame.scrap' extension&lt;br /&gt;gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -D_REENTRANT -I/usr/X11R6/include -I/usr/local/include/SDL -I/usr/include/python2.5 -c src/scrap.c -o build/temp.linux-i686-2.5/src/scrap.o&lt;br /&gt;In file included from src/scrap.c:59:&lt;br /&gt;src/scrap_x11.c: In function ‘_convert_format’:&lt;br /&gt;src/scrap_x11.c:77: error: ‘XA_PIXMAP’ undeclared (first use in this function)&lt;br /&gt;src/scrap_x11.c:77: error: (Each undeclared identifier is reported only once&lt;br /&gt;src/scrap_x11.c:77: error: for each function it appears in.)&lt;br /&gt;src/scrap_x11.c:79: error: ‘XA_BITMAP’ undeclared (first use in this function)&lt;br /&gt;src/scrap_x11.c: In function ‘_add_clip_data’:&lt;br /&gt;    &lt;br /&gt;............&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;My happiness was shortlived. I googled the problem and found someone back in 2005 having similiar problems trying to compile qt. It was something to do with not having the XFree86 development package. Turns out this package goes by the name libx11-dev for debian/ubuntu. &lt;br /&gt;&lt;br /&gt;I installed it but the problem remained so I turned to #pygame for help. Someone helpful there, I can't recall who, said that it seemed I was missing some X11 header files and specifically a file called Xatom.h. Off in circles again for while looking for a missing package. I was pointed toward packages.ubuntu.com.  &lt;br /&gt;&lt;br /&gt;I searched for all packages containing Xatom.h. and..... aha! "x11proto-core-dev". That must be it! &lt;br /&gt;&lt;pre class='blackboard'&gt;$&lt;br /&gt;sudo apt-get install x11proto-core-dev&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It was already installed.&lt;br /&gt;&lt;br /&gt;What now?  I harassed one of my mentors, Rene, and he said that it could be a problem with pygame-trunk/src/scrap_x11.c or that it could possibly be because I got a non X version of SDL. "Damn.., I don't think I did" &lt;br /&gt;&lt;br /&gt;He taught me about "sudo updatedb" and "locate X" with which I was able to confirm the existence of and location of Xatom.h on the system. He recommended that I use apt-get to get ready made packages of the dependencies and compile again.&lt;br /&gt;&lt;br /&gt;I managed to replace all the local versions of the dependencies with apt-get ones but the first time round I missed deleting a lot of them. I have not that much experience with linux. The first time I recompiled it worked but upon running the unit tests I found that 5 of them were failing.&lt;br /&gt;&lt;br /&gt;Rene, was quick to the rescue. "my guess is you're linking to your self installed sdl_image... try ldd `find . -name imageext.so`"&lt;br /&gt;&lt;br /&gt;That confirmed it. I hunted down all the non apt managed packages and exterminated them from the system then compiled pygame again.&lt;br /&gt;&lt;br /&gt;1 Unit test was still failing. IS still failing.&lt;br /&gt;&lt;br /&gt;&lt;pre class='blackboard'&gt;&lt;span class="lineNumber"&gt;1  &lt;/span&gt;======================================================================&lt;br /&gt;&lt;span class="lineNumber"&gt;2  &lt;/span&gt;FAIL: test_spritecollide (sprite_test.SpriteTest)&lt;br /&gt;&lt;span class="lineNumber"&gt;3  &lt;/span&gt;----------------------------------------------------------------------&lt;br /&gt;&lt;span class="lineNumber"&gt;4  &lt;/span&gt;Traceback (most recent call last):&lt;br /&gt;&lt;span class="lineNumber"&gt;5  &lt;/span&gt;  File "test/sprite_test.py", line 68, in test_spritecollide&lt;br /&gt;&lt;span class="lineNumber"&gt;6  &lt;/span&gt;    self.assertEqual(sprite.spritecollide(s1, ag2, dokill = False, collided = sprite.collide_rect_ratio(20.0)),[s2,s3])&lt;br /&gt;&lt;span class="lineNumber"&gt;7  &lt;/span&gt;AssertionError: [&amp;lt;Sprite sprite(in 1 groups)&amp;gt;, &amp;lt;Sprite sprite(in 1 groups)&amp;gt;] != [&amp;lt;Sprite sprite(in 1 groups)&amp;gt;, &amp;lt;Sprite sprite(in 1 groups)&amp;gt;]&lt;/pre&gt;&lt;br /&gt;This test passes fine under Windows.&lt;br /&gt;&lt;br /&gt;The hunt is on.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-3245815174371803548?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/3245815174371803548/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=3245815174371803548' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/3245815174371803548'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/3245815174371803548'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/05/compilation-of-blues.html' title='Compilation of Blues'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-43333553664286798</id><published>2008-04-29T02:41:00.000-07:00</published><updated>2009-07-19T01:40:04.765-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Journey of a Thousand Miles</title><content type='html'>&lt;object id="csSWF" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://active.macromedia.com/flash7/cabs/ swflash.cab#version=9,0,28,0" height="166" width="700"&gt;&lt;embed name="csSWF" src="http://www.fixmeputer.com/akalias.swf" bgcolor="#1a1a1a" quality="best" allowscriptaccess="always" allowfullscreen="true" scale="showall" flashvars="autostart=false" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" height="166" width="700"&gt;&lt;/embed&gt; &lt;/object&gt;&lt;br /&gt;&lt;br /&gt;On the advice of my brother I have changed the name of the blog to something more generic so I can keep the blog once the winter has passed. You will be hearing from me soon. I want your ideas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-43333553664286798?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/43333553664286798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=43333553664286798' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/43333553664286798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/43333553664286798'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/04/lots-todo.html' title='Journey of a Thousand Miles'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7254386064439146371.post-1323339100975422997</id><published>2008-04-22T20:30:00.000-07:00</published><updated>2009-07-19T01:40:18.994-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pygame'/><category scheme='http://www.blogger.com/atom/ns#' term='summer of code'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Winter Of Testing</title><content type='html'>Wow, my "Google Summer of Code" application was accepted. My inbox has been flooded with congratulations and introductions from people from all over the world, who likewise were accepted in the program. Brazil, China, Russia to name just a few places. Hundreds of people excited about being sponsored to work on a project that interests them "over the summer". I live in Australia, and summer has just ended; no more morning swims, I will leave that to the diehards. Winter is here and it looks like I will spend it underneath a blanket at the keyboard.&lt;br /&gt;&lt;br /&gt;For my GSOC project I will be extending the coverage of tests in pygame. With Py3K on the horizon and lots of other pygame ports in conception it's critical to get more tests in place. I will be extending the unittests and implementing some speed regression tests. Also, I will develop some interactive tests for things that are difficult/impossible/notWorthTheTime to test automatically.&lt;br /&gt;&lt;br /&gt;My general strategy will be "breadth first", cycling through the modules. As time allows I will pinpoint target modules for intensive testing based on community feedback. I look forward to working with the PyGame community and welcome any ideas and advice.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7254386064439146371-1323339100975422997?l=blog.akalias.net' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.akalias.net/feeds/1323339100975422997/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7254386064439146371&amp;postID=1323339100975422997' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/1323339100975422997'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7254386064439146371/posts/default/1323339100975422997'/><link rel='alternate' type='text/html' href='http://blog.akalias.net/2008/04/google-summer-of-code.html' title='Winter Of Testing'/><author><name>akalias</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
