<?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-5409861124354728237</id><updated>2011-09-09T09:20:35.605-04:00</updated><category term='ruby'/><category term='emacs-tips'/><category term='google-ai-challenge'/><category term='emacs'/><category term='wiki'/><category term='cli'/><category term='javascript'/><category term='SQL'/><category term='java'/><category term='web-development'/><category term='ai'/><category term='clj-doc'/><category term='documentation'/><category term='law'/><category term='debugging'/><category term='clojure'/><category term='cygwin'/><category term='semantic web'/><category term='dvcs'/><category term='tutorial'/><category term='vulgarization'/><category term='clojureql'/><category term='philosophy'/><category term='task-tutorial'/><category term='JDBC'/><category term='ruby-on-rails'/><category term='somalia'/><category term='compojure'/><category term='git'/><category term='python'/><category term='tips'/><category term='git-tips'/><category term='sandbar'/><category term='closure'/><category term='forms'/><category term='windows'/><category term='macro'/><category term='testing'/><category term='capistrano'/><category term='rant'/><category term='hardware'/><title type='text'>Mu</title><subtitle type='html'>Zen and the art of...</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>21</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-925647185806251853</id><published>2010-12-12T20:54:00.000-05:00</published><updated>2010-12-12T20:54:46.872-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='forms'/><category scheme='http://www.blogger.com/atom/ns#' term='web-development'/><category scheme='http://www.blogger.com/atom/ns#' term='sandbar'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><title type='text'>Improved Sandbar Forms</title><content type='html'>&lt;p&gt;There has been a flurry of improvements made to the Clojure web stackduring the past year. Compojure has matured, there's some non-trivialcode available (for example, see Brian Carper's&lt;a href="https://github.com/briancarper/cow-blog/"&gt;cow-blog&lt;/a&gt;) and the new&lt;a href="https://github.com/brentonashworth/sandbar"&gt;Sandbar&lt;/a&gt; library whichbrings a higher-level of abstraction on top of Compojure and Ring. Fornow, it provides a stateful session mechanism, authorization +authentication and a clever way of defining forms layout, processing andvalidation. Also there's much more to come, you can look at the detailsin the following&lt;a href="http://groups.google.com/group/sandbar-library/msg/02fa9fdc3fb8231e"&gt;roadmap&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;For today, I'll discuss the recent changes to the &lt;tt&gt;forms&lt;/tt&gt;namespace. There has been a lot of work done on that part during thepast week, but the changes to the API are minimal. I'll go over eachoptions of the defform macro, but first lets talk about the codebehind. As Stuart Halloway stated in his&lt;a href="http://pragprog.com/titles/shcloj/programming-clojure"&gt;book&lt;/a&gt;, thefirst rule of the Macro Club is: "Don't Write Macros." So the mostsignificant alteration is the rewrite of the defform macro. Previously,it was a big piece of code (134 lines) generating a bunch of definitionsof all sorts. It was quite hard to modify and had the usual constraintsof using macros that way. It now has been replaced by a much more simplemacro that call the new make-form function.&lt;/p&gt;&lt;p&gt;Lets see a sample form written for the current (0.3) version of Sandbar.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;forms/defform&lt;/span&gt; &lt;span class="function-name"&gt;group-form&lt;/span&gt; &lt;span class="string"&gt;"/group/edit"&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:fields&lt;/span&gt; [(forms/textfield &lt;span class="builtin"&gt;:name&lt;/span&gt;)&lt;br /&gt;           (forms/&lt;span class="type"&gt;select&lt;/span&gt; &lt;span class="builtin"&gt;:region&lt;/span&gt;&lt;br /&gt;                         (db/all-region)&lt;br /&gt;                         {&lt;span class="builtin"&gt;:id&lt;/span&gt; &lt;span class="builtin"&gt;:name&lt;/span&gt; &lt;span class="builtin"&gt;:prompt&lt;/span&gt; {&lt;span class="string"&gt;""&lt;/span&gt; &lt;span class="string"&gt;"Select a Region"&lt;/span&gt;}})&lt;br /&gt;           (forms/textarea &lt;span class="builtin"&gt;:description&lt;/span&gt;)]&lt;br /&gt;  &lt;span class="builtin"&gt;:load&lt;/span&gt; #(db/fetch-group %)&lt;br /&gt;  &lt;span class="builtin"&gt;:on-cancel&lt;/span&gt; &lt;span class="string"&gt;"/groups"&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:on-success&lt;/span&gt; #(&lt;span class="keyword"&gt;do&lt;/span&gt; (db/store-group %)&lt;br /&gt;                   (session/flash-put! &lt;span class="builtin"&gt;:user-message&lt;/span&gt;&lt;br /&gt;                                       &lt;span class="string"&gt;"Group has been saved."&lt;/span&gt;)&lt;br /&gt;                   &lt;span class="string"&gt;"/groups"&lt;/span&gt;)&lt;br /&gt;  &lt;span class="builtin"&gt;:properties&lt;/span&gt; {&lt;span class="builtin"&gt;:name&lt;/span&gt; &lt;span class="string"&gt;"Group's name:"&lt;/span&gt;&lt;br /&gt;               &lt;span class="builtin"&gt;:description&lt;/span&gt; &lt;span class="string"&gt;"Description:"&lt;/span&gt;})&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defroutes&lt;/span&gt; &lt;span class="function-name"&gt;group-form-routes&lt;/span&gt;&lt;br /&gt;  (group-form (&lt;span class="builtin"&gt;fn&lt;/span&gt; [request form] (views/layout form))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It was nice but had a severe limitation: you were forced to use a fixedset of routes. When creating a record, "/new" was appended to the givenURI else the "id" key was used. Also, the &lt;tt&gt;fields&lt;/tt&gt; option tended tobecome cluttered with the data source bindings. These are the two mainpoint I'll address here.&lt;/p&gt;&lt;p&gt;Firstly, lets rewrite the &lt;tt&gt;fields&lt;/tt&gt; option, it's quite similar intaking a vector of field descriptions. The difference is in the fielddescription functions that are taking the name of the field (as akeyword) followed by a list of optional key/value pairs.&lt;/p&gt;&lt;pre class="code"&gt;  &lt;span class="builtin"&gt;:fields&lt;/span&gt; [(forms/textfield &lt;span class="builtin"&gt;:name&lt;/span&gt;&lt;br /&gt;                            &lt;span class="builtin"&gt;:label&lt;/span&gt; &lt;span class="string"&gt;"Group's name:"&lt;/span&gt;)&lt;br /&gt;           (forms/&lt;span class="type"&gt;select&lt;/span&gt; &lt;span class="builtin"&gt;:region&lt;/span&gt;&lt;br /&gt;                         &lt;span class="builtin"&gt;:prompt&lt;/span&gt; {&lt;span class="string"&gt;""&lt;/span&gt; &lt;span class="string"&gt;"Select a Region"&lt;/span&gt;})&lt;br /&gt;           (forms/textarea  &lt;span class="builtin"&gt;:description&lt;/span&gt;&lt;br /&gt;                            &lt;span class="builtin"&gt;:label&lt;/span&gt; &lt;span class="string"&gt;"Description:"&lt;/span&gt;)]&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Each field functions can take a &lt;tt&gt;label&lt;/tt&gt; option, then most have anoptional boolean &lt;tt&gt;required&lt;/tt&gt; option which auto-generate thecorresponding validator for that field and finally there's the&lt;tt&gt;prompt&lt;/tt&gt; option for the select field. Any other options will beadded to the field's HTML attribute.&lt;/p&gt;&lt;p&gt;Secondly, there are the new options for managing the form action andmethod attributes. Each are prefixed by &lt;tt&gt;create&lt;/tt&gt; or &lt;tt&gt;update&lt;/tt&gt;followed by &lt;tt&gt;-action&lt;/tt&gt; or &lt;tt&gt;-method&lt;/tt&gt; whether it's for an action ormethod. They can take parametrized routes which will get theirparameters replaced by the matching route parameters from the incomingrequest.&lt;/p&gt;&lt;pre class="code"&gt;  &lt;span class="builtin"&gt;:create-action&lt;/span&gt; &lt;span class="string"&gt;"/groups"&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:update-action&lt;/span&gt; &lt;span class="string"&gt;"/groups/:id"&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:update-method&lt;/span&gt; &lt;span class="builtin"&gt;:put&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Thirdly, here's the new &lt;tt&gt;bindings&lt;/tt&gt; option which take a map of fieldnames followed by their respective binding information in a map.&lt;/p&gt;&lt;pre class="code"&gt;  &lt;span class="builtin"&gt;:bindings&lt;/span&gt; {&lt;span class="builtin"&gt;:region&lt;/span&gt; {&lt;span class="builtin"&gt;:value&lt;/span&gt; &lt;span class="builtin"&gt;:id&lt;/span&gt;&lt;br /&gt;                      &lt;span class="builtin"&gt;:visible&lt;/span&gt; &lt;span class="builtin"&gt;:name&lt;/span&gt;&lt;br /&gt;                      &lt;span class="builtin"&gt;:source&lt;/span&gt; (&lt;span class="builtin"&gt;constantly&lt;/span&gt; (db/all-regions))&lt;br /&gt;                      &lt;span class="builtin"&gt;:data&lt;/span&gt; &lt;span class="builtin"&gt;:id&lt;/span&gt;}}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The &lt;tt&gt;source&lt;/tt&gt; option needs a function that fetch the relevant data,the &lt;tt&gt;visible&lt;/tt&gt; option determines what field to show on the page andthe &lt;tt&gt;value&lt;/tt&gt; and &lt;tt&gt;data&lt;/tt&gt; options represent the actual value to usein the source data map and the form data map respectively.&lt;/p&gt;&lt;p&gt;Finally here's the whole code for this example using the new formsfeatures using RESTful routes.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;forms/defform&lt;/span&gt; &lt;span class="function-name"&gt;group-form&lt;/span&gt;&lt;br /&gt;  &lt;span class="doc"&gt;"Form handler for Group entity."&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:fields&lt;/span&gt; [(forms/textfield &lt;span class="builtin"&gt;:name&lt;/span&gt;&lt;br /&gt;                            &lt;span class="builtin"&gt;:label&lt;/span&gt; &lt;span class="string"&gt;"Group's name:"&lt;/span&gt;)&lt;br /&gt;           (forms/&lt;span class="type"&gt;select&lt;/span&gt; &lt;span class="builtin"&gt;:region&lt;/span&gt;&lt;br /&gt;                         &lt;span class="builtin"&gt;:prompt&lt;/span&gt; {&lt;span class="string"&gt;""&lt;/span&gt; &lt;span class="string"&gt;"Select a Region"&lt;/span&gt;})&lt;br /&gt;           (forms/textarea  &lt;span class="builtin"&gt;:description&lt;/span&gt;&lt;br /&gt;                            &lt;span class="builtin"&gt;:label&lt;/span&gt; &lt;span class="string"&gt;"Description:"&lt;/span&gt;)]&lt;br /&gt;  &lt;span class="builtin"&gt;:load&lt;/span&gt; #(db/fetch-group %)&lt;br /&gt;  &lt;span class="builtin"&gt;:on-cancel&lt;/span&gt; &lt;span class="string"&gt;"/groups"&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:on-success&lt;/span&gt; #(&lt;span class="keyword"&gt;do&lt;/span&gt; (db/store-group %)&lt;br /&gt;                   (session/flash-put! &lt;span class="builtin"&gt;:user-message&lt;/span&gt;&lt;br /&gt;                                       &lt;span class="string"&gt;"Group has been saved."&lt;/span&gt;)&lt;br /&gt;                   &lt;span class="string"&gt;"/groups"&lt;/span&gt;)&lt;br /&gt;  &lt;span class="builtin"&gt;:create-action&lt;/span&gt; &lt;span class="string"&gt;"/groups"&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:update-action&lt;/span&gt; &lt;span class="string"&gt;"/groups/:id"&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:update-method&lt;/span&gt; &lt;span class="builtin"&gt;:put&lt;/span&gt;&lt;br /&gt;  &lt;span class="builtin"&gt;:bindings&lt;/span&gt; {&lt;span class="builtin"&gt;:region&lt;/span&gt; {&lt;span class="builtin"&gt;:value&lt;/span&gt; &lt;span class="builtin"&gt;:id&lt;/span&gt;&lt;br /&gt;                      &lt;span class="builtin"&gt;:visible&lt;/span&gt; &lt;span class="builtin"&gt;:name&lt;/span&gt;&lt;br /&gt;                      &lt;span class="builtin"&gt;:source&lt;/span&gt; (&lt;span class="builtin"&gt;constantly&lt;/span&gt; (db/all-regions))&lt;br /&gt;                      &lt;span class="builtin"&gt;:data&lt;/span&gt; &lt;span class="builtin"&gt;:id&lt;/span&gt;}})&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defroutes&lt;/span&gt; &lt;span class="function-name"&gt;group-form-routes&lt;/span&gt;&lt;br /&gt;  (GET  &lt;span class="string"&gt;"/groups/new"&lt;/span&gt;      request (group-form request))&lt;br /&gt;  (POST &lt;span class="string"&gt;"/groups"&lt;/span&gt;          request (group-form request))&lt;br /&gt;  (GET  &lt;span class="string"&gt;"/groups/:id/edit"&lt;/span&gt; request (group-form request))&lt;br /&gt;  (PUT  &lt;span class="string"&gt;"/groups/:id"&lt;/span&gt;      request (group-form request)))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Furthermore, other modifications include that most &lt;tt&gt;defform&lt;/tt&gt; optionscan take a function of the request instead of just a value and thatredirection URIs in the &lt;tt&gt;on-success&lt;/tt&gt; and &lt;tt&gt;on-cancel&lt;/tt&gt; options canbe parametrized.&lt;/p&gt;&lt;p&gt;The forms namespace is still a work in progress and is still beingimproved. Particularly there is talk concerning the way forms getrendered, which currently lack flexibility as pointed out by David Nolenin this&lt;a href="http://groups.google.com/group/sandbar-library/browse_thread/thread/59864b9358915798"&gt;thread&lt;/a&gt;.Nothing has been done yet in this regard, so it's the right time foranyone interested to chime in this discussion.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-925647185806251853?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/925647185806251853/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/12/improved-sandbar-forms.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/925647185806251853'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/925647185806251853'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/12/improved-sandbar-forms.html' title='Improved Sandbar Forms'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-548953255842484695</id><published>2010-07-23T18:00:00.002-04:00</published><updated>2010-12-12T14:48:05.879-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web-development'/><category scheme='http://www.blogger.com/atom/ns#' term='emacs'/><category scheme='http://www.blogger.com/atom/ns#' term='ruby-on-rails'/><category scheme='http://www.blogger.com/atom/ns#' term='capistrano'/><title type='text'>Getting Back on Rails</title><content type='html'>&lt;p&gt;I've been pretty busy in the past months and didn't felt inspired enoughto write. Today is time to get back to writing in a human languageinstead of a computer one.&lt;/p&gt;&lt;h4&gt;Coming Back Home&lt;/h4&gt;&lt;p&gt;I've been involved in web development for more than ten years, but for along time I could only be labeled a designer. A little more than fouryears ago I began developing web sites using that awesome littleframework named &lt;em&gt;Ruby on Rails&lt;/em&gt;. After toying with it for less than ayear, I gave it up to go on the dangerous path of trying to &lt;strong&gt;really&lt;/strong&gt;understand what was going on behind all that magic. After learning a lotwhile dabbling into some lesser known frameworks like&lt;a href="http://pylonshq.com/"&gt;Pylons&lt;/a&gt;, &lt;a href="http://www.helma.org/"&gt;Helma&lt;/a&gt; andthen &lt;a href="http://weavejester.github.com/compojure/"&gt;Compojure&lt;/a&gt;, I now gotthe chance to code in a more professional setting using Ruby on Rails.&lt;/p&gt;&lt;h4&gt;Good Old Rails Has Changed&lt;/h4&gt;&lt;p&gt;For the better! Since four years ago, Rails has visibly matured and itis now used in a lot of &lt;a href="http://twitter.com"&gt;very&lt;/a&gt;&lt;a href="http://github.com"&gt;successful&lt;/a&gt; &lt;a href="http://www.shopify.com/"&gt;web sites&lt;/a&gt;.All this popularity pushed the framework to evolve and it has becomemuch more capable with the help of an army of plugins. Some simplefeatures were kicked out of the main codebase to be replaced byplugins. For example, the built-in pagination was removed from Rails 2.0and turned into a plugin called &lt;em&gt;classic_pagination&lt;/em&gt;. It has fallenout of favor for another plugin,&lt;a href="http://github.com/mislav/will_paginate/"&gt;will_paginate&lt;/a&gt;, which isreally much better.&lt;/p&gt;&lt;p&gt;Back then there was a generator called &lt;em&gt;login&lt;/em&gt; to build basicauthentication and user management features into your application. Thisgenerator has been deprecated and now there's&lt;a href="http://www.themomorohoax.com/2009/02/21/rails-2-3-authentication-comparison"&gt;multiple plugins&lt;/a&gt;available. I've only tried&lt;a href="http://github.com/binarylogic/authlogic"&gt;Authlogic&lt;/a&gt; by now and I'mpleased by its simplicity and customizability so far.&lt;/p&gt;&lt;p&gt;And there's a lot more! More conventions, more syntactic sugar, moreready to use plugins. One notable change is that Rails is now RESTful,meaning that it uses the HTTP protocol intelligently for routingrequests. For example, if a DELETE request with only the name of thecontroller is sent, by convention, Rails will call the destroyaction. One last thing to note is that ERb template files have given upthe &lt;tt&gt;.rhtml&lt;/tt&gt; file extension for a more versatile &lt;tt&gt;.erb&lt;/tt&gt; appendedafter the output file extension.&lt;/p&gt;&lt;h4&gt;Development Environment&lt;/h4&gt;&lt;p&gt;Well, I'm a staunch Emacs user, so I ended up using it for developingRails applications. Nonetheless, I was curious about the degree ofsupport offered by some IDEs, especially from the latest release ofNetBeans which some people say is getting pretty good. I've tried it fora couple of hours to find out that it was really not the kind ofenvironment I'd like to work in.&lt;/p&gt;&lt;p&gt;First, the wizard wasn't able to create a database in Derby because ittried using the inexistent root user. Then it was the included GlassFishserver that didn't want to use another port than 8080, completelyignoring the provided configuration file (which is WEBrick specific itappears). Those are all minor glitch, but it continued like that for anhour or two and soon I felt the need for this trusty Emacs &lt;em&gt;texteditor&lt;/em&gt;. The most glaring bug that really pushed me off NetBeans wasthe configuration screen for syntax highlighting, it was at some pointusing 90% of both cores on my machine! No wonder everybody saysMicrosoft have the lead in the IDE world, even I must say that it's adecent development environment after manually setting Emacs likeshortcuts (the included ones just don't cut it).&lt;/p&gt;&lt;h5&gt;Emacs Configuration&lt;/h5&gt;&lt;p&gt;There's two mode for Rails on Emacs, namely emacs-rails and&lt;a href="http://rinari.rubyforge.org/"&gt;Rinari&lt;/a&gt;. The first one is older and isquite outdated. It has been replaced by a new mode named&lt;a href="http://github.com/dima-exe/emacs-rails-reloaded/"&gt;emacs-rails-reloaded&lt;/a&gt;by the same author. It provides navigation between the multitude offiles in a Rails project, make it easy to call rake or generators and alot more. Rinari is a lightweight alternative that I haven't tried yetbut it looks quite capable too.&lt;/p&gt;&lt;p&gt;For editing the ERb templates, you can use the&lt;a href="http://ourcomments.org/Emacs/nXhtml/doc/nxhtml.html"&gt;nXhtml&lt;/a&gt; mode,which is already included in the Windows port of Emacs. I had somecompatibility issues between that version andemacs-rails-reloaded and had to install the&lt;a href="http://ourcomments.org/cgi-bin/emacsw32-dl-latest.pl"&gt;latest version&lt;/a&gt;.&lt;/p&gt;&lt;h4&gt;Deployment&lt;/h4&gt;&lt;p&gt;I had the opportunity of deploying an application and find out thatthere were great improvements made on this side.&lt;/p&gt;&lt;p&gt;First and foremost, is that incredible tool called&lt;a href="http://www.capify.org/index.php/Capistrano"&gt;Capistrano&lt;/a&gt;. It makedeploying Rails application in a consistent and reversible manner abreeze. With very little configuration, it will copy your files in adate/time tagged directory, help you easily start/stop your applicationserver(s) and get back online previously deployed versions easily. It'seven more than just a tool for RoR projects, you can understand it asbeing a sort of Makefile for remotely controlling multiple servers.&lt;/p&gt;&lt;p&gt;Finally, back in the days, most hosting provider offered only fastCGI ormod_rails on top of Apache for running your applications. Today there'smore options, but I won't go through all of them as I've still got muchto learn about them. For now I've learned that&lt;a href="http://www.modrails.com/"&gt;Passenger&lt;/a&gt; (previously mod_rails ormod_rack) is the recommended way of deploying a Rails app, it can useApache or Nginx as its web server.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-548953255842484695?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/548953255842484695/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/07/getting-back-on-rails.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/548953255842484695'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/548953255842484695'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/07/getting-back-on-rails.html' title='Getting Back on Rails'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-827219862759525457</id><published>2010-04-18T12:41:00.000-04:00</published><updated>2010-04-18T12:41:21.525-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='cli'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><category scheme='http://www.blogger.com/atom/ns#' term='cygwin'/><title type='text'>Cygwin Tip #1</title><content type='html'>&lt;h4&gt;Calling Java Under Cygwin&lt;/h4&gt;&lt;p&gt;Cygwin is a fantastic piece of software for those who, like me, can'tlive without a Linux-like command line and environment. But for anyperson who have tried to blend two completely different cultures, weknow it's not always easy to make them play well together. In this postI'll explain how to make Java's Windows version feel more like a Cygwinapplication.&lt;/p&gt;&lt;p&gt;The main problem comes from the way a file path differs between Windowsand the POSIX standard. The most obvious difference is the pathseparator issue, in Windows world it's &lt;tt&gt;\&lt;/tt&gt; while for the rest of theplanet it's &lt;tt&gt;/&lt;/tt&gt;. Another one is the way the root path(s) are beingrepresented, in Windows each drive/partition have it's own letters withits own root path. Cygwin being POSIX-based has a single root path(&lt;tt&gt;/&lt;/tt&gt;) and maps the Windows letters in a directory called&lt;tt&gt;/cygdrive&lt;/tt&gt;. Those problems are easily circumvented using a the&lt;tt&gt;cygpath&lt;/tt&gt; command:&lt;/p&gt;&lt;pre class="code"&gt;$ cygpath -aw '/cygdrive/c/Program Files/Java/jdk1.6.0_18/bin/java'&lt;br /&gt;C:\Program Files\Java\jdk1.6.0_18\bin\java&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;That's Not Enough&lt;/h4&gt;&lt;p&gt;It wouldn't be hard to call this command on the classpath argument whencalling java from cygwin, but there's another hindrance. The classpathusually contains more than one path, these paths being separated by acolon (:) in POSIX systems or by a semicolon (;) under Windows. It wouldbe also quite tiresome edit java calls manually so I came up with alittle Ruby script that can do the job for us.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt;!/bin/ruby&lt;br /&gt;&lt;/span&gt;&lt;span class="comment-delimiter"&gt;# &lt;/span&gt;&lt;span class="comment"&gt;Slightly obfuscated cygwin + windows java wrapper, automate cygpath&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;cpi = &lt;span class="type"&gt;ARGV&lt;/span&gt;.index(&lt;span class="string"&gt;"-cp"&lt;/span&gt;) + 1&lt;br /&gt;cp = &lt;span class="type"&gt;ARGV&lt;/span&gt;[cpi] &lt;span class="keyword"&gt;if&lt;/span&gt; cpi&lt;br /&gt;&lt;br /&gt;&lt;span class="type"&gt;XBCP&lt;/span&gt; = &lt;span class="string"&gt;"-Xbootclasspath/a:"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;xbcpi = &lt;span class="type"&gt;ARGV&lt;/span&gt;.index{|i|i=~&lt;span class="string"&gt;/^&lt;/span&gt;&lt;span class="variable-name"&gt;#{XBCP}&lt;/span&gt;&lt;span class="string"&gt;.*/&lt;/span&gt;}&lt;br /&gt;xbcp = &lt;span class="type"&gt;ARGV&lt;/span&gt;[xbcpi] &lt;span class="keyword"&gt;if&lt;/span&gt; xbcpi&lt;br /&gt;&lt;br /&gt;&lt;span class="keyword"&gt;if&lt;/span&gt; cp &lt;span class="keyword"&gt;or&lt;/span&gt; xbcpi&lt;br /&gt;  &lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;convert_paths&lt;/span&gt;(paths)&lt;br /&gt;    paths = paths.gsub(&lt;span class="string"&gt;':'&lt;/span&gt;, &lt;span class="string"&gt;';'&lt;/span&gt;).split(&lt;span class="string"&gt;';'&lt;/span&gt;)&lt;br /&gt;    paths.map{|p|&lt;span class="string"&gt;`cygpath -aw &lt;/span&gt;&lt;span class="variable-name"&gt;#{p}&lt;/span&gt;&lt;span class="string"&gt;`&lt;/span&gt;.strip}.join &lt;span class="string"&gt;';'&lt;/span&gt;&lt;br /&gt;  &lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;  &lt;span class="type"&gt;ARGV&lt;/span&gt;[cpi] = convert_paths(cp) &lt;span class="keyword"&gt;if&lt;/span&gt; cp&lt;br /&gt;  &lt;span class="type"&gt;ARGV&lt;/span&gt;[xbcpi] = &lt;span class="type"&gt;XBCP&lt;/span&gt; + convert_paths(xbcp.sub(&lt;span class="type"&gt;XBCP&lt;/span&gt;, &lt;span class="string"&gt;''&lt;/span&gt;)) &lt;span class="keyword"&gt;if&lt;/span&gt; xbcp&lt;br /&gt;&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;java = &lt;span class="string"&gt;'/cygdrive/c/Program Files/Java/jdk1.6.0_18/bin/java'&lt;/span&gt;&lt;br /&gt;cmd = [java].concat &lt;span class="type"&gt;ARGV&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;e&lt;/span&gt;(s); &lt;span class="string"&gt;"\"&lt;/span&gt;&lt;span class="variable-name"&gt;#{s.strip.gsub('"','\"')}&lt;/span&gt;&lt;span class="string"&gt;\""&lt;/span&gt;; end&lt;br /&gt;&lt;br /&gt;exec(cmd.map{|a|e a}.join(&lt;span class="string"&gt;' '&lt;/span&gt;))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This is more of a hack than a solid solution, but it's enough for mycurrent usage and hopefully yours. Now the Windows &lt;tt&gt;java&lt;/tt&gt; commandcan take POSIX paths without complaining under Windows, isn't thatmarvellous!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-827219862759525457?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/827219862759525457/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/04/cygwin-tip-1.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/827219862759525457'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/827219862759525457'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/04/cygwin-tip-1.html' title='Cygwin Tip #1'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-1294022577587488224</id><published>2010-04-15T12:46:00.000-04:00</published><updated>2010-04-15T12:46:30.797-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ai'/><category scheme='http://www.blogger.com/atom/ns#' term='semantic web'/><category scheme='http://www.blogger.com/atom/ns#' term='philosophy'/><category scheme='http://www.blogger.com/atom/ns#' term='vulgarization'/><title type='text'>Gödel, Escher, Bach and Smartphones</title><content type='html'>&lt;p&gt;After many many years, I'm still (slowly) reading&lt;a href="http://en.wikipedia.org/wiki/Gödel,_Escher,_Bach"&gt;GEB&lt;/a&gt;. That's because I'm wasting tomuch time reading the (whole) Internet to bother with printed books!Yet, I like to finish what I start (eventually) and yesterday I found areally interesting quote in that book. Mind you, it has been writtenthirty one years ago, at a time when personal computers were a noveltythat only a few lucky ones could afford. Most computer usage(&lt;a href="http://en.wikipedia.org/wiki/Punched_card"&gt;Punched Cards&lt;/a&gt;!) was being done inUniversities laboratories and these machines weren't fast enough to domost of what we take for granted today.&lt;/p&gt;&lt;h4&gt;Dogs, Garbage and Light Bulbs...&lt;/h4&gt;&lt;p&gt;The world have changed a lot since then and computers are incrediblymore useful now a day's. While discussing the Church-Turing thesis inchapter seventeen, Mr. Hofstadter goes on the representation ofknowledge about the real world and say:&lt;/p&gt;&lt;blockquote&gt;Because of the complexity of the world , it is hard to imagine a little pocket calculator that can answer questions put to it when you press a few buttons bearing labels such as "dog", "garbage", "light bulb", and so forth. In fact, so far it has proven to be extremely complicated to have a full-size high-speed computer answer questions about what appear to be rather simple subdomains of the real world.&lt;/blockquote&gt;&lt;p&gt;Hard to imagine back then, but today it's quite commonplace to seepeople asking such questions to their smartphones. Which bring me to thepoint of this post, is the Web can be considered a form of artificialintelligence? I'd say no, it's just a hack, yet a pretty useful one. Theraw data that can be found here isn't that different than theinformation that could be gathered in any other format. It is the searchcapabilities offered by various engines that truly makes the greatnessof the World Wide Web. Still, these facilities aren't quite smartenough, we still have to choose carefully the wording of our queries andclick around.&lt;/p&gt;&lt;h4&gt;The Semantic Web&lt;/h4&gt;&lt;p&gt;This is the next step, and a big one also. A lot of have been written onthe subject, a lot of code too and thus far that concept hasn't yielded(easily) usable tools. I think that most problems concerning semanticalrepresentation of data are intrinsic to the nature of information. AsOleg once said: "Without a reader, a book has no meaning." It's allabout asking the right question. And here is the missing link, doesnatural languages are good enough? Where's the real burden located, informulating a query or in understanding it?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-1294022577587488224?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/1294022577587488224/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/04/godel-escher-bach-and-smartphones.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/1294022577587488224'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/1294022577587488224'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/04/godel-escher-bach-and-smartphones.html' title='Gödel, Escher, Bach and Smartphones'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-411323033382356452</id><published>2010-02-22T17:02:00.000-05:00</published><updated>2010-02-22T17:02:56.439-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='clojureql'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><title type='text'>Redesigning ClojureQL's Frontend</title><content type='html'>&lt;p&gt;I've not updated this blog in a while as I've been pretty busy for thepast few weeks. I've got many projects on the table and one of them isstarting to get really interesting. I'm obviously talking about thesubject of this post,&lt;a href="http://www.gitorious.org/clojureql/"&gt;ClojureQL&lt;/a&gt;. I've mainly beenworking on the backend since the end of last year. In the meantime,Meikel has started a&lt;a href="http://clojureql.lighthouseapp.com/projects/34981/tickets/33"&gt;rework&lt;/a&gt;of the frontend to provide a cleaner API, free from the magicalartifacts introduced by using macros like in the current (0.9.7)version. This rework have been triggered by a&lt;a href="http://zef.me/2637/on-language-design-my-problem-with-clojureql"&gt;post&lt;/a&gt;carefully explaining the issue, courtesy of Zef.&lt;/p&gt;&lt;p&gt;I won't go further than a linkfest so here are the important links forthose interested in the future of ClojureQL:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://www.gitorious.org/clojureql/clojureql/commits/frontend-2.0"&gt;Gitorious Frontend Rework branch&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://www.gitorious.org/clojureql/pages/FrontendReworked"&gt;Frontend Rework Wiki Page&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://www.gitorious.org/clojureql/pages/DdlFrontendReworked"&gt;DDL Frontend Rework Wiki Page&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://www.gitorious.org/clojureql/pages/DataTypes"&gt;Data Types Wiki Page&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We're also ready to receive your suggestions on the brand new&lt;a href="http://groups.google.com/group/clojureql"&gt;clojureql group&lt;/a&gt;. Please,visit the Wiki pages and tell us about what you think.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-411323033382356452?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/411323033382356452/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/02/redesigning-clojureqls-frontend.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/411323033382356452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/411323033382356452'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/02/redesigning-clojureqls-frontend.html' title='Redesigning ClojureQL&apos;s Frontend'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-5778106154835906171</id><published>2010-02-07T16:47:00.001-05:00</published><updated>2010-02-10T15:57:11.962-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ai'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><category scheme='http://www.blogger.com/atom/ns#' term='google-ai-challenge'/><title type='text'>Google AI Challenge in Clojure</title><content type='html'>&lt;p&gt;These days, the web have been noisy about the brand new Google sponsored&lt;a href="http://csclub.uwaterloo.ca/contest/"&gt;AI Challenge&lt;/a&gt; organized by theUniversity of Waterloo Computer Science Club. I find that it's a greatinitiative and is a good occasion for &lt;strong&gt;amateur&lt;/strong&gt; AI researchers like meto do something else than web development or database relatedcoding. Being fond of Clojure, I've taken on the task of making astarter package for this language and making it available on a GitHub&lt;a href="http://github.com/budu/google-ai-challenge-tron-bot"&gt;repository&lt;/a&gt;. It'snot an official package yet, but it shouldn't be a problem as theorganizers seems really open.&lt;/p&gt;&lt;p&gt;The code has been translated from the Java starter package. I've triedto keep the map code close to the original, so it may not be the mostidiomatic Clojure code. There's four global refs containing the state ofthe game: &lt;tt&gt;width&lt;/tt&gt;, &lt;tt&gt;height&lt;/tt&gt;, &lt;tt&gt;walls&lt;/tt&gt; and &lt;tt&gt;players&lt;/tt&gt;. Thetwo first are simple integers, &lt;tt&gt;walls&lt;/tt&gt; is an hash map keyed withpoints (which are vectors) and the last one a vector of points. There'ssome convenient functions to access that data, &lt;tt&gt;wall?&lt;/tt&gt; to tell if awall is found at the given coordinates and two others to get eachplayers position, &lt;tt&gt;you&lt;/tt&gt; and &lt;tt&gt;them&lt;/tt&gt;. To move your bike you mustcall &lt;tt&gt;make-move&lt;/tt&gt; with one of &lt;tt&gt;:north&lt;/tt&gt;, &lt;tt&gt;:east&lt;/tt&gt;, &lt;tt&gt;:south&lt;/tt&gt;or &lt;tt&gt;:west&lt;/tt&gt;. The map code end up being nearly half the size of theJava version.&lt;/p&gt;&lt;p&gt;With this code we can rewrite the sample random bot in Clojure, I'llspare you any more explanation and only show the code.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;load&lt;/span&gt; &lt;span class="string"&gt;"map"&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;valid-moves&lt;/span&gt; [x y]&lt;br /&gt;  [(&lt;span class="keyword"&gt;when-not&lt;/span&gt; (wall? x (- y 1)) &lt;span class="builtin"&gt;:north&lt;/span&gt;)&lt;br /&gt;   (&lt;span class="keyword"&gt;when-not&lt;/span&gt; (wall? (+ x 1) y) &lt;span class="builtin"&gt;:east&lt;/span&gt;)&lt;br /&gt;   (&lt;span class="keyword"&gt;when-not&lt;/span&gt; (wall? x (+ y 1)) &lt;span class="builtin"&gt;:south&lt;/span&gt;)&lt;br /&gt;   (&lt;span class="keyword"&gt;when-not&lt;/span&gt; (wall? (- x 1) y) &lt;span class="builtin"&gt;:west&lt;/span&gt;)])&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;compact&lt;/span&gt; [coll]&lt;br /&gt;  (&lt;span class="builtin"&gt;filter&lt;/span&gt; identity coll))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;choose-at-random&lt;/span&gt; [coll]&lt;br /&gt;  (&lt;span class="keyword"&gt;let&lt;/span&gt; [size (&lt;span class="builtin"&gt;count&lt;/span&gt; coll)]&lt;br /&gt;    (&lt;span class="keyword"&gt;when&lt;/span&gt; (&amp;lt; 0 size)&lt;br /&gt;      (nth coll (rand-int size)))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;next-move&lt;/span&gt; []&lt;br /&gt;  (choose-at-random&lt;br /&gt;   (compact&lt;br /&gt;    (&lt;span class="builtin"&gt;apply&lt;/span&gt; valid-moves (you)))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;game-loop&lt;/span&gt; []&lt;br /&gt;  (&lt;span class="keyword"&gt;loop&lt;/span&gt; []&lt;br /&gt;    (initialize)&lt;br /&gt;    (make-move (next-move))&lt;br /&gt;    (&lt;span class="keyword"&gt;recur&lt;/span&gt;)))&lt;br /&gt;&lt;br /&gt;(game-loop)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Hack and be mery!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I've updated the GitHub repository with some improvementsand instructions on how to compile your entry. While discussing with oneof the contest organizer over their forum, I realized that even thoughClojure is more than fast enough for this kind of task, the Clojure jarloading time take a good chunk of the&lt;a href="http://csclub.uwaterloo.ca/contest/forums/viewtopic.php?f=10&amp;amp;t=102&amp;amp;p=602#p602"&gt;first turn time&lt;/a&gt;,so be careful. By the way, it's now an &lt;a href="http://csclub.uwaterloo.ca/contest/starter_packages.php"&gt;official package&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-5778106154835906171?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/5778106154835906171/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/02/google-ai-challenge-in-clojure.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/5778106154835906171'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/5778106154835906171'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/02/google-ai-challenge-in-clojure.html' title='Google AI Challenge in Clojure'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-5019806008329244203</id><published>2010-01-31T22:30:00.000-05:00</published><updated>2010-01-31T22:30:53.448-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='closure'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='task-tutorial'/><title type='text'>Closure Library Tutorial: Tasks (part 2)</title><content type='html'>&lt;p&gt;It's been nearly two months since I've written about JavaScript andnow is the time to follow up on the&lt;a href="http://whollyweirdwyrd.blogspot.com/2009/12/closure-library-tutorial-tasks-part-1.html"&gt;previous part&lt;/a&gt;of this&lt;a href="http://whollyweirdwyrd.blogspot.com/search/label/task-tutorial"&gt;tutorial&lt;/a&gt;. Sincethen, there have been some changes to the Closure library, nothingmajor, mostly improvements to the documentation and bug fixes.&lt;/p&gt;&lt;h4&gt;Sliders&lt;/h4&gt;&lt;p&gt;Adding a Slider is unsurprisingly as easy to do as any othercomponents. Contrary to those we already used though, it doesn't comewith a nice interface with pre-made CSS and images. Closure let youdecide how your sliders will look like, which is comprehensible as thiscontrol is really versatile and it would be hard to come up with a goodgeneric design for it. A slider element is composed of two DIVs, onerepresenting the slider itself, the other being the thumb. Both have aclass name to let you control their appearance. For the slider elementthis name is composed of a prefix found in the Slider's&lt;tt&gt;CSS_CLASS_PREFIX&lt;/tt&gt; static property followed by either of"-horizontal" or "-vertical" depending on the orientation. The thumb hasonly a single class name determined by the &lt;tt&gt;THUMB_CSS_CLASS&lt;/tt&gt;property, note that as the thumb is contained in the slider element,it can be styled depending on orientation too.&lt;/p&gt;&lt;p&gt;There's some rules to obey while styling a slider:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; a slider cannot be inlined, but you can use inline-block;&lt;/li&gt;&lt;li&gt; a slider cannot be floated;&lt;/li&gt;&lt;li&gt; the thumb position style must be set to absolute or relative.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Note that it's preferable to make both elements hide their overflowingcontents to prevent scrolling bars from appearing.&lt;/p&gt;&lt;h4&gt;Using Sliders&lt;/h4&gt;&lt;p&gt;As sliders cannot be floated, we have a problem with the existingcode. If you remember the way tasks were constructed in the previoustutorial, the action buttons were added to the summary DIV directly withtheir float styles set to right. This isn't the correct manner of doingthings anyway, so we'll fix that first. We'll group the task controlsinto a single floating element.&lt;/p&gt;&lt;pre class="code"&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.summaryControlsDiv = goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, { &lt;span class="string"&gt;'class'&lt;/span&gt;: &lt;span class="string"&gt;'controls'&lt;/span&gt; });&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.summaryDiv = goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, { &lt;span class="string"&gt;'class'&lt;/span&gt;: &lt;span class="string"&gt;'summary'&lt;/span&gt; }, &lt;span class="constant"&gt;this&lt;/span&gt;.summary, &lt;span class="constant"&gt;this&lt;/span&gt;.summaryControlsDiv);&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This break some ugly part of our code, the &lt;tt&gt;makeActionButton&lt;/tt&gt;function is being passed the task DIV and renders the button beingcreated by selecting the appropriate child node. We'll just do a quickfix and correct this issue in the next section.&lt;/p&gt;&lt;pre class="code"&gt;    button.render(element.childNodes[0].childNodes[1]);&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Another more subtle change we need to make is in &lt;tt&gt;makeButtons&lt;/tt&gt;, wemust change the order in which the action buttons are created.  We mustalso change some styles and add an entry for the new CSS class.&lt;/p&gt;&lt;pre class="code"&gt;      .task .summary .controls { float: right; }&lt;br /&gt;      .task .description { background: #&lt;span class="flyspell-incorrect"&gt;eee&lt;/span&gt;; border-top: solid 1&lt;span class="flyspell-duplicate"&gt;px&lt;/span&gt; #333; }&lt;br /&gt;      &lt;br /&gt;      .&lt;span class="flyspell-incorrect"&gt;taskButton&lt;/span&gt; { margin-top: -6&lt;span class="flyspell-duplicate"&gt;px&lt;/span&gt;; margin-left: 7&lt;span class="flyspell-duplicate"&gt;px&lt;/span&gt;; }&lt;br /&gt;      .&lt;span class="flyspell-incorrect"&gt;editorButton&lt;/span&gt; { margin: 0.2&lt;span class="flyspell-duplicate"&gt;em&lt;/span&gt; 0.3&lt;span class="flyspell-duplicate"&gt;em&lt;/span&gt; 0.5&lt;span class="flyspell-duplicate"&gt;em&lt;/span&gt;; }&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The following code create a slider, we'll stop event propagation thesame way it's done for tasks' action buttons.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.makeSlider = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;task&lt;/span&gt;, &lt;span class="variable-name"&gt;element&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (task.parent.id == &lt;span class="string"&gt;'taskList'&lt;/span&gt;) {&lt;br /&gt;        &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;slider&lt;/span&gt; = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;goog.ui.Slider&lt;/span&gt;;&lt;br /&gt;        slider.createDom();&lt;br /&gt;        slider.render(element.childNodes[0].childNodes[1]);&lt;br /&gt;&lt;br /&gt;        slider.addEventListener(&lt;br /&gt;            goog.ui.Component.EventType.CHANGE,&lt;br /&gt;            &lt;span class="keyword"&gt;function&lt;/span&gt;() { &lt;span class="comment-delimiter"&gt;/* &lt;/span&gt;&lt;span class="comment"&gt;TODO: something... */&lt;/span&gt; });&lt;br /&gt;&lt;br /&gt;        goog.events.listen(&lt;br /&gt;            slider.getContentElement(),&lt;br /&gt;            goog.events.EventType.CLICK,&lt;br /&gt;            &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;e&lt;/span&gt;) { e.stopPropagation(); });&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We need to call this function from &lt;tt&gt;makeDom&lt;/tt&gt; just before creatingthe task buttons. To make the page actually show the sliders, we'll addthe styles below.&lt;/p&gt;&lt;pre class="code"&gt;      .&lt;span class="flyspell-duplicate"&gt;goog&lt;/span&gt;-slider-horizontal, .goog-slider-thumb {&lt;br /&gt;        overflow: hidden;&lt;br /&gt;        height: 20&lt;span class="flyspell-duplicate"&gt;px&lt;/span&gt;;&lt;br /&gt;      }&lt;br /&gt;      &lt;br /&gt;      .goog-slider-horizontal {&lt;br /&gt;        background-color: #&lt;span class="flyspell-incorrect"&gt;ccc&lt;/span&gt;;&lt;br /&gt;        display: inline-block;&lt;br /&gt;        position: relative;&lt;br /&gt;        width: 200&lt;span class="flyspell-duplicate"&gt;px&lt;/span&gt;;&lt;br /&gt;      }&lt;br /&gt; &lt;br /&gt;      .&lt;span class="flyspell-duplicate"&gt;goog&lt;/span&gt;-slider-thumb {&lt;br /&gt;        background-color: #777;&lt;br /&gt;        position: absolute;&lt;br /&gt;        width: 20&lt;span class="flyspell-duplicate"&gt;px&lt;/span&gt;;&lt;br /&gt;      }&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Cleaning Up&lt;/h4&gt;&lt;p&gt;Earlier, we talked about how bad it was for &lt;tt&gt;makeActionButton&lt;/tt&gt; tohave to know where to render the button. We'll take the time to cleanthat up. If we look back at the code, one thing is obvious, we pass lotsof arguments around. That make the code overly functional and this isn'ta good approach here. Another thing is that we carefully initializedprototype members for every elements contained in a task and we end upnot using them.&lt;/p&gt;&lt;p&gt;For this section I'll skip the code, so use your imagination. Firstthing to do is to remove the element argument from functions havingit. For the controls, we'll use the object's properties,&lt;tt&gt;summaryControlsDiv&lt;/tt&gt; for action buttons and sliders, and&lt;tt&gt;editorContainer&lt;/tt&gt; for editor buttons. Finally, we need to replacethe &lt;tt&gt;taskDiv&lt;/tt&gt; local variable in &lt;tt&gt;makeDom&lt;/tt&gt; by a property thatwe'll name &lt;tt&gt;element&lt;/tt&gt;. We can't use it directly in our callbackfunction as it is a closure and &lt;tt&gt;this&lt;/tt&gt; doesn't point to the taskobject. We can work around that simply by using a temporary variable,we'll rewrite that code later anyway.&lt;/p&gt;&lt;h4&gt;Making the Sliders Do Something&lt;/h4&gt;&lt;p&gt;To put the sliders to good use, we'll make them control the priority ofa task. We'll first make the tasks display their priorities, to be able totest things manually.&lt;/p&gt;&lt;pre class="code"&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.priorityDiv =  goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, { &lt;span class="string"&gt;'class'&lt;/span&gt;: &lt;span class="string"&gt;'priority'&lt;/span&gt; }, &lt;span class="constant"&gt;this&lt;/span&gt;.priority.toString());&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.summaryDiv = goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, { &lt;span class="string"&gt;'class'&lt;/span&gt;: &lt;span class="string"&gt;'summary'&lt;/span&gt; },&lt;br /&gt;                                         &lt;span class="constant"&gt;this&lt;/span&gt;.summary,&lt;br /&gt;                                         &lt;span class="constant"&gt;this&lt;/span&gt;.summaryControlsDiv,&lt;br /&gt;                                         &lt;span class="constant"&gt;this&lt;/span&gt;.priorityDiv);&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We'll also have to add styling for the new priority DIV, it must floatto the right and have some padding on the right side. Now, lets add twothings to the &lt;tt&gt;makeSlider&lt;/tt&gt; function. Firstly, setting the slidervalue to the priority of the task currently being created. Secondly,replacing our TODO comment by changing the priority property as well asthe content of the priority DIV.&lt;/p&gt;&lt;pre class="code"&gt;        slider.setValue(&lt;span class="constant"&gt;this&lt;/span&gt;.priority);&lt;br /&gt;&lt;br /&gt;        &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;task&lt;/span&gt; = &lt;span class="constant"&gt;this&lt;/span&gt;;&lt;br /&gt;        slider.addEventListener(&lt;br /&gt;            goog.ui.Component.EventType.CHANGE,&lt;br /&gt;            &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;                task.priority = slider.getValue();&lt;br /&gt;                task.priorityDiv.innerHTML = task.priority;&lt;br /&gt;            });&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;There's a small bug with this code, can you find it?&lt;/p&gt;&lt;p&gt;If you delete or mark a task as done, then undo that action, you'll seethat the resurrected tasks' slider thumb is set at its lowest value. Ifyou insert an alert to display the sliders' value when the task isrecreated, you'll see that it's value is correct. Also the slider behave&lt;strike&gt;as if the thumb was at the correct position&lt;/strike&gt;&lt;a href="http://code.google.com/p/closure-library/issues/detail?id=111"&gt;strangely&lt;/a&gt;,it can even go into infinite loop. This all points out toward the factthat when we recreate our tasks, they're being added to an invisibleelement. We'll reorganize our code in the next section to circumventthis issue.&lt;/p&gt;&lt;h4&gt;Sorting Tasks&lt;/h4&gt;&lt;p&gt;The sliders are now working (with some issues) that's good but itdoesn't add much to our little application feature-wise. The obvious useof tasks priority would be to sort them. We'll need to do some majormodification to our code to do this though. Currently the tasks addthemselves to their containers and appear in whatever order they'rebeing appended. We'll need to create a new object to represent tasklists. I won't be showing all the changes made, just the most importantones, you can always refer to the&lt;a href="http://dl.dropbox.com/u/2682770/mu/tutorials/tasks/part_2/tasks.js"&gt;final code&lt;/a&gt;if you need more details. First, we'll need to provide a new class namefor our &lt;tt&gt;TaskList&lt;/tt&gt; prototype.&lt;/p&gt;&lt;pre class="code"&gt;goog.provide(&lt;span class="string"&gt;'mu.tutorial.tasks.TaskList'&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.TaskList = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;name&lt;/span&gt;, &lt;span class="variable-name"&gt;data&lt;/span&gt;, &lt;span class="variable-name"&gt;container&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.name = name;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.container = container;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.tasks = [];&lt;br /&gt;&lt;br /&gt;    &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;i&lt;/span&gt; = 0; i &amp;lt; data.length; i++) {&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.tasks.push(&lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;mu.tutorial.tasks.Task&lt;/span&gt;(data[i], &lt;span class="constant"&gt;this&lt;/span&gt;));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.sort()&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.render();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It's followed by its constructor which creates, sorts and renders thegiven tasks, this greatly simplify the &lt;tt&gt;makeTasks&lt;/tt&gt; function. Withthis modification, we introduced some unwanted duplication ofinformation, our tasks objects already have a reference to theircontainers. We'll just change that reference for another pointed at thelist it's associated to. We'll also change the way we're using the&lt;tt&gt;taskLists&lt;/tt&gt; namespace variable, it will now contains &lt;tt&gt;TaskList&lt;/tt&gt;objects. Now lets see how the &lt;tt&gt;render&lt;/tt&gt; function works.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.TaskList.&lt;span class="constant"&gt;prototype&lt;/span&gt;.noTasks = &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.container.appendChild(&lt;br /&gt;        goog.dom.createDom(&lt;span class="string"&gt;'h2'&lt;/span&gt;, { &lt;span class="string"&gt;'class'&lt;/span&gt;: &lt;span class="string"&gt;'empty'&lt;/span&gt; }, &lt;span class="string"&gt;'This list is empty!'&lt;/span&gt;));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.TaskList.&lt;span class="constant"&gt;prototype&lt;/span&gt;.render = &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;    goog.dom.removeChildren(&lt;span class="constant"&gt;this&lt;/span&gt;.container);&lt;br /&gt;&lt;br /&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="constant"&gt;this&lt;/span&gt;.tasks.length == 0)&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.noTasks();&lt;br /&gt;    &lt;span class="keyword"&gt;else&lt;/span&gt;&lt;br /&gt;        &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;i&lt;/span&gt; = 0; i &amp;lt; &lt;span class="constant"&gt;this&lt;/span&gt;.tasks.length; i++) {&lt;br /&gt;            &lt;span class="constant"&gt;this&lt;/span&gt;.tasks[i].makeDom();&lt;br /&gt;        }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It's basically the same code that was previously in &lt;tt&gt;makeTasks&lt;/tt&gt; withthe only difference that it clean up its container before adding newelements. We also moved &lt;tt&gt;noTasks&lt;/tt&gt; into the &lt;tt&gt;TaskList&lt;/tt&gt; object forconvenience.&lt;/p&gt;&lt;p&gt;Next, some new code. We've got an array of tasks and we'll sort it usingthe default JavaScript &lt;tt&gt;sort&lt;/tt&gt; function. Google has thought aboutadding helper functions for arrays including one for sorting, itsadvantage over the default JavaScript one is that it sorts numberscorrectly. Here this sort function wouldn't be helping us as we'llwrite our own compare function.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.defaultCompare = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;t1&lt;/span&gt;, &lt;span class="variable-name"&gt;t2&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; goog.array.defaultCompare(&lt;br /&gt;        t2.priority,&lt;br /&gt;        t1.priority);&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.TaskList.&lt;span class="constant"&gt;prototype&lt;/span&gt;.sort = &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.tasks.sort(mu.tutorial.tasks.defaultCompare);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We'll need to insert and remove tasks from our task lists. As they'realways sorted, the Closure Library has two functions to help us:&lt;tt&gt;binaryInsert&lt;/tt&gt; and &lt;tt&gt;binaryRemove&lt;/tt&gt;. These allow for fast manipulation of sorted arrays by using a &lt;a href="http://en.wikipedia.org/wiki/Binary_search_algorithm"&gt;binary search algorithm&lt;/a&gt;.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.TaskList.&lt;span class="constant"&gt;prototype&lt;/span&gt;.add = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;task&lt;/span&gt;) {&lt;br /&gt;    task.list = &lt;span class="constant"&gt;this&lt;/span&gt;;&lt;br /&gt;    &lt;br /&gt;    goog.array.binaryInsert(&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.tasks,&lt;br /&gt;        task,&lt;br /&gt;        mu.tutorial.tasks.defaultCompare);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.TaskList.&lt;span class="constant"&gt;prototype&lt;/span&gt;.remove = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;task&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.container.removeChild(task.element);&lt;br /&gt;    &lt;br /&gt;    goog.array.binaryRemove(&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.tasks,&lt;br /&gt;        task,&lt;br /&gt;        mu.tutorial.tasks.defaultCompare);&lt;br /&gt;&lt;br /&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="constant"&gt;this&lt;/span&gt;.tasks.length == 0)&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.render();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The &lt;tt&gt;add&lt;/tt&gt; method is very simple, it just set the current task listas the given task parent and insert it into the array. For the&lt;tt&gt;remove&lt;/tt&gt; method though, it's actually in charge of cleaning up theelement of the task being removed. It also ensures to call &lt;tt&gt;noTask&lt;/tt&gt;if there's no task left in that task list.&lt;/p&gt;&lt;p&gt;Next, we'll replace the &lt;tt&gt;clickActionButton&lt;/tt&gt; listener code by a callto a new &lt;tt&gt;Task&lt;/tt&gt; method called &lt;tt&gt;moveTo&lt;/tt&gt;. This method will be incharge of removing the task from its parent list and inserting it intothe specified one using the above methods.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.moveTo = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;target&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.list.remove(&lt;span class="constant"&gt;this&lt;/span&gt;);&lt;br /&gt;    mu.tutorial.tasks.taskLists[target].add(&lt;span class="constant"&gt;this&lt;/span&gt;);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Yet another change that merit a mention is the &lt;tt&gt;switchPanel&lt;/tt&gt;function which takes a new argument to know what &lt;tt&gt;TaskList&lt;/tt&gt; torender. This could be made cleaner, but we'll keep it that way for thispart of the tutorial.&lt;/p&gt;&lt;p&gt;When a user change the priority of a task, we better have themsorted. We'll create a new &lt;tt&gt;Task&lt;/tt&gt; method that will be called whensomeone stop using the slider. It make use of a new property of the&lt;tt&gt;Task&lt;/tt&gt; object to remember the previous value so as not to reload atask list if the priority didn't changed.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.reload = &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="constant"&gt;this&lt;/span&gt;.priority != &lt;span class="constant"&gt;this&lt;/span&gt;.previous_priority) {&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.list.sort();&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.list.render();&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.previous_priority = &lt;span class="constant"&gt;this&lt;/span&gt;.priority;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;But now we have a problem. When shall we call this function? I'vethought about it for some time and came up with a&lt;a href="http://groups.google.ca/group/closure-library-discuss/browse_thread/thread/dbbfb687ceb9099?hl=en"&gt;complex and convoluted solution&lt;/a&gt;.The correct solution, that is to use a timer and accumulate events,being too much bothersome for the scope of this tutorial, I've decidedto settle on a small hack I've found that should do the job.&lt;/p&gt;&lt;p&gt;By the principles of &lt;a href="http://en.wikipedia.org/wiki/You_ain't_gonna_need_it"&gt;YAGNI&lt;/a&gt;, Ifigured out that the sliders are to heavy to handle. Why would we needfour different ways of modifying a task priority? So, from now on, we'llonly listen for &lt;tt&gt;MOUSEOUT&lt;/tt&gt; and &lt;tt&gt;KEYUP&lt;/tt&gt; events.&lt;/p&gt;&lt;pre class="code"&gt;        goog.events.listen(&lt;br /&gt;            slider.getContentElement(),&lt;br /&gt;            [goog.events.EventType.MOUSEOUT,&lt;br /&gt;             goog.events.EventType.KEYUP],&lt;br /&gt;            &lt;span class="keyword"&gt;function&lt;/span&gt;() { task.reload(); });&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;There's still a problem with dragging the sliders, if only we could makethat issue disappear.&lt;/p&gt;&lt;h4&gt;Invisible Sliders&lt;/h4&gt;&lt;p&gt;Sometimes the best style is no styles at all. We must face it, thesliders are ugly. Making them look awesome can take some time for anon-designer like me. While thinking about this problem, I've came upwith a solution that would also simplify sliders event handling code. Wecould make the sliders invisible and put them on top of the prioritynumbers shown. That doesn't actually fix our issue, we only preventusers from thinking about dragging the slider thumb. This is just a hackand should only be done if you're really short on time. Talking of time,this part is much more longer than I expected. So I'll let youlook at&lt;a href="http://dl.dropbox.com/u/2682770/mu/tutorials/tasks/part_2/tasks.html"&gt;the result&lt;/a&gt;rather than the code as it's a very simple modification, mostly new DOMelements and CSS.&lt;/p&gt;&lt;h4&gt;Less Rendering and More Stability&lt;/h4&gt;&lt;p&gt;One last thing, our code is working like a charm and is more than fastenough for the small data set we're testing it with. But in real worldsituations, there's some bottlenecks that could potentially slow ourapplication down. I'll let the testing for another part, but we'llremove one of those bottleneck.&lt;/p&gt;&lt;p&gt;The most processor hungry function in our code is certainly the&lt;tt&gt;render&lt;/tt&gt; one. Presently, we're calling it every time the priority ofa task change. But if the order of tasks don't change, we don't need torender them again. Moreover, the JavaScript &lt;tt&gt;sort&lt;/tt&gt; function isn'tstable, two tasks having the same priority could be reordered. So, tokill two birds with one stone a second time, we'll make our own stablesort function. Well, in fact, we'll just take the code from&lt;tt&gt;goog.array.stableSort&lt;/tt&gt; and alter it to return true if the arraychanged.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.TaskList.prototype.sort = function() {&lt;br /&gt;    var arr = this.tasks&lt;br /&gt;    for (var i = 0; i &amp;lt; arr.length; i++) {&lt;br /&gt;        arr[i] = {index: i, value: arr[i]};&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    function stableCompareFn(obj1, obj2) {&lt;br /&gt;        return mu.tutorial.tasks.defaultCompare(obj1.value, obj2.value) ||&lt;br /&gt;            obj1.index - obj2.index;&lt;br /&gt;    };&lt;br /&gt;    &lt;br /&gt;    goog.array.sort(arr, stableCompareFn);&lt;br /&gt;&lt;br /&gt;    var changed = false;&lt;br /&gt;    for (var i = 0; i &amp;lt; arr.length; i++) {&lt;br /&gt;        if (i != arr[i].index)&lt;br /&gt;            changed = true;&lt;br /&gt;        arr[i] = arr[i].value;&lt;br /&gt;    }&lt;br /&gt;    return changed;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Then, it's only a matter of calling &lt;tt&gt;render&lt;/tt&gt; when the &lt;tt&gt;sort&lt;/tt&gt;call returns true in the &lt;tt&gt;reload&lt;/tt&gt; method.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.reload = &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="constant"&gt;this&lt;/span&gt;.priority != &lt;span class="constant"&gt;this&lt;/span&gt;.previous_priority) {&lt;br /&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="constant"&gt;this&lt;/span&gt;.list.sort())&lt;br /&gt;            &lt;span class="constant"&gt;this&lt;/span&gt;.list.render();&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.previous_priority = &lt;span class="constant"&gt;this&lt;/span&gt;.priority;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Phew, that was a big post, I hope you enjoyed it! I'm still not quitesure about what will be the subject of the next part, if you have anysuggestion, drop by a comment.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-5019806008329244203?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/5019806008329244203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/closure-library-tutorial-tasks-part-2.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/5019806008329244203'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/5019806008329244203'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/closure-library-tutorial-tasks-part-2.html' title='Closure Library Tutorial: Tasks (part 2)'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-2173990654121781842</id><published>2010-01-24T17:51:00.001-05:00</published><updated>2010-01-25T21:24:47.051-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tips'/><category scheme='http://www.blogger.com/atom/ns#' term='emacs'/><category scheme='http://www.blogger.com/atom/ns#' term='emacs-tips'/><title type='text'>Emacs Tip #1</title><content type='html'>&lt;p&gt;Emacs is a great editor, I won't elaborate on this as there's countlessblog posts already celebrating its countless features. After some yearsof usage, anyone using Emacs end up with a bloated &lt;tt&gt;.emacs&lt;/tt&gt; filethat may contains some good tips. I'll be explaining some of the tricksI'm using in a series of posts of which this one is the first.&lt;/p&gt;&lt;h3&gt;Web Search Shortcuts&lt;/h3&gt;&lt;p&gt;It's a known &lt;em&gt;fact&lt;/em&gt; (in the sense that it's a common&lt;a href="http://en.wikipedia.org/wiki/Series_of_tubes"&gt;Intertubes&lt;/a&gt; meme) that some people usetheir computer exclusively through Emacs. I'm not that fond of thiseditor and prefer to do things like navigating the web with&lt;a href="http://www.mozilla.com/en-US/firefox"&gt;a&lt;/a&gt;&lt;a href="http://www.google.com/chrome"&gt;real&lt;/a&gt;&lt;a href="http://www.opera.com/"&gt;browser&lt;/a&gt;. Emacs as a way to send URLs to one,using the &lt;a href="http://www.emacswiki.org/emacs/BrowseUrl"&gt;&lt;tt&gt;browse-url&lt;/tt&gt;&lt;/a&gt;function. Feeding an unencoded URL to it could yield a bad requestthough. To fix that, we can use &lt;tt&gt;url-hexify-string&lt;/tt&gt; from the urllibrary. Here's a simple example of putting these functions to good use.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;require&lt;/span&gt; '&lt;span class="constant"&gt;url&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defun&lt;/span&gt; &lt;span class="function-name"&gt;google&lt;/span&gt; ()&lt;br /&gt;  (interactive)&lt;br /&gt;  (&lt;span class="keyword"&gt;let&lt;/span&gt; ((url (&lt;span class="keyword"&gt;if&lt;/span&gt; (and transient-mark-mode mark-active)&lt;br /&gt;               (buffer-substring-no-properties&lt;br /&gt;                 (region-beginning)&lt;br /&gt;                 (region-end))&lt;br /&gt;               (thing-at-point 'symbol))))&lt;br /&gt;    (browse-url&lt;br /&gt;      (concat &lt;span class="string"&gt;"&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;http&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;://www.google.com/search?"&lt;/span&gt;&lt;br /&gt;        (format &lt;span class="string"&gt;"q=%s"&lt;/span&gt; (url-hexify-string url))))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This interactive function let us do a simple search with the text underthe currently active region or with the symbol found under the cursor ifthere's no active region. This can be useful, you're no longer forced tocopy-paste text from Emacs to your browser.&lt;/p&gt;&lt;p&gt;Next, we could try to send more complex queries to our preferred searchengine, which in this case is Google. Standard library documentationsuch as Java's API specification generally have URLs that are easilyqueryable. With Google's &lt;tt&gt;allinurl&lt;/tt&gt; keyword, we can make a functionto search for a specific class symbol.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defun&lt;/span&gt; &lt;span class="function-name"&gt;search-all-in-url&lt;/span&gt; (query site inurl)&lt;br /&gt;  (concat &lt;span class="string"&gt;"&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;http&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;://&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;www&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;.&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;google&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;.&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;com&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;/"&lt;/span&gt;&lt;br /&gt;    (format &lt;span class="string"&gt;"search?q=&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-incorrect"&gt;allinurl&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;:%s+site:%s+&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-incorrect"&gt;inurl&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;:%s&amp;amp;&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-incorrect"&gt;btnI&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;"&lt;/span&gt;&lt;br /&gt;      (url-hexify-string query)&lt;br /&gt;      (url-hexify-string site)&lt;br /&gt;      (url-hexify-string inurl))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defun&lt;/span&gt; &lt;span class="function-name"&gt;web-help&lt;/span&gt; (site inurl)&lt;br /&gt;  (browse-url (search-all-in-url (thing-at-point 'symbol) site inurl)))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;With &lt;tt&gt;web-help&lt;/tt&gt;, we can now define custom interactivefunctions. Currently in my &lt;tt&gt;.emacs&lt;/tt&gt; file, I've got two suchfunctions, one for Java and the other for Ruby.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defun&lt;/span&gt; &lt;span class="function-name"&gt;java-help&lt;/span&gt; ()&lt;br /&gt;  (interactive)&lt;br /&gt;  (web-help &lt;span class="string"&gt;"&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-incorrect"&gt;java&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;.sun.&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;com&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;"&lt;/span&gt; &lt;span class="string"&gt;"/&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-incorrect"&gt;javase&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;/6/&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-incorrect"&gt;docs&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;/&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-incorrect"&gt;api&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;/"&lt;/span&gt;))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defun&lt;/span&gt; &lt;span class="function-name"&gt;ruby-help&lt;/span&gt; ()&lt;br /&gt;  (interactive)&lt;br /&gt;  (web-help &lt;span class="string"&gt;"&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;www&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;.ruby-&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;doc&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;.&lt;/span&gt;&lt;span class="string"&gt;&lt;span class="flyspell-duplicate"&gt;org&lt;/span&gt;&lt;/span&gt;&lt;span class="string"&gt;"&lt;/span&gt; &lt;span class="string"&gt;"/core/"&lt;/span&gt;))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Then, it's a simple matter of adding key bindings for our newinteractive functions. Personally, I like to have them bound to thelower &lt;tt&gt;F*&lt;/tt&gt; keys.&lt;/p&gt;&lt;pre class="code"&gt;(global-set-key [(f1)] 'google)&lt;br /&gt;(global-set-key [(f2)] 'java-help)&lt;br /&gt;(global-set-key [(f3)] 'ruby-help)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;You might already use these shortcuts for other things (I think that F3serves to start recording macros) but you certainly still have someunbound key combinations!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I've corrected the second code example that I messed up  while writing this post.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-2173990654121781842?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/2173990654121781842/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/emacs-tip-1.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/2173990654121781842'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/2173990654121781842'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/emacs-tip-1.html' title='Emacs Tip #1'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-515260280418145544</id><published>2010-01-19T18:30:00.001-05:00</published><updated>2010-01-19T18:30:36.845-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='compojure'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><category scheme='http://www.blogger.com/atom/ns#' term='debugging'/><title type='text'>A Recursive Walk on a String</title><content type='html'>&lt;p&gt;Sometimes you don't pay attention to some details and this lead youright into the wrong direction. That's exactly what happened to me theother day. I had a specific problem I intended to solve for a while,nothing particularly difficult, but an interesting one. After hackingaround for an hour, the situation was getting weird and I decided to askfor help on the&lt;a href="http://groups.google.com/group/clojure/browse_thread/thread/9dcd2733cec0ee57"&gt;Clojure Google Group&lt;/a&gt;.This post is an account of how that situation arose and what have beendone to solve it.&lt;/p&gt;&lt;h4&gt;Walking Recursively&lt;/h4&gt;&lt;p&gt;The task is nearly as simple as explaining it. I needed a way to applya function to each strings contained in a tree of nested data structuresin Clojure. Not that easy to explain as you see, well you didn't reallysaw me rewrite the previous sentence a thousand times but you canprobably picture it. My first attempt is really naive and leverages theclojure.walk library written by Stuart Sierra. It's a small library thatprovides functions to perform non-recursive walking of generic Clojuredata structures. Lets first have a look at the walk functiondocumentation.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(doc clojure.walk/walk)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;-------------------------&lt;br /&gt;clojure.walk/walk&lt;br /&gt;([inner outer form])&lt;br /&gt;  Traverses form, an arbitrary data structure.  inner and outer are&lt;br /&gt;  functions.  Applies inner to each element of form, building up a&lt;br /&gt;  data structure of the same type, then applies outer to the result.&lt;br /&gt;  Recognizes all Clojure data structures except sorted-map-by.&lt;br /&gt;  Consumes seqs as with doall. &lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It isn't hard to take this function and use it in a recursive one to dothe job. Our function to transform strings could go in the innerargument, it's also where we'll add the recursive call. We don't needthe outer one, so we will just use the identity function.&lt;/p&gt;&lt;pre class="code"&gt;(use 'clojure.walk)&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;recursive-string-walk&lt;/span&gt; [f form]&lt;br /&gt;  (walk #(&lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="builtin"&gt;string?&lt;/span&gt; %) (f %) (recursive-string-walk f %))&lt;br /&gt;    identity form))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It works well but there's an issue with this way of doing things,recursion will end up filling up the stack which will ultimatelyoverflow. For the use I intend to make of this function, it isn't reallya big problem, but I always like to take these kind of challenges.&lt;/p&gt;&lt;h4&gt;A Buggy Test&lt;/h4&gt;&lt;p&gt;The first thing that came to my mind to test the above issue was tocreate a nested list of arbitrary depth. We can take the &lt;tt&gt;iterate&lt;/tt&gt;function to create an infinite sequence of nested function calls to&lt;tt&gt;list&lt;/tt&gt;, then &lt;tt&gt;take&lt;/tt&gt; and &lt;tt&gt;last&lt;/tt&gt; to fetch the &lt;em&gt;pit&lt;/em&gt; we wantto test with.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(def pit (iterate list "bottom!"))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;#'user/pit&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(take 6 pit)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;("bottom!" ("bottom!") (("bottom!")) ((("bottom!"))) (((("bottom!")))) ((((("bottom!"))))))&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(last (take 6 pit))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;((((("bottom!")))))&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(recursive-string-walk identity (last (take 6 pit)))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;((((("bottom!")))))&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(recursive-string-walk #(str "reached " %) (last (take 6 pit)))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;((((("reached bottom!")))))&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(recursive-string-walk #(str "reached " %) (last (take 1000 pit)))&lt;/span&gt;&lt;br /&gt;; Evaluation aborted.&lt;br /&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(recursive-string-walk #(str "reached " %) (last (take 100 pit)))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((("reached bottom!")))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Work great! Now lets create an informal test function. This functionshould take a range and a recursive walk function then will test itusing pits starting with the shallowest. This way we can find exactly atwhich point the overflow happen.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;test-walk&lt;/span&gt; [walker shallowest deepest]&lt;br /&gt;  (&lt;span class="keyword"&gt;doseq&lt;/span&gt; [depth (&lt;span class="builtin"&gt;range&lt;/span&gt; shallowest deepest)]&lt;br /&gt;    (println (walker #(&lt;span class="builtin"&gt;str&lt;/span&gt; depth &lt;span class="string"&gt;" reached "&lt;/span&gt; %)&lt;br /&gt;               (last (&lt;span class="builtin"&gt;take&lt;/span&gt; depth pit))))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We'll use it to make sure it works and see which depth our&lt;tt&gt;recursive-string-walk&lt;/tt&gt; can reach.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;(test-walk recursive-string-walk 400 500)&lt;br /&gt;...&lt;br /&gt;&lt;span class="slime-repl-output"&gt;((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((("486 reached bottom!")))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))&lt;br /&gt;(...(&lt;/span&gt;; Evaluation aborted.&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;On my computer, this function cannot go deeper than 486 (I wonder how itwould fare on a 486?) which isn't that much. It shouldn't be all thatdifficult to make a lazy version of that function. It boils down to lookfor sequences and wrap their recursive calls with &lt;tt&gt;lazy-seq&lt;/tt&gt;.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;lazy-recursive-string-walk&lt;/span&gt; [f form]&lt;br /&gt;  (walk #(&lt;span class="keyword"&gt;cond&lt;/span&gt;&lt;br /&gt;           (&lt;span class="builtin"&gt;string?&lt;/span&gt; %) (f %)&lt;br /&gt;           (&lt;span class="builtin"&gt;seq?&lt;/span&gt; %)    (lazy-seq (lazy-recursive-string-walk f %))&lt;br /&gt;           &lt;span class="builtin"&gt;:default&lt;/span&gt;    (lazy-recursive-string-walk f %))&lt;br /&gt;    identity form))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;From my understanding of lazy data structures and the walk library, thisversion should be able to handle data structures of infinite depth giveninfinite resources. However the test function didn't seems to be inagreement with me as it was crashing at 828. That's about the time Iasked for help and stopped thinking about this problem for a while.&lt;/p&gt;&lt;h4&gt;Lazy Zippers&lt;/h4&gt;&lt;p&gt;A week-end passed by and in the meantime, some people from the grouptried to help me, yet nobody found what was the real problem. That wouldhave been miraculous given the actual source of the bug. Furthermore, Iwas now more convinced than before than there was no bug in the lazyversion. Tom Hicks provided a lazy version of &lt;tt&gt;walk&lt;/tt&gt;, which wasbehaving in the same way. Laurent Petit also suggested that I try towrite a version using zippers.&lt;/p&gt;&lt;p&gt;It was an intriguing suggestion, I never used zippers but heard oftenabout them. Additionally, that day, MarkCC from &lt;em&gt;Good Math, Bad Math&lt;/em&gt;just posted&lt;a href="http://scienceblogs.com/goodmath/2010/01/zippers_making_functional_upda.php?"&gt;an article about them&lt;/a&gt;.So I learned what they are and how to use them. I must say this is avery neat functional programming technique, really worth the time tolearn.  It's not that hard, Mark has done a great job explainingit. I won't go into the details, but this version is quite easy tounderstand when you know that the &lt;tt&gt;next&lt;/tt&gt; function do a depth-firsttraversal automatically.&lt;/p&gt;&lt;pre class="code"&gt;(require '[clojure.zip &lt;span class="builtin"&gt;:as&lt;/span&gt; z])&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;lazy-recursive-string-walk-with-zipper&lt;/span&gt; [f form]&lt;br /&gt;  (&lt;span class="keyword"&gt;loop&lt;/span&gt; [loc (z/seq-zip form)]&lt;br /&gt;    (&lt;span class="keyword"&gt;if&lt;/span&gt; (z/end? loc)&lt;br /&gt;      (z/root loc)&lt;br /&gt;      (&lt;span class="keyword"&gt;recur&lt;/span&gt; (z/next&lt;br /&gt;               (&lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="builtin"&gt;string?&lt;/span&gt; (z/node loc))&lt;br /&gt;                 (z/replace loc (f (z/node loc)))&lt;br /&gt;                 loc))))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;To my surprise, the test function still overflowed the stack using thisversion. And it could simply not throw the StackOverflowError as itexecutes in a loop. Maybe the error wasn't coming from the lazy walkfunction after all.&lt;/p&gt;&lt;h4&gt;So Where's The Bug?&lt;/h4&gt;&lt;p&gt;I've been negligent about one thing, I didn't look closely enough at thestack traces. The one thrown by &lt;tt&gt;recursive-string-walk&lt;/tt&gt; happen to becompletely different than the one thrown by&lt;tt&gt;lazy-recursive-string-walk&lt;/tt&gt;. Here's both of them one after theother.&lt;/p&gt;&lt;pre class="code"&gt;No message.&lt;br /&gt;  [Thrown class java.lang.StackOverflowError]&lt;br /&gt;&lt;br /&gt;Restarts:&lt;br /&gt; 0: [&lt;span class="function-name"&gt;ABORT&lt;/span&gt;] Return to SLIME's top level.&lt;br /&gt;&lt;br /&gt;Backtrace:&lt;br /&gt;  0: &lt;span class="swank-clojure-dim-trace"&gt;clojure.lang.RT.boundedLength(RT.java:1128)&lt;/span&gt;&lt;br /&gt;  1: &lt;span class="swank-clojure-dim-trace"&gt;clojure.lang.RestFn.applyTo(RestFn.java:135)&lt;/span&gt;&lt;br /&gt;  2: &lt;span class="swank-clojure-dim-trace"&gt;clojure.core$apply__4370.invoke(core.clj:436)&lt;/span&gt;&lt;br /&gt;  3: clojure.walk$walk__7942.invoke(walk.clj:43)&lt;br /&gt;  4: recursive_string_walk$recursive_string_walk__4137.invoke(NO_SOURCE_FILE:1)&lt;br /&gt;  5: recursive_string_walk$recursive_string_walk__4137$fn__4139.invoke(NO_SOURCE_FILE:1)&lt;br /&gt;---&lt;br /&gt;No message.&lt;br /&gt;  [Thrown class java.lang.StackOverflowError]&lt;br /&gt;&lt;br /&gt;Restarts:&lt;br /&gt; 0: [ABORT] Return to SLIME's top level.&lt;br /&gt;&lt;br /&gt;Backtrace:&lt;br /&gt;  0: clojure.lang.RT.assoc(RT.java:666)&lt;br /&gt;  1: clojure.core$assoc__4259.invoke(core.clj:146)&lt;br /&gt;  2: clojure.lang.AFn.applyToHelper(AFn.java:179)&lt;br /&gt;  3: clojure.lang.RestFn.applyTo(RestFn.java:137)&lt;br /&gt;  4: clojure.lang.Ref.alter(Ref.java:174)&lt;br /&gt;  5: clojure.core$alter__4907.doInvoke(core.clj:1542)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The actual source of the exception is the print function that isrecursive on nested sequences. This make sense, why would someone wantto print such monsters. Because of that inattention I lost timesearching for an imaginary bug. Only a small modification is needed tomake our test function working.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;get-bottom&lt;/span&gt; [p]&lt;br /&gt;  (&lt;span class="keyword"&gt;loop&lt;/span&gt; [p (&lt;span class="builtin"&gt;first&lt;/span&gt; p)]&lt;br /&gt;    (&lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="builtin"&gt;seq?&lt;/span&gt; p) (&lt;span class="keyword"&gt;recur&lt;/span&gt; (&lt;span class="builtin"&gt;first&lt;/span&gt; p)) p)))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;test-walk&lt;/span&gt; [walker shallowest deepest]&lt;br /&gt;  (&lt;span class="keyword"&gt;doseq&lt;/span&gt; [depth (&lt;span class="builtin"&gt;range&lt;/span&gt; shallowest deepest)]&lt;br /&gt;    (println (get-bottom&lt;br /&gt;               (walker #(&lt;span class="builtin"&gt;str&lt;/span&gt; depth &lt;span class="string"&gt;" reached "&lt;/span&gt; %)&lt;br /&gt;                 (lazy-seq (last (&lt;span class="builtin"&gt;take&lt;/span&gt; depth pit))))))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We simply search for the bottom item and print it, this will also makethe test output clearer. We can now see what our original walk functioncan handle.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;(test-walk lazy-recursive-string-walk 100000 100001)&lt;br /&gt;...&lt;br /&gt;; Evaluation aborted.&lt;br /&gt;---&lt;br /&gt;Java heap space&lt;br /&gt;  [Thrown class java.lang.OutOfMemoryError]&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Well, the heap too has its limit! We would need to augment the JVM heapmemory size to be able to run the previous example. Lets reduce ourambitions and do some benchmarking instead.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(time (test-walk lazy-recursive-string-walk 10000 10001))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;10000 reached bottom!&lt;br /&gt;"Elapsed time: 25.138697 msecs"&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(time (test-walk lazy-recursive-string-walk-with-zipper 10000 10001))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;10000 reached bottom!&lt;br /&gt;"Elapsed time: 122.691972 msecs"&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;As you see, the version using clojure.walk is much faster. That'sperfectly understandable considering the more complex concept of zipperswhich have multiple uses aside walking trees. As both versions are fullylazy, the first one is the clear winner here.&lt;/p&gt;&lt;h4&gt;What Can We Use This For&lt;/h4&gt;&lt;p&gt;After all these trials and tribulations, you may wonder what use I mayhave for such a function. It's to help create functions or macros thatperform string replacement on all strings in a Clojure form. This can beuseful with Compojure while deploying your application to a differentroot path than "/" for example. The idea come from my experience withASP.NET in which URLs can contain a character (&lt;tt&gt;~&lt;/tt&gt;) to denote theweb site root path.&lt;/p&gt;&lt;pre class="code"&gt;(use 'clojure.contrib.str-utils)&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;expand*&lt;/span&gt; [expansions string]&lt;br /&gt;  (&lt;span class="builtin"&gt;reduce&lt;/span&gt; (&lt;span class="keyword"&gt;fn&lt;/span&gt; [s [regex replacement]]&lt;br /&gt;            (re-gsub regex replacement s))&lt;br /&gt;    string (&lt;span class="builtin"&gt;partition&lt;/span&gt; 2 expansions)))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;expand-form&lt;/span&gt; [url form]&lt;br /&gt;  (recursive-string-walk (partial expand* [#&lt;span class="string"&gt;"^~"&lt;/span&gt; url]) form))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Having the same functionality in a macro would be nice too.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defmacro&lt;/span&gt; &lt;span class="function-name"&gt;with-expansions&lt;/span&gt; [expansions &amp;amp; body]&lt;br /&gt;  `(&lt;span class="keyword"&gt;do&lt;/span&gt; ~@(recursive-string-walk (partial expand* expansions) body)))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defmacro&lt;/span&gt; &lt;span class="function-name"&gt;expand&lt;/span&gt; [url &amp;amp; body]&lt;br /&gt;  `(with-expansions [#&lt;span class="string"&gt;"^~"&lt;/span&gt; ~(&lt;span class="builtin"&gt;str&lt;/span&gt; url)] ~@body))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;To put this to use I've taken a function written by James Reeves afew months ago during&lt;a href="http://groups.google.com/group/compojure/msg/1312791be1d6e998"&gt;a discussion&lt;/a&gt;on the Compojure group. A function with the same name(&lt;tt&gt;with-context&lt;/tt&gt;) already exists in Compojure though, so we'll renameit to &lt;tt&gt;with-root-path&lt;/tt&gt;. The way it work is by binding the givencontext to a var and then we can use a function called &lt;tt&gt;url&lt;/tt&gt; to addthe context so that internal links still work once the application isdeployed elsewhere. Using the &lt;tt&gt;expand-form&lt;/tt&gt; function on &lt;tt&gt;html&lt;/tt&gt;'sarguments can reduce repetition and make the code less cluttered ifthere's a lot of such URLs.&lt;/p&gt;&lt;pre class="code"&gt;(use 'compojure)&lt;br /&gt;&lt;br /&gt;(declare *context*)&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;with-root-path&lt;/span&gt;&lt;br /&gt;  [ctx &amp;amp; route-seq]&lt;br /&gt;  (&lt;span class="keyword"&gt;let&lt;/span&gt; [handler (&lt;span class="builtin"&gt;apply&lt;/span&gt; routes route-seq)&lt;br /&gt;         pattern (re-pattern (&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"^"&lt;/span&gt; ctx &lt;span class="string"&gt;"(/.*)?"&lt;/span&gt;))]&lt;br /&gt;    (&lt;span class="keyword"&gt;fn&lt;/span&gt; [request]&lt;br /&gt;      (&lt;span class="keyword"&gt;if-let&lt;/span&gt; [[_ uri] (re-matches pattern (&lt;span class="builtin"&gt;:uri&lt;/span&gt; request))]&lt;br /&gt;        (&lt;span class="keyword"&gt;binding&lt;/span&gt; [*context* ctx]&lt;br /&gt;          (handler (&lt;span class="builtin"&gt;assoc&lt;/span&gt; request &lt;span class="builtin"&gt;:uri&lt;/span&gt; uri)))))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;url&lt;/span&gt; [path]&lt;br /&gt;  (&lt;span class="builtin"&gt;str&lt;/span&gt; *context* path))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;html-expand&lt;/span&gt; [&amp;amp; trees]&lt;br /&gt;  (&lt;span class="builtin"&gt;apply&lt;/span&gt; html (expand-form *context* trees)))&lt;br /&gt;&lt;br /&gt;(defroutes test-routes&lt;br /&gt;  (with-root-path &lt;span class="string"&gt;"/foo"&lt;/span&gt;&lt;br /&gt;    (GET &lt;span class="string"&gt;"/"&lt;/span&gt;    (html-expand [&lt;span class="builtin"&gt;:h1&lt;/span&gt; &lt;span class="string"&gt;"test"&lt;/span&gt;] [&lt;span class="builtin"&gt;:p&lt;/span&gt; &lt;span class="string"&gt;"~/"&lt;/span&gt;]))&lt;br /&gt;    (GET &lt;span class="string"&gt;"/bar"&lt;/span&gt; (html [&lt;span class="builtin"&gt;:h1&lt;/span&gt; &lt;span class="string"&gt;"test"&lt;/span&gt;] [&lt;span class="builtin"&gt;:p&lt;/span&gt; (url &lt;span class="string"&gt;"/bar"&lt;/span&gt;)]))&lt;br /&gt;    (ANY &lt;span class="string"&gt;"*"&lt;/span&gt; (page-not-found))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;start-test-server&lt;/span&gt; []&lt;br /&gt;  (run-server {&lt;span class="builtin"&gt;:port&lt;/span&gt; 8000} &lt;span class="string"&gt;"/*"&lt;/span&gt; (servlet test-routes)))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The first route use a special version of the &lt;tt&gt;html&lt;/tt&gt; function, it'sonly a slight improvement but I really like that way of doingthings. There's plenty of other possible use cases though, but thatshould be enough for this post.&lt;/p&gt;&lt;p&gt;P.S.: I know the title isn't &lt;tt&gt;correct&lt;/tt&gt;, but it sounds way betterthan &lt;em&gt;A Recursive Walk on Strings Contained in Arbitrary Nested DataStructures&lt;/em&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-515260280418145544?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/515260280418145544/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/recursive-walk-on-string.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/515260280418145544'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/515260280418145544'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/recursive-walk-on-string.html' title='A Recursive Walk on a String'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-8870816084018610204</id><published>2010-01-12T21:04:00.001-05:00</published><updated>2010-01-12T21:05:00.588-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='clj-doc'/><category scheme='http://www.blogger.com/atom/ns#' term='wiki'/><category scheme='http://www.blogger.com/atom/ns#' term='documentation'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><title type='text'>Documenting Clojure Code</title><content type='html'>&lt;p&gt;Clojure is a truly amazing language and still a very young one. There isalready a substantial quantity of documentation available for Clojureand some of its libraries. Yet, when talking about documentation, someis not enough. Sure, there's the REPL with all its bells and whistles,but sometimes I prefer to navigate through a well formatted API documentusing a browser. There's already some solutions for this, one on which Iworked for the defunct Compojure website (which was based on similarcode for Clojure) and another more serious attempt in clojure-contrib,&lt;a href="http://richhickey.github.com/clojure-contrib/gen-html-docs-api.html"&gt;gen-html-docs&lt;/a&gt;by Craig Andera. This library only output HTML and there arise a small(but annoying) problem: not all projects have their documentationformatted in HTML. For example, the new Compojure website runs on&lt;a href="http://www.dokuwiki.org"&gt;DokuWiki&lt;/a&gt; which have its own syntax andGitorious integrated Wiki, used for ClojureQL, is using&lt;a href="http://daringfireball.net/projects/markdown/"&gt;Markdown&lt;/a&gt;. If only everyWiki engine would understand &lt;a href="http://www.wikicreole.org"&gt;Creole&lt;/a&gt;!&lt;/p&gt;&lt;h3&gt;clj-doc&lt;/h3&gt;&lt;p&gt;To be able to solve the above problem, I decided to start a new projectcalled clj-doc. For now it's pretty much just a weekend coding session,but I'll continue to work on it so that one day it will be the best toolavailable to generate documentation for Clojure code. The basic usage isdone through the &lt;tt&gt;gen-doc&lt;/tt&gt; macro.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(use 'clj-doc)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;nil&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(gen-doc clj-doc.utils)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;("&amp;lt;!DOCTYPE html ...Documentation for clj-doc.utils...")&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This gives us a sequence of strings being the rendered output of eachnamespaces given using the default markup language. Namespaces couldalso be grouped using nested sequences, for now clj-doc support only onelevel of nesting. Another notable feature is that namespace groups canbe specified using regular expressions. Here's an example demonstratingall these methods at once.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(use 'clojure.contrib.pprint)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;nil&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(pprint (gen-doc clj-doc&lt;br /&gt;                       (clj-doc.markups clj-doc.generator clj-doc.utils)&lt;br /&gt;                       #"clj-doc\.markups\."))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;("&amp;lt;!DOCTYPE html ...Documentation for clj-doc..."&lt;br /&gt; "&amp;lt;!DOCTYPE html ...Documentation for clj-doc.markups, clj-doc.generator, clj-doc.utils..."&lt;br /&gt; "&amp;lt;!DOCTYPE html ...Documentation for clj-doc.markups.creole, clj-doc.markups.dokuwiki, ...")&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Writing to Files&lt;/h4&gt;&lt;p&gt;Usually you would want to write the documentation for your library to afile. I've added basic support for this with the &lt;tt&gt;gen-doc-to-file&lt;/tt&gt;macro. It behaves exactly as &lt;tt&gt;gen-doc&lt;/tt&gt; but writes each of the resultingdocumentation strings to a file.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(gen-doc-to-file "~/test.html" clj-doc)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;nil&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;If you use more than one group of namespaces, this macro will write theminto multiple files that will be numbered starting with zero byappending a number before the last dot if there's one, at the endotherwise. I intend to make this argument a format string where you canspecify more complex filenames in a next version. So, if you got anyideas about how to design or implement such feature, I encourage you toadd a comment to this post.&lt;/p&gt;&lt;h4&gt;Supported Markups&lt;/h4&gt;&lt;p&gt;Up until now, we've only been using the default output markup. The mainpoint of this library being generating something else than HTML, webetter have a way to use another markup language. Before talking abouthow to do that, lets look under the hood.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;ns&lt;/span&gt; clj-doc.markups.creole&lt;br /&gt;  &lt;span class="string"&gt;"Creole markup."&lt;/span&gt;&lt;br /&gt;  (use clj-doc.markups))&lt;br /&gt;&lt;br /&gt;(defmarkup&lt;br /&gt;  #^{&lt;span class="builtin"&gt;:doc&lt;/span&gt; &lt;span class="string"&gt;"Creole markup."&lt;/span&gt;}&lt;br /&gt;  creole&lt;br /&gt;  &lt;span class="builtin"&gt;:title&lt;/span&gt;        #(&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"\n= "&lt;/span&gt;     %     &lt;span class="string"&gt;" ="&lt;/span&gt;)&lt;br /&gt;  &lt;span class="builtin"&gt;:namespace&lt;/span&gt;    #(&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"\n== "&lt;/span&gt;    %    &lt;span class="string"&gt;" =="&lt;/span&gt;)&lt;br /&gt;  &lt;span class="builtin"&gt;:section&lt;/span&gt;      #(&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"\n=== "&lt;/span&gt;   %1  &lt;span class="string"&gt;" ===\n"&lt;/span&gt; %2)&lt;br /&gt;  &lt;span class="builtin"&gt;:var-name&lt;/span&gt;     #(&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"\n==== "&lt;/span&gt;  %  &lt;span class="string"&gt;" ===="&lt;/span&gt;)&lt;br /&gt;  &lt;span class="builtin"&gt;:var-arglist&lt;/span&gt;  #(&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"\n**"&lt;/span&gt; % &lt;span class="string"&gt;"**\\\\"&lt;/span&gt;)&lt;br /&gt;  &lt;span class="builtin"&gt;:var-doc&lt;/span&gt;      #(&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"\n\n"&lt;/span&gt; %))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This is the current implementation for the Creole Wiki markupgenerator. All markups are defined in the same way, they're basicallyonly struct maps, for more details see the &lt;tt&gt;clj-doc.markups&lt;/tt&gt;namespace. The library currently provides four output methods. We'vealready seen two of them, here's the complete list:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; creole&lt;/li&gt;&lt;li&gt; dokuwiki&lt;/li&gt;&lt;li&gt; html-simple&lt;/li&gt;&lt;li&gt; markdown&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To define your own markups, you can take one of the implementations,modify it and see how it work out, the keywords should be explicitenough. The library only look for markups into namespaces starting with"clj-doc.markups." though, but that's just a temporary limitation,eventually you'll be able to pass a markup struct map as an option.&lt;/p&gt;&lt;h4&gt;The Option Map&lt;/h4&gt;&lt;p&gt;The option map is an optional argument that must be specified before anynamespaces. It presently only support two keywords, &lt;tt&gt;:markup&lt;/tt&gt; and&lt;tt&gt;:separated-by&lt;/tt&gt;. The first letting you choose the output method andthe second used to change the default page separation behavior.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(gen-doc {:markup creole} clj-doc.markups.creole)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;("\n= Documentation for clj-doc.markups.creole =\n== clj-doc.markups.creole namespace ==\n=== others ===\n\n==== creole ====\n\nCreole markup.")&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The &lt;tt&gt;:separated-by&lt;/tt&gt; option can only take one value, &lt;tt&gt;namespace&lt;/tt&gt;,which makes the macros separate pages between namespaces instead ofgroups of them.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(pprint (gen-doc {:markup markdown :separated-by namespace} #"clj-doc\.markups\."))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;("\n#Documentation for clj-doc.markups.creole..."&lt;br /&gt; "\n#Documentation for clj-doc.markups.dokuwiki..."&lt;br /&gt; "\n#Documentation for clj-doc.markups.html-simple..."&lt;br /&gt; "\n#Documentation for clj-doc.markups.markdown...")&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;The Future&lt;/h4&gt;&lt;p&gt;This is a very early release done the &lt;em&gt;worse is better&lt;/em&gt; way, so it'sstill a pretty bare-bone tool. I'll be taking a pause this week to workon more (hopefully) profitable projects, so in the meantime I'll bepleased to ear about any suggestions to improve the way clj-docworks.&lt;/p&gt;&lt;h4&gt;Links&lt;/h4&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://github.com/budu/clj-doc"&gt;GitHub Project Page&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://gitorious.org/clojureql/pages/ApiDoc"&gt;Example output for ClojureQL&lt;/a&gt; (using Markdown)&lt;/li&gt;&lt;li&gt; &lt;a href="http://compojure.org/docs/api"&gt;Example output for Compojure&lt;/a&gt; (using DokuWiki syntax)&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/5409861124354728237-8870816084018610204?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/8870816084018610204/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/documenting-clojure-code.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/8870816084018610204'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/8870816084018610204'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/documenting-clojure-code.html' title='Documenting Clojure Code'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-8649416563489221556</id><published>2010-01-11T18:34:00.004-05:00</published><updated>2010-01-12T21:22:59.185-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='git-tips'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='tips'/><title type='text'>Git Tip #1</title><content type='html'>&lt;h3&gt;Changing Filename Case Under Cygwin&lt;/h3&gt;&lt;p&gt;When using Git on Windows XP, you're confronted with a basic problem,case changes aren't accounted for while renaming files as Windowsinternals doesn't have a clue about the difference between lower andupper case. You can actually change the casing, but you can't have twofiles using the same name with different casing for example. Thisbehavior obviously affect Git, which cannot detect filename casechanges.&lt;/p&gt;&lt;pre class="code"&gt;$ git init Initialized empty Git repository in /home/budu/tmp/.git/&lt;br /&gt;&lt;br /&gt;$ touch foo&lt;br /&gt;&lt;br /&gt;$ git add .&lt;br /&gt;&lt;br /&gt;$ git commit -m 'test' [master (root-commit) 5a1fbb3] test 0 files&lt;br /&gt;changed, 0 insertions(+), 0 deletions(-) create mode 100644 fo&lt;br /&gt;&lt;br /&gt;$ mv foo FOO&lt;br /&gt;&lt;br /&gt;$ l total 0 drwxr-xr-x+ 1 budu None 0 Jan 11 18:15 .git/ -rw-r--r-- 1&lt;br /&gt;budu None 0 Jan 11 17:59 FOO&lt;br /&gt;&lt;br /&gt;$ git status # On branch master nothing to commit (working directory&lt;br /&gt;clean)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This is an unfortunate situation which can be easily resolved by using asimple trick. The first thing you need to do is to copy the file youwant to modify to a different temporary name. Then you must use&lt;tt&gt;git rm&lt;/tt&gt; on the original file and finally rename the temporary fileto the new name with changed casing.&lt;/p&gt;&lt;pre class="code"&gt;$ cp FOO bar&lt;br /&gt;&lt;br /&gt;$ git rm foo rm 'foo'&lt;br /&gt;&lt;br /&gt;$ mv bar FOO&lt;br /&gt;&lt;br /&gt;$ git add .&lt;br /&gt;&lt;br /&gt;$ git status # On branch master # Changes to be committed: # (use "git&lt;br /&gt;reset HEAD &lt;file&gt;..." to unstage) # # renamed: foo -&amp;gt; FOO #&lt;br /&gt;&lt;/file&gt;&lt;/pre&gt;&lt;p&gt;One thing to note here is that when calling &lt;tt&gt;git rm&lt;/tt&gt; you have to usethe original file name if its casing is already changed.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-8649416563489221556?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/8649416563489221556/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/git-tip-1.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/8649416563489221556'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/8649416563489221556'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2010/01/git-tip-1.html' title='Git Tip #1'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-2958323545993049904</id><published>2009-12-31T20:10:00.002-05:00</published><updated>2009-12-31T20:10:30.202-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='clojureql'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Testing ClojureQL</title><content type='html'>&lt;p&gt;After working on ClojureQL for some time, I've became tired of runningmanual tests all the time, even with the&lt;a href="http://whollyweirdwyrd.blogspot.com/2009/12/debugging-clojureql.html"&gt;debug macro&lt;/a&gt;.So I began working on a test framework for this project. It's acombination of the test-is library and the concept of demos alreadybuilt-in. For now we only have them, but they aren't very comprehensiveand require a working connection for each backend you want totest. Furthermore, there's practically no validation of the results, weonly know that the database accept the expression that have been sent.Now that we're approaching the release of version 1.0 we need somethingmore flexible and exhaustive to keep the code stable.&lt;/p&gt;&lt;h4&gt;The Plan&lt;/h4&gt;&lt;p&gt;The idea of demo is not bad in itself, so I'll just complement it withtests that could be run without connection. It's all we need for day today development on ClojureQL, the actual execution of the generated SQLcode can be done only once in a while. To not break the DRY principle,I'll make them both use the same code, demos will be written using aspecial macro and tests will be generated automatically from them. Thedemos should also be improved by validating the results coming out ofthe tested databases.&lt;/p&gt;&lt;h4&gt;The Prototype&lt;/h4&gt;&lt;p&gt;Implementing this scheme directly into ClojureQL is not a simple taskthough, so I'll make a prototype to show how it works at a smallerscale. We'll use a very simple database written in Clojure and aClojureQL-like DSL that compile to pseudo-SQL statements. No backends,no intermediate forms and only two kind of statements: insertsand selects. We'll walk through this prototype below, it's merely ahundred lines long.&lt;/p&gt;&lt;h5&gt;The Database&lt;/h5&gt;&lt;p&gt;All data is kept in a ref, &lt;tt&gt;*db*&lt;/tt&gt;, which is a map of maps withstrings as the sole data type for keys as well as for values. The onlyfunction we really need here is &lt;tt&gt;exec&lt;/tt&gt;, which takes a compiledstatement and returns a result if no exception is raised. I've alsoincluded a simple helper function, &lt;tt&gt;reset&lt;/tt&gt;, to empty the database.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;*db*&lt;/span&gt; (ref {}))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;reset&lt;/span&gt; [] (&lt;span class="keyword"&gt;dosync&lt;/span&gt; (&lt;span class="builtin"&gt;ref-set&lt;/span&gt; *db* {})))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;exec-insert&lt;/span&gt; [stmt]&lt;br /&gt;  (&lt;span class="keyword"&gt;let&lt;/span&gt; [tokens (&lt;span class="builtin"&gt;drop&lt;/span&gt; 2 (seq (.split stmt &lt;span class="string"&gt;" "&lt;/span&gt;)))&lt;br /&gt;        table  (&lt;span class="builtin"&gt;first&lt;/span&gt; tokens)&lt;br /&gt;        values (&lt;span class="builtin"&gt;apply&lt;/span&gt; hash-map (&lt;span class="builtin"&gt;rest&lt;/span&gt; (&lt;span class="builtin"&gt;rest&lt;/span&gt; tokens)))]&lt;br /&gt;    (&lt;span class="keyword"&gt;dosync&lt;/span&gt; (&lt;span class="builtin"&gt;alter&lt;/span&gt; *db* assoc table&lt;br /&gt;              (&lt;span class="builtin"&gt;merge&lt;/span&gt; (@*db* table) values)))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;exec-select&lt;/span&gt; [stmt]&lt;br /&gt;  (&lt;span class="keyword"&gt;let&lt;/span&gt; [tokens (&lt;span class="builtin"&gt;drop&lt;/span&gt; 1 (seq (.split stmt &lt;span class="string"&gt;" "&lt;/span&gt;)))&lt;br /&gt;        table  (last tokens)&lt;br /&gt;        keys   (&lt;span class="builtin"&gt;take&lt;/span&gt; (- (&lt;span class="builtin"&gt;count&lt;/span&gt; tokens) 2) tokens)]&lt;br /&gt;    (&lt;span class="builtin"&gt;map&lt;/span&gt; #((@*db* table) %) keys)))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;exec&lt;/span&gt; [stmt]&lt;br /&gt;  (&lt;span class="keyword"&gt;condp&lt;/span&gt; #(.startsWith %2 %1) stmt&lt;br /&gt;    &lt;span class="string"&gt;"INSERT INTO "&lt;/span&gt; (exec-insert stmt)&lt;br /&gt;    &lt;span class="string"&gt;"SELECT "&lt;/span&gt;      (exec-select stmt)&lt;br /&gt;    (&lt;span class="keyword"&gt;throw&lt;/span&gt; (SQLException. &lt;span class="string"&gt;"error: unrecognized statement."&lt;/span&gt;))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;insert&lt;/span&gt; [name &amp;amp; key-val-pairs]&lt;br /&gt;  (&lt;span class="builtin"&gt;apply&lt;/span&gt; str &lt;span class="string"&gt;"INSERT INTO "&lt;/span&gt; name &lt;span class="string"&gt;" VALUES "&lt;/span&gt; (&lt;span class="builtin"&gt;interpose&lt;/span&gt; &lt;span class="string"&gt;" "&lt;/span&gt; key-val-pairs)))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;select&lt;/span&gt; [name &amp;amp; keys]&lt;br /&gt;  (&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"SELECT "&lt;/span&gt; (&lt;span class="builtin"&gt;apply&lt;/span&gt; str (&lt;span class="builtin"&gt;interpose&lt;/span&gt; &lt;span class="string"&gt;" "&lt;/span&gt; keys)) &lt;span class="string"&gt;" FROM "&lt;/span&gt; name))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Our DSL is composed of the &lt;tt&gt;insert&lt;/tt&gt; and &lt;tt&gt;select&lt;/tt&gt; functions,which output the compiled pseudo-SQL code that the database canexecute. It's a really dumb system, but we don't need more for thepurpose of this experiment.&lt;/p&gt;&lt;h3&gt;The Demos&lt;/h3&gt;&lt;p&gt;The demos will remain the heart of the ClojureQL testing framework. Themain objectives of the following code is to make it easy to define demosand to make their output human-readable. The trick is to also make themreusable, so demos will be defined as data, not code.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defmacro&lt;/span&gt; &lt;span class="function-name"&gt;defdemo&lt;/span&gt; [demo &amp;amp; stmts-results]&lt;br /&gt;  `(&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;~demo&lt;/span&gt;&lt;br /&gt;     ~(&lt;span class="builtin"&gt;vec&lt;/span&gt; (&lt;span class="builtin"&gt;map&lt;/span&gt; #(&lt;span class="builtin"&gt;vector&lt;/span&gt; (list 'quote (&lt;span class="builtin"&gt;first&lt;/span&gt; %))&lt;br /&gt;                         (list 'quote (second %)))&lt;br /&gt;             (&lt;span class="keyword"&gt;doall&lt;/span&gt; (&lt;span class="builtin"&gt;partition&lt;/span&gt; 2 stmts-results))))))&lt;br /&gt;&lt;br /&gt;(defdemo demo1&lt;br /&gt;  (insert 'test1 'foo 1) {&lt;span class="string"&gt;"test1"&lt;/span&gt; {&lt;span class="string"&gt;"foo"&lt;/span&gt; &lt;span class="string"&gt;"1"&lt;/span&gt;}}&lt;br /&gt;  (select 'test1 'foo)   (&lt;span class="string"&gt;"1"&lt;/span&gt;))&lt;br /&gt;&lt;br /&gt;(defdemo demo2&lt;br /&gt;  (insert 'test2 'foo 1 'bar 2) {&lt;span class="string"&gt;"test2"&lt;/span&gt; {&lt;span class="string"&gt;"bar"&lt;/span&gt; &lt;span class="string"&gt;"2"&lt;/span&gt;, &lt;span class="string"&gt;"foo"&lt;/span&gt; &lt;span class="string"&gt;"1"&lt;/span&gt;}}&lt;br /&gt;  (select 'test2 'foo 'bar)     (&lt;span class="string"&gt;"1"&lt;/span&gt; &lt;span class="string"&gt;"2"&lt;/span&gt;))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;run-demo&lt;/span&gt; [name]&lt;br /&gt;  (reset)&lt;br /&gt;  (println &lt;span class="string"&gt;"\n==="&lt;/span&gt; name &lt;span class="string"&gt;"===\n"&lt;/span&gt;)&lt;br /&gt;  (&lt;span class="keyword"&gt;doall&lt;/span&gt;&lt;br /&gt;    (&lt;span class="builtin"&gt;map&lt;/span&gt; (&lt;span class="keyword"&gt;fn&lt;/span&gt; [[stmt expected-result]]&lt;br /&gt;           (println &lt;span class="string"&gt;"statement: "&lt;/span&gt; stmt)&lt;br /&gt;           (&lt;span class="keyword"&gt;let&lt;/span&gt; [compiled-stmt (eval stmt)&lt;br /&gt;                 result        (exec compiled-stmt)]&lt;br /&gt;             (println &lt;span class="string"&gt;"compiled:  "&lt;/span&gt; compiled-stmt)&lt;br /&gt;             (println &lt;span class="string"&gt;"result:    "&lt;/span&gt; result &lt;span class="string"&gt;"\n"&lt;/span&gt;)&lt;br /&gt;             (is (= result expected-result)))) (eval (symbol name)))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;demos&lt;/span&gt; [&lt;span class="string"&gt;"demo1"&lt;/span&gt; &lt;span class="string"&gt;"demo2"&lt;/span&gt;])&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;run-demos&lt;/span&gt; []&lt;br /&gt;  (&lt;span class="builtin"&gt;every?&lt;/span&gt; identity&lt;br /&gt;    (&lt;span class="builtin"&gt;apply&lt;/span&gt; concat (&lt;span class="builtin"&gt;map&lt;/span&gt; run-demo demos))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The &lt;tt&gt;run-demo&lt;/tt&gt; function test everything in a demo and provide a niceoutput. It returns a boolean that is the result of the assertion made atthe end.&lt;/p&gt;&lt;h5&gt;The Tests&lt;/h5&gt;&lt;p&gt;We can now use the above to write some tests. The demos ensure us thatthe compiled statements are all valid. After that it's simply a matterof generating Clojure code, to define tests, and writing it to afile. The &lt;tt&gt;gen-test&lt;/tt&gt; function will take care of the first part and&lt;tt&gt;write-tests&lt;/tt&gt; the second one.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;gen-test&lt;/span&gt; [name]&lt;br /&gt;  (&lt;span class="keyword"&gt;let&lt;/span&gt; [demo (eval (symbol name))]&lt;br /&gt;    `(&lt;span class="keyword"&gt;deftest&lt;/span&gt; &lt;span class="function-name"&gt;~&lt;/span&gt;(symbol (.replace name &lt;span class="string"&gt;"demo"&lt;/span&gt; &lt;span class="string"&gt;"test"&lt;/span&gt;))&lt;br /&gt;       ~@(&lt;span class="builtin"&gt;map&lt;/span&gt; (&lt;span class="keyword"&gt;fn&lt;/span&gt; [[stmt _]]&lt;br /&gt;                `(is (= ~stmt ~(eval stmt)))) demo))))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;write-tests&lt;/span&gt; [filename]&lt;br /&gt;  (&lt;span class="keyword"&gt;when&lt;/span&gt; (run-demos)&lt;br /&gt;    (&lt;span class="keyword"&gt;with-open&lt;/span&gt; [writer (java.io.FileWriter. filename)]&lt;br /&gt;      (&lt;span class="keyword"&gt;binding&lt;/span&gt; [*out* writer]&lt;br /&gt;        (newline)&lt;br /&gt;        (&lt;span class="keyword"&gt;doseq&lt;/span&gt; [demo demos]&lt;br /&gt;          (pprint (gen-test demo))&lt;br /&gt;          (newline))))))&lt;br /&gt;&lt;br /&gt;(write-tests &lt;span class="string"&gt;"tests.clj"&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;(load-file &lt;span class="string"&gt;"tests.clj"&lt;/span&gt;)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Once the &lt;em&gt;tests&lt;/em&gt; file written, we can load it to run the tests itcontains. So now we can use &lt;tt&gt;run-demos&lt;/tt&gt; to test everything and&lt;tt&gt;run-tests&lt;/tt&gt; to test compilation only.&lt;/p&gt;&lt;h4&gt;Conclusion&lt;/h4&gt;&lt;p&gt;There's still many details to figure out before implementing thisframework. The namespace layout to be used for example or how tointegrate the test-is library depending on what Clojure version we end uptargeting. This is a work in progress so I'll keep this post updateduntil it's done. In the meantime, any comments or suggestions arewelcomed.&lt;/p&gt;&lt;p&gt;Happy New Year!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-2958323545993049904?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/2958323545993049904/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/testing-clojureql.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/2958323545993049904'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/2958323545993049904'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/testing-clojureql.html' title='Testing ClojureQL'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-5562431381055642729</id><published>2009-12-21T01:52:00.001-05:00</published><updated>2010-01-24T17:54:30.773-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rant'/><category scheme='http://www.blogger.com/atom/ns#' term='hardware'/><title type='text'>What the Hell is Happening at ATI?</title><content type='html'>I got an ATI card for building my last box, would have prefered a Nvidia one, but got a good deal on this card. Overall it's been a good investment, but the situation is slipping down. There have been more and more driver updates lately and now it's getting desperate. I rarely play these days, but all the time I tried in the past months, lots of games were crashing in various ways. Hoping to get these problems resolved, today I tried to install the new Catalyst 9.12 driver, but the installer fails, it simply vanish while using 10% of the processor! To figure out that one, I googled to see if I wasn't alone and there happen to be quite a lot of forum posts discussing the issue. Seeing their content wasn't really comforting either, lots of steps to fix the problem, too much for my current motivation. I'd rather get an Nvidia card instead, but can't currently fit this in my (ramen) budget, even tho entry-level cards are now really powerful. So no gaming for the future ahead, programming is much more fun anyway.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-5562431381055642729?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/5562431381055642729/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/what-hell-is-happening-at-ati.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/5562431381055642729'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/5562431381055642729'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/what-hell-is-happening-at-ati.html' title='What the Hell is Happening at ATI?'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-7328305647092400225</id><published>2009-12-21T00:12:00.002-05:00</published><updated>2009-12-21T00:19:10.841-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><category scheme='http://www.blogger.com/atom/ns#' term='macro'/><title type='text'>Writing a Help Macro.</title><content type='html'>&lt;p&gt;Emacs may seems like a pretty barebone tool for the uninitiated, butpack a lot of things under the hood. Especially when you're coding witha Lisp, Emacs really shines thanks to its renowned&lt;a href="http://common-lisp.net/project/slime/"&gt;slime mode&lt;/a&gt;. Yet, most of thepower of using a Lisp-like language with any editors lies in the use ofthe REPL. Clojure has a great set of functions and macros available tohelp you code using it, but these are dispersed around many librariescontained in clojure-contrib. As there's lots of such helpers, you haveto remember a bunch of names and refer to documentation often beforegetting used to all of them. Having these functionalities packed into asingle command could be very useful for beginners as well as forpractitioners. We'll first pass trough the code and review it, thenwe'll show how to use the &lt;tt&gt;help&lt;/tt&gt; macro.&lt;/p&gt;&lt;h4&gt;Help Macro&lt;/h4&gt;&lt;p&gt;We'll proceed in a top-bottom manner, starting with the namespacedefinition. We'll need some Java classes from clojure.lang package andalso add some aliases for the clojure-contrib libraries we'll be using.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;ns&lt;/span&gt; help-macro&lt;br /&gt;  &lt;span class="string"&gt;"The help macro regroups clojure-contrib most useful help functions and&lt;br /&gt;  macros into a single call."&lt;/span&gt;&lt;br /&gt;  (&lt;span class="keyword"&gt;import&lt;/span&gt; [clojure.lang IFn PersistentList Symbol])&lt;br /&gt;  (require&lt;br /&gt;    [clojure.contrib.classpath  &lt;span class="builtin"&gt;:as&lt;/span&gt; classpath]&lt;br /&gt;    [clojure.contrib.repl-utils &lt;span class="builtin"&gt;:as&lt;/span&gt; repl-utils]&lt;br /&gt;    [clojure.contrib.str-utils  &lt;span class="builtin"&gt;:as&lt;/span&gt; str-utils]&lt;br /&gt;    [clojure.contrib.ns-utils   &lt;span class="builtin"&gt;:as&lt;/span&gt; ns-utils]))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Then comes the definition of &lt;tt&gt;*help-usage*&lt;/tt&gt;, a simple var containinga string explaining how to use the help macro. That could have beenincluded in the docstring, but I chose to emulate the way command linescripts work instead.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;*help-usage*&lt;/span&gt;&lt;br /&gt;  #^{&lt;span class="builtin"&gt;:doc&lt;/span&gt; &lt;span class="string"&gt;"Help macro usage text."&lt;/span&gt;}&lt;br /&gt;  (&lt;span class="builtin"&gt;str&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"Usage: (help pwd)\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help classpath)\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help dir &amp;lt;ns&amp;gt;)\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help docs &amp;lt;ns&amp;gt;)\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help vars &amp;lt;ns&amp;gt;)\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help source &amp;lt;symbol&amp;gt;)\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help &amp;lt;class&amp;gt; [&amp;lt;n&amp;gt;])\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help &amp;lt;expr&amp;gt;)\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help &amp;lt;string&amp;gt;)\n"&lt;/span&gt;&lt;br /&gt;    &lt;span class="string"&gt;"       (help ? &amp;lt;query-type&amp;gt;)"&lt;/span&gt;))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;As you see, we'll support nine kinds of help queries, six of which usea command symbol while the three others are dispatched on the class oftheir first argument. Lets review each types of queries:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; &lt;tt&gt;pwd&lt;/tt&gt; - Returns the current working directory.&lt;/li&gt;&lt;li&gt; &lt;tt&gt;classpath&lt;/tt&gt; - Prints the current classpath.&lt;/li&gt;&lt;li&gt; &lt;tt&gt;dir&lt;/tt&gt; - Prints a sorted directory of public vars in a namespace.&lt;/li&gt;&lt;li&gt; &lt;tt&gt;docs&lt;/tt&gt; - Prints documentation for the public vars in a namespace.&lt;/li&gt;&lt;li&gt; &lt;tt&gt;vars&lt;/tt&gt; - Returns a sorted seq of symbols naming public vars in a namespace.&lt;/li&gt;&lt;li&gt; &lt;tt&gt;source&lt;/tt&gt; - Returns a string of the source code for the given symbol, if it can find it.&lt;/li&gt;&lt;li&gt; &lt;tt&gt;&amp;lt;class&amp;gt;&lt;/tt&gt; - Get help on class members, like &lt;tt&gt;clojure.contrib.repl-utils/show&lt;/tt&gt;.&lt;/li&gt;&lt;li&gt; &lt;tt&gt;&amp;lt;expr&amp;gt;&lt;/tt&gt; - Get help on the result of an expression, like &lt;tt&gt;clojure.contrib.repl-utils/expression-info&lt;/tt&gt;.&lt;/li&gt;&lt;li&gt; &lt;tt&gt;&amp;lt;string&amp;gt;&lt;/tt&gt; - Like &lt;tt&gt;find-doc&lt;/tt&gt;, but easier to type.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The help usage message is nice, but it would be great to have morespecific ones for each types of queries, like the above list. To dothis, we'll create a &lt;tt&gt;help-usage&lt;/tt&gt; function that will print variousmessages depending on the symbol given as first argument.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;help-usage&lt;/span&gt;&lt;br /&gt;  &lt;span class="string"&gt;"Returns documentation on help macro usage."&lt;/span&gt;&lt;br /&gt;  [query-type &amp;amp; args]&lt;br /&gt;  (&lt;span class="keyword"&gt;condp&lt;/span&gt; = query-type&lt;br /&gt;    'class     (doc clojure.contrib.repl-utils/show)&lt;br /&gt;    'expr      (doc clojure.contrib.repl-utils/expression-info)&lt;br /&gt;    'string    (doc find-doc)&lt;br /&gt;    (println&lt;br /&gt;      (&lt;span class="keyword"&gt;condp&lt;/span&gt; = query-type&lt;br /&gt;        'pwd       &lt;span class="string"&gt;"Returns the current working directory."&lt;/span&gt;&lt;br /&gt;        'classpath &lt;span class="string"&gt;"Prints the current classpath."&lt;/span&gt;&lt;br /&gt;        'dir       &lt;span class="string"&gt;"Prints a sorted directory of public vars in a namespace."&lt;/span&gt;&lt;br /&gt;        'docs      &lt;span class="string"&gt;"Prints documentation for the public vars in a namespace."&lt;/span&gt;&lt;br /&gt;        'vars      &lt;span class="string"&gt;"Returns a sorted seq of symbols naming public vars in a namespace."&lt;/span&gt;&lt;br /&gt;        'source    &lt;span class="string"&gt;"Returns a string of the source code for the given symbol, if it can find it."&lt;/span&gt;&lt;br /&gt;        &lt;span class="string"&gt;"This type of help query is not recognized."&lt;/span&gt;))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Each symbols are separated in two categories. For the simpler queries,we only display a description of what they do. For more complex onesthough, we show the documentation for the original function or macrousing the &lt;tt&gt;doc&lt;/tt&gt; macro. The function accept other arguments only toprevent exceptions in case of unintentional input.&lt;/p&gt;&lt;p&gt;We'll now look at the &lt;tt&gt;generic-help&lt;/tt&gt; multimethod, which will dispatchcalls on the class of its first argument. We'll only respond to threeclasses: Class, Clojure's PersistentList and String.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defmulti&lt;/span&gt; &lt;span class="function-name"&gt;generic-help&lt;/span&gt;&lt;br /&gt;  &lt;span class="string"&gt;"Makes the help macro generic on its first argument if no command found."&lt;/span&gt;&lt;br /&gt;  {&lt;span class="builtin"&gt;:arglists&lt;/span&gt; '([query args])}&lt;br /&gt;  (&lt;span class="keyword"&gt;fn&lt;/span&gt; [query _] (class query)))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defmethod&lt;/span&gt; &lt;span class="function-name"&gt;generic-help&lt;/span&gt; Class&lt;br /&gt;  [query args]&lt;br /&gt;  (&lt;span class="builtin"&gt;apply&lt;/span&gt; repl-utils/show (&lt;span class="builtin"&gt;cons&lt;/span&gt; query args)))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defmethod&lt;/span&gt; &lt;span class="function-name"&gt;generic-help&lt;/span&gt; PersistentList&lt;br /&gt;  [query args]&lt;br /&gt;  (repl-utils/expression-info (second query)))&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defmethod&lt;/span&gt; &lt;span class="function-name"&gt;generic-help&lt;/span&gt; String&lt;br /&gt;  [query args]&lt;br /&gt;  (find-doc query))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;A thing to note here is the reason why we take the second argument ofthe list in the PersistentList method. This is so because the &lt;tt&gt;help&lt;/tt&gt;macro quote all arguments it receives, but the expression query must bealready quoted.&lt;/p&gt;&lt;p&gt;We'll also add a default dispatch method which displays a warningmessage followed by the &lt;tt&gt;help&lt;/tt&gt; macro usage.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defmethod&lt;/span&gt; &lt;span class="function-name"&gt;generic-help&lt;/span&gt; &lt;span class="builtin"&gt;:default&lt;/span&gt;&lt;br /&gt;  [query _]&lt;br /&gt;  (println &lt;span class="string"&gt;"No help available for object of type "&lt;/span&gt; (class query))&lt;br /&gt;  (help-usage))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;All simple commands are contained in the &lt;tt&gt;*help-command*&lt;/tt&gt;map, which is used by the &lt;tt&gt;help*&lt;/tt&gt; function following it.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;*help-commands*&lt;/span&gt;&lt;br /&gt;  #^{&lt;span class="builtin"&gt;:doc&lt;/span&gt; &lt;span class="string"&gt;"This is a map containing all the commands for the help macro."&lt;/span&gt;}&lt;br /&gt;  { 'pwd       #(.getCanonicalPath (java.io.File. &lt;span class="string"&gt;"."&lt;/span&gt;))&lt;br /&gt;    'classpath #(println (str-utils/str-join &lt;span class="string"&gt;"\n"&lt;/span&gt; (classpath/classpath)))&lt;br /&gt;    'dir       ns-utils/print-dir&lt;br /&gt;    'docs      ns-utils/print-docs&lt;br /&gt;    'vars      ns-utils/ns-vars&lt;br /&gt;    'source    (comp println repl-utils/get-source)})&lt;br /&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;help*&lt;/span&gt;&lt;br /&gt;  &lt;span class="string"&gt;"Driver for the help macro."&lt;/span&gt;&lt;br /&gt;  [query args]&lt;br /&gt;  (&lt;span class="keyword"&gt;if-let&lt;/span&gt; [sc (get *help-commands* query)]&lt;br /&gt;    (&lt;span class="builtin"&gt;apply&lt;/span&gt; sc args)&lt;br /&gt;    (generic-help (&lt;span class="keyword"&gt;if&lt;/span&gt; (&lt;span class="builtin"&gt;symbol?&lt;/span&gt; query)&lt;br /&gt;                    (resolve query)&lt;br /&gt;                    query) args)))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This function look into the &lt;tt&gt;*help-command*&lt;/tt&gt; map to see if itcontains the given query symbol. If found, it calls the associatedrunnable, else it calls &lt;tt&gt;generic-help&lt;/tt&gt;. Before calling themultimethod, we first try to resolve the &lt;tt&gt;query&lt;/tt&gt; quoted expressionin case it's a symbol. This is because this function receives only thesymbol of a class when &lt;tt&gt;help&lt;/tt&gt; is called with a class name.&lt;/p&gt;&lt;p&gt;Finally, here's the &lt;tt&gt;help&lt;/tt&gt; macro in all it's glory.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defmacro&lt;/span&gt; &lt;span class="function-name"&gt;help&lt;/span&gt;&lt;br /&gt;  &lt;span class="string"&gt;"Get help for various kind of expressions, use without arguments for&lt;br /&gt;  detailed usage."&lt;/span&gt;&lt;br /&gt;  ([] `(println *help-usage*))&lt;br /&gt;  ([query &amp;amp; args]&lt;br /&gt;    (&lt;span class="keyword"&gt;let&lt;/span&gt; [quoted-args (&lt;span class="builtin"&gt;map&lt;/span&gt; #(list 'quote %) args)]&lt;br /&gt;      (&lt;span class="keyword"&gt;if&lt;/span&gt; (= '? query)&lt;br /&gt;        `(help-usage ~@quoted-args)&lt;br /&gt;        `(help* '~query (list ~@quoted-args))))))&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Usage&lt;/h4&gt;&lt;p&gt;Here's some examples of using the &lt;tt&gt;help&lt;/tt&gt; macro:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help pwd)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;"C:\\"&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help classpath)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;g:\libraries\java\swank-clojure-1.0-SNAPSHOT.jar&lt;br /&gt;g:\libraries\java\servlet-api-2.5-20081211.jar&lt;br /&gt;...&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help dir help-macro)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;*help-commands*&lt;br /&gt;*help-usage*&lt;br /&gt;generic-help&lt;br /&gt;help&lt;br /&gt;help*&lt;br /&gt;help-usage&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help docs help-macro)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;-------------------------&lt;br /&gt;help-macro/*help-commands*&lt;br /&gt;nil&lt;br /&gt;  nil&lt;br /&gt;...&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help vars help-macro)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;(*help-commands* *help-usage* generic-help help help* help-usage)&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help source +)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;(defn +&lt;br /&gt;  "Returns the sum of nums. (+) returns 0."&lt;br /&gt;  {:inline (fn [x y] `(. clojure.lang.Numbers (add ~x ~y)))&lt;br /&gt;   :inline-arities #{2}}&lt;br /&gt;  ([] 0)&lt;br /&gt;  ([x] (cast Number x))&lt;br /&gt;  ([x y] (. clojure.lang.Numbers (add x y)))&lt;br /&gt;  ([x y &amp;amp; more]&lt;br /&gt;   (reduce + (+ x y) more)))&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help String)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;===  public final java.lang.String  ===&lt;br /&gt;[ 0] static CASE_INSENSITIVE_ORDER : Comparator&lt;br /&gt;[ 1] static copyValueOf : String (char[])&lt;br /&gt;...&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-result"&gt;nil&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help String 1)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;#&amp;lt;Method public static java.lang.String java.lang.String.copyValueOf(char[])&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help '1)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;{:class java.lang.Integer, :primitive? false}&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help '(int 1))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;{:class int, :primitive? true}&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(help "help")&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;-------------------------&lt;br /&gt;clojure.main/help-opt&lt;br /&gt;([_ _])&lt;br /&gt;  Print help text for main&lt;br /&gt;-------------------------&lt;br /&gt;clojure.main/main&lt;br /&gt;([&amp;amp; args])&lt;br /&gt;...&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;h4&gt;Installing&lt;/h4&gt;&lt;p&gt;You can find the complete code&lt;a href="http://dl.dropbox.com/u/2682770/mu/help_macro.clj"&gt;here&lt;/a&gt;. To use it,place that file in your classpath and create an user script to be calledwhen stating a REPL. From there, you simply have to add the help-macronamespace with the &lt;tt&gt;use&lt;/tt&gt; function if you want to be able to justtype "(help...". To have it around in every namespace, you could add thefollowing function in the same file than the macro and use it instead of&lt;tt&gt;in-ns&lt;/tt&gt;.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defn&lt;/span&gt; &lt;span class="function-name"&gt;to-ns&lt;/span&gt; [n]&lt;br /&gt;  (&lt;span class="keyword"&gt;in-ns&lt;/span&gt; n)&lt;br /&gt;  (use 'help-macro))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Any comments, suggestions, improvements, insults?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-7328305647092400225?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/7328305647092400225/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/writing-help-macro.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/7328305647092400225'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/7328305647092400225'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/writing-help-macro.html' title='Writing a Help Macro.'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-3054803845972660677</id><published>2009-12-15T19:41:00.001-05:00</published><updated>2009-12-21T02:09:58.629-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='dvcs'/><title type='text'>Reading The F*ck!ng Manual</title><content type='html'>&lt;p&gt;While contributing code to ClojureQL, I made a huge mistake. That is,commiting changes to the master branch directly instead of using adevelopment branch. I had made a dozen commits, but in the meantime Laumerged the dev branch on ClojureQL's main repository. The end result wasthat the pull request I made after was incredibly huge as it includedall changes in the merge.&lt;/p&gt;&lt;p&gt;I'll walk you through all steps I've taken to fix this problem. We'llfirst create a dev branch, it should be a copy of the current masterbranch, which is the default behavior.&lt;/p&gt;&lt;pre class="code"&gt;$ git branch dev&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This will create a new branch named &lt;em&gt;dev&lt;/em&gt; that will be a copy of thecurrent one, &lt;em&gt;master&lt;/em&gt;. We'll then search for the first commit we'vemade using &lt;tt&gt;git log&lt;/tt&gt;.&lt;/p&gt;&lt;pre class="code"&gt;commit 07a516b03287b606d6a40b2e073cd3e3a1a74244&lt;br /&gt;Author: Nicolas Buduroi &lt;nbuduroi@gmail.com&gt;&lt;br /&gt;Date:   Wed Dec 9 18:52:35 2009 -0500&lt;br /&gt;&lt;br /&gt;    Removed global connection support for MySql demo as it breaks the build process when MySql is not installed.&lt;br /&gt;&lt;br /&gt;commit cc8f234fb61739fca36e982f68eef005b3d4389c&lt;br /&gt;Author: Lau_of_DK &lt;lau.jensen@bestinclass.dk&gt;&lt;br /&gt;Date:   Tue Dec 8 23:18:03 2009 +0100&lt;br /&gt;&lt;br /&gt;:&lt;br /&gt;&lt;/lau.jensen@bestinclass.dk&gt;&lt;/nbuduroi@gmail.com&gt;&lt;/pre&gt;&lt;p&gt;We must return the master branch to the state it was before commitingour first change. We can achieve this by using the reset command.&lt;/p&gt;&lt;pre class="code"&gt;$ git reset --hard cc8f234fb61739fca36e982f68eef005b3d4389c&lt;br /&gt;.gitignore: locally modified&lt;br /&gt;build.xml: locally modified&lt;br /&gt;ivy.xml: locally modified&lt;br /&gt;ivysettings.xml: locally modified&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Notice that you should use the &lt;tt&gt;--hard&lt;/tt&gt; flag to make sure Git cleanup the working repository. We can then update our master branch with themain repository. This assume you've already set up a remote Gitrepository named &lt;em&gt;main-repo&lt;/em&gt;, check&lt;a href="http://toolmantim.com/articles/setting_up_a_new_remote_git_repository"&gt;this page&lt;/a&gt;to see how to make one.&lt;/p&gt;&lt;pre class="code"&gt;$ git pull main-repo master&lt;br /&gt;From git://gitorious.org/clojureql/clojureql&lt;br /&gt; * branch            master     -&amp;gt; FETCH_HEAD&lt;br /&gt;Updating cc8f234..b85d55b&lt;br /&gt;Fast forward&lt;br /&gt; build.gradle                                       |   64 +++++++&lt;br /&gt; build.xml                                          |  182 --------------------&lt;br /&gt; ivy.xml                                            |   31 ----&lt;br /&gt; ivysettings.xml                                    |    9 -&lt;br /&gt; src/{dk/bestinclass =&amp;gt; }/clojureql.clj             |    4 +-&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Now, we need to switch to our dev branch with &lt;tt&gt;git checkout dev&lt;/tt&gt;,and there, issue the rebase command.&lt;/p&gt;&lt;pre class="code"&gt;$ git rebase master&lt;br /&gt;First, rewinding head to replay your work on top of it...&lt;br /&gt;Applying: Removed global connection support for MySql demo as it breaks the build process when MySql is not installed.&lt;br /&gt;error: src/dk/bestinclass/clojureql/demos/mysql.clj: does not exist in index&lt;br /&gt;Using index info to reconstruct a base tree...&lt;br /&gt;Falling back to patching base and 3-way merge...&lt;br /&gt;Renaming src/dk/bestinclass/clojureql/demos/mysql.clj =&amp;gt; src/clojureql/demos/mysql.clj&lt;br /&gt;Auto-merging src/clojureql/demos/mysql.clj&lt;br /&gt;Applying: Made demos code formatting more uniform.&lt;br /&gt;error: src/dk/bestinclass/clojureql/demos/derby.clj: does not exist in index&lt;br /&gt;error: src/dk/bestinclass/clojureql/demos/mysql.clj: does not exist in index&lt;br /&gt;error: src/dk/bestinclass/clojureql/demos/postgres.clj: does not exist in index&lt;br /&gt;Using index info to reconstruct a base tree...&lt;br /&gt;Falling back to patching base and 3-way merge...&lt;br /&gt;Renaming src/dk/bestinclass/clojureql/demos/derby.clj =&amp;gt; src/clojureql/demos/derby.clj&lt;br /&gt;Auto-merging src/clojureql/demos/derby.clj&lt;br /&gt;CONFLICT (rename/modify): Merge conflict in src/clojureql/demos/derby.clj&lt;br /&gt;Renaming src/dk/bestinclass/clojureql/demos/mysql.clj =&amp;gt; src/clojureql/demos/mysql.clj&lt;br /&gt;Auto-merging src/clojureql/demos/mysql.clj&lt;br /&gt;CONFLICT (rename/modify): Merge conflict in src/clojureql/demos/mysql.clj&lt;br /&gt;Renaming src/dk/bestinclass/clojureql/demos/postgres.clj =&amp;gt; src/clojureql/demos/postgres.clj&lt;br /&gt;Auto-merging src/clojureql/demos/postgres.clj&lt;br /&gt;CONFLICT (rename/modify): Merge conflict in src/clojureql/demos/postgres.clj&lt;br /&gt;Failed to merge in the changes.&lt;br /&gt;Patch failed at 0002.&lt;br /&gt;&lt;br /&gt;When you have resolved this problem run "git rebase --continue".&lt;br /&gt;If you would prefer to skip this patch, instead run "git rebase --skip".&lt;br /&gt;To restore the original branch and stop rebasing run "git rebase --abort".&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;OK, that doesn't look good: error, conflict, failed... Not reassuring,but looking more closely, this message concerns only the first patch,which I have to revert anyway. Lets see how to resolve this. You canfind the conflicting files with &lt;tt&gt;git status&lt;/tt&gt;. Here's an example ofsuch a file:&lt;/p&gt;&lt;pre class="code"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD:src/clojureql/demos/derby.clj&lt;br /&gt;(&lt;span class="keyword"&gt;ns&lt;/span&gt; clojureql.demos.derby&lt;br /&gt;=======&lt;br /&gt;&lt;span class="comment-delimiter"&gt;;; &lt;/span&gt;&lt;span class="comment"&gt;TEST ====================================================&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;(&lt;span class="keyword"&gt;ns&lt;/span&gt; dk.bestinclass.clojureql.demos.derby&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; Made demos code formatting more uniform.:src/dk/bestinclass/clojureql/demos/derby.clj&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Fix the conflict by picking one of the offered options. Then, onceeverything is cleaned up, you can tell the merge process to accept yourchanges with &lt;tt&gt;git add&lt;/tt&gt; and restart the rebase one.&lt;/p&gt;&lt;pre class="code"&gt;$ git add .&lt;br /&gt;&lt;br /&gt;$ git rebase --continue&lt;br /&gt;Applying: Made demos code formatting more uniform.&lt;br /&gt;Applying: Added build directory to ignored files.&lt;br /&gt;Applying: Changed PostreSQL backend to be more like MySQL one, follow non-nulls new standard.&lt;br /&gt;Applying: Updated create-table docstring to reflect recent changes.&lt;br /&gt;Applying: Added a shortcut in create-table's options to make all columns non null.&lt;br /&gt;Applying: Added support for multiple primary keys to create-table.&lt;br /&gt;Applying: Changed EmbedConnection class to EngineConnection interface for added flexibility in Derby backend.&lt;br /&gt;Applying: Removed useless prefer-method.&lt;br /&gt;Applying: Added the :unique option to create-table for all backends, needs refactoring.&lt;br /&gt;Applying: Refactored contraints using a list as argument for all backends.&lt;br /&gt;Applying: Refactored column options for all backends, made minor changes to list-constraint and cleaned up a little.&lt;br /&gt;Applying: Added new derivation for ::Generic as java.sql.Connection proxies are confused with com.mysql.jdbc.Connection ones, put prefer-method back in Postgres backend as it now cause problems.&lt;br /&gt;Applying: Added primary-key, unique and non-nulls options to generic create-table.&lt;br /&gt;Applying: Added the :defaults option to create-table for all backends.&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We must now return to the master branch where we'll merge the rebasedpatches.&lt;/p&gt;&lt;pre class="code"&gt;$ git merge dev&lt;br /&gt;Updating b85d55b..5f771d6&lt;br /&gt;Fast forward&lt;br /&gt; .gitignore                         |    1 +&lt;br /&gt; src/clojureql/backend.clj          |   49 ++++++++++++++++++++++++++++++++++-&lt;br /&gt; src/clojureql/backend/derby.clj    |   31 +++++++++++-----------&lt;br /&gt; src/clojureql/backend/mysql.clj    |   36 ++++++++++++++------------&lt;br /&gt; src/clojureql/backend/postgres.clj |   42 ++++++++++++++++++------------&lt;br /&gt; src/clojureql/demos/derby.clj      |   11 ++++---&lt;br /&gt; src/clojureql/demos/mysql.clj      |   16 ++++-------&lt;br /&gt; src/clojureql/demos/postgres.clj   |    2 +-&lt;br /&gt; src/clojureql/frontend.clj         |   18 ++++++++++---&lt;br /&gt; 9 files changed, 134 insertions(+), 72 deletions(-)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;That was simple, isn't it? Joking aside, this may look complex to theuninitiated, but it all make sense once you get used to it. One lastthing, after going through this, I had still to push my rebased commitsto the Gitorious clone. It didn't worked!&lt;/p&gt;&lt;pre class="code"&gt;$ git push origin master&lt;br /&gt;Enter passphrase for key '/home/budu/.ssh/id_rsa': &lt;br /&gt;To git@gitorious.org:~budu/clojureql/budu-clojureql.git&lt;br /&gt; ! [rejected]        master -&amp;gt; master (non-fast forward)&lt;br /&gt;error: failed to push some refs to 'git@gitorious.org:~budu/clojureql/budu-clojureql.git'&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;That problem happen to be caused by a safety measure that prevents usersfrom pushing a "non-fast forward" branch to a remote repository. Simplyput, when you mess to much with your repository it could end up causingtroubles, here's&lt;a href="http://kerneltrap.org/mailarchive/git/2007/11/18/425774"&gt;a message&lt;/a&gt;from Git's mailing list archives explaining things moreproperly. You need to use the &lt;tt&gt;--force&lt;/tt&gt; option to circumvent thatprotection.&lt;/p&gt;&lt;pre class="code"&gt;$ git push --force origin master&lt;br /&gt;Enter passphrase for key '/home/budu/.ssh/id_rsa': &lt;br /&gt;Counting objects: 117, done.&lt;br /&gt;Compressing objects: 100% (41/41), done.&lt;br /&gt;Writing objects: 100% (103/103), 17.40 KiB, done.&lt;br /&gt;Total 103 (delta 70), reused 80 (delta 62)&lt;br /&gt;To git@gitorious.org:~budu/clojureql/budu-clojureql.git&lt;br /&gt; + 42eb50e...5f771d6 master -&amp;gt; master (forced update)&lt;br /&gt;=&amp;gt; Syncing Gitorious... [OK]&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;I wrote this post for those who are in a hurry and didn't had the timeto learn Git properly before contributing to some project. But rememberthat it's always better to RTFM!&lt;/p&gt;&lt;p&gt;&lt;a href="http://xkcd.com/293/"&gt;&lt;img alt="RTFM" src="http://imgs.xkcd.com/comics/rtfm.png" style="margin: auto; display: block;"&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-3054803845972660677?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/3054803845972660677/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/reading-fckng-manual.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/3054803845972660677'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/3054803845972660677'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/reading-fckng-manual.html' title='Reading The F*ck!ng Manual'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-6440166418208829369</id><published>2009-12-14T20:57:00.004-05:00</published><updated>2009-12-15T01:26:26.863-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='clojureql'/><category scheme='http://www.blogger.com/atom/ns#' term='JDBC'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><category scheme='http://www.blogger.com/atom/ns#' term='debugging'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Debugging ClojureQL</title><content type='html'>&lt;p&gt;Since last week, I embarked on a new endeavor, contributing to&lt;a href="http://www.gitorious.org/clojureql/"&gt;ClojureQL&lt;/a&gt;. My future projectsinvolving Clojure will need to access some databases, some of whichdoesn't behave in the same way. Sure, there is already&lt;a href="http://richhickey.github.com/clojure-contrib/sql-api.html"&gt;clojure.contrib.sql&lt;/a&gt;that give you a wrapper around JDBC, but there are two problems withthis approach. First, JDBC is quite good at what it does, yet is not avery sophisticated tool. It gives you access to a portable subset ofSQL, that mainly support querying and updating data. This leaves a lotof advanced features out of the deal, these are still available, but ina non-portable way. The other way around, if a database system doesn'tsupport a common feature, JDBC cannot do anything about it, the drivercan though. Second, the clojure.contrib sql API has a very proceduralfeeling to it, it doesn't even let you play with an intermediary form asit executes statement directly. That's enough for basic databaseinteractions, but not for serious database agnostic development.&lt;/p&gt;&lt;p&gt;Lets put off advocacy and talk about something concrete. Whilecontributing to ClojureQL, I realized there was some issues withdebugging. It was working well for Postgres (which I had an instancerunning), I simply had to use the &lt;tt&gt;compile-sql&lt;/tt&gt; method. A problemarise when you're testing changes that affect all backends. In thiscase, to verify the SQL generated, you need a server for each DBMS youwant to test. After looking at the code for some time, I found an easyway of compiling statements without a connection. We can create mockobjects using Clojure's proxy macro and use them instead of liveconnections. For now, it's really simple as no backend implementationsare actually using the connector. To put this idea into practice, Iwrote a macro to debug multiple databases.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defmacro&lt;/span&gt; &lt;span class="function-name"&gt;debug&lt;/span&gt; [db ast]&lt;br /&gt;  (&lt;span class="keyword"&gt;let&lt;/span&gt; [connector (.getName (db *connectors*))]&lt;br /&gt;    `(compile-sql ~ast (&lt;span class="builtin"&gt;proxy&lt;/span&gt; [~(symbol connector)] []))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It uses a map containing the interfaces used by all backends withkeywords as keys.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;*connectors*&lt;/span&gt; {&lt;br /&gt;  &lt;span class="builtin"&gt;:postgres&lt;/span&gt; org.postgresql.PGConnection&lt;br /&gt;  &lt;span class="builtin"&gt;:mysql&lt;/span&gt;    com.mysql.jdbc.Connection&lt;br /&gt;  &lt;span class="builtin"&gt;:derby&lt;/span&gt;    org.apache.derby.iapi.jdbc.EngineConnection&lt;br /&gt;  &lt;span class="builtin"&gt;:generic&lt;/span&gt;  Object})&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;With this code you can easily debug statements for every databasesClojureQL support. We can add a final touch to be able to see the SQLoutput for all backends with a single command.&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defmacro&lt;/span&gt; &lt;span class="function-name"&gt;debug-and-print-all&lt;/span&gt; [ast]&lt;br /&gt;  (&lt;span class="keyword"&gt;let&lt;/span&gt; [longest (&lt;span class="builtin"&gt;reduce&lt;/span&gt; max (&lt;span class="builtin"&gt;map&lt;/span&gt; (comp count str) (&lt;span class="builtin"&gt;keys&lt;/span&gt; *connectors*)))&lt;br /&gt;        debug-and-print (&lt;span class="keyword"&gt;fn&lt;/span&gt; [db] `(println (&lt;span class="builtin"&gt;format&lt;/span&gt; (&lt;span class="builtin"&gt;str&lt;/span&gt; &lt;span class="string"&gt;"%1$-"&lt;/span&gt; ~longest &lt;span class="string"&gt;"s : %2$s"&lt;/span&gt;)&lt;br /&gt;                                           ~(subs (&lt;span class="builtin"&gt;str&lt;/span&gt; db) 1)&lt;br /&gt;                                           (debug ~db ~ast))))]&lt;br /&gt;    `(&lt;span class="keyword"&gt;do&lt;/span&gt; ~@(&lt;span class="builtin"&gt;map&lt;/span&gt; debug-and-print (&lt;span class="builtin"&gt;keys&lt;/span&gt; *connectors*)))))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Finally, you can see an example output of the &lt;tt&gt;debug-and-show-all&lt;/tt&gt;macro at the REPL using my ClojureQL&lt;a href="http://gitorious.org/%7Ebudu/clojureql/budu-clojureql"&gt;clone&lt;/a&gt;.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;clojureql-test&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(debug-and-print-all (create-table test [id int title text date date] :non-nulls * :primary-key id :auto-inc id :unique title))&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;postgres  : CREATE TABLE test (id SERIAL,title text NOT NULL,date date NOT NULL,PRIMARY KEY ("id"),UNIQUE ("title"))&lt;br /&gt;mysql     : CREATE TABLE test (id int NOT NULL  AUTO_INCREMENT ,title text NOT NULL ,date date NOT NULL ,PRIMARY KEY (`id`),UNIQUE (`title`)) &lt;br /&gt;derby     : CREATE TABLE test (id int NOT NULL  GENERATED ALWAYS AS IDENTITY ,title text NOT NULL ,date date NOT NULL ,PRIMARY KEY ("id"),UNIQUE ("title"))&lt;br /&gt;generic   : CREATE TABLE test (id int NOT NULL ,title text NOT NULL ,date date NOT NULL ,PRIMARY KEY ("id"),UNIQUE ("title"))&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This code is not working properly on the main repository for the moment,as there's some issues with Derby and the generic backend. It's enoughfor this post, I'll go back hacking my way through ClojureQL code tofind other useful tricks and speed up version 1.0 release.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-6440166418208829369?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/6440166418208829369/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/debugging-clojureql.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/6440166418208829369'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/6440166418208829369'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/debugging-clojureql.html' title='Debugging ClojureQL'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-1136298750924803892</id><published>2009-12-08T19:48:00.001-05:00</published><updated>2009-12-08T19:50:26.815-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='law'/><category scheme='http://www.blogger.com/atom/ns#' term='somalia'/><title type='text'>Somalia's Customary Law</title><content type='html'>&lt;p&gt;In recent years, there have been a growing interest for decentralization in information technologies. It started a long time ago when personal computers (and before them mini computers) replaced the gigantic computers of yore. Today many topics in software development are focused on decentralization like distributed version control, offline web applications or database sharding. This concept can be applied to many things around us, it's not only pertaining to our domain. Lately I've been thinking about what I called then a decentralized legal system. That name wasn't quite good and I learned that the correct term is &lt;a href="http://en.wikipedia.org/wiki/Polycentric_law"&gt;polycentric law&lt;/a&gt;. This type of legal structure has not been studied widely, and only managed to gather interest from researchers since the beginning of the nineties. It's at that time that Tom W. Bell wrote a paper entitled &lt;a href="http://osf1.gmu.edu/%7Eihs/w91issues.html"&gt;"Polycentric Law"&lt;/a&gt; in which he described various examples of such systems like the Anglo-Saxon customary law. For him, there are six main features that embodies this concept:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Focus on individual rights&lt;/li&gt;&lt;li&gt; Law is enforced by reciprocal agreement between the victim(s) and the accused party&lt;/li&gt;&lt;li&gt; Common procedures to help maintain order&lt;/li&gt;&lt;li&gt; Punitive actions are centered around paying back for the wrong deeds committed&lt;/li&gt;&lt;li&gt; Social exclusion is used as a powerful incentive&lt;/li&gt;&lt;li&gt; Laws are more adaptive to traditions and customs.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Anglo-Saxon's customary law had a surety mechanism known as borh (not to be confused with a &lt;a href="http://en.wikipedia.org/wiki/Burh"&gt;Burh&lt;/a&gt;) that was the foundation of their whole system of law. It was really simple, you take a dozen persons and then bind them together by making them pledge to be responsible for each others' actions. If one of them got a fine, the group must pay it, thus ill-behaving members were not very popular and could even be expelled . It's sure such a system wouldn't work well in modern time Britain, or in other prosperous countries for that matter, but at the time these were very coercive incentives. There were a lot of other similar customary law in ancient times, but we can find other kinds of decentralized systems in more civilized political structures. The Roman law for example allowed indigenous legal systems for non-Romans, some of which being polycentric, and we could also consider the European Union has a form of decentralized (or multi-layered) superstate, but it would be a stretch to call these polycentric.&lt;/p&gt;&lt;p&gt;This brings us to the subject of the day, Somalia's customary law, the &lt;a href="http://en.wikipedia.org/wiki/Xeer"&gt;Xeer&lt;/a&gt; legal system. It's what has defined Somalia since its inception and is more than thirteen centuries old. It has been and still is the principal way for Somalis to resolve their disputes despite the introduction of the Shari'a (which by the way is widely used for common civil cases like marriage, divorce, inheritance, etc.) and the Italian colonial government's attempt to get rid of traditional laws before the independence. The Xeer is considered to be completely indigenous and is perhaps the most evolved form of polycentric legal system still in use today. In spite of all the political instability, piracy and other problems plaguing this country, Somalia's economy is thriving compared to most other African nations and some people are attributing that to their customary law. Even the government of the autonomous &lt;a href="http://en.wikipedia.org/wiki/Somaliland"&gt;Somaliland&lt;/a&gt; region have tried to make themselves more legitimate in the eyes of their citizens by appointing elders (the Xeer system's judges) into the upper house of parliament, but that apparently didn't worked so well. I must note that Xeer's popularity in that part of Somalia is more pronounced as a consequence of the more relaxed British attitude toward their colonies. Since the Somalis gained their independence in the sixties, the state of Somalia had introduced various civil law systems, but the Xeer always prevailed, even with &lt;a href="http://en.wikipedia.org/wiki/Siad_Barre"&gt;Siad Barre&lt;/a&gt; efforts to forbade clanism and promote centralization.&lt;/p&gt;&lt;p&gt;The law is defined in terms of property rights, which make it more compensatory than punitive. This characteristic by itself position this system as a great example of the previously defined concept of polycentric law. Another aspect is shown in the way fines are being paid, not to a court or the government, but directly to the victim(s). There's also many traditions that aren't polycentric, like bigger fines for prominent members of society than for commoners and a staunch opposition to any form of taxation. It's a very secular system too, keeping religious concerns out of the way. It also normally takes precedence over the Shari'a, even though both systems generally occupy different niches.  There's a saying in Somalia that say:&lt;/p&gt;&lt;blockquote&gt;Diinta waa labaddali karaa, xeer se lam baddali karo&lt;/blockquote&gt;&lt;p&gt;That means "One can change his religion, one cannot change the law", which shows how secularism is ingrained into Somalis collective consciousness, especially when considering the predominance of a single religion in that country for centuries. But their secularism go even further than what is known elsewhere, as the law is even considered to be separated from the government. The Xeer system is administered and enforced by civilians, not government officials.&lt;/p&gt;&lt;p&gt;There's an insurance mechanism not unlike the bohr surety system. Generally the family of the offender is designated to be responsible for its actions, thus rehabilitation of criminals is the business of their respective family. In extreme cases, the family can disown one of its member, who then automatically becomes an outlaw. In such case, the only option left for the criminal is to leave the country. The way everything works can bear surprising resemblance to modern systems of law. There is specialized functions that can be related to the common court and law enforcement functions:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Odayal (Judges)&lt;/li&gt;&lt;li&gt; Xeer Boggeyaal (Jurists)&lt;/li&gt;&lt;li&gt; Guurtiyaal (Detectives)&lt;/li&gt;&lt;li&gt; Garxajiyaal (Attorneys)&lt;/li&gt;&lt;li&gt; Murkhaatiyal (Witnesses)&lt;/li&gt;&lt;li&gt; Waranle (Police officers)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Each Somali is appointed an Odayal at birth, chosen after long deliberation by the elders of the clan. That title can be lost at some point if the community decide so, making judges more careful and taking the interest of the collectivity before their own clan. The way everything work is intimately tied to the complex Somali family clan structure. This system is even scalable, as the Xeer not only rule disputes inside and between clans, but also across regions, between alliances of clans called jilibs. I won't go into the details of court procedures as this is pretty boring stuff (comically googling "pretty boring stuff" yields &lt;em&gt;`Pretty Boring Stuff': District Judges and Housing Possession Proceedings&lt;/em&gt; as first result!) One thing to note is the preponderance of oaths to settle complex situations, for instance when the witnesses contradict each other or if there's not enough witnesses to confirm a story. In these times, the person in question is asked to take an oath like "I swear by my virility." or "I swear by Allah.". Some oaths can even have consequences like the divorce oath that, if broken, make a marriage vow null and void. There's also a oath of innocence that the accused must take when the plaintiff fails to convince the judge of the validity of his case.&lt;/p&gt;&lt;p&gt;To wrap up everything, the long time appreciation of the Xeer by Somalis seems to be in sharp contrast with the usual cynicism most peoples have toward their legal system. Even though Somalia as its problems, which are less dramatic than some would have us to believe, it doesn't mean there's nothing to be learned from them.&lt;/p&gt;&lt;h3&gt;Further Readings&lt;/h3&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://www.hiiraan.com/op2/2008/oct/back_to_somali_roots.aspx"&gt;http://www.hiiraan.com/op2/2008/oct/back_to_somali_roots.aspx&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://rkba.org/libertarian/maccallum/MacCallum-Somalia98.html"&gt;http://rkba.org/libertarian/maccallum/MacCallum-Somalia98.html&lt;/a&gt;&lt;/li&gt;&lt;li&gt; &lt;a href="http://www.peterleeson.com/Better_Off_Stateless.pdf"&gt;http://www.peterleeson.com/Better_Off_Stateless.pdf&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/5409861124354728237-1136298750924803892?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/1136298750924803892/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/somalias-customary-law.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/1136298750924803892'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/1136298750924803892'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/somalias-customary-law.html' title='Somalia&apos;s Customary Law'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-7824146465139055013</id><published>2009-12-03T20:39:00.003-05:00</published><updated>2010-01-31T12:15:28.046-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='closure'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='task-tutorial'/><title type='text'>Closure Library Tutorial: Tasks (part 1)</title><content type='html'>&lt;p&gt;Today we'll create something with the recently released&lt;a href="http://code.google.com/closure/library/"&gt;Closure Library&lt;/a&gt;. Nothingamazing, just a simple application to manage daily tasks (I would haveliked to say a 'task manager' but that could be confusing). JavaScripthas finally matured and with this library, we nearly have all the bellsand whistles of a desktop environment graphical user interface.&lt;/p&gt;&lt;p&gt;The Closure Library is part of a set of tools that Google's engineershave been working on for&lt;a href="http://erik.eae.net/archives/2009/11/05/22.27.29/"&gt;quite some time&lt;/a&gt;and they have more than &lt;a href="http://mail.google.com"&gt;proved&lt;/a&gt;&lt;a href="http://maps.google.com"&gt;their&lt;/a&gt; &lt;a href="http://docs.google.com"&gt;worth&lt;/a&gt;. Eventhough not directly based on it, this library has been greatlyinfluenced by the &lt;a href="http://dojotoolkit.org/"&gt;Dojo Toolkit&lt;/a&gt;. I've neverused that framework before, most of my experience being with jQuery, soI'm not really qualified to compare them, learning Closure will beenough for now. Lets list some of the features offered by this library:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; History management&lt;/li&gt;&lt;li&gt; Solid and portable event handling (with a timer class and ways to delay or throttle events)&lt;/li&gt;&lt;li&gt; Internationalization&lt;/li&gt;&lt;li&gt; Basic support for spell checkers&lt;/li&gt;&lt;li&gt; A complete debugging and testing framework&lt;/li&gt;&lt;li&gt; DOM manipulation helpers&lt;/li&gt;&lt;li&gt; Code to help working with data sets&lt;/li&gt;&lt;li&gt; Cross-browser support for drawing using &lt;a href="http://en.wikipedia.org/wiki/Svg"&gt;SVG&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Vml"&gt;VML&lt;/a&gt; or the new HTML5 &lt;a href="http://en.wikipedia.org/wiki/Canvas_%28HTML_element%29"&gt;canvas element&lt;/a&gt;&lt;/li&gt;&lt;li&gt; A module system to dynamically load compiled JavaScript code&lt;/li&gt;&lt;li&gt; UI widgets and effects&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And there's much more! All that backed up by the company that dominatethe web, how could we ask for more? So in this tutorial we'llconcentrate on two areas: UI widgets and event handling. I know, this isalready covered in the&lt;a href="http://code.google.com/closure/library/docs/tutorial.html"&gt;Google code tutorial&lt;/a&gt;,but I found it to be plain and boring, that library deserves more.&lt;/p&gt;&lt;p&gt;This tutorial will build upon the official one, just refer toit if you're lost at some point. We begin by declaring the namespaceswe'll be providing and we'll also require the &lt;tt&gt;goog.dom&lt;/tt&gt; namespaceand the UI widgets we need.&lt;/p&gt;&lt;pre class="code"&gt;goog.provide(&lt;span class="string"&gt;'mu.tutorial.tasks'&lt;/span&gt;);&lt;br /&gt;goog.provide(&lt;span class="string"&gt;'mu.tutorial.tasks.Task'&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;goog.require(&lt;span class="string"&gt;'goog.dom'&lt;/span&gt;);&lt;br /&gt;goog.require(&lt;span class="string"&gt;'goog.ui.CustomButton'&lt;/span&gt;);&lt;br /&gt;goog.require(&lt;span class="string"&gt;'goog.ui.Toolbar'&lt;/span&gt;);&lt;br /&gt;goog.require(&lt;span class="string"&gt;'goog.ui.ToolbarButton'&lt;/span&gt;);&lt;br /&gt;goog.require(&lt;span class="string"&gt;'goog.ui.Zippy'&lt;/span&gt;);&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;First thing to note is that we have to provide not only the namespace,but the name of all classes in that namespace. That's because classesaren't really classes, they're still just plain JaveScriptprototypes. So class names are simply namespaces and are thus orthogonalto the inheritance mechanism. You can inherit from classes that aren'tbeing provided by any namespace. The &lt;tt&gt;provide&lt;/tt&gt; and &lt;tt&gt;require&lt;/tt&gt;functions are only there to check for dependencies and are also used bythe debug resolver.&lt;/p&gt;&lt;p&gt;Now lets add something new, a toolbar. Before delving into code, we'llhave to go fetch the stylesheet and images that can be found in the&lt;tt&gt;goog/demos&lt;/tt&gt; folder. In the meantime you can also get those for thebutton widget that we'll use later on. Another file not to forget isthe &lt;tt&gt;common.css&lt;/tt&gt; stylesheet that contains styles common to allwidgets. Here's all the code needed to create our toolbar.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.switchPanel = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;target&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;        goog.dom.$(&lt;span class="string"&gt;'taskList'&lt;/span&gt;).style.display = &lt;span class="string"&gt;"none"&lt;/span&gt;;&lt;br /&gt;        goog.dom.$(&lt;span class="string"&gt;'completedTaskList'&lt;/span&gt;).style.display = &lt;span class="string"&gt;"none"&lt;/span&gt;;&lt;br /&gt;        goog.dom.$(&lt;span class="string"&gt;'deletedTaskList'&lt;/span&gt;).style.display = &lt;span class="string"&gt;"none"&lt;/span&gt;;&lt;br /&gt;        goog.dom.$(&lt;span class="string"&gt;'settings'&lt;/span&gt;).style.display = &lt;span class="string"&gt;"none"&lt;/span&gt;;&lt;br /&gt;        goog.dom.$(target).style.display = &lt;span class="string"&gt;"block"&lt;/span&gt;;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.attachToolbarButton = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;toolbar&lt;/span&gt;, &lt;span class="variable-name"&gt;label&lt;/span&gt;, &lt;span class="variable-name"&gt;tooltip&lt;/span&gt;, &lt;span class="variable-name"&gt;target&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;button&lt;/span&gt; = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;goog.ui.ToolbarButton&lt;/span&gt;(label);&lt;br /&gt;    button.setTooltip(tooltip);&lt;br /&gt;    toolbar.addChild(button, &lt;span class="constant"&gt;true&lt;/span&gt;);&lt;br /&gt;    goog.events.listen(button.getContentElement(), goog.events.EventType.CLICK,&lt;br /&gt;        mu.tutorial.tasks.switchPanel(target));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.attachToolbar = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;container&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;toolbar&lt;/span&gt; = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;goog.ui.Toolbar&lt;/span&gt;();&lt;br /&gt;&lt;br /&gt;    mu.tutorial.tasks.attachToolbarButton(toolbar, &lt;span class="string"&gt;'Tasks'&lt;/span&gt;, &lt;span class="string"&gt;'List currently active tasks.'&lt;/span&gt;, &lt;span class="string"&gt;'taskList'&lt;/span&gt;);&lt;br /&gt;    mu.tutorial.tasks.attachToolbarButton(toolbar, &lt;span class="string"&gt;'Completed'&lt;/span&gt;, &lt;span class="string"&gt;'List completed tasks.'&lt;/span&gt;, &lt;span class="string"&gt;'completedTaskList'&lt;/span&gt;);&lt;br /&gt;    mu.tutorial.tasks.attachToolbarButton(toolbar, &lt;span class="string"&gt;'Trash'&lt;/span&gt;, &lt;span class="string"&gt;'List deleted tasks.'&lt;/span&gt;, &lt;span class="string"&gt;'deletedTaskList'&lt;/span&gt;);&lt;br /&gt;    mu.tutorial.tasks.attachToolbarButton(toolbar, &lt;span class="string"&gt;'Settings'&lt;/span&gt;, &lt;span class="string"&gt;'Settings'&lt;/span&gt;, &lt;span class="string"&gt;'settings'&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    toolbar.render(container);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;First, we create a toolbar, then attach all buttons and finally rendereverything in the given container. There's a function to help us attachbuttons, it add a tooltip and an event listener to switch between thevarious panels. Those panels are just a bunch of &lt;tt&gt;div&lt;/tt&gt;s that areshown successively by changing their display attribute, using the&lt;tt&gt;switchPanel&lt;/tt&gt; function.&lt;/p&gt;&lt;p&gt;For an application managing tasks, we better create a Task class. Thispart of the tutorial is very similar to Google's one. Task objects arestructurally identical to Note objects, with &lt;tt&gt;summary&lt;/tt&gt; instead&lt;tt&gt;title&lt;/tt&gt; and &lt;tt&gt;description&lt;/tt&gt; instead of &lt;tt&gt;content&lt;/tt&gt; asproperties.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;data&lt;/span&gt;, &lt;span class="variable-name"&gt;container&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.summary = data.summary;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.description = data.description;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.priority = data.priority;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.parent = container;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.closeEditor = &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;  &lt;span class="constant"&gt;this&lt;/span&gt;.contentElement.innerHTML = &lt;span class="constant"&gt;this&lt;/span&gt;.description;&lt;br /&gt;  &lt;span class="constant"&gt;this&lt;/span&gt;.contentElement.style.display = &lt;span class="string"&gt;"block"&lt;/span&gt;;&lt;br /&gt;  &lt;span class="constant"&gt;this&lt;/span&gt;.editorContainer.style.display = &lt;span class="string"&gt;"none"&lt;/span&gt;;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.openEditor = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;e&lt;/span&gt;) {&lt;br /&gt;  &lt;span class="constant"&gt;this&lt;/span&gt;.editorElement.value = &lt;span class="constant"&gt;this&lt;/span&gt;.description;&lt;br /&gt;  &lt;span class="constant"&gt;this&lt;/span&gt;.contentElement.style.display = &lt;span class="string"&gt;"none"&lt;/span&gt;;&lt;br /&gt;  &lt;span class="constant"&gt;this&lt;/span&gt;.editorContainer.style.display = &lt;span class="string"&gt;"inline"&lt;/span&gt;;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.save = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;e&lt;/span&gt;) {&lt;br /&gt;  &lt;span class="constant"&gt;this&lt;/span&gt;.description = &lt;span class="constant"&gt;this&lt;/span&gt;.editorElement.value;&lt;br /&gt;  &lt;span class="constant"&gt;this&lt;/span&gt;.closeEditor();&lt;br /&gt;};&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;As you see, there's nothing new here. I've also included the functionsthat will be used by the editor, then again, same thing as the officialtutorial. We'll only add one new kind of action. In fact this is afunction returning a &lt;a href="http://en.wikipedia.org/wiki/Closure_%28computer_science%29"&gt;closure&lt;/a&gt;that will move a task to the desired panel.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.clickActionButton = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;task&lt;/span&gt;, &lt;span class="variable-name"&gt;element&lt;/span&gt;, &lt;span class="variable-name"&gt;target&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;e&lt;/span&gt;) {&lt;br /&gt;        &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;parent&lt;/span&gt; = element.parentNode;&lt;br /&gt;        parent.removeChild(element);&lt;br /&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (parent.childNodes.length == 0)&lt;br /&gt;            mu.tutorial.tasks.noTasks(parent);&lt;br /&gt;&lt;br /&gt;        task.parent = mu.tutorial.tasks.taskLists[target];&lt;br /&gt;        &lt;span class="keyword"&gt;if&lt;/span&gt; (task.parent.childNodes[0].className == &lt;span class="string"&gt;'empty'&lt;/span&gt;)&lt;br /&gt;            task.parent.removeChild(task.parent.childNodes[0]);&lt;br /&gt;&lt;br /&gt;        task.makeDom();&lt;br /&gt;        e.stopPropagation();&lt;br /&gt;    };&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Here, the noTasks function only create an &lt;tt&gt;h2&lt;/tt&gt; header tag to tellthe user there's no tasks in that panel. The &lt;tt&gt;taskLists&lt;/tt&gt; namespacevariable is a simple dictionary that we'll fill later.&lt;/p&gt;&lt;p&gt;We're now ready to jump to the &lt;tt&gt;makeDom&lt;/tt&gt; function, where we createthe DOM structure for a task, add buttons, wire up event listeners anduse the Zippy class.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.makeDom = &lt;span class="keyword"&gt;function&lt;/span&gt;() {&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.summaryDiv = goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, { &lt;span class="string"&gt;'class'&lt;/span&gt;: &lt;span class="string"&gt;'summary'&lt;/span&gt; }, &lt;span class="constant"&gt;this&lt;/span&gt;.summary);&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.contentElement = goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, { &lt;span class="string"&gt;'class'&lt;/span&gt;: &lt;span class="string"&gt;'description'&lt;/span&gt; }, &lt;span class="constant"&gt;this&lt;/span&gt;.description);&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.editorElement = goog.dom.createDom(&lt;span class="string"&gt;'textarea'&lt;/span&gt;, &lt;span class="constant"&gt;null&lt;/span&gt;, &lt;span class="string"&gt;''&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.editorContainer = goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, {&lt;span class="string"&gt;'style'&lt;/span&gt;: &lt;span class="string"&gt;'display:none;'&lt;/span&gt;},&lt;br /&gt;                                              &lt;span class="constant"&gt;this&lt;/span&gt;.editorElement);&lt;br /&gt;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.descriptionContainer = goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, &lt;span class="constant"&gt;null&lt;/span&gt;,&lt;br /&gt;                                                   &lt;span class="constant"&gt;this&lt;/span&gt;.contentElement,&lt;br /&gt;                                                   &lt;span class="constant"&gt;this&lt;/span&gt;.editorContainer);&lt;br /&gt;&lt;br /&gt;    &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;taskDiv&lt;/span&gt; = goog.dom.createDom(&lt;span class="string"&gt;'div'&lt;/span&gt;, { &lt;span class="string"&gt;'class'&lt;/span&gt;: &lt;span class="string"&gt;'task'&lt;/span&gt; },&lt;br /&gt;                                     &lt;span class="constant"&gt;this&lt;/span&gt;.summaryDiv,&lt;br /&gt;                                     &lt;span class="constant"&gt;this&lt;/span&gt;.descriptionContainer);&lt;br /&gt;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.parent.appendChild(taskDiv);&lt;br /&gt;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.makeButtons(&lt;span class="constant"&gt;this&lt;/span&gt;, taskDiv);&lt;br /&gt;&lt;br /&gt;    goog.events.listen(&lt;span class="constant"&gt;this&lt;/span&gt;.contentElement, goog.events.EventType.CLICK,&lt;br /&gt;                       &lt;span class="constant"&gt;this&lt;/span&gt;.openEditor, &lt;span class="constant"&gt;false&lt;/span&gt;, &lt;span class="constant"&gt;this&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.zippy = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;goog.ui.Zippy&lt;/span&gt;(&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.summaryDiv,&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.descriptionContainer);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;There is two type of button for tasks, action buttons that will use the&lt;tt&gt;clickActionButton&lt;/tt&gt; function and editor buttons for the editor. Eachtype of button has a function to create them, add a class name, renderthem and add an event listener.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.makeActionButton = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;task&lt;/span&gt;, &lt;span class="variable-name"&gt;element&lt;/span&gt;, &lt;span class="variable-name"&gt;target&lt;/span&gt;, &lt;span class="variable-name"&gt;name&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;button&lt;/span&gt; = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;goog.ui.CustomButton&lt;/span&gt;(name);&lt;br /&gt;&lt;br /&gt;    button.addClassName(&lt;span class="string"&gt;'taskButton'&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    button.render(element.childNodes[0]);&lt;br /&gt;&lt;br /&gt;    goog.events.listen(&lt;br /&gt;        button.getContentElement(),&lt;br /&gt;        goog.events.EventType.CLICK,&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.clickActionButton(task, element, target));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.makeEditorButton = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;element&lt;/span&gt;, &lt;span class="variable-name"&gt;name&lt;/span&gt;, &lt;span class="variable-name"&gt;callback&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;button&lt;/span&gt; = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;goog.ui.CustomButton&lt;/span&gt;(name);&lt;br /&gt;&lt;br /&gt;    button.addClassName(&lt;span class="string"&gt;'editorButton'&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    button.render(element.childNodes[1].childNodes[1]);&lt;br /&gt;&lt;br /&gt;    goog.events.listen(&lt;br /&gt;        button.getContentElement(),&lt;br /&gt;        goog.events.EventType.CLICK,&lt;br /&gt;        callback, &lt;span class="constant"&gt;false&lt;/span&gt;, &lt;span class="constant"&gt;this&lt;/span&gt;);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Here's the &lt;tt&gt;makeButtons&lt;/tt&gt; function.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.Task.&lt;span class="constant"&gt;prototype&lt;/span&gt;.makeButtons = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;task&lt;/span&gt;, &lt;span class="variable-name"&gt;element&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (task.parent.id == &lt;span class="string"&gt;'taskList'&lt;/span&gt;) {&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.makeActionButton(task, element, &lt;span class="string"&gt;'completed'&lt;/span&gt;, &lt;span class="string"&gt;'Done'&lt;/span&gt;);&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.makeActionButton(task, element, &lt;span class="string"&gt;'deleted'&lt;/span&gt;, &lt;span class="string"&gt;'Delete'&lt;/span&gt;);&lt;br /&gt;    }&lt;br /&gt;    &lt;span class="keyword"&gt;else&lt;/span&gt;&lt;br /&gt;        &lt;span class="constant"&gt;this&lt;/span&gt;.makeActionButton(task, element, &lt;span class="string"&gt;'active'&lt;/span&gt;, &lt;span class="string"&gt;'Undo'&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.makeEditorButton(element, &lt;span class="string"&gt;'Save'&lt;/span&gt;, &lt;span class="constant"&gt;this&lt;/span&gt;.save);&lt;br /&gt;    &lt;span class="constant"&gt;this&lt;/span&gt;.makeEditorButton(element, &lt;span class="string"&gt;'Cancel'&lt;/span&gt;, &lt;span class="constant"&gt;this&lt;/span&gt;.closeEditor);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;The action buttons are attached to each tasks depending if the given taskis active or not. Active tasks can be deleted or marked as completed andwe can reactivate them after that. All tasks will remain editable fornow, so we add the editor buttons to each one of them.&lt;/p&gt;&lt;p&gt;Finally, the last function takes a bunch of raw task data, create Taskobjects and call their &lt;tt&gt;makeDom&lt;/tt&gt; method, inserting them into thegiven container. It also fills the &lt;tt&gt;taskLists&lt;/tt&gt; dictionary entry forthat list.&lt;/p&gt;&lt;pre class="code"&gt;mu.tutorial.tasks.makeTasks = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;name&lt;/span&gt;, &lt;span class="variable-name"&gt;data&lt;/span&gt;, &lt;span class="variable-name"&gt;container&lt;/span&gt;) {&lt;br /&gt;    &lt;span class="keyword"&gt;if&lt;/span&gt; (data.length == 0)&lt;br /&gt;        mu.tutorial.tasks.noTasks(container);&lt;br /&gt;    &lt;span class="keyword"&gt;else&lt;/span&gt;&lt;br /&gt;        &lt;span class="keyword"&gt;for&lt;/span&gt; (&lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;i&lt;/span&gt; = 0; i &amp;lt; data.length; i++) {&lt;br /&gt;            &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;task&lt;/span&gt; = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="type"&gt;mu.tutorial.tasks.Task&lt;/span&gt;(data[i], container);&lt;br /&gt;            task.makeDom();&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;    mu.tutorial.tasks.taskLists[name] = container;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;We still have to make use of all that code, an HTML page will containthe remaining parts. We only need one div for the toolbar, then fourothers for each sections of our application. After that we add some moreJavaScript to attach the toolbar and create some tasks to test ifeverything is working.&lt;/p&gt;&lt;pre class="code"&gt;  &amp;lt;&lt;span class="function-name"&gt;body&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span class="function-name"&gt;h1&lt;/span&gt;&amp;gt;&lt;span class="underline"&gt;&lt;span class="bold"&gt;Closure Library Tutorial&lt;/span&gt;&lt;/span&gt;&amp;lt;/&lt;span class="function-name"&gt;h1&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span class="function-name"&gt;div&lt;/span&gt; &lt;span class="variable-name"&gt;id&lt;/span&gt;=&lt;span class="string"&gt;"menu"&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span class="function-name"&gt;div&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span class="function-name"&gt;div&lt;/span&gt; &lt;span class="variable-name"&gt;id&lt;/span&gt;=&lt;span class="string"&gt;"taskList"&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span class="function-name"&gt;div&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span class="function-name"&gt;div&lt;/span&gt; &lt;span class="variable-name"&gt;id&lt;/span&gt;=&lt;span class="string"&gt;"completedTaskList"&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span class="function-name"&gt;div&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span class="function-name"&gt;div&lt;/span&gt; &lt;span class="variable-name"&gt;id&lt;/span&gt;=&lt;span class="string"&gt;"deletedTaskList"&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span class="function-name"&gt;div&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span class="function-name"&gt;div&lt;/span&gt; &lt;span class="variable-name"&gt;id&lt;/span&gt;=&lt;span class="string"&gt;"settings"&lt;/span&gt;&amp;gt;TODO...&amp;lt;/&lt;span class="function-name"&gt;div&lt;/span&gt;&amp;gt;&lt;br /&gt;    &amp;lt;&lt;span class="function-name"&gt;script&lt;/span&gt; &lt;span class="variable-name"&gt;type&lt;/span&gt;=&lt;span class="string"&gt;"text/javascript"&lt;/span&gt;&amp;gt;&lt;br /&gt;      &lt;span class="keyword"&gt;function&lt;/span&gt; &lt;span class="function-name"&gt;main&lt;/span&gt;() {&lt;br /&gt;&lt;br /&gt;          &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;menu&lt;/span&gt; = document.getElementById(&lt;span class="string"&gt;'menu'&lt;/span&gt;);&lt;br /&gt;          mu.tutorial.tasks.attachToolbar(menu);&lt;br /&gt;&lt;br /&gt;          &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;makeTaskData&lt;/span&gt; = &lt;span class="keyword"&gt;function&lt;/span&gt;(&lt;span class="variable-name"&gt;summary&lt;/span&gt;, &lt;span class="variable-name"&gt;description&lt;/span&gt;, &lt;span class="variable-name"&gt;priority&lt;/span&gt;) {&lt;br /&gt;              &lt;span class="keyword"&gt;return&lt;/span&gt; { &lt;span class="string"&gt;'summary'&lt;/span&gt;: summary, &lt;span class="string"&gt;'description'&lt;/span&gt;: description, &lt;span class="string"&gt;'priority'&lt;/span&gt;: priority };&lt;br /&gt;          };&lt;br /&gt;&lt;br /&gt;          &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;taskList&lt;/span&gt; = document.getElementById(&lt;span class="string"&gt;'taskList'&lt;/span&gt;);&lt;br /&gt;          mu.tutorial.tasks.makeTasks(&lt;span class="string"&gt;'active'&lt;/span&gt;, [&lt;br /&gt;              makeTaskData(&lt;span class="string"&gt;"Make the toolbar actually do something."&lt;/span&gt;, &lt;span class="string"&gt;"Need more studying of Toolbar demo."&lt;/span&gt;, 99),&lt;br /&gt;              makeTaskData(&lt;span class="string"&gt;"Make tasks editable."&lt;/span&gt;, &lt;span class="string"&gt;"Like in the Closure Library Notes tutorial."&lt;/span&gt;, 88),&lt;br /&gt;              makeTaskData(&lt;span class="string"&gt;"Create different task lists."&lt;/span&gt;, &lt;span class="string"&gt;"One for active tasks, another for completed ones and finally a list containing deleted tasks."&lt;/span&gt;, 100)&lt;br /&gt;              ], taskList);&lt;br /&gt;&lt;br /&gt;          &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;completedTaskList&lt;/span&gt; = document.getElementById(&lt;span class="string"&gt;'completedTaskList'&lt;/span&gt;);&lt;br /&gt;          mu.tutorial.tasks.makeTasks(&lt;span class="string"&gt;'completed'&lt;/span&gt;, [], completedTaskList);&lt;br /&gt;&lt;br /&gt;          &lt;span class="keyword"&gt;var&lt;/span&gt; &lt;span class="variable-name"&gt;deletedTaskList&lt;/span&gt; = document.getElementById(&lt;span class="string"&gt;'deletedTaskList'&lt;/span&gt;);&lt;br /&gt;          mu.tutorial.tasks.makeTasks(&lt;span class="string"&gt;'deleted'&lt;/span&gt;, [&lt;br /&gt;              { &lt;span class="string"&gt;'summary'&lt;/span&gt;: &lt;span class="string"&gt;'test'&lt;/span&gt;, &lt;span class="string"&gt;'description'&lt;/span&gt;: &lt;span class="string"&gt;'test'&lt;/span&gt;, &lt;span class="string"&gt;'priority'&lt;/span&gt;: 1 }&lt;br /&gt;              ], deletedTaskList);&lt;br /&gt;      }&lt;br /&gt;      main();&lt;br /&gt;    &amp;lt;/&lt;span class="function-name"&gt;script&lt;/span&gt;&amp;gt;&lt;br /&gt;  &amp;lt;/&lt;span class="function-name"&gt;body&lt;/span&gt;&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;You can grab the full HTML file&lt;a href="http://dl.dropbox.com/u/2682770/mu/tutorials/closure-library/1/tasks.html"&gt;here&lt;/a&gt;(look at the source or use "Save Link As...") and the JavaScript used&lt;a href="http://dl.dropbox.com/u/2682770/mu/tutorials/closure-library/1/tasks.js"&gt;there&lt;/a&gt;. Whentesting it, you'll notice the loading time can be long for a staticpage. Using Firebug net panel, we can see that the page ask for no lessthan 54 JavaScript files, that's a lot of requests! Over the wire, thiscan quickly become a problem, especially for high latency connections. Tofix this problem, we'll use the &lt;tt&gt;calcdeps.py&lt;/tt&gt; script found in the&lt;tt&gt;bin&lt;/tt&gt; directory. You simply have to call it like that:&lt;/p&gt;&lt;pre class="code"&gt;&amp;gt; ./closure/bin/calcdeps.py -i tasks.js -p . -o script &amp;gt; tasks.nodeps.js&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Still, it makes our little JS file a lot heavier, it now weight in at656KB, 650 more than the original. That's a lot, but can be mitigated byserver settings like file compression and expire headers.&lt;/p&gt;&lt;p&gt;Here's a&lt;a href="http://dl.dropbox.com/u/2682770/mu/tutorials/closure-library/1/tasks.nodeps.html"&gt;demo&lt;/a&gt;if you're not interested by setting up everything yourself. In the nexttutorial we'll learn to use sliders and color pickers to make thesettings panel do something and we'll make the active task list sortableby priority.&lt;/p&gt;&lt;p&gt;Any questions, corrections or insults?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-7824146465139055013?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/7824146465139055013/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/closure-library-tutorial-tasks-part-1.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/7824146465139055013'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/7824146465139055013'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/closure-library-tutorial-tasks-part-1.html' title='Closure Library Tutorial: Tasks (part 1)'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-2085542008980216353</id><published>2009-12-02T23:33:00.005-05:00</published><updated>2009-12-14T21:56:51.285-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='compojure'/><category scheme='http://www.blogger.com/atom/ns#' term='clojure'/><title type='text'>Overlooking the Obvious</title><content type='html'>&lt;p&gt;Monday, Tim Bray posted another&lt;a href="http://www.tbray.org/ongoing/When/200x/2009/11/30/Idiomatic-Clojure"&gt;article&lt;/a&gt;on Clojure in his Concur.next series. In there is some beautiful code(as idiomatic Clojure usually is) written by John Evans, in which therewas a function I wasn't familiar with. &lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(doc merge-with)&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-output"&gt;-------------------------&lt;br /&gt;clojure.core/merge-with&lt;br /&gt;([f &amp;amp; maps])&lt;br /&gt;  Returns a map that consists of the rest of the maps conj-ed onto&lt;br /&gt;  the first.  If a key occurs in more than one map, the mapping(s)&lt;br /&gt;  from the latter (left-to-right) will be combined with the mapping in&lt;br /&gt;  the result by calling (f val-in-result val-in-latter).&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;Although I get the general idea I think it's preferable to performexperiments after reading some documentation, just to be sure.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(merge-with #(+ %1 %2) {:a 1} {:a 2 :b 3} {:a 4 :b 5 :c 6})&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;{:c 6, :b 8, :a 7}&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(merge-with #(list %1 %2) {:a 1} {:a 2 :b 3} {:a 4 :b 5 :c 6})&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;{:c 6, :b (3 5), :a ((1 2) 4)}&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It does pretty much what the docs say, it's always great to find about auseful function you didn't knew about! Let's see some real code usingthis function. &lt;/p&gt;&lt;pre class="code"&gt;(&lt;span class="keyword"&gt;defn-&lt;/span&gt; &lt;span class="function-name"&gt;merge-map&lt;/span&gt;&lt;br /&gt;  &lt;span class="string"&gt;"Merges an inner map in 'from' into 'to'"&lt;/span&gt;&lt;br /&gt;  [to key from]&lt;br /&gt;  (merge-with merge to (select-keys from [key])))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This was extracted from &lt;a href="http://compojure.org/"&gt;Compojure&lt;/a&gt; source codefor response handling. It's used to merge the response's headers andsession maps when the &lt;tt&gt;update-response&lt;/tt&gt; multi-method is called on anobject of the Map class. That gave me an idea: the principle of using&lt;tt&gt;merge&lt;/tt&gt; as the &lt;em&gt;with&lt;/em&gt; function could be generalized into afunction that recursively merges maps. Let's to do it manually. &lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(merge {:a 1} {:a 2 :b 3} {:b 4 :c 5})&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;{:c 5, :b 4, :a 2}&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(merge-with merge {:a {:a 1 :b 2}} {:a {:a 3 :c 4}} {:a {:b 5 :c 6}})&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;{:a {:c 6, :a 3, :b 5}}&lt;br /&gt;&lt;/span&gt;&lt;span class="slime-repl-prompt"&gt;user&amp;gt; &lt;/span&gt;&lt;span class="slime-repl-input"&gt;(merge-with (partial merge-with merge) {:a {:a {:a 1} :b {:b 2}}} {:a {:a {:a 3} :c {:c 4}}} {:a {:b {:b 5} :c {:c 6}}})&lt;/span&gt;&lt;br /&gt;&lt;span class="slime-repl-result"&gt;{:a {:c {:c 6}, :a {:a 3}, :b {:b 5}}}&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;That's surely feasible, but I can't seems to find any use for such afunction, so I'll leave it as an exercise. The complexity of this taskcertainly negate the actual benefits gained from its limited usefulnessanyway. Is there anybody who disagree? &lt;/p&gt;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Mister Bray posted yet another  &lt;a href="http://www.tbray.org/ongoing/When/200x/2009/12/01/Clojure-Theses"&gt;article&lt;/a&gt;  on Clojure yesterday and proggit  &lt;a href="http://www.reddit.com/r/programming/comments/aa44a/eleven_theses_on_clojure/"&gt;bursted&lt;/a&gt;  in flames with relatively interesting (but heated) discussions on  Clojure versus everything under the sun. It also provoked  &lt;a href="http://www.reddit.com/r/programming/comments/aa44a/eleven_theses_on_clojure/c0gklup"&gt;craziness&lt;/a&gt;!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-2085542008980216353?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/2085542008980216353/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/overlooking-obvious.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/2085542008980216353'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/2085542008980216353'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/12/overlooking-obvious.html' title='Overlooking the Obvious'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-1477311244882187190</id><published>2009-11-18T22:20:00.024-05:00</published><updated>2009-12-14T22:06:34.023-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='wiki'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Wickedly Short Wikis</title><content type='html'>&lt;p&gt;Well well well, let's be a little more serious this time around and talkabout code. This is a technical blog (hum... interesting, a stockinstallation of emacs flyspell mode doesn't know about words like wiki,blog or even flyspell) after all, we ain't gonna talk about nothing!&lt;/p&gt;&lt;p&gt;So, today, I'll talk about wikis or, to be more precise, the softwarebehind them. We'll dissect a very simple implementation, just to seewhat they are all about. Basically, a wiki is a very simple concept,reduced to its core it's the combination of&lt;a href="http://c2.com/cgi/wiki$?WikiPrinciples"&gt;four principles&lt;/a&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt; Automatic link generation&lt;/li&gt;&lt;li&gt; Editable content&lt;/li&gt;&lt;li&gt; Simplified formating&lt;/li&gt;&lt;li&gt; Backlinks&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Upon realizing this, hordes of hackers from around the world flocked towrite the shortest one for glory and fame. And thus the &lt;a href="http://c2.com/cgi/wiki$?ShortestWikiContest"&gt;shortest wiki contest&lt;/a&gt; wasborn. As everybody expected the &lt;em&gt;winner&lt;/em&gt; used &lt;strike&gt;Perl&lt;/strike&gt; a mix of Perland shell script. This all happened a long time ago (in Internet time)and now I'll tears apart one of these very little beast.&lt;/p&gt;&lt;p&gt;I won't bother with the Perl ones as I'm not masochist. In fact, I'veactually already done it for&lt;a href="http://infomesh.net/2003/wypy/"&gt;&lt;tt&gt;wypy.py&lt;/tt&gt;&lt;/a&gt; a couple of weeksago. It's the best Python entry and also the best for a language that isnot Perl. Even then, I've started with the 23 lines version not loosetoo much time, just enough. The actual entry to the contest was 11 lineslong and contained only 814 characters, which is still nearly four timeslarger than the winning one, that's short! In this version, the code isat least divided into four functions. It begins with a simple one,&lt;tt&gt;load&lt;/tt&gt;, which returns the content of a text file if it exists, elsea string:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="comment-delimiter"&gt;#&lt;/span&gt;&lt;span class="comment"&gt; ex is a shortcut for os.path.exists&lt;/span&gt;&lt;br /&gt;&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;load&lt;/span&gt;(n): &lt;span class="keyword"&gt;return&lt;/span&gt; (ex(&lt;span class="string"&gt;'w/'&lt;/span&gt;+n) &lt;span class="keyword"&gt;and&lt;/span&gt; &lt;span class="py-builtins"&gt;open&lt;/span&gt;(&lt;span class="string"&gt;'w/'&lt;/span&gt;+n).read()) &lt;span class="keyword"&gt;or&lt;/span&gt; &lt;span class="string"&gt;''&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;This isn't really exciting, just a good usage of logical operators. Thenext one has more meat to it, let's see the code first:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;fs&lt;/span&gt;(s): &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="py-builtins"&gt;reduce&lt;/span&gt;(&lt;span class="keyword"&gt;lambda&lt;/span&gt; s, r: re.sub(&lt;span class="string"&gt;'(?m)'&lt;/span&gt;+r[0], r[1], s), ((&lt;span class="string"&gt;'\r'&lt;/span&gt;,&lt;span class="string"&gt;''&lt;/span&gt;),&lt;br /&gt;   (&lt;span class="string"&gt;'(^|[^A-Za-z0-9?])(([A-Z][a-z]+){2,})'&lt;/span&gt;, &lt;span class="keyword"&gt;lambda&lt;/span&gt; m: (m.group(1) + &lt;span class="string"&gt;'%s&amp;lt;a hr'&lt;/span&gt; \&lt;br /&gt;    &lt;span class="string"&gt;'ef=wypy?%s'&lt;/span&gt;+m.group(2)+&lt;span class="string"&gt;'%s&amp;gt;%s&amp;lt;/a&amp;gt;'&lt;/span&gt;) % ((m.group(2),&lt;span class="string"&gt;'p='&lt;/span&gt;,&lt;span class="string"&gt;'&amp;amp;amp;q=e'&lt;/span&gt;,&lt;span class="string"&gt;'?'&lt;/span&gt;),&lt;br /&gt;   (&lt;span class="string"&gt;''&lt;/span&gt;,&lt;span class="string"&gt;''&lt;/span&gt;,&lt;span class="string"&gt;''&lt;/span&gt;,m.group(2)))[ex(&lt;span class="string"&gt;'w/'&lt;/span&gt;+m.group(2))]),  (&lt;span class="string"&gt;'^\{\{$'&lt;/span&gt;,&lt;span class="string"&gt;'\n&amp;lt;ul&amp;gt;'&lt;/span&gt;),&lt;br /&gt;   (&lt;span class="string"&gt;'^\* '&lt;/span&gt;,&lt;span class="string"&gt;'&amp;lt;li&amp;gt;'&lt;/span&gt;),  (&lt;span class="string"&gt;'^}}$'&lt;/span&gt;,&lt;span class="string"&gt;'&amp;lt;/ul&amp;gt;'&lt;/span&gt;),  (&lt;span class="string"&gt;'^---$'&lt;/span&gt;,&lt;span class="string"&gt;'&amp;lt;hr&amp;gt;'&lt;/span&gt;),  (&lt;span class="string"&gt;'\n\n'&lt;/span&gt;,&lt;span class="string"&gt;'&amp;lt;p&amp;gt;'&lt;/span&gt;),&lt;br /&gt;   (&lt;span class="string"&gt;'(ht|f)tp:[^&amp;lt;&amp;gt;"\s]+'&lt;/span&gt;,&lt;span class="string"&gt;'&amp;lt;a href="\g&amp;lt;0&amp;gt;"&amp;gt;\g&amp;lt;0&amp;gt;&amp;lt;/a&amp;gt;'&lt;/span&gt;)), cgi.escape(s))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It look awful, but upon closer inspection we see that the noise ismostly regular expressions and some compact markup in a formatstring. All in all, it's a &lt;a href="http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29"&gt;fold&lt;/a&gt;taking the escaped input string as initial value and reducing a list oftuples which are composed of a regular expression and either of a stringor a function. These happen to be the two first arguments of&lt;tt&gt;re.sub&lt;/tt&gt; which is the function used in the lambda expression fed toreduce. There's one little unexplained detail in there, why does&lt;tt&gt;'(?m)'&lt;/tt&gt; is prepended to every regular expressions? After somegoogling, I've found this explanation:&lt;/p&gt;&lt;blockquote&gt;Caret and dollar match after and before newlines for the remainder of the regular expression. (Older regex flavors may apply this to the entire regex.)&lt;/blockquote&gt;&lt;p&gt;Well, I'll confess I don't truly understand what this is suppose tomean, but it doesn't look that important anyway, let's move on.&lt;/p&gt;&lt;p&gt;Now the &lt;tt&gt;do&lt;/tt&gt; function, obviously the one actually doing something!It's a kind of dispatching function (using a dictionary) performingdifferent type of actions depending on the value of the first parameter:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;do&lt;/span&gt;(m, n): &lt;span class="keyword"&gt;return&lt;/span&gt; {&lt;span class="string"&gt;'get'&lt;/span&gt;:&lt;span class="string"&gt;'&amp;lt;h1&amp;gt;WyPy:&amp;lt;a href=wypy?p=%s&amp;amp;amp;q=f&amp;gt;%s&amp;lt;/a&amp;gt;&amp;lt;/h1&amp;gt;('&lt;/span&gt; \&lt;br /&gt;   &lt;span class="string"&gt;'&amp;lt;a href=wypy?p=%s&amp;amp;amp;q=e&amp;gt;edit me&amp;lt;/a&amp;gt;)&amp;lt;p&amp;gt;%s'&lt;/span&gt; % (n, n, n, fs(load(n)) &lt;span class="keyword"&gt;or&lt;/span&gt; n),&lt;br /&gt;   &lt;span class="string"&gt;'edit'&lt;/span&gt;: &lt;span class="string"&gt;'&amp;lt;form action=wypy?%s method=POST&amp;gt;&amp;lt;h1&amp;gt;%s&amp;lt;input type=hidden name=p'&lt;/span&gt; \&lt;br /&gt;   &lt;span class="string"&gt;' value=%s&amp;gt; &amp;lt;input type=submit&amp;gt;&amp;lt;/h1&amp;gt;&amp;lt;textarea name=t rows=15 cols=80&amp;gt;%s&amp;lt;/'&lt;/span&gt; \&lt;br /&gt;   &lt;span class="string"&gt;'textarea&amp;gt;&amp;lt;/form&amp;gt;'&lt;/span&gt; % (n, fs(n), n, load(n) &lt;span class="keyword"&gt;or&lt;/span&gt; &lt;span class="string"&gt;"Describe %s"&lt;/span&gt; % n), &lt;span class="string"&gt;'find'&lt;/span&gt;: &lt;br /&gt;   (&lt;span class="string"&gt;'&amp;lt;h1&amp;gt;Links: %s&amp;lt;/h1&amp;gt;'&lt;/span&gt; % fs(n))+fs(&lt;span class="string"&gt;'{{\n* %s\n}}'&lt;/span&gt; % &lt;span class="string"&gt;'\n* '&lt;/span&gt;.join(&lt;span class="py-builtins"&gt;filter&lt;/span&gt;(&lt;br /&gt;    &lt;span class="keyword"&gt;lambda&lt;/span&gt; s: &lt;span class="py-builtins"&gt;open&lt;/span&gt;(&lt;span class="string"&gt;'w/'&lt;/span&gt;+s).read().find(n) &amp;gt; -1, os.listdir(&lt;span class="string"&gt;'w'&lt;/span&gt;))))}.get(m)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;It is quite simple actually, get returns a wiki page, edit is obviousand find make a page containing a list of links. The &lt;tt&gt;main&lt;/tt&gt; functionthen wires up everything to be called by a CGI process and also add apage title.&lt;/p&gt;&lt;pre class="code"&gt;&lt;span class="keyword"&gt;def&lt;/span&gt; &lt;span class="function-name"&gt;main&lt;/span&gt;(f): &lt;br /&gt;   n = f.get(&lt;span class="string"&gt;'p'&lt;/span&gt;) &lt;span class="keyword"&gt;or&lt;/span&gt; env(&lt;span class="string"&gt;"QUERY_STRING"&lt;/span&gt;) &lt;span class="keyword"&gt;or&lt;/span&gt; &lt;span class="string"&gt;''&lt;/span&gt;; n = (&lt;span class="string"&gt;'HomePage'&lt;/span&gt;,n)[n.isalpha()]&lt;br /&gt;   &lt;span class="py-builtins"&gt;print&lt;/span&gt; &lt;span class="string"&gt;"Content-type: text/html; charset=utf-8\r\n\r\n&amp;lt;title&amp;gt;%s&amp;lt;/title&amp;gt;"&lt;/span&gt; % n&lt;br /&gt;   &lt;span class="keyword"&gt;if&lt;/span&gt; env(&lt;span class="string"&gt;"REQUEST_METHOD"&lt;/span&gt;) == &lt;span class="string"&gt;"POST"&lt;/span&gt;: &lt;span class="py-builtins"&gt;open&lt;/span&gt;(&lt;span class="string"&gt;'w/'&lt;/span&gt;+n, &lt;span class="string"&gt;'w'&lt;/span&gt;).write(f[&lt;span class="string"&gt;'t'&lt;/span&gt;])&lt;br /&gt;   &lt;span class="py-builtins"&gt;print&lt;/span&gt; do({&lt;span class="string"&gt;'e'&lt;/span&gt;:&lt;span class="string"&gt;'edit'&lt;/span&gt;, &lt;span class="string"&gt;'f'&lt;/span&gt;:&lt;span class="string"&gt;'find'&lt;/span&gt;}.get(f.get(&lt;span class="string"&gt;'q'&lt;/span&gt;)) &lt;span class="keyword"&gt;or&lt;/span&gt; &lt;span class="string"&gt;'get'&lt;/span&gt;, n)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;That's it, short and sweet. A last interesting point, the only code thatI had trouble understanding was this: &lt;tt&gt;('HomePage',n)[n.isalpha()]}&lt;/tt&gt;which is quite confusing on first sight. It cleverly use the implicitconversion from boolean to integer to choose between the two items of atuple. It's in fact an old school way of simulating the ternaryoperator, that feature having been added to Python 2.5 in 2006. Thattrick might be well know amongst pythonitas, but I'm not one of them asI rarelly code in Python.&lt;/p&gt;&lt;p&gt;While dissecting this code I've refactored (or might I say &lt;em&gt;degolfed&lt;/em&gt;)it into a 45 lines version that you can grab&lt;a href="http://dl.dropbox.com/u/2682770/mu/wypy.py"&gt;here&lt;/a&gt;. I've got a nearlyfinished version of this code translated to Clojure, I'll follow up onthis post once it's done, but that may take a while as I'm having funwith the recently released Closure Library.&lt;/p&gt;&lt;p&gt;P.S.: Ward's Wiki is down at the time of this writing so the two firstlinks of this post might not work for some time. You can try to accessthem through Google's cached links.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-1477311244882187190?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/1477311244882187190/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/11/wickedly-short-wikis.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/1477311244882187190'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/1477311244882187190'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/11/wickedly-short-wikis.html' title='Wickedly Short Wikis'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5409861124354728237.post-3527596266456924588</id><published>2009-11-15T19:57:00.011-05:00</published><updated>2009-12-02T17:07:03.191-05:00</updated><title type='text'>Hello World!</title><content type='html'>&lt;p&gt;Hello world! I have currently nothing interesting to say, just postingto see how posts are looking with my selected theme. I have to fill upthis post a little bit, so that I'll be able to better judge theappearance of a sufficiently large body of text. How much morecompletely useless text I still have to write for that state to beattained is not yet clear in my mind. Maybe I'll continue until I'm tootired, or not. There's a chance I'll write it in pieces, spread overmany days. Anyhow, this isn't very important, even for a post hopefullydevoid of any meaning.&lt;/p&gt;&lt;p&gt;Going the Lorem Ipsum way isn't an option, though, as I'm hopelesslytired of reading that text. Moreover I don't even read Latin. Whichbring me to a potentially interesting subject, that is: "How can weassess the pertinence of a given text?" It's quite hard to determine howinteresting such a subject is and I fear I could not reveal all of itsintricacies. Well, let's not discourage ourselves with these pettyconcerns and write some more. First and foremost, it's more than obviousthat pertinence is a relative concept. Not everybody would be equallyinterested in reading some collection of more of less related wordsarranged according to a somewhat coherent set of semantic rules.&lt;/p&gt;&lt;p&gt;And how much the actual meaning is affected by styling concerns? Can itbe actually obscured by them? Does a poorly written piece of textcontaining the same meaning as a masterpiece is necessarily of lesservalue. Maybe it can have some advantages over the better one, like itcould be of a smaller size, thus requiring less time to read. Is thisexample even possible? Theoretically and on the top of my head I'd sayyes, but that would be more an exception than a rule.&lt;/p&gt;&lt;p&gt;OK, now this post is starting to look like it may be big enough. Atlast, the end of the tunnel is near, I can feel it. I'll stop anytimenow...&lt;/p&gt;&lt;p&gt;No, that wasn't really it. There's still more words for me to write andfor you to read, however silly they are. Those two actions willevidently not be done at the same time, unless referring to myself asthe reader. And here's another potentially interesting question arising:"Can we write and read at the same time?" Well not really interesting, Iwould even say that nobody care about that question outside someneurologists somewhere found on this planet. Which bring me to anotherquestion, this one incredibly more asinine than the previous ones: "Isthere neurologists on other planets?" I'll abstain on commenting on thisone though.&lt;/p&gt;&lt;p&gt;In the end, I just hope I'm not making too much sense as it risks tocontradict the main objective of this post. I also wonder how manygrammatical and styling mistakes I managed to make. There's certainlysome vestigial artifacts of my mother tongue here and there. Doespunctuation is well used? Did I correctly pluralized each and everywords? Will some benevolent readers point them out for me?&lt;/p&gt;&lt;p&gt;P.S.: As usual I have more questions than answers!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5409861124354728237-3527596266456924588?l=whollyweirdwyrd.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://whollyweirdwyrd.blogspot.com/feeds/3527596266456924588/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/11/hello-world.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/3527596266456924588'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5409861124354728237/posts/default/3527596266456924588'/><link rel='alternate' type='text/html' href='http://whollyweirdwyrd.blogspot.com/2009/11/hello-world.html' title='Hello World!'/><author><name>budu</name><uri>http://www.blogger.com/profile/09819173281750148724</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_9Tl5ulVDxSY/SwBYdWByqRI/AAAAAAAAAAM/bHg88B2VQM0/S220/mu-gravatar.png'/></author><thr:total>2</thr:total></entry></feed>
