<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>Graceful Exits &#187; projects</title>
	<atom:link href="http://www.jpstacey.info/blog/category/projects/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.jpstacey.info/blog</link>
	<description>Garbage collection, in a very real sense</description>
	<pubDate>Thu, 17 Jul 2008 19:08:52 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.5.1</generator>
	<language>en</language>
			<item>
		<title>Firefox/Sage bookmarks to Google Reader import</title>
		<link>http://www.jpstacey.info/blog/2008/07/17/firefoxsage-bookmarks-to-google-reader-import/</link>
		<comments>http://www.jpstacey.info/blog/2008/07/17/firefoxsage-bookmarks-to-google-reader-import/#comments</comments>
		<pubDate>Thu, 17 Jul 2008 19:04:54 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[formats]]></category>

		<category><![CDATA[import/export]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[quickies]]></category>

		<category><![CDATA[bookmarks]]></category>

		<category><![CDATA[DTD]]></category>

		<category><![CDATA[export]]></category>

		<category><![CDATA[firefox]]></category>

		<category><![CDATA[google]]></category>

		<category><![CDATA[import]]></category>

		<category><![CDATA[opml]]></category>

		<category><![CDATA[reader]]></category>

		<category><![CDATA[sage]]></category>

		<category><![CDATA[standard]]></category>

		<category><![CDATA[transform]]></category>

		<category><![CDATA[xml]]></category>

		<category><![CDATA[xslt]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/?p=186</guid>
		<description><![CDATA[When OPML is OPML but it isn't OPML]]></description>
			<content:encoded><![CDATA[<p>Want to migrate your RSS bookmarks from Firefox (or its RSS-reading addon, Sage) to Google Reader? I did, just now.</p>
<p>Christopher Hinze has written a great <a href="https://addons.mozilla.org/en-US/firefox/addon/2625" >Firefox addon that exports bookmarks to OPML 1.0</a>. Unfortunately, OPML is a bit of an <a href="http://www.opml.org/spec1" >anything-goes specification</a>. So although Hinze&#8217;s plugin produces valid OPML, it isn&#8217;t the same sort of valid OPML that Google Reader expects. Google Reader, in fact, gags and chokes on Hinze&#8217;s OPML, and refuses to import it.</p>
<p>The main problem is that the &lt;outline/&gt; element, the basic hierarchical building block for OPML, <a href="http://www.opml.org/spec1#limits" >will take <em>any attributes</em></a>. What does that mean in practice? Well, here&#8217;s what Hinze&#8217;s export produces:</p>
<blockquote class="code"><p>
&lt;outline text=&#8221;Coding&#8221;><br />
&nbsp;&nbsp;&lt;outline type=&#8221;link&#8221; text=&#8221;Joel on Software&#8221; url=&#8221;http://www.joelonsoftware.com/rss.xml&#8221;   /><br />
&lt;/outline>
</p></blockquote>
<p>and here&#8217;s the result of Google Reader exporting its own store of RSS bookmarks:</p>
<blockquote class="code"><p>
&lt;outline title=&#8221;Coding&#8221; text=&#8221;Coding&#8221;><br />
&nbsp;&nbsp;&lt;outline text=&#8221;drupal.org - Community plumbing&#8221;<br />
&nbsp;&nbsp;&nbsp;&nbsp;title=&#8221;drupal.org - Community plumbing&#8221; type=&#8221;rss&#8221;<br />
&nbsp;&nbsp;&nbsp;&nbsp;xmlUrl=&#8221;http://drupal.org/node/feed&#8221; htmlUrl=&#8221;http://drupal.org&#8221;/><br />
&lt;/outline>
</p></blockquote>
<p>To a computer, these are fundamentally two different data formats: the URLs are stored in different attributes, and there are attributes on each that either have different values or are not present on the other. Someone did a <a href="http://static.userland.com/gems/radiodiscuss/opmlDtd.txt" >DTD for OPML</a>: looking at those two apparently analogous fragments above you have to ask yourself why they bothered.</p>
<p>Help is at hand, though. This sort of problem is bread and butter to XSLT, and <a href="/applications/google/ff2gr_opml.xsl" >here&#8217;s an XSL transform for converting Firefox OPML to Google Reader OPML</a>. If you have <code>xsltproc</code> installed on your system, you would type:</p>
<blockquote class="code"><p>
xsltproc http://www.jpstacey.info/applications/google/ff2gr_opml.xsl bookmarks.opml > fixed_bookmarks.opml
</p></blockquote>
<p>Or download the XSLT&#8212;it&#8217;s released under GPL2&#8212;and run it locally, changing that URL there to a local file location.</p>
<p>One thing to note: the XSLT will remove an outline wrapped around your bookmarks with title &#8220;Sage Feeds&#8221; (case-sensitive). So you can export that branch of your bookmarks, and the XSLT will strip the wrapper off and you <em>won&#8217;t</em> import a load of bookmarks tagged &#8220;Sage Feeds&#8221;. If you don&#8217;t like this behaviour then either rename your Sage bookmark container, or learn XSLT: it won&#8217;t kill you.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/07/17/firefoxsage-bookmarks-to-google-reader-import/feed/</wfw:commentRss>
		</item>
		<item>
		<title>The Straight Edge minimalist Wordpress theme</title>
		<link>http://www.jpstacey.info/blog/2008/07/16/the-straight-edge-minimalist-wordpress-theme/</link>
		<comments>http://www.jpstacey.info/blog/2008/07/16/the-straight-edge-minimalist-wordpress-theme/#comments</comments>
		<pubDate>Wed, 16 Jul 2008 21:19:00 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[design]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[software]]></category>

		<category><![CDATA[alpha]]></category>

		<category><![CDATA[download]]></category>

		<category><![CDATA[php]]></category>

		<category><![CDATA[straightedge]]></category>

		<category><![CDATA[theme]]></category>

		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/?p=172</guid>
		<description><![CDATA[The Straight Edge theme is now available for download.]]></description>
			<content:encoded><![CDATA[<p><a href="/blog/2008/06/30/embracing-minimalism/" >As promised</a>, I&#8217;m releasing <a href="/blog/files/code/straightedge.tgz" >the Straight Edge theme</a> used on this blog under GPL2. </p>
<p>There&#8217;s a brief README.txt in the zipped archive linked above, but the theme&#8217;s main features are:</p>
<ul>
<li>XHTML compatible (in core theme files)</li>
<li>Minimal, semantic markup</li>
<li><em>No sidebar</em></li>
<li>Excerpts on archive and category pages</li>
<li>Implicit RSS feeds: the only orange icon is in your browser chrome</li>
<li>Adaptive top navigation</li>
<li>Separate pages for archives, categorisation and blogrolls</li>
<li>Next/previous rel links in header</li>
<li>Support for special pages e.g. blogroll, tag cloud</li>
</ul>
<p>The todo list includes:</p>
<ul>
<li>Implement my Blogthis! plugin, while trying to keep minimalist</li>
<li>Unobtrusive hiding of elements, using jQuery</li>
<li>Improve styling</li>
</ul>
<p>The theme is in a fairly alpha state. The PHP is fairly straightforward, apart from some neat theme functions, but don&#8217;t blame me if everything goes bang.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/07/16/the-straight-edge-minimalist-wordpress-theme/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Hardy Heron and the Dell Precision M4300</title>
		<link>http://www.jpstacey.info/blog/2008/07/16/hardy-heron-and-the-dell-precision-m4300/</link>
		<comments>http://www.jpstacey.info/blog/2008/07/16/hardy-heron-and-the-dell-precision-m4300/#comments</comments>
		<pubDate>Wed, 16 Jul 2008 19:16:10 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[hardware]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[quickies]]></category>

		<category><![CDATA[software]]></category>

		<category><![CDATA[alsa]]></category>

		<category><![CDATA[dell]]></category>

		<category><![CDATA[display]]></category>

		<category><![CDATA[hardy]]></category>

		<category><![CDATA[heron]]></category>

		<category><![CDATA[install]]></category>

		<category><![CDATA[laptop]]></category>

		<category><![CDATA[linux]]></category>

		<category><![CDATA[m4300]]></category>

		<category><![CDATA[nvidia]]></category>

		<category><![CDATA[precision]]></category>

		<category><![CDATA[sound]]></category>

		<category><![CDATA[ubuntu]]></category>

		<category><![CDATA[upgrade]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/?p=184</guid>
		<description><![CDATA[Summary: it just works.]]></description>
			<content:encoded><![CDATA[<p>In brief: the problems discussed <a href="http://www.jpstacey.info/blog/2008/01/07/the-full-sensory-experience-of-linux-on-a-dell-m4300-sound-vision-and-tinfoil-hat-microwaves/" >here</a> and <a href="http://www.jpstacey.info/blog/2007/08/28/laptop-and-linux-the-fixes-for-a-dell-precision-m4300/" >here</a> go away under the most recent <a href="http://www.ubuntu.com/" >Ubuntu</a> release, Hardy Heron, which I can generally recommend.</p>
<p>Alsa seems stable and graphics support is present from installation onwards. Enabling fancier 3D compiz effects requires the nvidia-glx-new package; compiz spots this, however and prompts for installation. All very smooth. Wireless works; my VoIP headset works; but I haven&#8217;t yet tested Bluetooth.</p>
<p>The only problem was in upgrading from Gutsy: my previous peregrinations had rendered my hybrid distribution shafted and incapable of upgrade. This isn&#8217;t a problem, though, if one has installed the /home directory (and in my case the /music one too) on a separate partition: the Ubuntu Live CD will blat the root partition with Heron, but leave the other partitions alone if you so require. Don&#8217;t resize any of your partitions during installation, though, or you&#8217;ll lose everything. Everything!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/07/16/hardy-heron-and-the-dell-precision-m4300/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Embracing minimalism</title>
		<link>http://www.jpstacey.info/blog/2008/06/30/embracing-minimalism/</link>
		<comments>http://www.jpstacey.info/blog/2008/06/30/embracing-minimalism/#comments</comments>
		<pubDate>Mon, 30 Jun 2008 12:47:34 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[design]]></category>

		<category><![CDATA[news]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[quickies]]></category>

		<category><![CDATA[software]]></category>

		<category><![CDATA[alpha]]></category>

		<category><![CDATA[minimalism]]></category>

		<category><![CDATA[pilgrim]]></category>

		<category><![CDATA[straightedge]]></category>

		<category><![CDATA[theme]]></category>

		<category><![CDATA[tomayko]]></category>

		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/?p=170</guid>
		<description><![CDATA[Graceful Exits goes straight-edge with Straight Edge, a minimalist theme written by yours truly.]]></description>
			<content:encoded><![CDATA[<p>After re-reading <a href="http://www.jpstacey.info/blog/2008/06/22/rss-feeds-keep-them-well-hidden/" >my earlier post</a>, which was in general agreement with Pilgrim and Tomayko&#8217;s minimalism, I decided to practise what I had preached and write a minimalist theme implementing some of the applications of the principles of minimalism. </p>
<p>It&#8217;s called Straight Edge, and I&#8217;ve switched to it today. Once I&#8217;ve finished alpha-testing it I&#8217;ll write more about it, and offer it for download if anyone&#8217;s interested.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/06/30/embracing-minimalism/feed/</wfw:commentRss>
		</item>
		<item>
		<title>User loading and saving in Drupal 5.x</title>
		<link>http://www.jpstacey.info/blog/2008/06/09/user-loading-and-saving-in-drupal-5x/</link>
		<comments>http://www.jpstacey.info/blog/2008/06/09/user-loading-and-saving-in-drupal-5x/#comments</comments>
		<pubDate>Mon, 09 Jun 2008 15:48:48 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[diagnostics]]></category>

		<category><![CDATA[hacking]]></category>

		<category><![CDATA[information]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[software]]></category>

		<category><![CDATA[5.x]]></category>

		<category><![CDATA[api]]></category>

		<category><![CDATA[core]]></category>

		<category><![CDATA[drupal]]></category>

		<category><![CDATA[hook]]></category>

		<category><![CDATA[id]]></category>

		<category><![CDATA[profile]]></category>

		<category><![CDATA[user]]></category>

		<category><![CDATA[user_load]]></category>

		<category><![CDATA[user_save]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/2008/06/09/user-loading-and-saving-in-drupal-5x/</guid>
		<description><![CDATA[Workflows of Drupal's user load and save functionality: spot the hooks and win a programmatical prize.]]></description>
			<content:encoded><![CDATA[<p>Recently at <a href="http://torchbox.com/" >Torchbox</a> we&#8217;ve been looking into how to build extra functionality on top of Drupal users. The standard Drupal user object is a combination of the contents from the users table, plus any properties provided by the core <code>profile</code> module. This means that the Drupal user is a combination of rows (and admittedly deserialized, structured data) from a couple of tables in a relational database. </p>
<div style="float: right;"><a title="flowcharts of user_load and user_save" href="#user-flowcharts"><img src="/blog/files/drupal/core/drupal_user_teaser.gif" alt="flowcharts of user_load and user_save" width="145" height="184" /></a></div>
<p>That works just fine for most purposes, but we may have to bring in content from not just outside the core Drupal tables but outside the core database, and even on a remote server through webservices. To this end we&#8217;ve decomposed the core <code>user</code> module&#8217;s <code>user_load()</code> and <code>user_save()</code> functions. This helps us understand better both the workflow and at what points in it our own code can motor into life, query all those extra resources (or set those queries in motion), assemble the rest of the user++ object, and then hand control back over to Drupal.</p>
<p>For those who don&#8217;t know much about Drupal, its core has a <em>hook-based</em> API structure. At certain points in its workflow, it checks all the modules for functions following certain naming conventions (typically the module name followed by the hook name e.g. <code>mymodule_init</code> on response startup, or <code>mymodule_block</code> to return details about the module&#8217;s support for Drupal page furniture). Any matching hook functions are called in the order defined by module weightings, and then page processing will generally continue: you can crowbar a grind-to-a-halt <code>exit()</code> in your hook, but it&#8217;d be unwise, as you can never be sure what tidying up Drupal might need to do after your hook. Outside these hooks, your code has little control over Drupal&#8217;s core functioning, unless you stub out entirely the bits of core you need yourself, and get your request to use those bits instead.z</p>
<p>Because of the way they let your code tag along with Drupal&#8217;s powerful core, hooks are essential to developing modules in the most <em>Drupalish</em> way. With that in mind, <a name="user-flowcharts"></a>here are flow diagrams of the three basic aspects of user functionality&#8212;create new, load, save existing&#8212;lifted straight from examination of the code:</p>
<ul>
<li><a href="/blog/files/drupal/core/drupal_user_load.gif">Loading an existing user with user_load</a></li>
<li><a href="/blog/files/drupal/core/drupal_user_create.gif">Creating a user: saving a new user with user_save</a></li>
<li><a href="/blog/files/drupal/core/drupal_user_update.gif">Updating a user: saving an existing user with user_save</a></li>
</ul>
<p>Although you have to save a user before you can load them, I&#8217;ve put this functionality first in the above (admittedly unordered) list. There are two main reasons for this:</p>
<ol>
<li><code>user_save</code> actually calls <code>user_load</code> a number of times, once or twice, to &#8220;refresh&#8221; the user object</li>
<li><code>user_load</code> is a more primitive function and so bears examination first</li>
</ol>
<p>Stripped down, <code>user_load</code> consists of: querying the database for a core user record matching the search criteria; returning this and the extended profile data; unserializing a free-data field and inserting it into the user object; discovering user roles; <strong>triggering <code>hook_user('load')</code></strong> and returning the object (or boolean false, if no user found).</p>
<p>What this reveals (which I didn&#8217;t realise before) is that <em>the anonymous user is in the Drupal users table</em>, with ID=0. Otherwise, searching for this user would return no records, and the anonymous user object could not be instantiated. You could therefore attach rich data to the anonymous user, if you were in a hacky mood.</p>
<p>The two <code>user_save</code> workflows are fairly similar. Creating a user means obtaining an ID from the database: because some MySQL providers have poorer feature sets than others, referential integrity is ensured at the application level rather than the database level. In place of obtaining an ID, user update <strong>calls <code>hook_user('update')</code> to pre-process</strong> the user. Both workflows then set aside special fields, such as the user&#8217;s password, user roles and any profile fields managed by that module (determined from <code>user_fields()</code>). Then they save this data into the database in slightly different orders, with user creation <code>calling </code><code>hook_user('insert')</code> early on, and the update procedure <code>calling </code><code>hook_user('after_update')</code> much later in the process, just before determining the external authentication mappings (e.g. OpenID) and returning the user object.</p>
<p>What does this mean for us? Well, we&#8217;ll want varying amounts of data to piggyback on the core user object, so we have somewhere to cache it. Ideally this data won&#8217;t be summoned&#8212;brought out of the distributed data &#8216;cloud&#8217;&#8212;on every request/response cycle, so we&#8217;ll need to do some local cacheing, but not so much that we&#8217;ll get out of synch with the cloud (or that we&#8217;ll duplicate sensitive data). We think that, given the pair of hooks in <code>user_save</code> for existing users, we&#8217;ll have just enough leverage to do this: the first hook will effectively &#8220;tear down&#8221; our extra data, so we can do what we want with it, and store it somewhere temporarily; the second hook will &#8220;set up&#8221; the user for the rest of the request, by putting all that data back in. The existence of <code>user_load</code> within <code>user_save</code> complicates things somewhat, but at the same time it gives us some more wiggle room, because each call to that function fires another hook.</p>
<p>A Drupal hook is worth a thousand lines of module code, but they&#8217;re still a bit few and far between for some workflows. Hopefully the accompanying images will help anyone reading to find them, and ditch those thousand lines before they&#8217;re even written.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/06/09/user-loading-and-saving-in-drupal-5x/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Postcode Anywhere and MailBuild integration with Drupal</title>
		<link>http://www.jpstacey.info/blog/2008/05/22/postcode-anywhere-and-mailbuild-integration-with-drupal/</link>
		<comments>http://www.jpstacey.info/blog/2008/05/22/postcode-anywhere-and-mailbuild-integration-with-drupal/#comments</comments>
		<pubDate>Thu, 22 May 2008 20:28:26 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[projects]]></category>

		<category><![CDATA[software]]></category>

		<category><![CDATA[createsend]]></category>

		<category><![CDATA[drupal]]></category>

		<category><![CDATA[drupal-5.x]]></category>

		<category><![CDATA[googlecode]]></category>

		<category><![CDATA[gpl]]></category>

		<category><![CDATA[mailbuild]]></category>

		<category><![CDATA[module]]></category>

		<category><![CDATA[newsletter]]></category>

		<category><![CDATA[pamb]]></category>

		<category><![CDATA[postcode]]></category>

		<category><![CDATA[postcodeanywhere]]></category>

		<category><![CDATA[rest]]></category>

		<category><![CDATA[soap]]></category>

		<category><![CDATA[sourceforge]]></category>

		<category><![CDATA[torchbox]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/2008/05/22/postcode-anywhere-and-mailbuild-integration-with-drupal/</guid>
		<description><![CDATA[Integrate SOAP-y web services with Drupal at a low level using PAMB.]]></description>
			<content:encoded><![CDATA[<p>As a result of building a website for a <a href="http://torchbox.com/" >Torchbox</a> client, I came up with a <a href="http://drupal.org/" >Drupal</a> 5.x module to query the Postcode Anywhere and MailBuild webservices (if they look like an unlikely mix, don&#8217;t worry: they&#8217;re not coupled together generally, so you can use one without the other). I&#8217;ve been meaning to make it live for ages, but never got round to scrubbing the client&#8217;s data out of it. Now that we&#8217;ve unearthed the module for other work then I&#8217;ve finally finished cleaning it.</p>
<p>First, a bit of background. <a href="http://www.postcodeanywhere.co.uk/" >Postcode Anywhere</a> provides a per-lookup paid webservice, converting UK postcodes into valid house names/numbers, streets, towns etc. It allows you to accept a user&#8217;s submission of a postcode to e.g. a signup form, then present them with a list of sample addresses, rather than them having to fill in all of their address details.</p>
<p>There&#8217;s only really one method exposed for the PA functionality, and it takes a text postcode and returns an array of results:</p>
<blockquote class="code"><p>
$data = pamb_pa_get_bypostcode($postcode);
</p>
</blockquote>
<p><a href="http://www.mailbuild.com/" >MailBuild and CreateSend</a>, on the other hand, provide a way to store email lists and send newsletter campaigns to subscribers. However, our client also wanted to use it to store snail-mail addresses, so they could use it for both requests for printed matter and also to keep subscribers up to date via email. It was possible to implement this using custom fields, of which the service permits around a dozen: address line 1, address line 2 etc.</p>
<p>There&#8217;s an <a href="http://drupal.org/project/mailbuild" >existing Mailbuild module</a>, which technically does the trick, but it&#8217;s overkill compared to what the client wanted. pamb&#8217;s integration is essentially just &#8220;push&#8221;: it passes the results of a form submission to CreateSend, by using a <i>template</i> (the one you&#8217;ve moved to your theme directory) to &#8220;theme&#8221; the data as an XML packet. If there are no problems then the system returns nothing; otherwise, it returns a structure of data including the HTTP response and the SOAP packet from the request.</p>
<p>Here&#8217;s some sample code:</p>
<blockquote class="code"><p>
/* Send SOAP packet */<br />
$no_probs = pamb_mb_send_soap($form_id, $form_values);</p>
<p>/* If OK, set a generic message and redirect: the redirect will pick up<br />
   on the generic message and know all has gone well */<br />
if(!isset($no_probs)) {<br />
&nbsp;&nbsp;drupal_set_message(t(AUTO_SUCCESS));<br />
}</p>
<p>/* If not OK, $no_probs contains the diagnostics */<br />
else {<br />
&nbsp;&nbsp;/* do something else */<br />
&nbsp;&nbsp;drupal_set_message(t(AUTO_ERROR));<br />
}
</p>
</blockquote>
<p>For now, for want of a better name, the module is called &#8220;pamb.&#8221; And [edit] the code is now <a href="http://sourceforge.net/projects/drupal-pamb/" >available on Sourceforge</a> under the <a href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GPL</a>: that should make it compatible with <a href="http://drupal.org/LICENSE.txt">Drupal&#8217;s core licence</a>. </p>
<ol>
<li><a href="http://sourceforge.net/svn/?group_id=228839" >Download pamb</a>: currently only available in subversion, but clients exist for Linux, Windows and OSX.</li>
<li>Unpack it into your <code>modules</code> directory</li>
<li>Edit the defines at the top of the file for your own Postcode Anywhere or MailBuild/CreateSend accounts.</li>
<li>Stick the <code>pamb_soap.tpl.php</code> in your theme directory</li>
<li>Switch on the module in the admin interface</li>
</ol>
<p>You&#8217;re now ready to start developing with its handful of functions. Feedback more than welcome, as given Drupal&#8217;s minimal support for mailing-list functionality in core it&#8217;d be good to make this into a fully-functional module for similar third-party contact services, and even port it to 6.x.</p>
<p>[edit 2008-06-17: now hosted on Sourceforge]</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/05/22/postcode-anywhere-and-mailbuild-integration-with-drupal/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Cheaper rail journeys with Matthew and Spliticket</title>
		<link>http://www.jpstacey.info/blog/2008/03/13/cheaper-rail-journeys-with-matthew-and-spliticket/</link>
		<comments>http://www.jpstacey.info/blog/2008/03/13/cheaper-rail-journeys-with-matthew-and-spliticket/#comments</comments>
		<pubDate>Thu, 13 Mar 2008 21:39:28 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[formats]]></category>

		<category><![CDATA[hacking]]></category>

		<category><![CDATA[layers]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[useability]]></category>

		<category><![CDATA[accessibility]]></category>

		<category><![CDATA[mashup]]></category>

		<category><![CDATA[matthew]]></category>

		<category><![CDATA[rail]]></category>

		<category><![CDATA[somerville]]></category>

		<category><![CDATA[split]]></category>

		<category><![CDATA[spliticket]]></category>

		<category><![CDATA[tickets]]></category>

		<category><![CDATA[train]]></category>

		<category><![CDATA[traintimes]]></category>

		<category><![CDATA[wiki]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/2008/03/13/cheaper-rail-journeys-with-matthew-and-spliticket/</guid>
		<description><![CDATA[I&#8217;ve built something, just in time for me to crowbar it awkwardly into conversations at OKCon.
Matthew Somerville is well known for his accessible takes on rubbish websites: most useful is Traintimes, a layer on top of one of the equally poor commercial British rail sites; most notorious is Accessible Odeon, a fixing of the Odeon [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve built something, just in time for me to crowbar it awkwardly into conversations at OKCon.</p>
<p>Matthew Somerville is well known for his accessible takes on rubbish websites: most useful is <a href="http://traintimes.org.uk/">Traintimes</a>, a layer on top of one of the equally poor commercial British rail sites; most notorious is <a href="http://www.dracos.co.uk/odeon/" >Accessible Odeon</a>, a fixing of the Odeon Cinema&#8217;s website that put their own substandard development to shame so much that in 2004 they had Matthew&#8217;s version taken down with legal threats. Remember: just because you&#8217;re successful doesn&#8217;t mean you&#8217;re not stupid.</p>
<p>Recently, Matthew gave an <a href="http://oxford.geeknights.net/" ><abbr title="Oxford Geek Nights">OGN</abbr></a> <a href="http://oxford.geeknights.net/2007/july-25th/talks/MatthewSomerville.mp4" title="'Train in Vain' by Matthew Somerville (mp4 video)">talk on split tickets</a>. These are train journeys which one cross-country train company sells at exhorbitant rates, whereas the components of the journey can be bought separately from the local companies for considerably less. This is what we infer; the rail websites try to keep it all quiet, in the hope that you might lose interest and stump up. The whole headshakeworthy situation is either a stinging indictment of the stupidity of privatizing a rail network and making passengers jump through ridiculous hoops to glean even the tiniest advantage, or a perfect demonstration of how the choice-enriched consumer can leverage capitalism in action: take your pick. </p>
<p>To come to the point: Matthew has set up a wiki where people have been adding the <a href="http://www.dracos.co.uk/wiki/Trains/SplitTickets" >split tickets they&#8217;ve worked out</a> in an <i>ad-hoc</i> fashion. Last Sunday I added K&#8217;s astonishing 47% saving for Oxford&#8211;Cardiff to the end of it, and there&#8217;s been little activity since. The page hasn&#8217;t got fantastic Google juice&#8212;&#8221;split tickets&#8221; means too many other things&#8212;and is just non-dynamic HTML, editable through the wiki but otherwise searchable only by eye.</p>
<p>At the moment, of course, there&#8217;s no reason for Matthew to expend any more effort on such a small and barely popular data set. But last Sunday I had a sudden instinctive jolt, to the effect that: more people would be likely to take advantage of split tickets if there was an easier way of looking up other people&#8217;s discoveries (my colleagues were recently trying to find split tickets and the wiki page was harder to use than Traintimes, for that very purpose).</p>
<p>With this in mind, here&#8217;s my take on accessificating the wiki page: <a href="http://www.jpstacey.info/applications/spliticket/">Spliticket</a>. It accesses a cached version of the page, and then using Python&#8217;s HTMLParser to hack away at the HTML it returns either a HTML or XML representation of any journey it finds. The idea is that it&#8217;s a bit easier to use on your mobile device, and lets you pin down journeys better. I&#8217;ve also included an option for searching strictly for the same journey: I&#8217;m not sure if split tickets&#8212;even returns&#8212;always work when the journey is reversed.</p>
<p>Spliticket also supports friendly URLs (inspired by Traintimes itself), so York to Edinburgh in HTML becomes &lt;<a href="http://www.jpstacey.info/applications/spliticket/html/york/edinburgh/" >http://www.jpstacey.info/applications/spliticket/html/york/edinburgh/</a>&gt; and the aforementioned Oxford to Cardiff route in XML becomes: &lt;<a href="http://www.jpstacey.info/applications/spliticket/xml/oxford/cardiff/">http://www.jpstacey.info/applications/spliticket/xml/oxford/cardiff/</a>&gt; .</p>
<p>Mostly this was just something to fill idle hours, and also to convince myself of some ideas I&#8217;ve had recently about loose coupling, data reuse and open data. Hopefully at the same time (a) Matthew won&#8217;t take it as a dig, (b) others might find some casual use for it see it, and (c) a select few might see it as a testament to the ease of freeing information from solid if unsemantic markup. And maybe in six months&#8217; time we&#8217;ll all be booking split tickets as a matter of course; by then, of course, Spliticket will probably be gratefully obsolete, replaced by the fully-fledged application that poor, embittered rail passengers deserve. I&#8217;m sure Matthew will build it if so.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/03/13/cheaper-rail-journeys-with-matthew-and-spliticket/feed/</wfw:commentRss>
<enclosure url="http://oxford.geeknights.net/2007/july-25th/talks/MatthewSomerville.mp4" length="15044283" type="video/mp4" />
		</item>
		<item>
		<title>How to see Last.fm from the other side of the room with LastJS</title>
		<link>http://www.jpstacey.info/blog/2008/03/02/how-to-see-lastfm-from-the-other-side-of-the-room-with-lastjs/</link>
		<comments>http://www.jpstacey.info/blog/2008/03/02/how-to-see-lastfm-from-the-other-side-of-the-room-with-lastjs/#comments</comments>
		<pubDate>Sun, 02 Mar 2008 14:16:00 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[hacking]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[quickies]]></category>

		<category><![CDATA[software]]></category>

		<category><![CDATA[accessibility]]></category>

		<category><![CDATA[bookmarklet]]></category>

		<category><![CDATA[interface]]></category>

		<category><![CDATA[javascript]]></category>

		<category><![CDATA[jquery]]></category>

		<category><![CDATA[lastfm]]></category>

		<category><![CDATA[lastjs]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/2008/03/02/how-to-see-lastfm-from-the-other-side-of-the-room-with-lastjs/</guid>
		<description><![CDATA[Back from a rather physical near-week of team building with work, I&#8217;ve been listening to Last.fm today to recuperate and nurse my tired muscles. But with the music playing on our crusty, trusty old purple iMac in one corner, and me in the other reading, drinking tea or otherwise recuperating, how can I tell at [...]]]></description>
			<content:encoded><![CDATA[<p>Back from a rather physical near-week of team building with work, I&#8217;ve been listening to Last.fm today to recuperate and nurse my tired muscles. But with the music playing on our crusty, trusty old purple iMac in one corner, and me in the other reading, drinking tea or otherwise recuperating, how can I tell at a glance what the current artist is?</p>
<p>Last.fm&#8217;s web interface isn&#8217;t optimized for the sort of &#8220;media station&#8221; use we put it to, and their downloadable widget won&#8217;t work on the antiquated version of OSX that I&#8217;ve just about managed to squeeze onto dear old Purpurea. So today I&#8217;ve spent some time writing a bookmarklet to allow me (and you, dear reader) to hook into Last.fm and its Flash player&#8217;s behaviour, to update a larger-view popup window with the necessary information.</p>
<p>Here&#8217;s the bookmarklet&#8212;which with attention to catchy names I&#8217;ve called LastJS&#8212;with spaces added for legibility:</p>
<blockquote class="code"><p>
javascript:void(function(){<br />
&nbsp;&nbsp;var&nbsp;s1=document.createElement(&#8217;script&#8217;);<br />
&nbsp;&nbsp;var&nbsp;s2=document.createElement(&#8217;script&#8217;);<br />
&nbsp;&nbsp;s1.src=&#8217;http://www.jpstacey.info/applications/lastjs/jquery.js&#8217;;<br />
&nbsp;&nbsp;s2.src=&#8217;http://www.jpstacey.info/applications/lastjs/last.js&#8217;;</p>
<p>&nbsp;&nbsp;var&nbsp;h=document.getElementsByTagName(&#8217;head&#8217;)[0];<br />
&nbsp;&nbsp;h.appendChild(s1);<br />
&nbsp;&nbsp;h.appendChild(s2);<br />
}())
</p>
</blockquote>
<p>(As the above suggests, the bulk of the code is at <a href="http://www.jpstacey.info/applications/lastjs/last.js">http://www.jpstacey.info/applications/lastjs/last.js</a>.)</p>
<p>And here&#8217;s what you need to do to use it:</p>
<ol>
<li>Add this link to your browser favourites (IE7) or bookmarks (everyone else): <a href="//www.jpstacey.info/applications/lastjs/last.js';var%20h=document.getElementsByTagName('head')[0];h.appendChild(s1);h.appendChild(s2);}())">LastJS bookmarklet</a>.</li>
<li>While on a Last.FM &#8220;now playing&#8221; page e.g. <a href="http://www.last.fm/listen/artist/Hold%2520Steady/similarartists">for The Hold Steady</a>, click the bookmarklet.</li>
<li>This will pull an instance of jQuery and LastJS off my own server, and add a &#8220;click for big&#8221; link under the title.</li>
<li>Click on the link. A new window will open, containing the artist information (track information is a bit obfuscated until the Flash player loads the next track).</li>
<li>Wait. As the track changes, the new window will update with the new track information.</li>
<li>The text is purposefully large yet not too large. If you&#8217;ve got a big monitor you can make it even bigger by increasing the base font size in your browser. To do this in Firefox press CTRL-+ (or Apple-+); I think it&#8217;s something similar in IE7.</li>
</ol>
<p>Feel free to poke around at the code, and distribute it (under GPL, for what it&#8217;s worth: I tried looking into precisely which variant I wanted but my knees started to ache). Here are some of the problems it has to solve:</p>
<ol>
<li>Last.fm uses Prototype, and so needs jQuery to restore the <code>$</code> variable using <code>jQuery.noConflict()</code>.</li>
<li>Events in the Flash player trigger functions in the <code>window</code> scope: by putting a new method in for a given event handler you can hook into Flash events. Care has to be taken, though, not to wrap and re-wrap too often&#8212;the bookmarklet might after all be re-clicked&#8212;or you might provoke a recursion warning.</li>
<li>Popup blockers mean that you can&#8217;t always guarantee the window will exist, and the code has to check accordingly.</li>
<li>Firefox seems to blank the contents of the popup window shortly after it&#8217;s opened, so any writing of text to it has to be delayed, lest it be lost by this clear-out.</li>
</ol>
<p>All probably needs work, but feel free to have a play. It doesn&#8217;t circumvent any DRM or anything, of course, but I&#8217;ve no idea whether it invalidates terms of use, so <i>caveat fructor</i>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/03/02/how-to-see-lastfm-from-the-other-side-of-the-room-with-lastjs/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Drupal 6.0 out</title>
		<link>http://www.jpstacey.info/blog/2008/02/13/drupal-60-out/</link>
		<comments>http://www.jpstacey.info/blog/2008/02/13/drupal-60-out/#comments</comments>
		<pubDate>Wed, 13 Feb 2008 21:06:45 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[content]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[software]]></category>

		<category><![CDATA[useability]]></category>

		<category><![CDATA[6.0]]></category>

		<category><![CDATA[cms]]></category>

		<category><![CDATA[drupal]]></category>

		<category><![CDATA[one-click]]></category>

		<category><![CDATA[open-source]]></category>

		<category><![CDATA[release]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/2008/02/13/drupal-60-out/</guid>
		<description><![CDATA[Drupal 6.0 released. The smoothness of D6&#8217;s interaction with both user and developer is really breathtaking these days: as close to one-click installation as you&#8217;re likely to get on shared hosting; modules to help you port your own modules over from D5; and even automatically downloaded updates to (unhacked!) core. I had a look at [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://drupal.org/drupal-6.0">Drupal 6.0 released</a>. The smoothness of D6&#8217;s interaction with both user and developer is really breathtaking these days: as close to one-click installation as you&#8217;re likely to get on shared hosting; modules to help you port your <em>own</em> modules over from D5; and even automatically downloaded updates to (unhacked!) core. I had a look at the release candidates but owing to other responsibilities I haven&#8217;t had a chance to sit down and play with the actual release.</p>
<p>(With any luck D6 still contains my three characters of contribution fixing <a>OpenID autodiscovery</a>.)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/02/13/drupal-60-out/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Cutting down on your carbon is easier when everyone&#8217;s doing it</title>
		<link>http://www.jpstacey.info/blog/2008/01/14/cutting-down-on-your-carbon-is-easier-when-everyones-doing-it/</link>
		<comments>http://www.jpstacey.info/blog/2008/01/14/cutting-down-on-your-carbon-is-easier-when-everyones-doing-it/#comments</comments>
		<pubDate>Mon, 14 Jan 2008 21:27:08 +0000</pubDate>
		<dc:creator>jps</dc:creator>
		
		<category><![CDATA[non-programming]]></category>

		<category><![CDATA[projects]]></category>

		<category><![CDATA[action]]></category>

		<category><![CDATA[advice]]></category>

		<category><![CDATA[carbon]]></category>

		<category><![CDATA[charlbury]]></category>

		<category><![CDATA[co2]]></category>

		<category><![CDATA[crag]]></category>

		<category><![CDATA[dioxide]]></category>

		<category><![CDATA[group]]></category>

		<category><![CDATA[help]]></category>

		<category><![CDATA[levy]]></category>

		<category><![CDATA[limit]]></category>

		<category><![CDATA[rationing]]></category>

		<category><![CDATA[targets]]></category>

		<category><![CDATA[wychwood]]></category>

		<guid isPermaLink="false">http://www.jpstacey.info/blog/2008/01/14/cutting-down-on-your-carbon-is-easier-when-everyones-doing-it/</guid>
		<description><![CDATA[Carbon rationing action groups (CRAGs) are groups of people who agree to measure their carbon (dioxide) use. They also agree on an upper limit for the year, and a per-tonne levy for anyone exceeding it. As the twelve months pass them by, they meet, chat, swap tips and advice, and generally try to meet the [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.carbonrationing.org.uk/">Carbon rationing action groups</a> (CRAGs) are groups of people who agree to measure their carbon (dioxide) use. They also agree on an upper limit for the year, and a per-tonne levy for anyone exceeding it. As the twelve months pass them by, they meet, chat, swap tips and advice, and generally try to meet the targets. There may, God willing, be good-natured joshing. And once that&#8217;s all over and done with, they do it all again next year, but: <a href="http://en.wikipedia.org/wiki/Contraction_and_Convergence">with a lower upper limit</a>. And so on.</p>
<p>Tom Dyson is <a href="http://www.throwingbeans.org/wychwood_crag.html">starting a CRAG for the Wychwood area</a>: that is, more or less anywhere within around fifteen miles of Charlbury in West Oxfordshire. If you&#8217;re interested, and you live in Witney, Eynsham, Burford, Chipping Norton, Kidlington, Woodstock, Stow-on-the-Wold, Hook Norton&#8230; then come along. It might change your way of life.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.jpstacey.info/blog/2008/01/14/cutting-down-on-your-carbon-is-easier-when-everyones-doing-it/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
