<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Loose Bits</title>
 <link href="http://loose-bits.com/atom.xml" rel="self"/>
 <link href="http://loose-bits.com/"/>
 <updated>2016-10-05T17:13:03+00:00</updated>
 <id>http://loose-bits.com</id>
 <author>
   <name>Ryan Roemer</name>
   <email>ryan@loose-bits.com</email>
 </author>

 
 <entry>
   <title>Writing a Technical Book, Part 4 - Marketing &amp; Beyond</title>
   <link href="http://loose-bits.com/2015/10/10/writing-a-technical-book-part-4.html"/>
   <updated>2015-10-10T00:00:00+00:00</updated>
   <id>http://loose-bits.com/2015/10/10/writing-a-technical-book-part-4</id>
   <content type="html">&lt;h2 id=&quot;marketing-amp-post-publication&quot;&gt;Marketing &amp;amp; Post-Publication&lt;/h2&gt;

&lt;p&gt;In my fourth and final post about my adventure writing
&lt;strong&gt;&lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt;, I take a look back on what has
happened since the book was published in July, 2013, discuss the
marketing initiatives we used to drive book sales, and finally reflect on the
awkward, tough, and wonderful experience of writing my first book.&lt;/p&gt;

&lt;p&gt;As it’s now been over two years since my book’s publication and the first post
in this series, I’ll just chalk up this tardy final post to something
quite familiar in the book publishing world – &lt;em&gt;deadlines slip&lt;/em&gt;. And as a
refresher, here are some quick links to the previous posts in this series:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://loose-bits.com/2013/08/04/writing-a-technical-book-part-1.html&quot;&gt;Finding and starting a book project&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://loose-bits.com/2013/11/25/writing-a-technical-book-part-2.html&quot;&gt;Authoring a technical book&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://loose-bits.com/2014/11/26/writing-a-technical-book-part-3.html&quot;&gt;Publishing a technical book&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;marketing-amp-selling-a-book&quot;&gt;Marketing &amp;amp; Selling a Book&lt;/h2&gt;

&lt;p&gt;In my latest post on &lt;a href=&quot;http://loose-bits.com/2014/11/26/writing-a-technical-book-part-3.html&quot;&gt;publishing the book&lt;/a&gt;, I discussed all of the
legwork needed to translate the various chapter drafts into a book that
you could purchase. On July 12th, 2013, the book was published, after which
Packt (my publisher) turned our focus to marketing the book.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h3 id=&quot;technical-article-amp-silence&quot;&gt;Technical Article &amp;amp; Silence&lt;/h3&gt;

&lt;p&gt;After notifying me of the formal publication announcement, my commissioning
editor requested a technical article of about 5 pages for Packt to publish so
that we could garner some search engine goodness and drive people to the book’s
sale page. I was a little wary of yet more writing after the book, but the
editor said that we could just grab a section of the book itself as the article
content. I put together a recommended list of book sections to serve as the
basis for the proposed article.&lt;/p&gt;

&lt;p&gt;This ended up being a huge fiasco. The editor dropped the issue for
over a week, then changed directions and said a technical committee
would select the book portion for the article for me. After a last email on
July 25th, all communication with the marketing / technical folks just
&lt;strong&gt;went dead silent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I wasn’t much too concerned, as it was nice to have a break from the back-and-
forth of requested book work. I started doing some of my own book publicity via
LinkedIn and Twitter and just generally took it easy.&lt;/p&gt;

&lt;h3 id=&quot;a-marketing-executive-amp-actual-marketing&quot;&gt;A Marketing Executive &amp;amp; Actual Marketing&lt;/h3&gt;

&lt;p&gt;On Sept. 3rd, over a month after any communication, my commissioning editor sent
over an email introducing me to a marketing executive. The marketing executive
seemed to be a little more put together and wanted to launch various different
initiatives for the book with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The ill-fated article project&lt;/li&gt;
  &lt;li&gt;A book giveaway&lt;/li&gt;
  &lt;li&gt;Book reviews&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;oh-the-article-it-hurts&quot;&gt;Oh, the Article. It Hurts.&lt;/h4&gt;

&lt;p&gt;For the article, the marketing executive asked “why hasn’t this been published
yet?” and I forwarded on the circuitous email chain of inaction. The
commissioning editor finally chimed in again and blamed the editorial
department. The email chains went in further circles and then the article
project just got dropped due to no one really following through.&lt;/p&gt;

&lt;h4 id=&quot;free-books&quot;&gt;Free Books!&lt;/h4&gt;

&lt;p&gt;The book giveaway was really easy. I announced a contest based on a template
from the publisher – offering five copies of the book to the best five blog
comments about frontend testing. The contest got some good responses and we
gave out some books.&lt;/p&gt;

&lt;h4 id=&quot;book-reviews&quot;&gt;Book Reviews&lt;/h4&gt;

&lt;p&gt;For book reviews, I solicited a lot of local area Seattle JavaScript developers
that I new and respected, and reached out the local JavaScript meetup group.
Packt provided free copies of the book in exchange for Amazon reviews.
Fortunately, I received a good amount of interest and got about 15 potential
reviewers that I hooked up with the marketing executive to receive free books.&lt;/p&gt;

&lt;p&gt;About five reviews went live on Amazon within the first two weeks of our
reviewing campaign, and my intuition is that these reviews probably had the most
positive impact on sales of the book of anything we tried. Since the original
push, I ended up with eight solicited reviews (with the standard disclaimer of
“I received this book in exchange for reviewing”) and two organic reviews from
people I don’t know. While I was originally nervous about the book’s reception,
I was pleased to end up with nine 5-star and one 4-star &lt;a href=&quot;http://www.amazon.com/Backbone-js-Testing-Ryan-Roemer/dp/178216524X/ref=sr_1_8?ie=UTF8&amp;amp;qid=1444516246&amp;amp;sr=8-8#customerReviews&quot;&gt;reviews&lt;/a&gt; on Amazon.&lt;/p&gt;

&lt;p&gt;After all the planned marketing campaigns finished, the marketing executive
announced “we’re done!” and moved on to one of presumably many other book
campaigns for Packt. He was fairly competent and focused on the task at hand,
which was a welcome relief from some of the other mishaps along the way.&lt;/p&gt;

&lt;h2 id=&quot;and-everything-after&quot;&gt;… and Everything After&lt;/h2&gt;

&lt;p&gt;After marketing push ended, my obligations to the book project mostly wrapped up
as well. Every three months I get a royalty statement – the book continues to
sell in the low hundreds, with the vast majority of purchases being ebooks. The
marketing executive periodically drops a note to encourage me to tweet about
Packt’s latest company-wide marketing campaign. And, I occasionally check my
Amazon page for new reviews or sales rank.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;So, was it worth it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Definitely&lt;/strong&gt;. The book writing, publishing, and marketing process was
a positive experience on several levels:&lt;/p&gt;

&lt;p&gt;I honed my technical writing skills and discovered that I could write an entire
friggin’ book.&lt;/p&gt;

&lt;p&gt;I became recognized (at least at some level) as an expert on JavaScript testing
and Backbone.js. I’ve talked with other technical authors and we agree on the
broad proposition that you are usually not an expert when selected to write a
book, but by the time you are &lt;strong&gt;done&lt;/strong&gt; with the book you &lt;em&gt;will be&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I met and interacted with a good chunk of the Seattle area JavaScript community.
My book writing period coincided with a move from DC to Seattle and in seeking
feedback about the book, I made some really good connections to the development
community in my new home. And, best of all, when giving a lightning talk about
the book at a local meetup, I ended up meeting my future business partner and
co-founder of &lt;a href=&quot;http://formidablelabs.com/&quot;&gt;Formidable Labs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Would I do it again?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’m not sure the future holds another book project for me. The enormous time
commitments, editorial bumps and mishaps, and lack of any real money at the end
of the road all factor against it.&lt;/p&gt;

&lt;p&gt;Having one book under your belt is &lt;strong&gt;great&lt;/strong&gt;. But I’m wary about the marginal
value in community outreach, personal brand, etc. that I would receive from
another one.&lt;/p&gt;

&lt;p&gt;So, in the meantime, I’ve been focusing more on things like organizing
conferences and local meetups, and writing open source software. And, you know
what? Those things, coupled with not having book work on the horizon, is pretty
nice.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Writing a Technical Book, Part 3 - Publication</title>
   <link href="http://loose-bits.com/2014/11/26/writing-a-technical-book-part-3.html"/>
   <updated>2014-11-26T00:00:00+00:00</updated>
   <id>http://loose-bits.com/2014/11/26/writing-a-technical-book-part-3</id>
   <content type="html">&lt;h2 id=&quot;publishing-a-book&quot;&gt;Publishing a Book&lt;/h2&gt;

&lt;p&gt;At long last, this is the third post in a series on my experiences writing
&lt;strong&gt;&lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt;, published in July, 2013. In this post,
I’ll dive into the publication process for my book and expand on the
following takeaway points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;There is a good amount of boring, necessary work on your book to turn
final drafts into correctly laid-out, ready-for-printing pages.&lt;/li&gt;
  &lt;li&gt;You need to keep a keen eye to &lt;em&gt;everything&lt;/em&gt; about your book, including the
cover / marketing language and especially your publisher’s book webpage.&lt;/li&gt;
  &lt;li&gt;This is your last chance to catch both small and &lt;em&gt;extremely large&lt;/em&gt; errors.&lt;/li&gt;
  &lt;li&gt;Working though the final mile of publication with your publisher can be
frustrating…&lt;/li&gt;
  &lt;li&gt;… but it feels wonderful to actually have the book done, published and
in a very literal sense, “shipped”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;youve-written-a-book-now-what&quot;&gt;You’ve Written a Book. Now What?&lt;/h2&gt;

&lt;p&gt;In my previous post on &lt;a href=&quot;http://loose-bits.com/2013/11/25/writing-a-technical-book-part-2.html&quot;&gt;authoring a book&lt;/a&gt;, I recount writing the
individual chapters of the book, going through technical and editorial review,
and submitting the “final” draft for publication at the end of April, 2013.&lt;/p&gt;

&lt;p&gt;After this final submission, I asked the publisher’s project folks what to
expect from that point to actual publication and was told that the process for
each chapter was something along the lines of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Technical editing.&lt;/li&gt;
  &lt;li&gt;Copy editing&lt;/li&gt;
  &lt;li&gt;Indexing key terms&lt;/li&gt;
  &lt;li&gt;Layout adjustments for actual, rendered book pages&lt;/li&gt;
  &lt;li&gt;Proofreading&lt;/li&gt;
  &lt;li&gt;“Prefinal” submission for the author’s final review&lt;/li&gt;
  &lt;li&gt;Finalization and clubbing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those steps was forecast to take 1-2 days and run in parallel for all
the chapters.&lt;/p&gt;

&lt;p&gt;It was at that time that I also reminded Packt (my publisher) that we still
needed to formally update the book’s title from “Backbone Testing” to
“Backbone.js Testing” in all of the materials, something Packt repeatedly told
me was easy to do throughout the process.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;a-break--how-hard-can-a-title-change-be&quot;&gt;A Break + How Hard Can a Title Change Be?&lt;/h2&gt;

&lt;p&gt;As my publisher had pushed me quite hard the entire time I was writing my
chapters to get everything done as fast as possible, I was surprised to hear
mostly radio silence for around twenty days. Finally, I started poking around
Packt’s website to see if they had started any pre-publication activity, and to
my pleasant surprise, they had – putting up a web page for my book.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Unfortunately&lt;/em&gt;, they somehow managed to screw up my title change request and
renamed the book to “Testing Backbone.js”. Issues aside about the difficulty
of getting two simple words right and in the correct order, I was extremely
disappointed – a publishing house should be able to correctly assign a book
title. And, Packt had submitted the incorrect title for pre-publication book
pages on Amazon, Barnes &amp;amp; Noble, and other online book retailers.&lt;/p&gt;

&lt;p&gt;Somehow making things worse, the page that Packt put up for pre-publication was
nonsensically worded in its description of the book and why prospective readers
should buy it – with repeatedly copy-and-pasted sentences and gems like “Test
your web applications using backbone’s tried and tested techniques and industry
best practices”. (For those not familiar with Backbone.js, just trust me that
the last statement is pure technical gibberish).&lt;/p&gt;

&lt;p&gt;So, I emailed my commissioning editor, only to find out that they had been on
break for some time (which probably explained the lack of editorial activity). I
finally tracked down my previous commissioning editor and roped them in to
fixing the mess.&lt;/p&gt;

&lt;p&gt;It turns out that Packt had just been more or less guessing at the marketing and
book cover metadata – bullet points about why to purchase the book, the title
tagline, etc. So, I undertook reviewing all the book metadata and online content
and provided my publishers with new content for all of it. Although time
consuming, I really wanted something that would make sense from the point of
view of an actual prospective reader, and hopefully make the book seem like a
good purchase.&lt;/p&gt;

&lt;p&gt;Through all of this mess, there were some actual silver linings. I got to
actually choose a picture for the book cover, which I chose from one of my
wife’s photographs from nearby our house. Here’s the cover again for
reference:&lt;/p&gt;

&lt;div class=&quot;pull-center&quot;&gt;
  &lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;
    &lt;img class=&quot;bordered&quot; style=&quot;width: 65%; max-width: 350px;&quot; alt=&quot;Backbone.js Testing&quot; title=&quot;Backbone.js Testing&quot; src=&quot;http://loose-bits.com/media/img/2013/06/13/book-cover.jpg&quot; /&gt;
  &lt;/a&gt;
  &lt;p /&gt;
&lt;/div&gt;

&lt;p&gt;And, I did learn about &lt;em&gt;all&lt;/em&gt; of the details of the book cover and website
marketing content because I was so paranoid about something else inaccurate or
nonsensical being written on my behalf about the book.&lt;/p&gt;

&lt;h2 id=&quot;prefinal-and-final-editing&quot;&gt;Prefinal and Final Editing&lt;/h2&gt;

&lt;p&gt;After the diversion of the book title / marketing material, Packt finally
got the first chapters back to me for prefinal review on May 30th, supposedly
targeting a “to the printers” date of June 10th. When I asked about the delay
and impending deadline, the publisher responded that they had actually been
short-staffed the entire time after I submitted my final drafts. We jointly
agreed to push back the actual final deadline.&lt;/p&gt;

&lt;p&gt;The prefinal and final editing process was mostly boring but important work. I
was sent PDFs of what would be close to the final layout and look for the
chapters to review, with comments and suggestions from the publisher. The
technical editors and proofreaders didn’t find many issues and sometimes
mis-identified correct writing as errors. But, they did ultimately find a number
of real errors we were able to correct.&lt;/p&gt;

&lt;p&gt;It was also a good opportunity for me to re-read my chapters after about a
month’s break. At that point, I didn’t really have a chance to make substantive
changes (which would dramatically affect the layout work), but I was able to
gauge the readability and substantive arch of the book as a whole. And, I also
found a few bugs and errors on my own in the process.&lt;/p&gt;

&lt;p&gt;Packt and I went back and forth either one or two times per chapter, circling
around a narrowing group of sentences and words to discuss, and then finally
arrived at the “good enough!” point. On July 3rd, I sent over the final approval
of all the remaining chapters. And, on July 12th, I received confirmation that
the book was uploaded to the printers and thus officially published / available
for purchase.&lt;/p&gt;

&lt;h2 id=&quot;its-published&quot;&gt;It’s Published!&lt;/h2&gt;

&lt;p&gt;I will post a final article on the post-publication / marketing experience with
my book, but the short version is that I did feel a huge relief actually getting
the book out the door and curiosity as to what would happen next.&lt;/p&gt;

&lt;p&gt;Up until that point, I didn’t really have a lot of time for reflection along the
way as to how good or bad the book was, or whether or not folks would actually
buy it once it went on sale. But after publication, and especially when I was
helping marketing the book, I did feel some degree of nervousness and
anticipation as to what developers out in the wild would think of something that
I had dedicated a good chunk of the past half year writing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But, more on that in the last post in this series…&lt;/em&gt;&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Backbone.js with React Views</title>
   <link href="http://loose-bits.com/2014/11/22/backbone-and-react.html"/>
   <updated>2014-11-22T00:00:00+00:00</updated>
   <id>http://loose-bits.com/2014/11/22/backbone-and-react</id>
   <content type="html">
&lt;p&gt;&lt;em&gt;Cross-posted from the
&lt;a href=&quot;http://formidablelabs.com/blog/2014/11/21/backbone-and-react/&quot;&gt;Formidable Labs blog&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;On Nov. 18, 2014, I presented a talk on
“&lt;a href=&quot;http://slides.formidablelabs.com/201411-react-backbone.html&quot;&gt;Backbone.js with React Views&lt;/a&gt;” at the inaugural
&lt;a href=&quot;http://www.meetup.com/seattle-react-js/events/216736502/&quot;&gt;Seattle ReactJS Meetup&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;post-media&quot;&gt;
  &lt;div class=&quot;post-media-grid&quot;&gt;
    &lt;img class=&quot;post-media-col-1-2&quot; src=&quot;http://loose-bits.com/media/img/2014/11/22/backbone-logo.png&quot; alt=&quot;&quot; /&gt;
    &lt;img class=&quot;post-media-col-1-2&quot; src=&quot;http://loose-bits.com/media/img/2014/11/22/react-logo.png&quot; alt=&quot;&quot; /&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;backbonejs&quot;&gt;Backbone.js&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://backbonejs.org/&quot;&gt;Backbone.js&lt;/a&gt; powers some of the largest web applications on the Internet and
we use the framework extensively at Formidable Labs. It provides simple and
flexible abstractions that developers can use to organize and create large-scale applications.&lt;/p&gt;

&lt;p&gt;At the same time, Backbone.js application development runs in to complexities
with issues such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Heavy handed DOM wiping and reflows.&lt;/li&gt;
  &lt;li&gt;Preventing memory leaks and dreaded “&lt;a href=&quot;http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/&quot;&gt;Zombie Views&lt;/a&gt;”.&lt;/li&gt;
  &lt;li&gt;Composing and aggregating child views correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;react&quot;&gt;React&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href=&quot;http://facebook.github.io/react/&quot;&gt;React&lt;/a&gt;. React is a UI (view) layer for web applications that uses
virtual DOM rendering and a modular architecture for creating reusable, web
components. It offers some exciting new possibilities as a replacement for
Backbone.js views, while keeping the rest of the framework pieces that
Backbone.js does very well.&lt;/p&gt;

&lt;h2 id=&quot;backbonejs-with-react&quot;&gt;Backbone.js with React!&lt;/h2&gt;

&lt;p&gt;In my talk, I walk through a simple application, “&lt;a href=&quot;http://formidablelabs.github.io/notes-react-exoskeleton/app.html&quot;&gt;Notes&lt;/a&gt;” (&lt;em&gt;live demo&lt;/em&gt;),
that uses Backbone.js with React views. The full source is available from one
of our &lt;a href=&quot;https://github.com/FormidableLabs/notes-react-exoskeleton&quot;&gt;GitHub repos&lt;/a&gt;.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;The talk &lt;a href=&quot;http://slides.formidablelabs.com/201411-react-backbone.html&quot;&gt;slides&lt;/a&gt; go in to more detail, but some of the interesting
results we get with a React-based view layer include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A slimmer overall minified bundle. With React as the view layer, we can remove
dependencies on jQuery and Underscore.js.&lt;/li&gt;
  &lt;li&gt;Server-side rendering. This is a &lt;strong&gt;huge&lt;/strong&gt; feature, and actually quite easy
with React. Even better, if you have a single-page JavaScript application
for your site, React can &lt;em&gt;bootstrap&lt;/em&gt; the HTML of the page server-side and then
startup the app without re-rendering &lt;em&gt;any&lt;/em&gt; HTML!&lt;/li&gt;
  &lt;li&gt;… and some other pretty cool stuff!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To dive into the talk, hop on over to the &lt;a href=&quot;http://slides.formidablelabs.com/201411-react-backbone.html&quot;&gt;presentation website&lt;/a&gt;.
Or, for the more traditional folks, the talk
&lt;a href=&quot;http://www.slideshare.net/RyanRoemer/backbonejs-with-react-views-server-rendering-virtual-dom-and-more&quot;&gt;SlideShare deck&lt;/a&gt; is available (with downloadable PDF).&lt;/p&gt;

&lt;div class=&quot;embed embed-slides&quot;&gt;
  &lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/41777786&quot; class=&quot;slideshare&quot; width=&quot;512&quot; height=&quot;421&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Making JavaScript Tests Fast, Easy, &amp; Friendly</title>
   <link href="http://loose-bits.com/2014/08/11/making-javascript-tests-fast-easy-friendly.html"/>
   <updated>2014-08-11T00:00:00+00:00</updated>
   <id>http://loose-bits.com/2014/08/11/making-javascript-tests-fast-easy-friendly</id>
   <content type="html">&lt;p&gt;&lt;em&gt;Cross-posted from the
&lt;a href=&quot;http://formidablelabs.com/blog/2014/08/11/making-javascript-tests-fast-easy-friendly/&quot;&gt;Formidable Labs blog&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A few weeks ago, I presented a talk on
“&lt;a href=&quot;http://cascadiajs2014.formidablelabs.com/&quot;&gt;Making JavaScript Tests Fast, Easy, &amp;amp; Friendly&lt;/a&gt;” at the
&lt;a href=&quot;http://2014.cascadiajs.com/&quot;&gt;CascasdiaJS 2014&lt;/a&gt; conference in Portland, OR.
The talk looks beyond the traditional technical details of wiring up
test frameworks and focuses on creating processes and environments that help
developers actually &lt;strong&gt;write&lt;/strong&gt; and &lt;strong&gt;run&lt;/strong&gt; application tests.&lt;/p&gt;

&lt;h2 id=&quot;making-the-tests-faster-easier-friendlier&quot;&gt;Making the Tests Faster, Easier, Friendlier&lt;/h2&gt;

&lt;p&gt;The motivation for the talk comes from our work with developer teams of various
sizes at Formidable Labs, where we have observed the challenges, successes, and
failures of test development as a part of the overall development workflow.
Essentially, these experiences can be culled down to the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If tests aren’t &lt;strong&gt;easy to write&lt;/strong&gt;, then developers won’t &lt;em&gt;write&lt;/em&gt; them.&lt;/li&gt;
  &lt;li&gt;If tests aren’t &lt;strong&gt;fast to run&lt;/strong&gt;, then developers won’t &lt;em&gt;run&lt;/em&gt; them.&lt;/li&gt;
  &lt;li&gt;If the organizational culture isn’t test-&lt;strong&gt;friendly&lt;/strong&gt;, then developers won’t
write &lt;em&gt;or&lt;/em&gt; run the tests (and they’ll probably be unhappy to boot).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The talk dives into some tips and tricks to address these three broad goals
as follows:&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Easy&lt;/strong&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;strong&gt;Set a Foundation&lt;/strong&gt;: Build an infrastructure everyone can use.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Lower Barriers to Entry&lt;/strong&gt;: Help new developers get acclimated with
code and mentorship.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Write it Down&lt;/strong&gt;: Document everything test-related and keep things
up-to-date.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Teach &amp;amp; Learn&lt;/strong&gt;: Provide onboarding and small, introductory
test assignments.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fast&lt;/strong&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;strong&gt;Know What to Look For&lt;/strong&gt;: Focus on the application behaviors most
likely to be slowing down your tests (e.g., network and waits).&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Find the Slow Pokes&lt;/strong&gt;: Use your test tools to identify &lt;em&gt;what&lt;/em&gt; is
slow during tests.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Fake It&lt;/strong&gt;: Programmatically “fake” the parts of your application that
are slow during the tests.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Offer Shortcuts&lt;/strong&gt;: Provide developers with subsets of tests to run
when the whole thing is too slow to regularly run.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Friendly&lt;/strong&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;strong&gt;Find Champions&lt;/strong&gt;: Encourage and promote developers who are considered
project-wide testing leads.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Win the Higher-Ups Over&lt;/strong&gt;: Learn to speak your managers’ language to
get top-down support in your organization for developer time spent
writing and running tests.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are just some of the tips that we’ve found useful for making testing
better in our client projects at Formidable Labs. But, there are surely other
good techniques for building a strong culture of testing in other
organizations. (And, we’d love to hear your experiences in our comments below!)&lt;/p&gt;

&lt;h2 id=&quot;media&quot;&gt;Media&lt;/h2&gt;

&lt;p&gt;The full &lt;a href=&quot;http://youtu.be/BK3dQUjwo9Q?list=UUIP244iNzbn4iEkDOgczvcQ&quot;&gt;video&lt;/a&gt; of my talk is available from &lt;a href=&quot;http://youtu.be/BK3dQUjwo9Q?list=UUIP244iNzbn4iEkDOgczvcQ&quot;&gt;YouTube&lt;/a&gt;.
As an aside, the &lt;a href=&quot;https://www.youtube.com/user/cascadiajs/videos&quot;&gt;CascadiaJS YouTube Channel&lt;/a&gt; has videos from the
other (fantastic) talks at this year’s conference.&lt;/p&gt;

&lt;div class=&quot;embed embed-video&quot;&gt;
  &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;//www.youtube.com/embed/BK3dQUjwo9Q?list=UUIP244iNzbn4iEkDOgczvcQ&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;For those just looking for the slides, you can try the
&lt;a href=&quot;http://cascadiajs2014.formidablelabs.com/&quot;&gt;presentation website&lt;/a&gt; (with live executable code samples!) or the
more traditional &lt;a href=&quot;https://www.slideshare.net/RyanRoemer/cascadiajs-2014-making-javascript-tests-fast-easy-friendly&quot;&gt;SlideShare site&lt;/a&gt;, where the final PDF resides.&lt;/p&gt;

&lt;div class=&quot;embed embed-slides&quot;&gt;
  &lt;iframe src=&quot;//www.slideshare.net/slideshow/embed_code/37707398&quot; class=&quot;slideshare&quot; width=&quot;512&quot; height=&quot;421&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>chai-jq - jQuery Assertions for Chai</title>
   <link href="http://loose-bits.com/2013/11/27/chai-jq-test-plugin.html"/>
   <updated>2013-11-27T00:00:00+00:00</updated>
   <id>http://loose-bits.com/2013/11/27/chai-jq-test-plugin</id>
   <content type="html">&lt;h2 id=&quot;a-new-jquery-plugin-for-chai&quot;&gt;A New jQuery Plugin for Chai&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://chaijs.com/&quot;&gt;Chai&lt;/a&gt; is a wonderful JavaScript test assertion library that I rely on
extensively (usually using it in conjunction with the &lt;a href=&quot;http://mochajs.org/&quot;&gt;Mocha&lt;/a&gt; test
framework library). Chai has an intuitive, natural-language approach to
assertions that enables you tests to read almost like an English narrative.
Chai also supports plugins which extend the base assertion API.&lt;/p&gt;

&lt;p&gt;Chai is quite often used for frontend JavaScript testing, and specifically for
testing jQuery elements in an application web page. There is an existing
&lt;a href=&quot;https://github.com/chaijs/chai-jquery&quot;&gt;chai-jquery&lt;/a&gt; plugin for Chai, which is extensive and really
quite neat. Unfortunately, it has a &lt;a href=&quot;https://github.com/chaijs/chai-jquery/issues/30&quot;&gt;few issues&lt;/a&gt; with
overriding built-in Chai assertions like &lt;code class=&quot;highlighter-rouge&quot;&gt;have&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;length&lt;/code&gt; in ways that
changes the underlying Chai API.&lt;/p&gt;

&lt;p&gt;With that motivation in mind, I hacked together a quick jQuery plugin,
&lt;a href=&quot;http://formidablelabs.github.io/chai-jq/&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;chai-jq&lt;/code&gt;&lt;/a&gt; for Chai that has a separate &lt;code class=&quot;highlighter-rouge&quot;&gt;$&lt;/code&gt;-prefixed namespace to
avoid collisions with existing Chai assertions, and put the project up on
&lt;a href=&quot;https://github.com/FormidableLabs/chai-jq&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-chai-jq-plugin&quot;&gt;The chai-jq Plugin&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;http://formidablelabs.github.io/chai-jq/&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;chai-jq&lt;/code&gt;&lt;/a&gt; plugin has full documentation at the
&lt;a href=&quot;http://formidablelabs.github.io/chai-jq/&quot;&gt;project website&lt;/a&gt;, including installation instructions.
The plugin works in all of the following environments:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Browser&lt;/strong&gt;: Via a standard &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag include.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Browser + AMD&lt;/strong&gt;: Via an AMD library like &lt;a href=&quot;requirejs.org&quot;&gt;RequireJS&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Node.js + JsDom&lt;/strong&gt;: In Node.js using the JsDom browser environment
emulator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In terms of what &lt;a href=&quot;http://formidablelabs.github.io/chai-jq/&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;chai-jq&lt;/code&gt;&lt;/a&gt; provides, here is a brief tour of the
API (stolen from the project docs):&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h3 id=&quot;visible&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$visible&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the element is visible.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div&amp;gt;&amp;amp;nbsp;&amp;lt;/div&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$visible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/visible-selector/&quot;&gt;http://api.jquery.com/visible-selector/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;hidden&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$hidden&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the element is hidden.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div style=\&quot;display: none\&quot; /&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$hidden&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/hidden-selector/&quot;&gt;http://api.jquery.com/hidden-selector/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;valstringregexp&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$val(string|regexp)&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the element value matches a string or regular expression.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;input value='foo' /&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;and&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^foo/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/val/&quot;&gt;http://api.jquery.com/val/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;classstring&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$class(string)&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the element has a class match.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div class='foo bar' /&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;and&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/hasClass/&quot;&gt;http://api.jquery.com/hasClass/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;attrname-string&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$attr(name, string)&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the target has exactly the given named attribute, or
asserts the target contains a subset of the attribute when using the
&lt;code class=&quot;highlighter-rouge&quot;&gt;include&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;contain&lt;/code&gt; modifiers.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div id=\&quot;hi\&quot; foo=\&quot;bar time\&quot; /&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;and&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;contain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/attr/&quot;&gt;http://api.jquery.com/attr/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;propname-value&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$prop(name, value)&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the target has exactly the given named property.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;input type=\&quot;checkbox\&quot; checked=\&quot;checked\&quot; /&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$prop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;checked&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;and&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$prop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;checkbox&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/prop/&quot;&gt;http://api.jquery.com/prop/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;htmlstring&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$html(string)&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the target has exactly the given HTML, or
asserts the target contains a subset of the HTML when using the
&lt;code class=&quot;highlighter-rouge&quot;&gt;include&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;contain&lt;/code&gt; modifiers.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div&amp;gt;&amp;lt;span&amp;gt;foo&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;span&amp;gt;foo&amp;lt;/span&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;and&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;contain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/html/&quot;&gt;http://api.jquery.com/html/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;textstring&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$text(string)&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the target has exactly the given text, or
asserts the target contains a subset of the text when using the
&lt;code class=&quot;highlighter-rouge&quot;&gt;include&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;contain&lt;/code&gt; modifiers.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div&amp;gt;&amp;lt;span&amp;gt;foo&amp;lt;/span&amp;gt; bar&amp;lt;/div&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo bar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;and&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;contain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/text/&quot;&gt;http://api.jquery.com/text/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;cssname-string&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;.$css(name, string)&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Asserts that the target has exactly the given CSS property, or
asserts the target contains a subset of the CSS when using the
&lt;code class=&quot;highlighter-rouge&quot;&gt;include&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;contain&lt;/code&gt; modifiers.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div style=\&quot;width: 50px; border: 1px dotted black;\&quot; /&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;width&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;50px&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;and&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;have&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;border-top-style&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dotted&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;See: &lt;a href=&quot;http://api.jquery.com/css/&quot;&gt;http://api.jquery.com/css/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://formidablelabs.github.io/chai-jq/&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;chai-jq&lt;/code&gt;&lt;/a&gt; plugin is just getting off the ground and is
likely still rough around the edges. Any &lt;a href=&quot;https://github.com/FormidableLabs/chai-jq/issues&quot;&gt;issue reports&lt;/a&gt;,
&lt;a href=&quot;https://github.com/FormidableLabs/chai-jq/pulls&quot;&gt;pull requests&lt;/a&gt;, and feedback are most welcome.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Writing a Technical Book, Part 2 - Authoring</title>
   <link href="http://loose-bits.com/2013/11/25/writing-a-technical-book-part-2.html"/>
   <updated>2013-11-25T00:00:00+00:00</updated>
   <id>http://loose-bits.com/2013/11/25/writing-a-technical-book-part-2</id>
   <content type="html">&lt;h2 id=&quot;writing-a-book&quot;&gt;Writing a Book&lt;/h2&gt;

&lt;p&gt;This is the second in a series of posts on my experiences writing my first
technical book, &lt;strong&gt;&lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt;, published in July, 2013.
In this article, I reflect on the task of authoring the core of the book - from
writing the first pages to finishing off the last round of technical edits and
submission of the full draft of the book to my publisher.&lt;/p&gt;

&lt;p&gt;A rough overview of salient tips and experiences from this part of the
book-writing journey includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure to have a reliable backup of your work - expect to lose all your
files (and your computer).&lt;/li&gt;
  &lt;li&gt;Before writing anything, plan and outline everything.&lt;/li&gt;
  &lt;li&gt;The writing process will take much longer than you think it will.&lt;/li&gt;
  &lt;li&gt;The writing process is also tiring, stressful, and boring.&lt;/li&gt;
  &lt;li&gt;Technical review will bruise your ego and leave you with a much better book.&lt;/li&gt;
  &lt;li&gt;Once you get most of the way through the drafting process, you (the author)
hold the power in your relationship with your publisher.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;where-we-left-off&quot;&gt;Where we Left Off&lt;/h2&gt;

&lt;p&gt;In my previous post on &lt;a href=&quot;http://loose-bits.com/2013/08/04/writing-a-technical-book-part-1.html&quot;&gt;starting a book project&lt;/a&gt;, I discussed how I
got roped into writing a technical book and the process from initial contact
from the publisher to signing a book contract. As part of the negotiations, we
agreed on the following ultimate chapter outline (with estimated page count and
due dates):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Setting up a Test Infrastructure (10 pages) - Jan. 8, 2013&lt;/li&gt;
  &lt;li&gt;Creating a Backbone.js Application Test Plan (8 pages) - Jan. 16, 2013&lt;/li&gt;
  &lt;li&gt;Test Assertions, Specs, and Suites (20 pages) - Jan. 28, 2013&lt;/li&gt;
  &lt;li&gt;Test Spies (7 pages) - Feb. 4, 2013&lt;/li&gt;
  &lt;li&gt;Test Stubs and Mocks (15 pages) - Feb. 11, 2013&lt;/li&gt;
  &lt;li&gt;Headless Web Testing (12 pages) - Feb. 19, 2013&lt;/li&gt;
  &lt;li&gt;Appendix A: Other JavaScript Test Frameworks (8 pages) - Feb. 25, 2013&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a spoiler for the rest of this post - I came nowhere near making most of
the chapter deadlines.&lt;/p&gt;

&lt;h2 id=&quot;starting-on-the-book&quot;&gt;Starting on the Book&lt;/h2&gt;

&lt;p&gt;After signing the contract in mid-December, I found that I suddenly had my first
chapter due Jan. 8 and subsequent chapters following quickly after that. Shortly
thereafter, I dug in and started getting my authoring and coding tools set up,
enhancing my book outline, and dove into writing the first chapter.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h3 id=&quot;nuts-and-bolts---storage-and-backups&quot;&gt;Nuts and Bolts - Storage and Backups&lt;/h3&gt;

&lt;p&gt;As a purely logistical matter, my book chapters were authored as Microsoft
Word documents and have images in PNG format. The source code examples are
in HTML, JavaScript, and CSS. And, being a consummate techie, I knew that I
needed a reliable and flexible digital storage system. My main goals for this
setup were:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Provide a disaster recovery solution if my laptop dies.&lt;/li&gt;
  &lt;li&gt;Provide “checkpoints” to go back to earlier versions of my work to find
cut text and alternative previous drafts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The source code samples were going to be released as an open source
project and were pure, usable source code, so the storage and backup solution
was quite obvious - Git and &lt;a href=&quot;http://github.com&quot;&gt;GitHub&lt;/a&gt;. You can find the
ultimate home of the code samples at the book’s &lt;a href=&quot;https://github.com/ryan-roemer/backbone-testing/&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the book’s textual sources however, Microsoft Word documents typically don’t
play well with Git, and I the book’s text as an open source project.
Nonetheless, after reviewing a lot of alternative storage systems (Dropbox,
Amazon S3, etc.), I decided to go ahead and use Git with a private repository.
My rationale was that Git still provided checkpoints to early MS Word document
drafts, even if I couldn’t use all of the normal Git tools with the documents.&lt;/p&gt;

&lt;p&gt;I cannot emphasize enough the importance of backups when writing a book, and
preferably something that checkpoints drafts. There are a variety of bad things
that can happen to both your computer (crashes, etc.) and documents (like MS
Word files becoming corrupted). In my case, I had my laptop stolen in May during
the middle of the authoring process - I didn’t lose any of my book progress
(code or text) as I was able to easily restore my existing work on another
computer with just a few downloads.&lt;/p&gt;

&lt;h3 id=&quot;planning-and-outlining&quot;&gt;Planning and Outlining&lt;/h3&gt;

&lt;p&gt;Turning to the substantive end of kicking off the book, I began reworking and
expanding on my detailed book outline that I had begun as part of the book
scoping and negotiation process with Packt.&lt;/p&gt;

&lt;p&gt;The original outline had about one page of bullet points per chapter. I turned
this into a series of sections and sub-sections for Chapter 1. Then, I filled in
as many substantive bullet points as I could for each section and sub-section to
essentially write the structure and outline content for the entire first chapter
before diving in and actually writing text.&lt;/p&gt;

&lt;p&gt;I strongly recommend detailed outlines for any large writing project as it
forces you to collect your thoughts, hone the overall message and protects you
from writing lots of text in directions that will later be cut in the editorial
process. Or, putting in software development terms, you don’t start a large
web application by writing code top-to-bottom - instead, you create a general
design and architecture first (say, model-view-controller or something), and
then you plan and implement the parts. The same goes for writing, if you want
your end result to be cogent and well crafted.&lt;/p&gt;

&lt;p&gt;This was also a chance for me to revisit the vision for the book as a whole, as
the first chapter will really start limiting and focus the world for the book.
My book was about using testing technologies (frameworks, assertions, and fakes)
for a specific web framework, Backbone.js (with models, views, controllers, and
other framework components). The big decision made in the original outline was
to split chapters by different test technology, and &lt;em&gt;not&lt;/em&gt; by web framework
component.&lt;/p&gt;

&lt;p&gt;At the same time, I needed to weave each testing lesson &lt;em&gt;back&lt;/em&gt; to the
Backbone.js framework (the book title is, after all, &lt;strong&gt;Backbone.js&lt;/strong&gt; Testing),
so I decided to create a reasonably complex Backbone.js sample application to
use throughout the book. The application is a simple
note-taking web page, named
&lt;a href=&quot;http://backbone-testing.com/notes/app/index.html&quot;&gt;Notes&lt;/a&gt;, and I tested
different parts of Notes in each chapter using the specific chapter’s test
technology.&lt;/p&gt;

&lt;p&gt;I think this approach was ultimately successful in unifying the test concepts
with Backbone.js application fundamentals, but it also required a lot of
upfront work, as the Notes sample application essentially needed to be coded
and tested very early on, before most of the chapters were written.&lt;/p&gt;

&lt;h2 id=&quot;and-off-we-go-the-first-chapter&quot;&gt;And Off We Go (The First Chapter)&lt;/h2&gt;

&lt;p&gt;With the Chapter 1 outline prepared and Git repositories ready to go, I
started writing the first chapter, “Setting up a Test Infrastructure”. The
chapter essentially comprised downloading all of the libraries needed to run and
execute the book samples and create a “Hello World”-ish test infrastructure.&lt;/p&gt;

&lt;p&gt;Chapter 1 came together reasonably easily on a fairly relaxed scheduled, as
it wasn’t due until the first week of January. I had time to create the example
application and tests that would be used throughout the entire book. I had
a little bit of acclimating to formatting the document per required guidelines
and generating acceptable screenshot image files, but it wasn’t too bad.&lt;/p&gt;

&lt;p&gt;I turned in the chapter draft on the scheduled due date, and thus started my
first milestone for actually &lt;em&gt;writing&lt;/em&gt; a book!&lt;/p&gt;

&lt;h2 id=&quot;digging-in-the-other-chapters&quot;&gt;Digging In (The Other Chapters)&lt;/h2&gt;

&lt;p&gt;Delivering the first draft of Chapter 1 on time and in reasonably good shape
did feel like an accomplishment. However, even at that point I realized that
the original negotiated schedule was going to end up being far too aggressive
in light of having a (more than) full time “day job” and trying to keep some
semblance of a life outside of technical-related matters.&lt;/p&gt;

&lt;p&gt;As it turns out, an outside factor then ended up blowing the schedule away.
During the time I was writing the first chapter and after signing on to the book
project, my wife interviewed for, and accepted, a new position in Seattle. When
everything was finalized, I suddenly was faced with a cross-country move from
Washington, DC to Seattle, WA in mid-February.&lt;/p&gt;

&lt;p&gt;I raised the issue with my publisher upon turning in my first chapter, and we
agreed on a revised schedule, effectively spreading out the due dates for
chapters 2 and 3 through the end of February. We left the rest of the chapter
deadlines as more of a “cross that bridge when we come to it”-type thing.&lt;/p&gt;

&lt;p&gt;Over the next few months, I dug in and cranked out the chapters in my nights
and weekends (with a generous interruption for moving to Seattle). The writing
was relatively straightforward in terms of subject matter and getting it
down on paper, but the process itself was quite difficult and taxing.&lt;/p&gt;

&lt;p&gt;If I were to categorize the toughest parts of writing out the initial chapter
drafts during this big push, the top items would be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Exhaustion&lt;/strong&gt;:
I was pretty tired when writing. I started working on the book many nights
after 10:00 pm or before 4:00 am. It’s hard to think when you’re tired, and
harder to write.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scope Creep&lt;/strong&gt;:
The chapters ended up being longer than originally anticipated, making the
work take longer. I tried to keep to the proposed outline’s estimates, but
there were some topics that just couldn’t be covered in the expected
number of pages.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reviewing Work&lt;/strong&gt;:
Authoring a first draft is not the end of your work. After a chapter
submission, the publisher would forward the chapter draft to the technical
reviewers, who added comments or made suggested changes. Then, the publisher
would present their work back to me for chapter revisions. There was no
real schedule for this, and it wasn’t factored into the underlying schedule
of chapter first drafts, yet ended up being a significant portion of work.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Time Zones&lt;/strong&gt;:
The majority of the publisher’s editorial staff was based out of India,
meaning that communication almost always took a full day and was a half-day
out of sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end of the day, writing a book is tedious work and not always that fun
for these and other reasons. The most important traits and tendencies I found
to counter these obstacles and difficulties include the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Write and Review Quickly&lt;/strong&gt;:
I can write reasonably well-organized text fairly quickly. Despite my
lofty hopes of an erudite tome for the ages, the real goals for a technical
book is something that is correct, accessible, and not horrendously dry.
(The exception to this rule is Larry Wall’s
“&lt;a href=&quot;http://shop.oreilly.com/product/9780596000271.do&quot;&gt;Programming Perl&lt;/a&gt;” book,
better known as the “Camel Book”, which is probably the most humorous and
well-written technical book I’ve ever read.)
From my experience, when I could actually get the time to write, I was able
to reliably crank out drafts and revisions to my chapters.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Don’t Get Bored Easily&lt;/strong&gt;:
The subject matter of technical books is fairly dry. And, given how long
you will spend engrossed in a single topic, it’s bound to be quite boring
at times. It thus helps to have really good focus on a single topic and
being able to push through in a methodical manner. I do pretty well with the
latter - the topic of my book wasn’t always engrossing, but at least in
chapter-long increments, I was able to always move forward and continue.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Detailed-Oriented&lt;/strong&gt;:
My publisher didn’t have the absolutely best reputation for the textual
quality of their books. Apparently repeated, grievous editing mistakes and
oversights somehow find their way into at least a number of the final
published books, if the Amazon book reviews are to be believed.
In my case, I made it my duty to proofread the heck out of everything I wrote
and again at every review opportunity. My personal preference for proofing
was to print out paper versions of each chapter, and attack it with a red
pen, old-school style. However you slice it, it is ultimately the author’s
responsibility to keep the book in good shape. And, as the next series in
the post will discuss, it is also important to keep an eye on your editor /
publisher to make sure they’re not &lt;strong&gt;adding&lt;/strong&gt; errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;technical-review&quot;&gt;Technical Review&lt;/h2&gt;

&lt;p&gt;One of the benefits of working with a publisher, as opposed to self-publishing
or the like, was the recruitment of some very capable and smart technical
reviewers. Packt found two software developers well known in the community for
Backbone.js and frontend testing generally.&lt;/p&gt;

&lt;p&gt;After I submitted each first draft chapter to Packt, they would assign the
chapters to the technical reviewers to add comments and make suggested text
changes. Then, I would receive the in-draft and separate comments from the
reviewers and rework each chapter into the second draft.&lt;/p&gt;

&lt;p&gt;Going through technical review is a humbling process - not only did my reviewers
point out all of the errors in the chapter text, they also gave qualitative
ratings for each chapter, many of which were quite low for my first drafts. One
reviewer in particular was often quite critical in overall assessment and the
numerous comments he would pepper my drafts with. Yet, I couldn’t have been more
happy with all of the ego-bruising / “this is horrible”-type criticism – the
feedback was invaluable for correcting errors, getting a sense of my overall
message, and knowing that at least two highly intelligent developers had worked
through the text.&lt;/p&gt;

&lt;p&gt;As far as work went, simple errors and omissions pointed out by the technical
editors were easy to correct. However, some were quite extensive to revise. For
example, a reviewer suggested that I should re-organize the entirety of my
example code’s file structure. Evaluating the comment, I realized that he was
right, and spent a good deal of time retooling everything in the code and in the
book examples and text. I think the book ended up much better overall for the
change, but at the time it was really a pain to change.&lt;/p&gt;

&lt;h2 id=&quot;getting-through-the-editorial-process&quot;&gt;Getting Through the Editorial Process&lt;/h2&gt;

&lt;p&gt;I managed to get drafts of the first four chapters submitted by mid-March.
After my submission of Chapter 4, my publisher notified me that my editor was
changing roles in the company and that I would be assigned a new editor.&lt;/p&gt;

&lt;p&gt;Right out of the gate, the new editor notified me that my chapters had deviated
from the estimates in the outline and that I would have to cut down my previous
chapters. The new editor presumably reviewed all the chapters submitted up until
that point and figured it was time for house cleaning.&lt;/p&gt;

&lt;p&gt;I replied rather strongly that to try and go back and retrofit already
completed work at that time would be time-consuming, difficult, and likely
make the overall book worse for the changes. At the time I was writing Chapter
5, and the notion of stopping everything to go back to old chapters was just
dreadful.&lt;/p&gt;

&lt;p&gt;As an aside here, it’s worth mentioning the power dynamics of the author and
publisher. In my situation, Packt essentially provided no money upfront, a
quite meager advance on the completion of the entire book, and was essentially
relying on my good graces to finish the thing. Although technically they could
take my partially finished work and have another author pick it up mid-stream
if our relationship truly went south, the practical logistics of them being
able to actually go through with that would have been very difficult.&lt;/p&gt;

&lt;p&gt;My intuition is that many book projects similarly set up to mine fail midway
through the process due to external constraints or pressures. Put another way,
if something came up on my end, there was really no way for my publisher to
force me to finish. And, once I was over halfway through my first draft of the
book, the publisher had strong incentives to see me complete it.&lt;/p&gt;

&lt;p&gt;So, when I pushed back on the new editor’s extensive rewrite proposal with an
alternate plan, Packt was quite amenable and the editor backed down pretty
easily.&lt;/p&gt;

&lt;p&gt;To address with the overall size of the book, I cut the planned Appendix (a
survey of related technologies to by book’s core subject matter) instead of
revising existing chapters. The Appendix was already going to be a pain to
research and write, and figured offering it up as a “sacrifice” to reclaim page
count would be the best way to speed things along.&lt;/p&gt;

&lt;h2 id=&quot;submission&quot;&gt;Submission!&lt;/h2&gt;

&lt;p&gt;I submitted the final drafts of all chapters at the end of April 2013. I also
finished and submitted meta-level drafts of things like my biography and the
preface to the book.&lt;/p&gt;

&lt;p&gt;As a foreshadowing event, I also reminded my editors that they had agreed to
change the title of the book from “Backbone Testing” to “Backbone.js Testing”
(just adding the three characters “.js”).&lt;/p&gt;

&lt;p&gt;The editors accepted my submission and gave me a rough outline of the process
that would unfold from there on out to actually publish the book, a story that
we will dive into in next post in this series.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Learn Frontend Testing</title>
   <link href="http://loose-bits.com/2013/10/18/seattlejs-learn-frontend-testing.html"/>
   <updated>2013-10-18T00:00:00+00:00</updated>
   <id>http://loose-bits.com/2013/10/18/seattlejs-learn-frontend-testing</id>
   <content type="html">&lt;h2 id=&quot;learn-frontend-testing&quot;&gt;Learn Frontend Testing&lt;/h2&gt;

&lt;p&gt;As a part of &lt;a href=&quot;http://formidablelabs.com/&quot;&gt;Formidable Labs’&lt;/a&gt; series of development education events, I
led a “&lt;a href=&quot;http://www.meetup.com/seattlejs/events/139993642/&quot;&gt;Learn Frontend Testing&lt;/a&gt;” workshop on Oct. 16, 2013 for
the &lt;a href=&quot;http://www.meetup.com/seattlejs/&quot;&gt;SeattleJS&lt;/a&gt; meetup group. My slides are available at the following
locations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://formidablelabs.github.com/learn-frontend-testing/&quot;&gt;&lt;strong&gt;Web Presentation&lt;/strong&gt;&lt;/a&gt;: A live,
navigable &lt;a href=&quot;https://github.com/hakimel/reveal.js/&quot;&gt;reveal.js&lt;/a&gt; website.
(&lt;em&gt;Note&lt;/em&gt;: use the space bar to advance slides and arrow keys to navigate.)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://formidablelabs.github.com/learn-frontend-testing/learn-frontend-testing.pdf&quot;&gt;&lt;strong&gt;PDF&lt;/strong&gt;&lt;/a&gt;:
A download-able PDF.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, here’s an embedded format:&lt;/p&gt;

&lt;div class=&quot;embed embed-slides&quot;&gt;
  &lt;iframe src=&quot;http://www.slideshare.net/slideshow/embed_code/27309486&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; allowfullscreen=&quot;allowfullscreen&quot; webkitallowfullscreen=&quot;webkitallowfullscreen&quot; mozallowfullscreen=&quot;mozallowfullscreen&quot;&gt;&amp;nbsp;&lt;/iframe&gt;
&lt;/div&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;workshop-topics&quot;&gt;Workshop Topics&lt;/h2&gt;

&lt;p&gt;My presentation walks through testing some very basic JavaScript application
code in the following steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Install all of the necessary test libraries and create a test driver HTML page
to hook up the application libraries and run the whole test harness.&lt;/li&gt;
  &lt;li&gt;Create Mocha suites and tests.&lt;/li&gt;
  &lt;li&gt;Write assertions to verify application behavior with Chai.&lt;/li&gt;
  &lt;li&gt;Spy and stub out application behavior with Sinon.JS.&lt;/li&gt;
  &lt;li&gt;Automate all of our tests with PhantomJS to run them from the command line
without a web browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test stack the presentation uses is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://mochajs.org/&quot;&gt;Mocha&lt;/a&gt;&lt;/strong&gt;: Test framework for
organizing and running our tests.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://chaijs.com/&quot;&gt;Chai&lt;/a&gt;&lt;/strong&gt;: Natural language-focused test assertion
library.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://sinonjs.org/&quot;&gt;Sinon.JS&lt;/a&gt;&lt;/strong&gt;: Test fake library providing spies
and stubs for observing and mutating application behavior during tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test concepts and technology stack is the same one I use in my book,
&lt;strong&gt;&lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt;. You can see the additional test samples
beyond those contained in the presentation at:
&lt;a href=&quot;http://backbone-testing.com/&quot;&gt;http://backbone-testing.com/&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;try-it-out&quot;&gt;Try it Out!&lt;/h2&gt;

&lt;p&gt;All of the source code for the presentation (include our finished tests) is
available at the workshop’s
&lt;a href=&quot;https://github.com/FormidableLabs/learn-frontend-testing&quot;&gt;GitHub repo&lt;/a&gt;
and can be installed using git:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git clone https://github.com/FormidableLabs/learn-frontend-testing.git&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Packt Holiday eBook Sale</title>
   <link href="http://loose-bits.com/2013/10/16/packt-holiday-ebook-sale.html"/>
   <updated>2013-10-16T00:00:00+00:00</updated>
   <id>http://loose-bits.com/2013/10/16/packt-holiday-ebook-sale</id>
   <content type="html">&lt;h2 id=&quot;packt-holiday-discount&quot;&gt;Packt Holiday Discount&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.packtpub.com/&quot;&gt;Packt Publishing&lt;/a&gt; is offering a pretty respectable &lt;a href=&quot;http://bit.ly/1bqvB29&quot;&gt;50% off&lt;/a&gt;
all of their eBooks through Oct. 17th. They’ve got a Columbus Day special code
of &lt;a href=&quot;http://bit.ly/1bqvB29&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;COL50&lt;/code&gt;&lt;/a&gt; to enter during checkout. (For those not quite comfortable
with Columbus Day, consider it a &lt;a href=&quot;http://theoatmeal.com/comics/columbus_day&quot;&gt;Bartolomé Day&lt;/a&gt; discount.)&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Backbone.js Testing - Book Contest</title>
   <link href="http://loose-bits.com/2013/09/09/backbone-testing-contest.html"/>
   <updated>2013-09-09T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2013/09/09/backbone-testing-contest</id>
   <content type="html">&lt;h2 id=&quot;win-free-copies-of-backbonejs-testing&quot;&gt;Win Free Copies of Backbone.js Testing&lt;/h2&gt;

&lt;p&gt;I’m pleased to announce that &lt;a href=&quot;http://www.packtpub.com/&quot;&gt;Packt Publishing&lt;/a&gt; is organizing a
contest to give away copies of my book, &lt;strong&gt;&lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt;!&lt;/p&gt;

&lt;div class=&quot;pull-center&quot;&gt;
  &lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;
    &lt;img class=&quot;bordered&quot; style=&quot;width: 50%; max-width: 250px;&quot; alt=&quot;Backbone.js Testing&quot; title=&quot;Backbone.js Testing&quot; src=&quot;http://loose-bits.com/media/img/2013/06/13/book-cover.jpg&quot; /&gt;
  &lt;/a&gt;
  &lt;p /&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Five&lt;/strong&gt; participants stand a chance to win digital copies of the book.&lt;/p&gt;

&lt;h2 id=&quot;book-overview&quot;&gt;Book Overview&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt; covers &lt;a href=&quot;http://backbonejs.org/&quot;&gt;Backbone.js&lt;/a&gt; test
architecture and development, and in brief summary will help you:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create comprehensive test infrastructures.&lt;/li&gt;
  &lt;li&gt;Understand and utilize modern frontend testing techniques and libraries.&lt;/li&gt;
  &lt;li&gt;Use mocks, spies, and fakes to effortlessly test and observe complex
Backbone.js application behavior.&lt;/li&gt;
  &lt;li&gt;Automate tests to run from the command line, shell, or practically anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;enter-the-contest-win-the-book&quot;&gt;Enter the Contest, Win the Book!&lt;/h2&gt;

&lt;p&gt;All you need to do is leave a comment below with what interests you most about
the book. You can get a good sense of the book’s content by looking at it’s
product description on Packt’s &lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;book page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deadline&lt;/strong&gt;: The contest will close in one week’s time. Winners will be
contacted by Packt via email, so be sure to use your real email address when
you comment! (Alternately, after leaving a comment you can instead email me
at &lt;a href=&quot;&amp;#109;&amp;#097;&amp;#105;&amp;#108;&amp;#116;&amp;#111;:&amp;#114;&amp;#121;&amp;#097;&amp;#110;&amp;#064;&amp;#108;&amp;#111;&amp;#111;&amp;#115;&amp;#101;&amp;#045;&amp;#098;&amp;#105;&amp;#116;&amp;#115;&amp;#046;&amp;#099;&amp;#111;&amp;#109;&quot;&gt;&amp;#114;&amp;#121;&amp;#097;&amp;#110;&amp;#064;&amp;#108;&amp;#111;&amp;#111;&amp;#115;&amp;#101;&amp;#045;&amp;#098;&amp;#105;&amp;#116;&amp;#115;&amp;#046;&amp;#099;&amp;#111;&amp;#109;&lt;/a&gt; if you don’t want your
email address to publicly appear on this page.)&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Sphinx Bootstrap Theme 0.3.0 - Bootstrap v3 and more!</title>
   <link href="http://loose-bits.com/2013/09/08/sphinx-bootstrap-theme-bootstrap-3.html"/>
   <updated>2013-09-08T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2013/09/08/sphinx-bootstrap-theme-bootstrap-3</id>
   <content type="html">
&lt;h2 id=&quot;bootstrap-v3-comes-to-sphinx&quot;&gt;Bootstrap v3 comes to Sphinx&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme&quot;&gt;Bootstrap Theme&lt;/a&gt; for &lt;a href=&quot;http://sphinx.pocoo.org/&quot;&gt;Sphinx&lt;/a&gt; has hit a new milestone with
version
&lt;a href=&quot;https://pypi.python.org/pypi/sphinx-bootstrap-theme/0.3.0&quot;&gt;0.3.0&lt;/a&gt;,
bringing in version 3.0.0 of the &lt;a href=&quot;https://twitter.com/&quot;&gt;Twitter&lt;/a&gt; &lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Bootstrap&lt;/a&gt;
library.&lt;/p&gt;

&lt;p&gt;[![Sphinx Bootstrap Theme v0.3.0][img_sbt]][img_sbt]
[img_sbt]: http://loose-bits.com/media/img/2013/09/08/sbt.png&lt;/p&gt;

&lt;p&gt;A lot has changed since the last &lt;a href=&quot;http://loose-bits.com/2013/04/10/sphinx-bootstrap-theme-bootswatch.html&quot;&gt;v0.2.0 blog post&lt;/a&gt;, the most
conspicuous being that you can now specify to use either Bootstrap v2.3.2
or v3.0.0 via the theme options:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;html_theme_options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Choose Bootstrap version.&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Values: &quot;3&quot; (default) or &quot;2&quot; (in quotes)&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'bootstrap_version'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Thanks to the awesome work by &lt;a href=&quot;https://github.com/MiCHiLU&quot;&gt;MiCHiLU&lt;/a&gt;, the
Sphinx Bootstrap Theme can easily switch between Bootstrap v2.x and v3.x
with full support for all of the features, including
&lt;a href=&quot;http://bootswatch.com/&quot;&gt;Bootswatch&lt;/a&gt; CSS extensions.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;changes-v020---v030&quot;&gt;Changes: v0.2.0 - v0.3.0&lt;/h2&gt;

&lt;p&gt;It’s been a while since I have posted updates for the theme, so here is the
laundry list of all of the goodness added to the theme since my last blog
post.&lt;/p&gt;

&lt;h3 id=&quot;v030&quot;&gt;v0.3.0&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Add Bootstrap v3.0.0 with legacy option for v2.3.2. (&lt;a href=&quot;https://github.com/MiCHiLU&quot;&gt;@MiCHiLU&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v029&quot;&gt;v0.2.9&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Add &lt;code class=&quot;highlighter-rouge&quot;&gt;navbar_links&lt;/code&gt; theme option. (&lt;a href=&quot;https://github.com/newgene&quot;&gt;@newgene&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Add &lt;code class=&quot;highlighter-rouge&quot;&gt;navbarextra&lt;/code&gt; block in “layout.html”. (&lt;a href=&quot;https://github.com/grncdr&quot;&gt;@grncdr&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v028&quot;&gt;v0.2.8&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Sphinx compatible Sidebars. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Topnav sidebarrel can now be disabled. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Topnav page nav menu can now be disabled. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v027&quot;&gt;v0.2.7&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Add custom nav bar links. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Fix wrapping of line numbers in code includes. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Truncate long page titles in navigation bar. (&lt;a href=&quot;https://github.com/aababilov&quot;&gt;@aababilov&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v026&quot;&gt;v0.2.6&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Use network path for Bootswatch (&lt;a href=&quot;https://github.com/nail&quot;&gt;@nail&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Switch from distribute to setuptools. (Suggested by &lt;a href=&quot;https://github.com/thedrow&quot;&gt;@thedrow&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v025&quot;&gt;v0.2.5&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Search page styling. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v024&quot;&gt;v0.2.4&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Adjust the max width of field lists. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Update to Bootstrap v2.3.2.&lt;/li&gt;
  &lt;li&gt;Navbar search box now uses bootstrap search-query class. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Field-list tables now have an inherited width. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v023&quot;&gt;v0.2.3&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Put navbar within a &lt;code class=&quot;highlighter-rouge&quot;&gt;container&lt;/code&gt;. (&lt;a href=&quot;https://github.com/inducer&quot;&gt;@inducer&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Add &lt;code class=&quot;highlighter-rouge&quot;&gt;navbar_site_name&lt;/code&gt; for renaming site nav. tab. (Suggested by &lt;a href=&quot;https://github.com/inducer&quot;&gt;@inducer&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v022&quot;&gt;v0.2.2&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Better literal markup handling for Bootstrap code formatting. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Scroll window when jumping to an anchor. (&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;v021&quot;&gt;v0.2.1&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Fix code styling collision for cross references and inline code blocks.
(&lt;a href=&quot;https://github.com/russell&quot;&gt;@russell&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;happy-theming&quot;&gt;Happy Theming&lt;/h2&gt;

&lt;p&gt;I’ve been humbled and excited by all of the great community work on the theme,
adding much needed functionality and flexibility. Please keep the
&lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/pulls&quot;&gt;pull requests&lt;/a&gt;
coming and help us continue to develop the friendship between
&lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Bootstrap&lt;/a&gt; and &lt;a href=&quot;http://sphinx.pocoo.org/&quot;&gt;Sphinx&lt;/a&gt;!&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Writing a Technical Book, Part 1 - Starting the Project</title>
   <link href="http://loose-bits.com/2013/08/04/writing-a-technical-book-part-1.html"/>
   <updated>2013-08-04T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2013/08/04/writing-a-technical-book-part-1</id>
   <content type="html">&lt;h2 id=&quot;finding-and-starting-a-book-project&quot;&gt;Finding and Starting a Book Project&lt;/h2&gt;

&lt;p&gt;I published my first technical book, &lt;strong&gt;&lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt;,
in July, 2013. Now that I am coming down off the high of actually getting the
book out to the world, I thought that I would take a little bit of time to
reflect on my journey and write a series of posts about the adventures and
tribulations of writing a technical book.&lt;/p&gt;

&lt;p&gt;In this first post, I will look back on how I got roped into becoming an author,
planning and scoping a prospective book outline, and signing a contract to begin
work on the book.&lt;/p&gt;

&lt;h2 id=&quot;why-write-a-book&quot;&gt;Why Write a Book?&lt;/h2&gt;

&lt;p&gt;Writing a book is an enormous time commitment, puts pressure on your personal
and work obligations, and has little or no chance of being financially a
worthwhile endeavor.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;So, why do it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The short answer is that I liked the proposed topic, I was already
interested in writing, and Packt Publishing had some fortuitous timing in
pitching the project.&lt;/p&gt;

&lt;h3 id=&quot;a-good-challenging-topic&quot;&gt;A good, challenging topic&lt;/h3&gt;

&lt;p&gt;Frontend web application testing has been a topic of keen interest and
frustration for me for quite some time. Historically, testing JavaScript in web
pages has been an arduous task, with very few good options and enormous amount
of pain in any solution. At the same time, there is enormous need for this type
of testing as more and more logic is pushed from a traditional backend
application (e.g., a Django or Rails app) to the frontend.&lt;/p&gt;

&lt;p&gt;In my day-to-day work over the past few years, I’ve noticed that the biggest
holes in application test coverage and sources of &lt;em&gt;most actual bugs&lt;/em&gt; have been
in frontend JavaScript code. Accordingly, I have now spent a good amount of
time implementing and writing about testing on the client side. In 2011,
I wrote &lt;a href=&quot;http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html&quot;&gt;two&lt;/a&gt; &lt;a href=&quot;http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and_16.html&quot;&gt;posts&lt;/a&gt; on an early frontend testing
solution I came up with using Env.js and Rhino. The technologies were
unreliable and hacked together, but at that point, at least it was &lt;em&gt;something&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Fast-forwarding to today, with the rapid rise of Node.js on the backend and a
whole host of new and exciting browser libraries, there are a lot more options
for frontend testing. At Curiosity Media, we had a large Backbone.js web
application that we were able to get a solid, modern test harness around using
&lt;a href=&quot;http://mochajs.org/&quot;&gt;Mocha&lt;/a&gt;, &lt;a href=&quot;http://chaijs.com/&quot;&gt;Chai&lt;/a&gt; and &lt;a href=&quot;http://sinonjs.org/&quot;&gt;Sinon.JS&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;i-missed-writing&quot;&gt;I missed writing&lt;/h3&gt;

&lt;p&gt;By way of a bit more background, software engineering is my second career.
Before becoming a full-time geek, I was an intellectual property attorney,
focusing on mostly computer software and hardware matters.&lt;/p&gt;

&lt;p&gt;While some folks think that the life of a lawyer mostly involves standing up
and arguing in court (e.g., “I object!”), the reality is that as a junior
associate attorney, you spend nearly all of your time reading and writing legal
documents of some type or another. Moreover, as a patent attorney, most of your
time is spent writing patent applications and related legal documents.
And, patent writing is essentially technical writing, albeit with a bit of a
legal bent.&lt;/p&gt;

&lt;p&gt;So, coming back to my current life as a software engineer, while I occasionally
write blog posts and articles, it had been several years since any substantive
writing project. And, I kind of missed it.&lt;/p&gt;

&lt;h3 id=&quot;and-it-seemed-like-the-right-time&quot;&gt;And, it seemed like the right time&lt;/h3&gt;

&lt;p&gt;With that background, &lt;a href=&quot;http://www.packtpub.com/&quot;&gt;Packt Publishing&lt;/a&gt; had the good timing to
approach me with a book proposal for testing Backbone.js applications in late
October of 2012.&lt;/p&gt;

&lt;p&gt;I’m not sure the process was particularly selective in my case, as Packt has a
reputation for somewhat aggressive recruitment strategies for book authors and
reviewers (e.g., they send a lot of emails). But, the subject matter was a good
fit for my frontend testing interests, and the book size seemed to make the
project tractable.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;choosing-a-publisher&quot;&gt;Choosing a Publisher&lt;/h2&gt;

&lt;p&gt;I didn’t really “choose” a publisher, as I was not seriously considering a book
project until Packt presented the packaged concept to me. As such, I didn’t go
through the steps of deciding to write a book, then trying to choose a
publisher and whether or not to self-publish - but the topic is one of great
interest to folks I have chatted with incidentally about technical book
authorship.&lt;/p&gt;

&lt;p&gt;I will get more into the intricacies of working with my specific publisher
throughout the rest of these posts. As to the broad question of working with
a traditional publisher versus a self-publishing service like
&lt;a href=&quot;https://leanpub.com/&quot;&gt;Leanpub&lt;/a&gt;, the things that my publisher helped with that
were really valuable includes the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Technical Reviewers&lt;/strong&gt;: Packt recruited two community JavaScript developers
to review the technical content for accuracy and suggestions. The reviewers
for my book were absolutely top notch, and provided enormously useful
feedback and comments.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Content Editing&lt;/strong&gt;: A normal publishing house will provide proofreaders and
content editors to fix language and writing errors. Packt does not always
have the best reputation in this regard, so in my authoring, I viewed the
final responsibility for all editing being my own. At the same time, the
editors and proofreaders did find several misspellings, etc. over the course
of my drafts.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Layout and Formatting&lt;/strong&gt;: A publishing house will take care of all of the
formatting issues, particularly those that require manual editing after the
automated processes finish producing a print-worthy PDF. I’m not sure how
far along the self-publishing services are in this regard, and in any case
I didn’t have to do any of this work - I just had to review and approve
the print-ready PDFs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Publishing and Distribution&lt;/strong&gt;: Packt takes care of the publication in
paper and eBook form. They also got the book listed on Amazon and other
third party sites with proper metadata, etc. (although they needed a
couple of rounds to get it right).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Marketing&lt;/strong&gt;: Traditional publishing houses will also market your book via
email, website articles, and other means to get the word out in ways you
won’t always be able to do yourself. In my case, Packt is in the process of
creating an article around some of the book content to publish on their site
and drive interest. Then, they’ll launch and email and social media campaign.
I have no idea how that will go, but it should be interesting to see if it
actually drums up more interest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From what I can intuit, the disadvantages of going with a traditional publisher
include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Control&lt;/strong&gt;: Someone else is approving drafts and driving the entire process.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;People and Resources&lt;/strong&gt;: As an author, you are one of many to the publisher,
and if they haven’t staffed your book properly, you can end up waiting a long
time for the project to move forward. As one example in my case, after I
finished all of the drafts for the book, Packt sat on the book for an entire
month without progress as they had some internally shuffling of editors and
staff.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Pressure&lt;/strong&gt;: With a traditional publishing house, you are on someone else’s
time line, and in Packt’s case, they drove really hard to getting drafts
in. At the same time, some authors might enjoy having a third party provide
incentive to keep with the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would I self-publish my next book? I’m not sure. (I’m still not sure I’ll ever
write another technical book.) But, were I to go down the authoring rabbit hole
once again, I would strongly consider self-publishing given the advances and
control you have over the entire process.&lt;/p&gt;

&lt;p&gt;In any case, as far as my story here goes, I ended up with Packt. And I will
say that in my case, the book was most likely finished much faster with Packt
than with any of the other publishing options or companies.&lt;/p&gt;

&lt;h2 id=&quot;the-book-proposal&quot;&gt;The Book Proposal&lt;/h2&gt;

&lt;p&gt;My first contact with Packt was from an “author relationship executive”, which
I think is something like an author recruiter. The tentative project outline
Packt proposed was for a 90-page book with five chapters covering Backbone.js
application testing concepts and tools.&lt;/p&gt;

&lt;p&gt;Once I expressed some interest, the author relationship executive passed me on
to a “commissioning editor”, who is the main editor for the book. I would
later find out that there are various editor roles, as well as many different
people iterating through those roles during the course of writing the book.&lt;/p&gt;

&lt;p&gt;The commissioning editor answered my initial queries and communicated the
basic logistics of the book - drafts are written in Microsoft Word,
the loose drafting and editorial process, etc. The expected page count got
ramped down to 80 pages, which Packt considers a “mini” book - a short,
concise book on a specific topic.&lt;/p&gt;

&lt;p&gt;The editor then sent me a helper “outline kit” for me to put together a target
outline for the book, which added up to 60 pages of content. The goal was to
fill in a per-chapter outline for each of the five proposed chapters. I did
this, then proposed an additional chapter (on automated web testing) and
an appendix. The Packt team liked the outline and my suggested additions, and
bumped the allowed page count to 80 pages.&lt;/p&gt;

&lt;p&gt;After a bit more back and forth, I finished a final draft of the book outline,
which Packt approved, and then we moved forward to the contract stage. The
takeaway from the proposal process was that we ironed out the chapter structure
and length, which would then constrain the writing process and define what parts
were going to be due.&lt;/p&gt;

&lt;h2 id=&quot;the-book-contract&quot;&gt;The Book Contract&lt;/h2&gt;

&lt;p&gt;After approving the book proposal, Packt sent me a book contract for review.
The contract most entailed expected details of obligations for the author in
writing the book, what happens if the author fails to deliver chapters or
stop working on the book, etc.&lt;/p&gt;

&lt;h3 id=&quot;the-legalese&quot;&gt;The legalese&lt;/h3&gt;

&lt;p&gt;Although sometimes scary and written in abstruse legalese, a contract is
essentially a negotiation in paper form. An important thing to remember that
any point is up for grabs, modifiable or removable. With that in mind, I dove
into the contract and started dissecting some of the points that might need
changes.&lt;/p&gt;

&lt;p&gt;One of the biggest issues with the contract was that Packt put in a clause
titled “Option on future Works”, which basically obligated me to give Packt the
first right to publish my &lt;em&gt;next two books&lt;/em&gt; after the one we were discussing.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The Author shall give the Publisher the first opportunity to read and consider
for publication the next 2 works on any computer-related subject that the
Author seeks to have published.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To me, this clause was clearly ridiculous - it is one thing to agree to a book
contract when I’ve never written one before nor worked with Packt before, but
it’s quite another to say the first &lt;strong&gt;three&lt;/strong&gt; books I would write would be at
Packt’s publishing discretion. So, I asked Packt to remove the clause
entirely.&lt;/p&gt;

&lt;p&gt;A separate issue that wasn’t directly addressed in the contract was the
licensing of the code samples that accompanied the book. The code samples
technically fall under the copyrights of the book generally. However, I
wanted to release all of the code samples as an open source project, so that
I could easily publish the code, get community improvements and bug fixes,
and get the code out earlier than the book. So, I asked that Packt allow all
of the code samples to be separately published to GitHub as an open source
project under the MIT license (the most common license for the components
I was using).&lt;/p&gt;

&lt;p&gt;Packt agreed to both changes without any further issue or push back.&lt;/p&gt;

&lt;h3 id=&quot;the-time-frame&quot;&gt;The time frame&lt;/h3&gt;

&lt;p&gt;Packt set up an incredibly aggressive time frame for completing the first
drafts of the book - something short of two months. As I had planned a December
vacation, I pushed back a little bit and got the following schedule
(assuming a starting date of January) for my chapter drafts:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Setting up a Test Infrastructure - Jan. 8, 2013&lt;/li&gt;
  &lt;li&gt;Creating a Backbone.js Application Test Plan - Jan. 16, 2013&lt;/li&gt;
  &lt;li&gt;Test Assertions, Specs, and Suites - Jan. 28, 2013&lt;/li&gt;
  &lt;li&gt;Test Spies - Feb. 4, 2013&lt;/li&gt;
  &lt;li&gt;Test Stubs and Mocks - Feb. 11, 2013&lt;/li&gt;
  &lt;li&gt;Headless Web Testing - Feb. 19, 2013&lt;/li&gt;
  &lt;li&gt;Appendix A: Other JavaScript Test Frameworks - Feb. 25, 2013&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In retrospect, this schedule was &lt;strong&gt;insane&lt;/strong&gt; and was in no way feasible given
that I had a full time job and was expecting to write on nights and weekends.
But, the story of the how all of the deadlines (both mine and my publisher’s)
slipped over the course of the book is for another post.&lt;/p&gt;

&lt;p&gt;Suffice to say that I should have padded the draft chapter due dates
in the contract by at least a factor of 1.5 from what I have here. Being a
first time author, I really had no real idea how long things would actually
take, and my intuition now tells me that drafting chapters is slower your
first time.&lt;/p&gt;

&lt;h3 id=&quot;the-money&quot;&gt;The money&lt;/h3&gt;

&lt;p&gt;The contract also specified the remuneration details - advance and royalties.
Packt’s royalty rate is 16% of all book revenue, and is explained in further
detail on their &lt;a href=&quot;http://authors.packtpub.com/content/royalties&quot;&gt;royalties page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The advance amount was pretty minimal - $600 - with one-fourth due on
completion of all drafts, and the rest due on publication. As an &lt;em&gt;advance&lt;/em&gt;,
you get to keep this amount, but it cuts against any royalties from the book
after publication until it is accounted for.&lt;/p&gt;

&lt;p&gt;Unless you happen to have an amazing prolific topic, money should not be a
large motivating factor in choosing to write a technical book as you will
invariably be disappointed. You’ll note that money was nowhere in my
discussion of motivations to write a book in the above sections.&lt;/p&gt;

&lt;p&gt;Software developers are fairly well-paid; authors
tend not to be. Put another way, I have never done the math as to how many
hours I spent writing the book versus how much I &lt;em&gt;could&lt;/em&gt; have made in side
software consulting gigs, nor do I plan to in the future - the opportunity cost
in terms of pure dollars would be really depressing.&lt;/p&gt;

&lt;p&gt;At the same time, I &lt;em&gt;did&lt;/em&gt; find so many benefits from writing a technical book -
learning a subject in depth, working with development communities in depth,
and just meeting random folks along the way who were interested in the topic
or have even read the book or online code samples.&lt;/p&gt;

&lt;h2 id=&quot;off-to-write-a-book&quot;&gt;Off to Write a Book&lt;/h2&gt;

&lt;p&gt;Having considered everything and deciding to jump in,
I signed the revised book contract with Packt in December, 2012 and officially
became on the hook for writing a book. Packt then sent me an “author
bundle” of documents, which included style and writing guidelines and templates
for the draft chapters.&lt;/p&gt;

&lt;p&gt;Packt uses Microsoft Word (or OpenOffice) documents for drafting and relies
on custom styles to eventually reformat the book for publication. I would
much have preferred to use a text-based document approach such as Markdown
or LaTeX, because there is so much more control over the document content
and structure, and code examples are much more likely to be properly
preserved.&lt;/p&gt;

&lt;p&gt;And with these reference materials and a loose outline of my topic in hand, I
began writing the draft of my first chapter…&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;And I’ll pick up the authoring adventure from this point on in the next
post in this series.&lt;/em&gt;)&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Book Announcement - Backbone.js Testing</title>
   <link href="http://loose-bits.com/2013/06/13/backbone-testing-book-announced.html"/>
   <updated>2013-06-13T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2013/06/13/backbone-testing-book-announced</id>
   <content type="html">
&lt;h2 id=&quot;backbonejs-testing-book&quot;&gt;Backbone.js Testing Book&lt;/h2&gt;

&lt;p&gt;I am very pleased to announce that I am in the last stages of publishing
&lt;strong&gt;&lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;Backbone.js Testing&lt;/a&gt;&lt;/strong&gt; - a book on &lt;a href=&quot;http://backbonejs.org/&quot;&gt;Backbone.js&lt;/a&gt; test
architecture and development due in final form around the end of June.&lt;/p&gt;

&lt;div class=&quot;pull-center&quot;&gt;
  &lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;
    &lt;img class=&quot;bordered&quot; alt=&quot;Backbone.js Testing&quot; title=&quot;Backbone.js Testing&quot; src=&quot;http://loose-bits.com/media/img/2013/06/13/book-cover.jpg&quot; /&gt;
  &lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;A brief abstract of the book’s content is available from the
&lt;a href=&quot;http://www.packtpub.com/&quot;&gt;Packt Publishing&lt;/a&gt; &lt;a href=&quot;http://www.packtpub.com/backbonejs-testing/book&quot;&gt;website&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Frontend web applications are soaring in popularity and the
Backbone.js library is leading this charge with a modular,
lightweight approach for organizing JavaScript web applications. At
the same time, testing client-side JavaScript and Backbone.js
programs remains a difficult and tedious undertaking.&lt;/p&gt;

  &lt;p&gt;Backbone.js Testing brings sensible practices and current techniques
to the challenges of Backbone.js test development. The book
introduces fundamental testing concepts, comprehensive test
infrastructure design, and practical exercises to easily and
systematically test modern JavaScript web applications.&lt;/p&gt;

  &lt;p&gt;The book progresses from Mocha test suites and Chai assertions to
advanced test mocks and stubs with Sinon.JS. The requisite libraries
and utilities are introduced with in-depth examples and best
practices for integration with your applications. The book guides
you through the test planning and implementation processes for your
application models, views, routers, and other Backbone.js
components.&lt;/p&gt;

  &lt;p&gt;Backbone.js Testing gives you the tools, examples, and assistance to
test your Backbone.js web applications thoroughly, quickly, and with
confidence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To provide a better idea of the book’s structure, the tentative chapter titles
are as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Setting up a Test Infrastructure&lt;/li&gt;
  &lt;li&gt;Creating a Backbone.js Application Test Plan&lt;/li&gt;
  &lt;li&gt;Test Assertions, Specs, and Suites&lt;/li&gt;
  &lt;li&gt;Test Spies&lt;/li&gt;
  &lt;li&gt;Test Stubs and Mocks&lt;/li&gt;
  &lt;li&gt;Automated Web Testing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of the code examples in the book are provided online at the website
&lt;a href=&quot;http://backbone-testing.com&quot;&gt;backbone-testing.com&lt;/a&gt; and as an open source
&lt;a href=&quot;https://github.com/ryan-roemer/backbone-testing&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;the-road-to-my-first-book&quot;&gt;The Road to my First Book&lt;/h2&gt;

&lt;p&gt;Application testing is difficult, and frontend JavaScript testing can often be
a downright nightmare. After years of working through difficulties in frontend
testing practices and technologies, I have gained a lot of experience through
the various trials and tribulations bringing me to my current role in
architecting (and testing) large Backbone.js applications.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.packtpub.com/&quot;&gt;Packt Publishing&lt;/a&gt; approached me towards the end of last year about
authoring a book on the subject and I signed on. My hope with the project is
that I will be able to provide a resource to reduce the pain in frontend tests
with a structured programming guide and practical examples using
various (exciting and interesting) modern testing libraries.&lt;/p&gt;

&lt;p&gt;It has been a stressful,
hectic, and wonderful experience writing the book, and I’m really looking
forward to completing the project. And, I will be happy to be able to reclaim
my nights and weekends that have been devoted to the book over the past
couple of months.&lt;/p&gt;

&lt;p&gt;After the book is published, I plan on writing a series of posts about
authoring a technical book, with all of the ups and downs that the process
entails. In the meantime, I just have to proof each chapter one final time and
it should be off to the presses, so to speak!&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Node.js in Production</title>
   <link href="http://loose-bits.com/2013/05/10/seattle-nodejs-production-talk.html"/>
   <updated>2013-05-10T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2013/05/10/seattle-nodejs-production-talk</id>
   <content type="html">&lt;h2 id=&quot;nodejs-in-production&quot;&gt;Node.js in Production&lt;/h2&gt;

&lt;p&gt;Curiosity Media runs the world’s largest Spanish learning website,
&lt;a href=&quot;http://spanishdict.com&quot;&gt;SpanishDict.com&lt;/a&gt;, backed by many different
&lt;a href=&quot;http://nodejs.org&quot;&gt;Node.js&lt;/a&gt; applications. Since rolling out our first Node.js
applications over a year ago, we have collected a fair amount of good and
bad operational experiences.&lt;/p&gt;

&lt;p&gt;I created a talk about our journey with Node.js for the
&lt;a href=&quot;http://www.meetup.com/Seattle-Node-js/&quot;&gt;Seattle Node.js Meetup&lt;/a&gt; group’s &lt;a href=&quot;http://www.meetup.com/Seattle-Node-js/events/115959992/&quot;&gt;May 8th, 2013 event&lt;/a&gt;.
My slides are available at the following locations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://ryan-roemer.github.io/seanode-prod-talk/&quot;&gt;&lt;strong&gt;GitHub Site&lt;/strong&gt;&lt;/a&gt;: A live,
navigable &lt;a href=&quot;https://github.com/hakimel/reveal.js/&quot;&gt;reveal.js&lt;/a&gt; website.
(&lt;em&gt;Note&lt;/em&gt;: use the space bar to advance slides and arrow keys to navigate.)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.slideshare.net/RyanRoemer/seanode-prodtalk&quot;&gt;&lt;strong&gt;SlideShare&lt;/strong&gt;&lt;/a&gt;: More
traditional format, with download (PDF) links.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, here’s an embedded format:&lt;/p&gt;

&lt;div class=&quot;embed embed-slides&quot;&gt;
  &lt;iframe src=&quot;http://www.slideshare.net/slideshow/embed_code/20880870&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; allowfullscreen=&quot;allowfullscreen&quot; webkitallowfullscreen=&quot;webkitallowfullscreen&quot; mozallowfullscreen=&quot;mozallowfullscreen&quot;&gt;&amp;nbsp;&lt;/iframe&gt;
&lt;/div&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;five-nodejs-production-tips&quot;&gt;Five Node.js Production Tips&lt;/h2&gt;

&lt;p&gt;My talk discussed the following five topics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Know when to Node&lt;/strong&gt;: &lt;em&gt;Should&lt;/em&gt; you use Node.js?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Keep up with Node&lt;/strong&gt;: Stay up to date with Node.js and libraries.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Design for failure&lt;/strong&gt;: Fail and recover at &lt;em&gt;multiple levels&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Isolate services&lt;/strong&gt;: Separate &lt;em&gt;resource&lt;/em&gt; and &lt;em&gt;failure&lt;/em&gt; classes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Analyze everything&lt;/strong&gt;: Data drives &lt;em&gt;problem discovery&lt;/em&gt; and &lt;em&gt;action&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is, of course, much more to running Node.js in production, but hopefully
my talk and slides provide a bit of insight into some of the considerations
to keep in mind when taking a Node.js application to production.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Sphinx Bootstrap Theme 0.2.0 - Now with Bootswatch!</title>
   <link href="http://loose-bits.com/2013/04/10/sphinx-bootstrap-theme-bootswatch.html"/>
   <updated>2013-04-10T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2013/04/10/sphinx-bootstrap-theme-bootswatch</id>
   <content type="html">
&lt;h2 id=&quot;bringing-bootswatch-to-sphinx&quot;&gt;Bringing Bootswatch to Sphinx&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme&quot;&gt;Bootstrap Theme&lt;/a&gt; for &lt;a href=&quot;http://sphinx.pocoo.org/&quot;&gt;Sphinx&lt;/a&gt; documentation continues to
evolve, and I’m pleased to announce the v0.2.0 release. The theme integrates
&lt;a href=&quot;https://twitter.com/&quot;&gt;Twitter&lt;/a&gt;’s &lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Bootstrap&lt;/a&gt; library into Sphinx,
and v0.2.0 adds new support for &lt;a href=&quot;http://bootswatch.com/&quot;&gt;Bootswatch&lt;/a&gt; CSS extensions, which
provide a lot of great new UI possibilities for your documentation. We’ve
also internally updated to Bootstrap 2.3.1.&lt;/p&gt;

&lt;p&gt;The project’s &lt;a href=&quot;http://ryan-roemer.github.com/sphinx-bootstrap-theme/README.html&quot;&gt;demonstration site&lt;/a&gt; now uses the
“&lt;a href=&quot;http://bootswatch.com/united/&quot;&gt;united&lt;/a&gt;” Bootswatch theme to give a clean
and well… &lt;em&gt;orange-ish&lt;/em&gt; feel to the site. Here is a screenshot of the
demonstration site in a desktop viewport:&lt;/p&gt;

&lt;p&gt;[![Theme with Bootswatch][img_demo_desk_th]][img_demo_desk]
[img_demo_desk_th]: http://loose-bits.com/media/img/2013/04/10/demo_desk_th.png
[img_demo_desk]: http://loose-bits.com/media/img/2013/04/10/demo_desk.png&lt;/p&gt;

&lt;p&gt;The project is available for download from &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme&quot;&gt;GitHub&lt;/a&gt; and
&lt;a href=&quot;http://pypi.python.org/pypi/sphinx-bootstrap-theme/0.2.0&quot;&gt;PyPi&lt;/a&gt;. You are encouraged to update and try out some Bootswatch
goodness!&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;adding-bootswatch&quot;&gt;Adding Bootswatch&lt;/h2&gt;

&lt;p&gt;As a preliminary note, the use of Bootswatch themes is entirely optional, and
disabled by default - the project starts with a vanilla Bootstrap appearance,
which you can customize to your heart’s content. To enable a specific
Bootswatch theme, set the following option in your
“&lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/blob/master/demo/source/conf.py&quot;&gt;conf.py&lt;/a&gt;” file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;html_theme_options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Bootswatch (http://bootswatch.com/) theme.&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Options are nothing with &quot;&quot; (default) or the name of a valid theme such&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# as &quot;amelia&quot; or &quot;cosmo&quot;.&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Note that this is served off CDN, so won't be available offline.&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'bootswatch_theme'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;united&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The list of Bootswatch themes you can now try out include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/amelia/&quot;&gt;&lt;strong&gt;amelia&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/cerulean/&quot;&gt;&lt;strong&gt;cerulean&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/cosmo/&quot;&gt;&lt;strong&gt;cosmo&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/cyborg/&quot;&gt;&lt;strong&gt;cyborg&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/journal/&quot;&gt;&lt;strong&gt;journal&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/readable/&quot;&gt;&lt;strong&gt;readable&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/simplex/&quot;&gt;&lt;strong&gt;simplex&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/slate/&quot;&gt;&lt;strong&gt;slate&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/spacelab/&quot;&gt;&lt;strong&gt;spacelab&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/spruce/&quot;&gt;&lt;strong&gt;spruce&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/superhero/&quot;&gt;&lt;strong&gt;superhero&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bootswatch.com/united/&quot;&gt;&lt;strong&gt;united&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a final note, the Bootstrap CSS files are served off of &lt;strong&gt;CDN&lt;/strong&gt;, which means
that if you are offline, your site will have a non-themed, vanilla Bootstrap
appearance. We are considering refactoring the Bootswatch code to be available
offline as well.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Sphinx Bootstrap Theme 0.1.6 - Bootstrap and Other Updates</title>
   <link href="http://loose-bits.com/2013/02/12/sphinx-bootstrap-theme-updates.html"/>
   <updated>2013-02-12T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2013/02/12/sphinx-bootstrap-theme-updates</id>
   <content type="html">&lt;h2 id=&quot;sphinx-bootstrap-theme&quot;&gt;Sphinx Bootstrap Theme&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme&quot;&gt;Sphinx Bootstrap Theme&lt;/a&gt; is an extension for the &lt;a href=&quot;http://sphinx.pocoo.org/&quot;&gt;Sphinx&lt;/a&gt;
documentation tool, used for &lt;a href=&quot;http://python.org/&quot;&gt;Python&lt;/a&gt;-based API documentation and
static website authoring. The theme integrates &lt;a href=&quot;https://twitter.com/&quot;&gt;Twitter&lt;/a&gt;
&lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Bootstrap&lt;/a&gt;, which is a wildly popular frontend framework. The
&lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme&quot;&gt;theme GitHub project&lt;/a&gt; provides a &lt;a href=&quot;http://ryan-roemer.github.com/sphinx-bootstrap-theme/README.html&quot;&gt;demonstration site&lt;/a&gt;
using the theme.&lt;/p&gt;

&lt;p&gt;The theme project continues to move along, and as it has been a while since my
&lt;a href=&quot;http://loose-bits.com/2012/11/19/sphinx-bootstrap-theme-updates.html&quot;&gt;last update post&lt;/a&gt;, here are some of the changes in version
&lt;a href=&quot;http://pypi.python.org/pypi/sphinx-bootstrap-theme/0.1.6&quot;&gt;0.1.6&lt;/a&gt; (since 0.1.0):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Update to Bootstrap v2.3.0.&lt;/li&gt;
  &lt;li&gt;Enable configuration of the depth of the Global TOC (“Site” tab).&lt;/li&gt;
  &lt;li&gt;Add footer divider and various other styling tweaks.&lt;/li&gt;
  &lt;li&gt;Add Bootstrap styling for tables and inline code snippets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, much of the underlying rendering JavaScript code has been
revised. For example, Twitter Bootstrap v2.3.0 handles behavior like dropdown
menu clicks in mobile much better than previous versions, enabling removal of
some hacks and patches in the theme.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;theme-options&quot;&gt;Theme Options&lt;/h2&gt;

&lt;p&gt;Here is the current list of options you can set for the theme in the “conf.py”
configuration file, as used by the demo site in “&lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/blob/master/demo/source/conf.py&quot;&gt;conf.py&lt;/a&gt;”:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;html_theme_options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Global TOC depth for &quot;site&quot; navbar tab. (Default: 1)&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Switching to -1 shows all levels.&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'globaltoc_depth'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# HTML navbar class (Default: &quot;navbar&quot;) to attach to &amp;lt;div&amp;gt; element.&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# For black navbar, do &quot;navbar navbar-inverse&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'navbar_class'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;navbar navbar-inverse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Fix navigation bar to top of page?&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Values: &quot;true&quot; (default) or &quot;false&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'navbar_fixed_top'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Location of link to source.&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Options are &quot;nav&quot; (default), &quot;footer&quot; or anything else to exclude.&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'source_link_position'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;nav&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Sphinx Bootstrap Theme Updates - Mobile, Dropdowns, and More</title>
   <link href="http://loose-bits.com/2012/11/19/sphinx-bootstrap-theme-updates.html"/>
   <updated>2012-11-19T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2012/11/19/sphinx-bootstrap-theme-updates</id>
   <content type="html">
&lt;h2 id=&quot;sphinx-bootstrap-theme-updates&quot;&gt;Sphinx Bootstrap Theme Updates!&lt;/h2&gt;

&lt;p&gt;Almost a year ago, I created the &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme&quot;&gt;Sphinx Bootstrap Theme&lt;/a&gt; project to
bring together two of my favorite open source technologies - Sphinx and Twitter
Bootstrap. &lt;a href=&quot;http://sphinx.pocoo.org/&quot;&gt;Sphinx&lt;/a&gt; is a widely-used &lt;a href=&quot;http://python.org/&quot;&gt;Python&lt;/a&gt;-based authoring
tool for creating static websites and API documentation. &lt;a href=&quot;https://twitter.com/&quot;&gt;Twitter&lt;/a&gt;
&lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Bootstrap&lt;/a&gt; is a frontend JavaScript / CSS framework that offers a
consistent UI experience, typography, grid systems, navigation / button / other
components, and responsive features for mobile design.&lt;/p&gt;

&lt;p&gt;Since its introduction, I now use Sphinx with the Bootstrap Theme
for my open source Python projects and we use the theme for all internal
technical documentation at work. Coming up on the project’s anniversary, it
seemed appropriate to add some tweaks and enhancements to increase the usability
and extensibility of the theme.&lt;/p&gt;

&lt;p&gt;Improvements now available in version 0.1.0 include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Upgraded the Bootstrap libraries to version 2.2.1.&lt;/li&gt;
  &lt;li&gt;Added new theme options to control UI appearance and behavior.&lt;/li&gt;
  &lt;li&gt;Navigation bar menus now have hierarchical sub-menus for a more intuitive
experience.&lt;/li&gt;
  &lt;li&gt;Reworked the underlying layout code to support fully responsive design,
allowing any site to look great on mobile devices like an iPhone or iPad.&lt;/li&gt;
  &lt;li&gt;Created a PyPI package to permit installation via
&lt;code class=&quot;highlighter-rouge&quot;&gt;pip install sphinx_bootstrap_theme&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let’s see how everything looks and works with the new v0.1.0 theme.&lt;/p&gt;

&lt;h2 id=&quot;theme-options&quot;&gt;Theme Options&lt;/h2&gt;

&lt;p&gt;The theme has several new options that you can set in the “conf.py”
configuration file.&lt;/p&gt;

&lt;h3 id=&quot;bootstrap-theme-demo-site&quot;&gt;Bootstrap Theme Demo Site&lt;/h3&gt;

&lt;p&gt;Let’s start with the Bootstrap Theme’s own
&lt;a href=&quot;http://ryan-roemer.github.com/sphinx-bootstrap-theme/README.html&quot;&gt;demonstration website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;[![Sphinx Bootstrap Theme Desktop View][img_sbt_desk]][img_sbt_desk]
[img_sbt_desk]: http://loose-bits.com/media/img/2012/11/19/sbt_desk.png&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;The demo site has the following theme option settings in
“&lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/blob/master/demo/source/conf.py&quot;&gt;conf.py&lt;/a&gt;”:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;html_theme_options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# HTML navbar class (Default: &quot;navbar&quot;) to attach to &amp;lt;div&amp;gt;.&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# For black navbar, do &quot;navbar navbar-inverse&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'navbar_class'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;navbar navbar-inverse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Fix navigation bar to top of page?&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Values: &quot;true&quot; (default) or &quot;false&quot;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'navbar_fixed_top'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Location of link to source.&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Options are &quot;nav&quot; (default), &quot;footer&quot;.&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'source_link_position'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;nav&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notably, this gives us a dark navigation bar that is fixed to the top
of the page during any scrolling.&lt;/p&gt;

&lt;h3 id=&quot;django-cloud-browser&quot;&gt;Django Cloud Browser&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/ryan-roemer/django-cloud-browser/&quot;&gt;Django Cloud Browser&lt;/a&gt; is a reusable &lt;a href=&quot;https://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt; application for
browsing and downloading files stored in various cloud datastores (e.g., Amazon
Web Services S3, Rackspace Cloud Files). The &lt;a href=&quot;http://ryan-roemer.github.com/django-cloud-browser/&quot;&gt;API documentation&lt;/a&gt;
uses the Bootstrap Theme with (mostly) the default options.&lt;/p&gt;

&lt;p&gt;[![Django Cloud Browser][img_sbt_cb]][img_sbt_cb]
[img_sbt_cb]: http://loose-bits.com/media/img/2012/11/19/sbt_cb.png&lt;/p&gt;

&lt;p&gt;The only real tweak is setting &lt;code class=&quot;highlighter-rouge&quot;&gt;'source_link_position': &quot;footer&quot;&lt;/code&gt; to move
the documentation source code link down from the navigation bar to the bottom
of each page.&lt;/p&gt;

&lt;h2 id=&quot;navigation-menu-dropdowns&quot;&gt;Navigation Menu Dropdowns&lt;/h2&gt;

&lt;p&gt;The navigation bar menus are now hierarchically arranged into dropdowns and
sub-menus for easier navigation. This looks a lot better than the previous
versions (which just used hacked-up CSS whitespace padding).&lt;/p&gt;

&lt;p&gt;[![Navbar Menu Dropdowns][img_sbt_nav_dropdown_hl]][img_sbt_nav_dropdown_hl]
[img_sbt_nav_dropdown]: http://loose-bits.com/media/img/2012/11/19/sbt_nav_dropdown.png
[img_sbt_nav_dropdown_hl]: http://loose-bits.com/media/img/2012/11/19/sbt_nav_dropdown_hl.png&lt;/p&gt;

&lt;h2 id=&quot;mobile-ui-support&quot;&gt;Mobile UI Support&lt;/h2&gt;

&lt;p&gt;One of the most popular aspects of the Bootstrap library is its great support
for &lt;a href=&quot;http://twitter.github.com/bootstrap/scaffolding.html#responsive&quot;&gt;responsive mobile design&lt;/a&gt;. These responsive features are
integrated into version 0.1.0 of the theme.&lt;/p&gt;

&lt;p&gt;The Bootstrap Theme now renders a collapsed, clickable navigation bar in
smaller viewports (like a mobile phone and some tablet orientations). Viewing
the theme demo site in an iPhone gives us a slightly more terse UI.&lt;/p&gt;

&lt;p&gt;[![Mobile Phone View][img_sbt_ios_th]][img_sbt_ios]
[img_sbt_ios_th]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_th.png
[img_sbt_ios]: http://loose-bits.com/media/img/2012/11/19/sbt_ios.png&lt;/p&gt;

&lt;p&gt;Clicking on the right navigation bar button gives us a basic first-level
dropdown menu.&lt;/p&gt;

&lt;p&gt;[![Mobile Phone Navbar][img_sbt_ios_nav_hl_th]][img_sbt_ios_nav_hl]
[img_sbt_ios_nav_th]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_th.png
[img_sbt_ios_nav]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav.png
[img_sbt_ios_nav_hl_th]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_hl_th.png
[img_sbt_ios_nav_hl]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_hl.png&lt;/p&gt;

&lt;p&gt;And we can further click through sub-menus to navigate to the desired section
or page of a website.&lt;/p&gt;

&lt;p&gt;[![Mobile Phone Navbar Menu][img_sbt_ios_nav_dropdown_hl_th]][img_sbt_ios_nav_dropdown_hl]
[img_sbt_ios_nav_dropdown_th]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_dropdown_th.png
[img_sbt_ios_nav_dropdown]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_dropdown.png
[img_sbt_ios_nav_dropdown_hl_th]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_dropdown_hl_th.png
[img_sbt_ios_nav_dropdown_hl]: http://loose-bits.com/media/img/2012/11/19/sbt_ios_nav_dropdown_hl.png&lt;/p&gt;

&lt;p&gt;With these and other responsive Bootstrap features, the Sphinx Bootstrap Theme
now gives your API documentation or website real usability for mobile devices.&lt;/p&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;The Bootstrap Theme supports installation via &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/downloads&quot;&gt;download&lt;/a&gt; as
detailed in my &lt;a href=&quot;http://loose-bits.com/2011/12/09/sphinx-twitter-bootstrap-theme.html#installation&quot;&gt;previous blog post&lt;/a&gt; and now adds full
&lt;a href=&quot;http://pypi.python.org/pypi/sphinx-bootstrap-theme&quot;&gt;PyPI&lt;/a&gt; installation support as a Python package.&lt;/p&gt;

&lt;p&gt;To get the theme from PyPI, use &lt;code class=&quot;highlighter-rouge&quot;&gt;pip&lt;/code&gt; to install it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;pip install sphinx_bootstrap_theme&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In your Sphinx “conf.py” configuration file, import the theme module
at the top:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;sphinx_bootstrap_theme&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then, configure the HTML theme values in “conf.py”, using the module to get
values for &lt;code class=&quot;highlighter-rouge&quot;&gt;html_theme_path&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Activate the theme.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;html_theme&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bootstrap'&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;html_theme_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sphinx_bootstrap_theme&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_html_theme_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And, that’s pretty much it. The theme should now be available to Sphinx for
documentation building.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The Bootstrap Theme brings Twitter Bootstrap to Sphinx sites and with version
0.1.0 adds even more useful Bootstrap features and options. So try it out,
see if Bootstrap is right for your Sphinx website, and please pass on any
feedback (&lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/issues&quot;&gt;issue reports&lt;/a&gt;, &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/pulls&quot;&gt;pull requests&lt;/a&gt;, etc.).&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Shared JavaScript Code with AMD/RequireJS</title>
   <link href="http://loose-bits.com/2012/11/15/nodedc-amd-requirejs-talk.html"/>
   <updated>2012-11-15T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2012/11/15/nodedc-amd-requirejs-talk</id>
   <content type="html">
&lt;h2 id=&quot;amdrequirejs&quot;&gt;AMD/RequireJS&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/amdjs/amdjs-api/wiki/AMD&quot;&gt;Asynchronous Module Definition&lt;/a&gt; (&lt;em&gt;AMD&lt;/em&gt;) provides a framework for
predictable loading of JavaScript modules and dependencies. AMD is an extension
of (and possible transport for) the &lt;a href=&quot;http://www.commonjs.org/&quot;&gt;CommonJS&lt;/a&gt; specification.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://requirejs.org/&quot;&gt;RequireJS&lt;/a&gt; library implements AMD with loading, bundling and
other functionality. It brings sensible imports (with &lt;code class=&quot;highlighter-rouge&quot;&gt;define&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;require&lt;/code&gt;)
to frontend JavaScript, instead of the usual array of script includes into the
global namespace. Interestingly, as &lt;a href=&quot;http://nodejs.org&quot;&gt;Node.js&lt;/a&gt; implements CommonJS-style
&lt;code class=&quot;highlighter-rouge&quot;&gt;require&lt;/code&gt;’s, there are also support modules to bring Node.js code under AMD.&lt;/p&gt;

&lt;p&gt;Take both of things together and we have the real potential for shared
JavaScript code across the browser and server. As we use this exact approach
at work for our new Backbone.js frontend / Express Node.js backend web
application, I created a short talk that I presented at the  November 11, 2012
&lt;a href=&quot;http://www.meetup.com/node-dc/events/89233812/&quot;&gt;Node.DC&lt;/a&gt; meetup, entitled “Shared Code with AMD/RequireJS”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/nodedc-requirejs-talk/&quot;&gt;![AMD/RequireJS Talk][img_talk]&lt;/a&gt;
[img_talk]: http://loose-bits.com/media/img/2012/11/15/nodedc-requirejs.png&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;source-code-and-demo&quot;&gt;Source Code and Demo&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/ryan-roemer/nodedc-requirejs-talk/&quot;&gt;source&lt;/a&gt; for the presentation is available on GitHub. The
repository further includes all of the &lt;a href=&quot;https://github.com/ryan-roemer/nodedc-requirejs-talk/tree/master/demo&quot;&gt;demonstration code&lt;/a&gt; I used to
create a shared JavaScript library and then expose it via the frontend (as
straight JavaScript) and the backend (as a REST service).&lt;/p&gt;

&lt;p&gt;The talk and the source give a good overview and run-through of how to implement
simple shared code, but to give a better sense of the final result, here is a
RequireJS-compliant code module (for shuffling elements in a string) that has
an &lt;a href=&quot;http://underscorejs.org/&quot;&gt;underscore&lt;/a&gt; dependency, and works in both the browser
and as a standard module in Node.js:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Node.js hook boilerplate.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// This adds the `define` function to Node.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'function'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'amdefine'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Define wrapper gives us `require`&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Looks like a normal `require` for Node.js.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Note: Both browser and Node.js should use&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// the **same** version of libraries.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'underscore'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Slice a string into a list of items&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// and return the shuffled values.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;shuffle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;[\s&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Export our shuffle function by returning.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;shuffle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We add some boilerplate at the top of the library for Node.js, then use
the &lt;code class=&quot;highlighter-rouge&quot;&gt;define&lt;/code&gt; function to make a &lt;code class=&quot;highlighter-rouge&quot;&gt;require&lt;/code&gt; import function available to both the
browser and Node.js, and we’re off and code sharing!&lt;/p&gt;

&lt;p&gt;AMD, RequireJS, and shared code are topics that have a lot more depth,
features, and challenges. Hopefully this provides the start to a journey into
better JavaScript dependency management and more extensible full stack web
application development.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Deck.js Starter</title>
   <link href="http://loose-bits.com/2012/11/05/deck-js-starter.html"/>
   <updated>2012-11-05T10:00:00+00:00</updated>
   <id>http://loose-bits.com/2012/11/05/deck-js-starter</id>
   <content type="html">
&lt;h2 id=&quot;deckjs-starter&quot;&gt;Deck.js Starter&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://imakewebthings.com/deck.js/&quot;&gt;Deck.js&lt;/a&gt; is a modern presentation framework for creating interactive
HTML-based interactive slide decks. The framework provides:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A basic boilerplate.&lt;/li&gt;
  &lt;li&gt;CSS themes.&lt;/li&gt;
  &lt;li&gt;Extensions including a menu, “go to” button, transitions, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create a presentation, simply edit the HTML files and host a static website
and you have an auto-magic slide deck, available to the world.&lt;/p&gt;

&lt;p&gt;I have now done a couple of deck.js-based presentations, and find it
incredibly useful, but noticed I was re-implementing the same scaffolding
enhancements. I wrapped up these common enhancements in a GitHub project,
&lt;a href=&quot;https://github.com/ryan-roemer/deck.js-starter&quot;&gt;Deck.js Starter&lt;/a&gt;. The notable enhancements the starter kit has are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://jade-lang.com&quot;&gt;Jade templates&lt;/a&gt; with &lt;a href=&quot;http://daringfireball.net/projects/markdown/&quot;&gt;Markdown&lt;/a&gt; support for faster slide authoring.&lt;/li&gt;
  &lt;li&gt;Executable JavaScript code snippets using &lt;a href=&quot;http://codemirror.net/&quot;&gt;CodeMirror&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Executable CoffeeScript code snippets via some specific tweaks I made to
the &lt;a href=&quot;https://github.com/iros/deck.js-codemirror&quot;&gt;CodeMirror deck.js extension&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, you can download and install the starter kit, and just start
authoring new slides. The starting deck provides a short demo and is available
at:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/deck.js-starter&quot;&gt;![Deck.js Starter][img_ds_demo]&lt;/a&gt;
[img_ds_demo]: http://loose-bits.com/media/img/2012/11/05/deck_slides.png&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;deckjs-introduction&quot;&gt;Deck.js Introduction&lt;/h2&gt;

&lt;p&gt;Getting up to speed &lt;a href=&quot;http://imakewebthings.com/deck.js/&quot;&gt;deck.js&lt;/a&gt; is pretty straightforward: check out
a &lt;a href=&quot;http://imakewebthings.com/deck.js/introduction&quot;&gt;tutorial / exemplary demo&lt;/a&gt; or dive right into the
&lt;a href=&quot;http://imakewebthings.com/deck.js/docs/&quot;&gt;docs&lt;/a&gt; or &lt;a href=&quot;https://github.com/imakewebthings/deck.js/wiki&quot;&gt;wiki&lt;/a&gt;. After the basics are mastered,
there are plenty of avenues for further exploration / additions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Themes&lt;/strong&gt;: Deck.js includes three by default and there are several more
available as open source projects.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Extensions&lt;/strong&gt;: Deck.js includes a menu, navigation and other controls by
default. There is a fantastic open source ecosystem around deck providing
all manner of additional extensions. To review a few:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Remote control a presentation.&lt;/li&gt;
      &lt;li&gt;Add a laser pointer to the presentation online.&lt;/li&gt;
      &lt;li&gt;Easy “presenter” views.&lt;/li&gt;
      &lt;li&gt;… and many more!&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt;: Several open source projects offer meta-level enhancements like
markdown support, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;working-with-deckjs&quot;&gt;Working with Deck.js&lt;/h2&gt;

&lt;p&gt;I have used deck for two of my presentations to date. My basic workflow was to
fork the repository from GitHub, then make my edits and add in extensions.
Finally, I pushed the static web project to GitHub Pages for free hosting.&lt;/p&gt;

&lt;p&gt;For a better sense of what the end products look like, here are the two live
presentation decks.&lt;/p&gt;

&lt;h3 id=&quot;getting-started-with-nodejs-in-the-cloud&quot;&gt;Getting Started with Node.js in the Cloud&lt;/h3&gt;

&lt;p&gt;My talk to the Nova Node meetup on using Node.js in the cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/novanode-cloud-talk/&quot;&gt;![Node.js cloud presentation][img_pres]&lt;/a&gt;
[img_pres]: http://loose-bits.com/media/img//2012/03/24/nodejs-cloud.png&lt;/p&gt;

&lt;h3 id=&quot;things-i-like-about-coffeescript&quot;&gt;5 Things I Like About CoffeeScript&lt;/h3&gt;

&lt;p&gt;A simple CoffeeScript survey I presented at the Node.DC meetup.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/nodedc-coffeescript-talk/&quot;&gt;![CoffeeScript Talk][img_talk]&lt;/a&gt;
[img_talk]: http://loose-bits.com/media/img//2012/08/16/nodedc-coffeescript.png&lt;/p&gt;

&lt;h2 id=&quot;a-starter-kit&quot;&gt;A Starter Kit&lt;/h2&gt;

&lt;p&gt;After writing these presentations and looking to my next one, I realized I was
writing the same boilerplate again, and decided to finally wrap everything up
in a concise, opinionated starting point: &lt;a href=&quot;https://github.com/ryan-roemer/deck.js-starter&quot;&gt;Deck.js Starter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get started, checkout the &lt;a href=&quot;http://ryan-roemer.github.com/deck.js-starter&quot;&gt;online demo&lt;/a&gt;, and then review the
&lt;a href=&quot;https://github.com/ryan-roemer/deck.js-starter/blob/master/README.md&quot;&gt;README&lt;/a&gt;. The “tl;dr” version is:&lt;/p&gt;

&lt;p&gt;First, clone the repository:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git clone https://github.com/ryan-roemer/deck.js-starter.git my-presentation
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Next, make sure Node.js is installed, and install all the necessary support
packages.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cd my-presentation
$ npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Once you are ready to author, start the “watch” task that will automatically
build “&lt;a href=&quot;https://github.com/ryan-roemer/deck.js-starter/blob/master/layout.jade&quot;&gt;layout.jade&lt;/a&gt;” and “&lt;a href=&quot;https://github.com/ryan-roemer/deck.js-starter/blob/master/index.jade&quot;&gt;index.jade&lt;/a&gt;” into the
“index.html” static page. You can then preview your slide deck at “index.html”:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ npm run-script watch
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Authoring is pretty easy. To add themes, extensions, or extra scripts, edit
“&lt;a href=&quot;https://github.com/ryan-roemer/deck.js-starter/blob/master/layout.jade&quot;&gt;layout.jade&lt;/a&gt;”. For slides, edit “&lt;a href=&quot;https://github.com/ryan-roemer/deck.js-starter/blob/master/index.jade&quot;&gt;index.jade&lt;/a&gt;” and
add &lt;code class=&quot;highlighter-rouge&quot;&gt;section.slide&lt;/code&gt; elements following the boilerplate as a guide. Make sure to
take a look at the following possibilities:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executable JavaScript&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/deck.js-starter/#js&quot;&gt;![Deck.js Starter][img_js_demo]&lt;/a&gt;
[img_js_demo]: http://loose-bits.com/media/img/2012/11/05/deck_js.png&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Executable CoffeeScript&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/deck.js-starter/#cs&quot;&gt;![Deck.js Starter][img_cs_demo]&lt;/a&gt;
[img_cs_demo]: http://loose-bits.com/media/img/2012/11/05/deck_cs.png&lt;/p&gt;

&lt;p&gt;After exploring the basics of the starter kit, feel free to add in any other
Deck.js extensions or other useful components or tweaks. You’ll end up with
an interactive, visually appealing presentation that you can easily author and
host online.&lt;/p&gt;

&lt;p&gt;As a parting thought, the Deck.js Starter was written in a couple of hours over
the weekend. I do plan to enhance it as I author real presentations in the
future (particularly with an eye towards adding more themes). But any
feedback or enhancements are most welcome.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Responsive Web Design with Twitter Bootstrap</title>
   <link href="http://loose-bits.com/2012/09/30/responsive-web-design-twitter-bootstrap.html"/>
   <updated>2012-09-30T18:37:07+00:00</updated>
   <id>http://loose-bits.com/2012/09/30/responsive-web-design-twitter-bootstrap</id>
   <content type="html">
&lt;h2 id=&quot;bootstrap&quot;&gt;Bootstrap&lt;/h2&gt;

&lt;p&gt;Twitter’s &lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Bootstrap&lt;/a&gt; library is one of the most popular front-end
frameworks, with an amazing adoption rate by various web sites, from small
to large.  Bootstrap provides:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Responsive Features&lt;/strong&gt;: Enables seamless transitions between desktop, tablet,
and mobile view sizes, with intuitive look-and-feel switching and custom
single-view overrides.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Layout Support&lt;/strong&gt;: Fixed and fluid grid systems for web design.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Style Components&lt;/strong&gt;: Really nice looking form helpers, fonts, tables, etc.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;UI Components&lt;/strong&gt;: Dropdown menus, buttons, accordions, alerts, progress bars
and more, with JavaScript support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, Bootstrap makes it easy to design a responsive website (that looks
good in desktop or mobile views) and add user interface components that keep
with the overall user experience.&lt;/p&gt;

&lt;h2 id=&quot;moving-loose-bits-to-bootstrap&quot;&gt;Moving Loose Bits to Bootstrap&lt;/h2&gt;

&lt;p&gt;Loose Bits (this blog) previously had a non-responsive design - the main
heading bar was far too wide when viewed on a mobile phone (like my iPhone).
I had been itching to make this site mobile-friendly, and finally decided to
bite the bullet and integrate Bootstrap.&lt;/p&gt;

&lt;p&gt;I downloaded the &lt;a href=&quot;https://github.com/twitter/bootstrap&quot;&gt;full bootstrap build&lt;/a&gt; from source, so that I could
customize parts of the framework and only add in what I needed. As this blog
is hosted as an open source project on &lt;a href=&quot;http://github.com&quot;&gt;GitHub&lt;/a&gt;, the source code
and build system is available for checkout or download at my
&lt;a href=&quot;https://github.com/ryan-roemer/loose-bits&quot;&gt;Loose Bits repository&lt;/a&gt;.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;While I did customize some colors and variables for Bootstrap and spent
some time inserting Bootstrap-compatible CSS classes in my HTML layout, I have
to say the overall experience was quite easy, required minimal changes, and
I am pleased with the final website.&lt;/p&gt;

&lt;p&gt;The different layout effects for various viewports can be viewed simply by
resizing a browser window on a desktop computer. In desktop view, I’ve retooled
the main header and navigation bar to look like:&lt;/p&gt;

&lt;div class=&quot;pull-center&quot;&gt;
  &lt;img class=&quot;bordered&quot; src=&quot;http://loose-bits.com/media/img/2012/09/30/desktop.png&quot; /&gt;
  &lt;p /&gt;
&lt;/div&gt;

&lt;p&gt;My layout uses the basic “inverse” (dark) Bootstrap theme and integrates the
fantastic &lt;a href=&quot;http://fortawesome.github.com/Font-Awesome/&quot;&gt;Font Awesome&lt;/a&gt; icons for each navigation bar menu item.&lt;/p&gt;

&lt;h2 id=&quot;loose-bits-on-mobile&quot;&gt;Loose Bits on Mobile!&lt;/h2&gt;

&lt;p&gt;Moving on to the mobile experience, the header and full navigation bars are
replaced by a title-only heading with a clickable navigation menu button.
Resizing a browser window down to mobile phone size (or if already on a mobile
device), we get:&lt;/p&gt;

&lt;div class=&quot;pull-center&quot;&gt;
  &lt;img class=&quot;bordered&quot; src=&quot;http://loose-bits.com/media/img/2012/09/30/mobile.png&quot; /&gt;
  &lt;p /&gt;
&lt;/div&gt;

&lt;p&gt;I’ve used the simple navigation collapse feature to have a clickable menu of
all of the previous navigation bar items, so as to more effectively use the
reduced real estate in a mobile view:&lt;/p&gt;

&lt;div class=&quot;pull-center&quot;&gt;
  &lt;img class=&quot;bordered&quot; src=&quot;http://loose-bits.com/media/img/2012/09/30/mobile-menu.png&quot; /&gt;
  &lt;p /&gt;
&lt;/div&gt;

&lt;p&gt;The rest of the website has a few other adjustments in mobile view mode, nearly
all of which auto-magically happen without any further tweaks to Bootstrap
configuration or JavaScript hacking.&lt;/p&gt;

&lt;p&gt;At the end of the day, I’m pretty happy with the Bootstrap development
experience and the end result for my website.
Like any web framework under active development, there are still a few
gotcha’s - like some loss of browser compatibility and weird JavaScript
bugs (e.g., my navigation buttons sometimes disappear).
But, ultimately, for anyone searching for the fastest path to a reliable and
intuitive responsive web site, I would definitely recommend looking at Twitter
Bootstrap.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>5 Things I Like About CoffeeScript</title>
   <link href="http://loose-bits.com/2012/08/16/five-things-i-like-about-coffeescript.html"/>
   <updated>2012-08-16T11:30:00+00:00</updated>
   <id>http://loose-bits.com/2012/08/16/five-things-i-like-about-coffeescript</id>
   <content type="html">
&lt;h2 id=&quot;coffeescript&quot;&gt;CoffeeScript&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://coffeescript.org/&quot;&gt;CoffeeScript&lt;/a&gt; is self-described as “a little language that compiles into
JavaScript”. CoffeeScript implements a subset of the full JavaScript language
and adds a little bit of common boilerplate code to take out some of the
less savory parts of JavaScript. CoffeeScript works both in &lt;a href=&quot;http://nodejs.org&quot;&gt;Node.js&lt;/a&gt;
on the backend, as well as in the browser.&lt;/p&gt;

&lt;p&gt;I gave a short survey talk at the August 15, 2012 &lt;a href=&quot;http://www.meetup.com/node-dc/events/73746422/&quot;&gt;Node.DC&lt;/a&gt; meetup,
“5 Things I Like About CoffeeScript”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/nodedc-coffeescript-talk/&quot;&gt;![CoffeeScript Talk][img_talk]&lt;/a&gt;
[img_talk]: http://loose-bits.com/media/img/2012/08/16/nodedc-coffeescript.png&lt;/p&gt;

&lt;p&gt;The talk’s &lt;a href=&quot;https://github.com/ryan-roemer/nodedc-coffeescript-talk/&quot;&gt;source&lt;/a&gt; is available on GitHub, and uses the
awesome &lt;a href=&quot;http://imakewebthings.com/deck.js/&quot;&gt;deck.js&lt;/a&gt; presentation framework with the
&lt;a href=&quot;https://github.com/iros/deck.js-codemirror&quot;&gt;CodeMirror plugin&lt;/a&gt; to enable editable and runnable code samples. I
further hacked up the CodeMirror plugin to additionally make the CoffeeScript
code examples executable (CodeMirror only does JavaScript by default).&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;five-cool-things-in-coffeescript&quot;&gt;Five Cool Things in CoffeeScript&lt;/h2&gt;

&lt;p&gt;For the impatient, the five topics I covered are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Syntax&lt;/li&gt;
  &lt;li&gt;Functions&lt;/li&gt;
  &lt;li&gt;Classes&lt;/li&gt;
  &lt;li&gt;Existential Operator&lt;/li&gt;
  &lt;li&gt;Loops, Comprehensions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The talk just skims the surface of the powerful (yet slim) CoffeeScript
language. For further reading / real introductory tutorials, I would suggest:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://coffeescript.org/&quot;&gt;CoffeeScript Documentation&lt;/a&gt;: The official API.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://coffeescriptcookbook.com/&quot;&gt;The CoffeeScript Cookbook&lt;/a&gt;: Lots of “recipes” for common
programming problems and projects.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://autotelicum.github.com/Smooth-CoffeeScript/&quot;&gt;Smooth Coffeescript&lt;/a&gt;: Interactive online eBook with live
code execution and editing.&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Better Data Slinging with Node.js Readable/Writable Streams and Pipes</title>
   <link href="http://loose-bits.com/2012/08/02/nodejs-read-write-streams-pipes.html"/>
   <updated>2012-08-02T14:00:00+00:00</updated>
   <id>http://loose-bits.com/2012/08/02/nodejs-read-write-streams-pipes</id>
   <content type="html">&lt;h2 id=&quot;nodejs-streams&quot;&gt;Node.js Streams&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://nodejs.org&quot;&gt;Node.js&lt;/a&gt; provides an extensible and fast platform for web servers,
proxies, and middle-tier services. Node.js applications often utilize
some transformation from one data format (e.g., a database or cloud
store) to another (e.g., an HTML or JSON page).&lt;/p&gt;

&lt;p&gt;Most folks are familiar with the callback-style of hooking together various
JavaScript data components in a Node.js program. However, an often overlooked
and very powerful data binding abstraction for Node.js is found in the
&lt;a href=&quot;http://nodejs.org/api/stream.html&quot;&gt;stream&lt;/a&gt; class.&lt;/p&gt;

&lt;p&gt;Streams are an abstract interface for data objects in Node.js which can be
readable and/or writable. They can be hooked from one to another in
a similar style to Unix pipes – in fact, the stream operation we’ll mostly
focus on here is the not-coincidentally-named &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt;. Some potential
advantages of stream pipes over other binding styles include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Often much less code for the actual binding (can just call &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Streams can handle pausing / resuming of data flows. Implementing classes,
however, have to implement this logic internally if supported.&lt;/li&gt;
  &lt;li&gt;Don’t have to set specific callbacks or listeners for intermediate data
events – just &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt; the stream and forget it.&lt;/li&gt;
  &lt;li&gt;Avoiding buffering by processing data and re-emitting it directly to another
stream (unless &lt;em&gt;all&lt;/em&gt; of the data is required in one chunk).&lt;/li&gt;
  &lt;li&gt;Compatible with the many Node.js core modules that already implement
streams, including HTTP, HTTPS, and file and process I/O.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a brief example, we can create a download client to retrieve
a web page and write it to a file as follows (ignoring error handling):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Get Google's home page.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'http'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://www.google.com/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// The callback provides the response readable stream.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Then, we open our output text stream.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;outStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'fs'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;out.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Pipe the input to the output, which writes the file.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;outStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notice that we didn’t set any explicit “&lt;code class=&quot;highlighter-rouge&quot;&gt;data&lt;/code&gt;” listeners or buffer any of
the data, even if it came in as chunks. We simply &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt;‘ed it with our
two stream objects: &lt;code class=&quot;highlighter-rouge&quot;&gt;response&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;outStream&lt;/code&gt;.  The output of &lt;code class=&quot;highlighter-rouge&quot;&gt;response&lt;/code&gt; is
hooked to the input of &lt;code class=&quot;highlighter-rouge&quot;&gt;outStream&lt;/code&gt; and we’re done.&lt;/p&gt;

&lt;p&gt;More importantly (as we’ll get to below), we can add many more &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt; calls
to do other transformations / data-slinging inline to our chained call.
Ultimately, it just takes a little bit of glue code to hook together data flows
in a terse and efficient manner with streams.&lt;/p&gt;

&lt;h2 id=&quot;the-stream-interfaces&quot;&gt;The Stream Interfaces&lt;/h2&gt;

&lt;p&gt;So how do we do this for our own classes and objects?&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;The Node.js &lt;a href=&quot;http://nodejs.org/api/stream.html&quot;&gt;streams&lt;/a&gt; documentation offers the full rundown of how to
implement the interfaces, but we’ll give an abbreviated version here to get
going. All of the code examples discussed in this post are available as a
GitHub &lt;a href=&quot;https://gist.github.com/3221453&quot;&gt;gist&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;readable-streams&quot;&gt;Readable Streams&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://nodejs.org/api/stream.html#stream_readable_stream&quot;&gt;Readable Streams&lt;/a&gt; must emit “&lt;code class=&quot;highlighter-rouge&quot;&gt;data&lt;/code&gt;” events whenever they have
data to be read and “&lt;code class=&quot;highlighter-rouge&quot;&gt;end&lt;/code&gt;” when the data stream is finished. The implementing
constructor should also set &lt;code class=&quot;highlighter-rouge&quot;&gt;this.readable = true&lt;/code&gt;. The interface
additionally provides a lot more implementer flexibility for things like
pausing and resuming a stream, as well as resource management and cleanup.&lt;/p&gt;

&lt;h3 id=&quot;writable-streams&quot;&gt;Writable Streams&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://nodejs.org/api/stream.html#stream_writable_stream&quot;&gt;Writable Streams&lt;/a&gt; must implement the &lt;code class=&quot;highlighter-rouge&quot;&gt;write()&lt;/code&gt; method to
accept new data chunks into the stream and the &lt;code class=&quot;highlighter-rouge&quot;&gt;end()&lt;/code&gt; method to instruct the
stream that the data is finished. The implementing constructor should also set
&lt;code class=&quot;highlighter-rouge&quot;&gt;this.writable = true&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;putting-it-together&quot;&gt;Putting it Together&lt;/h3&gt;

&lt;p&gt;Let’s take a look at a simple example of a custom-implemented readable and
writable stream that simply passes through data – data input is simply
emitted unchanged as output.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Set both readable and writable in constructor.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Inherit from base stream class.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'util'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'stream'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Extract args to `write` and emit as `data` event.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'data'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Extract args to `end` and emit as `end` event.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is essentially the bare minimum for a readable and writable stream class.
Not too much work! And for more complicated streams, we can simply augment
&lt;code class=&quot;highlighter-rouge&quot;&gt;write&lt;/code&gt;/&lt;code class=&quot;highlighter-rouge&quot;&gt;data&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;end&lt;/code&gt;/&lt;code class=&quot;highlighter-rouge&quot;&gt;end&lt;/code&gt; to do whatever data transformations we want.&lt;/p&gt;

&lt;p&gt;We can now take the web scraping example from above and add the pass-through
stream in the middle with the same effect – we still get a file written to
output.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Download the same page again, but with the NOP stream&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// in the middle.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'http'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://www.google.com/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;outStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'fs'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;out.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;outStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Checking the output file (“out.txt”), we can see the download results are the
same as our original example.&lt;/p&gt;

&lt;p&gt;And in fact, we could even reuse the pass-through stream multiple times to
illustrate repeated &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt; data flows (although there’s absolutely no
practical sense to the following):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Wow, that's a lot of nop's!&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NopStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// OK, finally write out to file.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;outStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So that’s the basics. Let’s look at creating something a tad more useful.&lt;/p&gt;

&lt;h2 id=&quot;lets-upper-case-some-data&quot;&gt;Let’s Upper-case Some Data!&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;UpperCaseStream&lt;/code&gt; class takes a data source (in string or &lt;code class=&quot;highlighter-rouge&quot;&gt;Buffer&lt;/code&gt;
format) and converts string data into upper case letters. Not ultimately
that useful or extensible, but it’s definitely a data transformation that can
illustrate the ease of creation and use of a custom stream.&lt;/p&gt;

&lt;p&gt;We mostly take our simple pass-through stream above and add a custom
&lt;code class=&quot;highlighter-rouge&quot;&gt;_transform&lt;/code&gt; helper method to transform the data in either a &lt;code class=&quot;highlighter-rouge&quot;&gt;write()&lt;/code&gt; or
&lt;code class=&quot;highlighter-rouge&quot;&gt;end()&lt;/code&gt; call, then re-emit the upper-cased data in a &lt;code class=&quot;highlighter-rouge&quot;&gt;data&lt;/code&gt; event.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;cm&quot;&gt;/**
 * A simple upper-case stream converter.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UpperCaseStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;util&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;inherits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UpperCaseStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;stream&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * Handle various params and upper-case string data.
 *
 * Signature can be in format of:
 *  - string, [encoding]
 *  - buffer
 *
 * Our example implementation hacks the data into a simpler
 # (string) form -- real implementations would need more.
 */&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;UpperCaseStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_transform&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Here, we'll just shortcut to a string.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Upper-case the string and emit data event with transformed data.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toUpperCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * Stream write (override).
 */&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;UpperCaseStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * Stream end (override).
 */&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;UpperCaseStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;end&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To test things out, we can take an input file (“input.txt”), read it in,
upper case all text, then write it out to “out.txt” using three streams.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createReadStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;input.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;out.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;upperCase&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;UpperCaseStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Open our read input, uppercase it, then write out.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;upperCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The resulting output file is now uppercased! All in all, not that amazing,
but considering the ease of our implementation, other (more useful)
read/write stream applications could include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;XML to JSON conversion.&lt;/li&gt;
  &lt;li&gt;Unzipping zipped data.&lt;/li&gt;
  &lt;li&gt;Image resizing.&lt;/li&gt;
  &lt;li&gt;… any other transformations you’d like to use with your existing streams.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Streams provide a great means of binding together lots of data in a sane and
manageable way. Beyond the core library documents, there are a lot of great
stream introductions for further reference:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.dump.ly/post/19819897856/why-node-js-streams-are-awesome&quot;&gt;“Why Node.js Streams are Awesome”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://docs.jit.su/articles/advanced/streams/how-to-use-stream-pipe&quot;&gt;“How to Use stream.pipe”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://felixge.s3.amazonaws.com/11/nodejs-streams.pdf&quot;&gt;“Streams, Pipes and Mega Pipes”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://maxogden.com/node-streams&quot;&gt;“Node Streams: How do they work?”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Getting Started with Node.js in the Cloud - Presentation</title>
   <link href="http://loose-bits.com/2012/03/24/novanode-nodejs-cloud-presentation.html"/>
   <updated>2012-03-24T22:08:21+00:00</updated>
   <id>http://loose-bits.com/2012/03/24/novanode-nodejs-cloud-presentation</id>
   <content type="html">
&lt;h2 id=&quot;getting-started-with-nodejs-in-the-cloud&quot;&gt;Getting Started with Node.js in the Cloud&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.meetup.com/Nova-Node&quot;&gt;NovaNode&lt;/a&gt; had its &lt;a href=&quot;http://www.meetup.com/Nova-Node/events/52749282/&quot;&gt;first meetup event&lt;/a&gt; on March 20, 2012
at &lt;a href=&quot;http://spanishdict.com/&quot;&gt;SpanishDict&lt;/a&gt;’s offices. I gave a talk titled
“Getting Started with Node.js in the Cloud” covering Node.js application
development and deployment to the cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/novanode-cloud-talk/&quot;&gt;![Node.js cloud presentation][img_pres]&lt;/a&gt;
[img_pres]: http://loose-bits.com/media/img/2012/03/24/nodejs-cloud.png&lt;/p&gt;

&lt;p&gt;My talk goes through a first simple “hello world” application and builds up to
a realtime chat application using Redis and websockets. We deploy the
applications to &lt;a href=&quot;http://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; and cover everything from logs and monitoring
to add-on services and scaling. The talk concludes with an assessment and use
cases for deploying on a Platform-as-a-Service provider like Heroku versus an
Infrastructure-as-a-Service provider like &lt;a href=&quot;http://aws.amazon.com/&quot;&gt;Amazon Web Services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://ryan-roemer.github.com/novanode-cloud-talk/&quot;&gt;presentation&lt;/a&gt; is available live on GitHub, and uses the
&lt;a href=&quot;http://imakewebthings.com/deck.js/&quot;&gt;deck.js&lt;/a&gt; presentation framework. I have also posted the full demo
&lt;a href=&quot;https://github.com/ryan-roemer/novanode-cloud-talk/&quot;&gt;source code&lt;/a&gt; to GitHub, including the chat server that we used
during the meetup. Try it out!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Return-Oriented Programming - Systems, Languages, and Applications</title>
   <link href="http://loose-bits.com/2012/03/24/return-oriented-programming-acm-tissec-paper.html"/>
   <updated>2012-03-24T16:40:56+00:00</updated>
   <id>http://loose-bits.com/2012/03/24/return-oriented-programming-acm-tissec-paper</id>
   <content type="html">&lt;h2 id=&quot;return-oriented-programming&quot;&gt;Return-Oriented Programming&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Return-oriented_programming&quot;&gt;Return-oriented programming&lt;/a&gt; is a software exploit technique to take
over a program by diverting control flow without injecting any code. At UCSD,
I did most of my graduate research around this specific attack, working with
&lt;a href=&quot;http://ucsd.erikbuchanan.com/&quot;&gt;Erik Buchanan&lt;/a&gt;, &lt;a href=&quot;http://cseweb.ucsd.edu/~hovav/&quot;&gt;Hovav Shacham&lt;/a&gt;, and &lt;a href=&quot;http://cseweb.ucsd.edu/~savage/&quot;&gt;Stefan Savage&lt;/a&gt;.
Now three years later, the ACM Transactions of Information and System Security
(TISSEC) journal has published our full article,
“&lt;a href=&quot;http://dl.acm.org/citation.cfm?id=2133377&quot;&gt;Return-Oriented Programming: Systems, Languages, and Applications&lt;/a&gt;”
in the March 2012 issue.&lt;/p&gt;

&lt;p&gt;For a great introduction to return-oriented programming attacks, see
Hovav’s Black Hat presentation,
“&lt;a href=&quot;http://cseweb.ucsd.edu/~hovav/dist/blackhat08.pdf&quot;&gt;Return-oriented Programming: Exploitation without Code Injection&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;By way of a little background, software exploit techniques such as buffer
overflows traditionally injected code into a vulnerable buffer, and then
pointed control to that injected code whereby the attacker executed their own
instructions. The original attack was succinctly described in Aleph One’s
“&lt;a href=&quot;http://insecure.org/stf/smashstack.html&quot;&gt;Smashing The Stack For Fun And Profit&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;Software vendors responded to injected code attacks with various defenses, one
popular being “write XOR execute” (“W⊕X”), whereby memory is either
writable (e.g., data structure storage) or executable (e.g., library code), but
&lt;strong&gt;not both&lt;/strong&gt;. The logic behind the defense was that an attacker could inject
arbitrary data in writable memory but could not execute it, thus preventing
the classic type of buffer overflow attack.&lt;/p&gt;

&lt;p&gt;However, preventing code &lt;em&gt;injection&lt;/em&gt; is not sufficient to prevent arbitrary
&lt;em&gt;computation&lt;/em&gt;. Following a long line of W⊕X exploit research,
return-oriented programming works by taking existing executable code (e.g.,
loaded libraries) and using small chunks of it in unintended ways by hijacking
control flow via compromising the stack or writable memory.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;return-oriented-programming-systems-languages-and-applications&quot;&gt;Return-Oriented Programming: Systems, Languages, and Applications&lt;/h2&gt;

&lt;p&gt;Erik and I became interested in return-oriented programming when we read
Hovav’s paper that introduced the technique,
“&lt;a href=&quot;http://cseweb.ucsd.edu/~hovav/papers/s07.html&quot;&gt;The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86).&lt;/a&gt;”.
Hovav focused on the x86 architecture and hypothesized
that the attack would possibly be mitigated on a RISC architecture.&lt;/p&gt;

&lt;p&gt;Fortunately, Erik and I were both TA’s for the SPARC (a RISC architecture)
assembly programming course for undergraduates, and figured we could apply the
attack further than originally anticipated. We were extremely lucky to get
Hovav on board with our research and to later succeed in porting the attack
to the SPARC architecture. We presented our findings in a paper at the 2008
&lt;a href=&quot;http://www.sigsac.org/ccs/CCS2008/&quot;&gt;ACM Conference on Computer and Communications Security&lt;/a&gt; (CCS). We
were later invited to turn our conference paper into a full journal paper
for TISSEC.&lt;/p&gt;

&lt;p&gt;We essentially coalesced both the x86 and SPARC research to be the definitive
statement on return-oriented programming, and submitted our draft in early
2009. Academic publishing does not always move at the most rapid pace, and it
took about three years to finally get “Return-Oriented Programming: Systems,
Languages, and Applications” published.&lt;/p&gt;

&lt;p&gt;I think the paper is a good statement of the original development and first
level extensions of the attack (and I’m obviously biased in recommending it,
as I’m an author). But, it is worth pointing out that a &lt;strong&gt;lot&lt;/strong&gt; has happened
in the time since we submitted our draft, and the state of the art in
return-oriented programming has advanced &lt;em&gt;significantly&lt;/em&gt; past that described
in our paper. Since our original research, the attack has found its way into
&lt;a href=&quot;http://www.sciencedaily.com/releases/2009/08/090810161902.htm&quot;&gt;voting machines&lt;/a&gt;, &lt;a href=&quot;http://www.slideshare.net/amiable_indian/cisco-ios-attack-defense-the-state-of-the-art&quot;&gt;routers&lt;/a&gt;, &lt;a href=&quot;http://www.theregister.co.uk/2011/08/04/secret_iphone_hacking_tool/&quot;&gt;mobile devices&lt;/a&gt; and other
places. It is fascinating (and scary) to see the ways in which the
technique has now entered the mainstream, but I’m at least happy to have (in
a small way) helped expose the problem to software vendors early to give
them a chance to build up defenses against return-oriented programming.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>NovaNode Inaugural Meetup - Getting Started with Node.js in the Cloud</title>
   <link href="http://loose-bits.com/2012/03/06/novanode-nodejs-cloud-meetup.html"/>
   <updated>2012-03-06T15:01:00+00:00</updated>
   <id>http://loose-bits.com/2012/03/06/novanode-nodejs-cloud-meetup</id>
   <content type="html">
&lt;h2 id=&quot;nodejs-in-nova&quot;&gt;Node.js in NoVA!&lt;/h2&gt;

&lt;p&gt;We recently launched the &lt;a href=&quot;http://www.meetup.com/Nova-Node/&quot;&gt;NovaNode meetup&lt;/a&gt; group focusing on the
&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; event-driven JavaScript framework. We have a great set
of talks lined up for our &lt;a href=&quot;http://www.meetup.com/Nova-Node/events/52749282/&quot;&gt;inaugural meetup event&lt;/a&gt; on March 20,
2012 at 6:30 pm. We’re hosting things at &lt;a href=&quot;http://spanishdict.com/&quot;&gt;SpanishDict&lt;/a&gt;’s offices in
Arlington, VA near the Clarendon metro stop.&lt;/p&gt;

&lt;div class=&quot;pull-center&quot;&gt;
  &lt;a href=&quot;http://www.meetup.com/Nova-Node/&quot;&gt;&lt;img class=&quot;bordered&quot; alt=&quot;Nova Node&quot; src=&quot;http://loose-bits.com/media/img/2012/03/06/nova_node.png&quot; /&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;Our talks are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;“Getting Started with Node.js in the Cloud”&lt;/strong&gt;, which I’ll present.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“Everyauth: OAuth for Busy Nerds”&lt;/strong&gt; by Jason Bond Pratt, Co-founder of
&lt;a href=&quot;http://launch.tixelated.com/&quot;&gt;Tixelated&lt;/a&gt;. OAuth is the standard interface for web services
integration for user authentication, and is your starting point for linking
an application to Google, Facebook, Twitter, etc. Jason’s talk will take
us through Everyauth’s basic architecture and integration with other data
stores like MongoDB.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;getting-started-with-nodejs-in-the-cloud&quot;&gt;Getting Started with Node.js in the Cloud&lt;/h2&gt;

&lt;p&gt;Node.js provides a solid platform for scalable and responsive web applications.
Development in Node.js further has a lower learning curve compared to many
highly-concurrent / asynchronous frameworks as it uses JavaScript, which is
very familiar territory for web developers.&lt;/p&gt;

&lt;p&gt;The cloud complements Node.js by providing the means to deploy applications
rapidly and scale up with no upfront investment costs. The “big” cloud
providers such as &lt;a href=&quot;http://aws.amazon.com/&quot;&gt;Amazon Web Services&lt;/a&gt; and &lt;a href=&quot;http://www.rackspace.com/cloud/&quot;&gt;Rackspace&lt;/a&gt; easily support
an infrastructures for hosting Node.js applications. Moreover, there has
been a burst of Platform-as-a-Service contenders that provide even easier
Node.js application hosting, including: &lt;a href=&quot;http://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt;,
&lt;a href=&quot;http://nodejitsu.com/&quot;&gt;NodeJitsu&lt;/a&gt; and even Microsoft’s &lt;a href=&quot;http://www.windowsazure.com/en-us/develop/nodejs/&quot;&gt;Azure&lt;/a&gt; platform.&lt;/p&gt;

&lt;p&gt;In my talk, I’ll walk through a basic Node.js application that we develop,
deploy and then scale easily in the cloud. I’ll discuss best practices and
what opportunities and pitfalls lie ahead when taking your Node.js applications
to the cloud.&lt;/p&gt;

&lt;p&gt;Hope to see you at the meetup!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Async.js Presentation for Joint Node.DC / DC jQuery Meetup</title>
   <link href="http://loose-bits.com/2012/02/21/aysnc-js-talk-node-dc-jquery.html"/>
   <updated>2012-02-21T20:54:38+00:00</updated>
   <id>http://loose-bits.com/2012/02/21/aysnc-js-talk-node-dc-jquery</id>
   <content type="html">
&lt;h2 id=&quot;asyncjs-a-javascript-control-flow-library&quot;&gt;Async.js, a JavaScript Control Flow Library&lt;/h2&gt;

&lt;p&gt;On February 20, 2012, I gave a talk on &lt;a href=&quot;https://github.com/caolan/async&quot;&gt;Async.js&lt;/a&gt; to the joint
meetup of &lt;a href=&quot;http://www.meetup.com/node-dc/events/49905452/&quot;&gt;Node.DC&lt;/a&gt; and &lt;a href=&quot;http://www.meetup.com/DC-jQuery-Users-Group/events/51798912/&quot;&gt;DC jQuery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Async.js is a control flow library that provides great abstractions for writing
serial, parallel, and combined asynchronous code without ending up in the
dreaded trenches of nested callback insanity.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ryan-roemer.github.com/nodedc-async-talk/#/title&quot;&gt;![Async.js presentation][img_pres]&lt;/a&gt;
[img_pres]: http://loose-bits.com/media/img/2012/02/21/async-talk.png&lt;/p&gt;

&lt;p&gt;My &lt;a href=&quot;http://ryan-roemer.github.com/nodedc-async-talk/#/title&quot;&gt;presentation&lt;/a&gt; is available live on GitHub, and uses
&lt;a href=&quot;http://bartaz.github.com/impress.js&quot;&gt;Impress.js&lt;/a&gt; for &lt;a href=&quot;http://prezi.com/&quot;&gt;Prezi&lt;/a&gt;-like functionality in the browser.
As a consequence, it might not render in older browsers and IE. My talk covers
the basic abstractions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Series&lt;/li&gt;
  &lt;li&gt;Waterfalls&lt;/li&gt;
  &lt;li&gt;Parallel&lt;/li&gt;
  &lt;li&gt;Collections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and more, so check it out and feel free to ping me with any questions or
comments!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Authenticated, Static Web Sites on Google App Engine</title>
   <link href="http://loose-bits.com/2012/01/08/authenticated-static-html-site-with-google-app-engine.html"/>
   <updated>2012-01-08T14:00:00+00:00</updated>
   <id>http://loose-bits.com/2012/01/08/authenticated-static-html-site-with-google-app-engine</id>
   <content type="html">&lt;h2 id=&quot;static-html-web-site-hosting-with-google-app-engine&quot;&gt;Static HTML Web Site Hosting with Google App Engine&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://code.google.com/appengine/&quot;&gt;Google App Engine&lt;/a&gt; is a platform-as-a-service (PAAS) product that
provides scalable, cloud-hosted web applications using Google’s massive
engineering infrastructure. While App Engine is primarily used by web
developers (e.g., programming in Python or Java), it offers three features
which make it uniquely helpful for static site hosting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Arbitrary static file handling.&lt;/li&gt;
  &lt;li&gt;Extensible authentication support.&lt;/li&gt;
  &lt;li&gt;Very inexpensive (most likely free) for hosting static content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, we’ll walk through uploading a static HTML site to App Engine,
and configuring it such that it requires users to log in to via a Google Apps
domain account before viewing any content. This is a common situation for
organizations already using Google Apps to manage email, documents, etc.
that want to host a web site without having extra configuration hassles.
App Engine essentially takes care of all the authentication / user management,
and you just have to upload the static web site.&lt;/p&gt;

&lt;p&gt;The assumption throughout the rest of this post is that you already have a
domain name managed by Google Apps (e.g., “example.com”). We will create
an App Engine application and restrict it to users of the specific Google
Apps domain, requiring a login of a user “@example.com”. Then we’ll upload
the static site and verify that authentication works as expected.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;create-an-authenticated-app-engine-application&quot;&gt;Create an Authenticated App Engine Application&lt;/h2&gt;

&lt;p&gt;The first step is to create an App Engine web application. You’ll need to
&lt;a href=&quot;https://appengine.google.com/&quot;&gt;sign up&lt;/a&gt; for an App Engine account, &lt;a href=&quot;http://code.google.com/appengine/downloads.html&quot;&gt;download&lt;/a&gt;
the App Engine Python SDK (make sure you get the &lt;strong&gt;Python&lt;/strong&gt; one!), and you
should read the “&lt;a href=&quot;http://code.google.com/appengine/docs/python/gettingstarted/&quot;&gt;getting started&lt;/a&gt;” documentation. Also, if given
the option to “install command symlinks”, make sure you choose to do this
(which will give us an &lt;code class=&quot;highlighter-rouge&quot;&gt;appcfg.py&lt;/code&gt; executable in our path for use later).&lt;/p&gt;

&lt;p&gt;Once App Engine is setup and installed, we can create the actual application.
The important thing to point out here is that you must select the
authentication method you want for your web site &lt;strong&gt;at&lt;/strong&gt; creation time, as it
cannot be changed later. (Although, you can create a &lt;em&gt;new&lt;/em&gt; application with
different authentication and delete your original application).&lt;/p&gt;

&lt;p&gt;Take a moment to review the App Engine &lt;a href=&quot;http://code.google.com/appengine/articles/auth.html&quot;&gt;authentication&lt;/a&gt; article,
as we’re basically going to follow these steps. Go to the
&lt;a href=&quot;http://appengine.google.com/start/createapp&quot;&gt;create application&lt;/a&gt; web page. Fill in the following information:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Application Identifier&lt;/strong&gt;: Choose a descriptive name for your site (e.g.
“internal-docs”. There cannot be an existing matching identifier, so have
some alternates handy. This will result in a domain name of
“internal-docs.appspot.com” (or whatever your identifier is) for your
finished website.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Application Title&lt;/strong&gt;: A free text description of your website. Feel free to
put anything in that provides a simple title for your site.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Authentication Options (Advanced)&lt;/strong&gt;: You’ll need to click the “edit” link
which then gives us three options for authentication: (1) “Open to all Google
Accounts users (default)”, (2) “Restricted to the following Google Apps
domain:”, or (3) “(Experimental) Open to all users with an OpenID Provider”.
Click the button for “Restricted to the following Google Apps domain:” and
enter your Google Apps-managed domain (e.g., “example.com”). I should point
out again that you &lt;em&gt;already&lt;/em&gt; should have Google Apps set up for the domain
name you are entering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From there you can click “Create Application” and the application should be
created. Make sure to keep your application identifier handy.&lt;/p&gt;

&lt;p&gt;To enable Google Apps domain authentication for the new application, we need
to follow the instructions in the App Engine &lt;a href=&quot;http://code.google.com/appengine/articles/auth.html&quot;&gt;authentication&lt;/a&gt; article.
Basically, you need to open a web browser to:
“http://www.google.com/a/YOUR DOMAIN” and click on the “Dashboard” tab. Go to
“Service settings” and click on the “Add more services” link. In the
“Other services” section, there will be a place to add an App Engine service.
Type in your application identifier code here and click “Add it now”. This
will hook up your specific Google Apps Domain with the App Engine service.&lt;/p&gt;

&lt;h2 id=&quot;configure-and-upload-static-web-site&quot;&gt;Configure and Upload Static Web Site&lt;/h2&gt;

&lt;p&gt;The next step is to gather your static web site files, add an application
configuration, and upload all the content to the App Engine application.
We will place all of our application content in a directory called “my_site”
(or something else of your choosing).  You are best off keeping this directory
under a source control management system (e.g., git), so that you can monitor,
track and revert changes to all of your files.&lt;/p&gt;

&lt;h3 id=&quot;configuration&quot;&gt;Configuration&lt;/h3&gt;

&lt;p&gt;We need an application configuration file call “app.yaml” in the root
of our project directory. This file controls various aspects of the
application, including how the application routes URLs to handlers. We’ll
use a configuration that handles all static file types (including HTML), and
just simply serves them.&lt;/p&gt;

&lt;p&gt;There are &lt;a href=&quot;http://blog.engelke.com/2008/07/30/google-appengine-for-web-hosting/&quot;&gt;various&lt;/a&gt; &lt;a href=&quot;http://www.instantfundas.com/2011/02/how-to-host-static-websites-on-google.html&quot;&gt;other&lt;/a&gt; posts out there discussing
configurations for static web sites on App Engine, but the best configuration
that I found was a &lt;a href=&quot;https://gist.github.com/873098&quot;&gt;gist&lt;/a&gt; by GitHub user “&lt;a href=&quot;https://github.com/darktable&quot;&gt;darktable&lt;/a&gt;”.
However, this configuration didn’t including authentication, so I forked the
gist and added authentication attributes to produce our final
&lt;a href=&quot;https://raw.github.com/gist/1570659/app.yaml&quot;&gt;app.yaml&lt;/a&gt; file that you should download to “my_site/app.yaml”.
You can also view a basic &lt;a href=&quot;https://gist.github.com/1570659#file_readme.markdown&quot;&gt;Readme&lt;/a&gt; file and other information at
the GitHub &lt;a href=&quot;https://gist.github.com/1570659&quot;&gt;gist&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;Here’s a snippet of the “app.yaml” file that you’ll need to slightly modify:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;s&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;you-app-name-here&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;python&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;api_version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;s&quot;&gt;default_expiration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;30d&quot;&lt;/span&gt;

&lt;span class=&quot;s&quot;&gt;handlers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/(.*\.(appcache|manifest))&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;mime_type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;text/cache-manifest&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;static_files&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;static/\1&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;static/(.*\.(appcache|manifest))&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;expiration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;0m&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;required&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# ... OTHER CONTENT SNIPPED ...&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# site root&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;static_files&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;static/index.html&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;upload&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;static/index.html&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;expiration&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;15m&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;required&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After downloading to “my_site/app.yaml”, update the
&lt;code class=&quot;highlighter-rouge&quot;&gt;application: you-app-name-here&lt;/code&gt; directive with the specific App Engine
application identifier you chose in the application creation section above.&lt;/p&gt;

&lt;h3 id=&quot;static-content&quot;&gt;Static Content&lt;/h3&gt;

&lt;p&gt;Now that we have a configuration file, create a folder named “my_site/static”
which will house actual static web site. As we want to check that the
authentication works first before uploading potentially sensitive information,
I would recommend creating a test HTML page that just contains the content
“It worked!” and adding that as “my_site/static/index.html”.&lt;/p&gt;

&lt;p&gt;Now, we should have a project layout that looks like:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;my_site/
  app.yaml
  static/
    index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;At this point we can upload the full site to our static server using
&lt;a href=&quot;http://code.google.com/appengine/docs/python/tools/uploadinganapp.html&quot;&gt;appcfg.py&lt;/a&gt;. Make sure that we have &lt;code class=&quot;highlighter-rouge&quot;&gt;appcfg.py&lt;/code&gt; available:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;which appcfg.py
/usr/local/bin/appcfg.py&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you don’t get an executable path back (any path is fine as long as
&lt;em&gt;something&lt;/em&gt; is returned by the &lt;code class=&quot;highlighter-rouge&quot;&gt;which&lt;/code&gt; command), then review the App Engine
“&lt;a href=&quot;http://code.google.com/appengine/docs/python/gettingstarted/&quot;&gt;getting started&lt;/a&gt;” documents for installation of the runtime.&lt;/p&gt;

&lt;p&gt;Assuming we do have &lt;code class=&quot;highlighter-rouge&quot;&gt;appcfg.py&lt;/code&gt; available, change directory in your terminal
to the directory containing the “my_site” project folder and upload the static
site with the following command:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;appcfg.py update my_site&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You will have to enter your Google credentials here. After the upload
finishes, you should be able to open a web browser to:
“&amp;lt;your application identifier&amp;gt;.appspot.com”. If you are authenticated to
your Google Apps domain, you should see the “It worked!” test page. If not, you
should be prompted to login to your Google Apps domain. A good way to test
the authentication works is to open a new Google Chrome Incognito window. It
should always force a new Google Apps login if you have configured things
properly. If the authentication doesn’t work quite right, review the
App Engine &lt;a href=&quot;http://code.google.com/appengine/articles/auth.html&quot;&gt;authentication&lt;/a&gt; page for tips and pointers, or leave a
comment below on this post.&lt;/p&gt;

&lt;p&gt;Assuming authentication does work correctly, then you can now remove the
test “index.html” file and upload your real site content to the
“my_site/static” directory. Every time you change the content, make sure
to re-upload the project with &lt;code class=&quot;highlighter-rouge&quot;&gt;appcfg.py&lt;/code&gt; and enjoy your static web site!&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Twitter Bootstrap Theme for Sphinx</title>
   <link href="http://loose-bits.com/2011/12/09/sphinx-twitter-bootstrap-theme.html"/>
   <updated>2011-12-09T16:00:00+00:00</updated>
   <id>http://loose-bits.com/2011/12/09/sphinx-twitter-bootstrap-theme</id>
   <content type="html">
&lt;h2 id=&quot;bringing-twitter-bootstrap-to-sphinx&quot;&gt;Bringing Twitter Bootstrap to Sphinx&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Bootstrap&lt;/a&gt; is an open source CSS/JS framework from the folks at
&lt;a href=&quot;https://twitter.com/&quot;&gt;Twitter&lt;/a&gt; that provides base typography, layout (grids), forms / form
inputs, tables and navigation, and has been gaining wide popularity among
developers. In my personal experience with a couple of sites and projects,
Bootstrap really provides a great starting point for a frontend that provides
good extensibility over time.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://sphinx.pocoo.org/&quot;&gt;Sphinx&lt;/a&gt; is the most popular &lt;a href=&quot;http://python.org/&quot;&gt;Python&lt;/a&gt; documentation generator
tool. It has fantastic Python module docstring / comment parsing in addition
to a sensible static pages framework, providing a great final web site built
from both inline code documentation and outside web pages.&lt;/p&gt;

&lt;p&gt;In the process of starting up a number of new Sphinx projects, I decided that
I would really like to use Bootstrap as the underlying UI framework.
Fortunately, Sphinx supports custom theme plugins, so I spent a couple of days
hacking, and created a &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme&quot;&gt;Sphinx Bootstrap Theme&lt;/a&gt;, which is now
available on GitHub.&lt;/p&gt;

&lt;p&gt;To show all of the coolness of Bootstrap and Sphinx, I’ve put up a simple
&lt;a href=&quot;http://ryan-roemer.github.com/sphinx-bootstrap-theme&quot;&gt;demo&lt;/a&gt;, which provides a skeleton Sphinx site with the project
&lt;a href=&quot;http://ryan-roemer.github.com/sphinx-bootstrap-theme/README.html&quot;&gt;README&lt;/a&gt; file rendered as content. Here is a screenshot:&lt;/p&gt;

&lt;p&gt;[![Sphinx Bootstrap Theme Demo][img_sbt_teaser_th]][img_sbt_teaser]
[img_sbt_teaser_th]: http://loose-bits.com/media/img/2011/12/09/sbt_teaser_thumb.png
[img_sbt_teaser]: http://loose-bits.com/media/img/2011/12/09/sbt_teaser.png&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;Sphinx themes can be installed either as a directory of files or a zip file.
For simplicity’s sake, we’ll go with a pre-packaged zip file installation here.&lt;/p&gt;

&lt;p&gt;First, set up a themes directory, which typically is something like
“_themes” located in your Sphinx source directory:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /path/to/source
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;mkdir -p _themes&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, download the most current “&lt;a href=&quot;https://github.com/downloads/ryan-roemer/sphinx-bootstrap-theme/bootstrap.zip&quot;&gt;bootstrap.zip&lt;/a&gt;” archive from
GitHub. Note that older zipped theme files are available at the
&lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/downloads&quot;&gt;downloads&lt;/a&gt; page, with git hash suffixes.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /path/to/source/_themes
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;wget https://github.com/downloads/ryan-roemer/sphinx-bootstrap-theme/bootstrap.zip&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As an alternative, you can just clone the theme repository, and place the
“bootstrap” directory in your themes directory. The “bootstrap.zip” file
should be the most up-to-date version, but getting the directory from source
will remain the authoritative current version.&lt;/p&gt;

&lt;p&gt;Finally, add the themes directory path and theme name to your source Sphinx
“conf.py” file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Activate the theme.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abspath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'_themes'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;html_theme_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'_themes'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;html_theme&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bootstrap'&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Optional. Use a shorter name to conserve nav. bar space.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;html_short_title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Demo'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Rebuild your Sphinx documentation, and you should now have the Bootstrap theme
up and running! See the &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/blob/master/README.rst&quot;&gt;README&lt;/a&gt; for more information on
customizing and hacking on the theme.&lt;/p&gt;

&lt;h2 id=&quot;brief-tour&quot;&gt;Brief Tour&lt;/h2&gt;

&lt;p&gt;The Bootstrap Sphinx theme basically rolls up the site-wide / page-level
table of contents, page navigation, and search form into the main navigation
top bar.&lt;/p&gt;

&lt;h3 id=&quot;site-navigation&quot;&gt;Site Navigation&lt;/h3&gt;

&lt;p&gt;The first dropdown tab is the “Site” button (expanded and highlighted below).
Internally this wraps up the &lt;code class=&quot;highlighter-rouge&quot;&gt;globaltoc&lt;/code&gt; template using the &lt;code class=&quot;highlighter-rouge&quot;&gt;toctree&lt;/code&gt;
directive.&lt;/p&gt;

&lt;p&gt;[![Site Navigation][img_sbt_nav_site_th]][img_sbt_nav_site]
[img_sbt_nav_site_th]: http://loose-bits.com/media/img/2011/12/09/sbt_nav_site_thumb.png
[img_sbt_nav_site]: http://loose-bits.com/media/img/2011/12/09/sbt_nav_site.png&lt;/p&gt;

&lt;p&gt;For both the site and page navigation dropdowns, the Bootstrap theme uses
JavaScript hackery to unpack the internal list structure and convert it to
an ordered flat list with hierarchy displayed using extra left padding
corresponding to levels. The natural levels of table of content displayed
normally by Sphinx should be preserved in the dropdown menus.&lt;/p&gt;

&lt;h3 id=&quot;page-navigation&quot;&gt;Page Navigation&lt;/h3&gt;

&lt;p&gt;The second dropdown tab is the “Page” navigation for table of contents link
within a single page. This wraps the &lt;code class=&quot;highlighter-rouge&quot;&gt;localtoc&lt;/code&gt; template using the &lt;code class=&quot;highlighter-rouge&quot;&gt;toc&lt;/code&gt;
directive.&lt;/p&gt;

&lt;p&gt;[![Page Navigation][img_sbt_nav_page_th]][img_sbt_nav_page]
[img_sbt_nav_page_th]: http://loose-bits.com/media/img/2011/12/09/sbt_nav_page_thumb.png
[img_sbt_nav_page]: http://loose-bits.com/media/img/2011/12/09/sbt_nav_page.png&lt;/p&gt;

&lt;h3 id=&quot;search&quot;&gt;Search&lt;/h3&gt;

&lt;p&gt;The right “Search” box in the navigation top bar connects to the built-in,
client-side search that Sphinx provides. Here we search for the term
“bootstrap”:&lt;/p&gt;

&lt;p&gt;[![Search Navigation][img_sbt_nav_search_th]][img_sbt_nav_search]
[img_sbt_nav_search_th]: http://loose-bits.com/media/img/2011/12/09/sbt_nav_search_thumb.png
[img_sbt_nav_search]: http://loose-bits.com/media/img/2011/12/09/sbt_nav_search.png&lt;/p&gt;

&lt;p&gt;… which gives us the following page matches:&lt;/p&gt;

&lt;p&gt;[![Search Results][img_sbt_search_th]][img_sbt_search]
[img_sbt_search_th]: http://loose-bits.com/media/img/2011/12/09/sbt_search_thumb.png
[img_sbt_search]: http://loose-bits.com/media/img/2011/12/09/sbt_search.png&lt;/p&gt;

&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;

&lt;p&gt;The Bootstrap Theme provides an easy means for Twitter Bootstrap integration
for my current Sphinx sites. But, as I’ve just put together the theme this
week, it is probably still rough around the edges. Any
&lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/issues&quot;&gt;issue reports&lt;/a&gt;, &lt;a href=&quot;https://github.com/ryan-roemer/sphinx-bootstrap-theme/pulls&quot;&gt;pull requests&lt;/a&gt;, and feedback are most
welcome.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Sunny.js Presentation and Demo Server</title>
   <link href="http://loose-bits.com/2011/10/20/node-sunny-presentation-and-proxy-demo.html"/>
   <updated>2011-10-20T16:00:00+00:00</updated>
   <id>http://loose-bits.com/2011/10/20/node-sunny-presentation-and-proxy-demo</id>
   <content type="html">
&lt;h2 id=&quot;sunnyjs-presentation&quot;&gt;Sunny.js Presentation&lt;/h2&gt;

&lt;p&gt;I gave a lightning talk at last night’s &lt;a href=&quot;http://nodedc-october-eorg.eventbrite.com/&quot;&gt;Node.js Meetup&lt;/a&gt; introducing
&lt;a href=&quot;http://sunnyjs.org&quot;&gt;Sunny.js&lt;/a&gt;, a multi-cloud library for &lt;a href=&quot;http://nodejs.org&quot;&gt;Node.js&lt;/a&gt;. For more
background on Sunny, see my previous &lt;a href=&quot;http://loose-bits.com/2011/10/16/node-sunny-cloud-library.html&quot;&gt;blog post&lt;/a&gt; to get started
with the library.&lt;/p&gt;

&lt;p&gt;Instead of the usual PowerPoint deck, I used &lt;a href=&quot;http://prezi.com&quot;&gt;Prezi&lt;/a&gt;, which I have
been wanting to try out for some time. Here is the
&lt;a href=&quot;http://prezi.com/tlaj11poclwg/sunnyjs/&quot;&gt;Sunny.js prezi&lt;/a&gt; I gave at the meetup:&lt;/p&gt;

&lt;div class=&quot;embed embed-slides&quot;&gt;
  &lt;object id=&quot;prezi_tlaj11poclwg&quot; name=&quot;prezi_tlaj11poclwg&quot; classid=&quot;clsid:D27CDB6E-AE6D-11cf-96B8-444553540000&quot;&gt;
    &lt;param name=&quot;movie&quot; value=&quot;http://prezi.com/bin/preziloader.swf&quot; /&gt;
    &lt;param name=&quot;allowfullscreen&quot; value=&quot;true&quot; /&gt;
    &lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot; /&gt;
    &lt;param name=&quot;bgcolor&quot; value=&quot;#ffffff&quot; /&gt;
    &lt;param name=&quot;flashvars&quot; value=&quot;prezi_id=tlaj11poclwg&amp;amp;lock_to_path=0&amp;amp;color=ffffff&amp;amp;autoplay=no&amp;amp;autohide_ctrls=0&quot; /&gt;
    &lt;embed id=&quot;preziEmbed_tlaj11poclwg&quot; name=&quot;preziEmbed_tlaj11poclwg&quot; src=&quot;http://prezi.com/bin/preziloader.swf&quot; type=&quot;application/x-shockwave-flash&quot; allowfullscreen=&quot;true&quot; allowscriptaccess=&quot;always&quot; flashvars=&quot;prezi_id=tlaj11poclwg&amp;amp;lock_to_path=0&amp;amp;color=ffffff&amp;amp;autoplay=no&amp;amp;autohide_ctrls=0&quot; /&gt;
  &lt;/object&gt;
&lt;/div&gt;

&lt;p&gt;Although the Prezi took some time to put together (especially being my first
time using the service), I was very happy with the appearance and flow of the
presentation.&lt;/p&gt;

&lt;h2 id=&quot;simple-cloudweb-proxy-server&quot;&gt;Simple Cloud/Web Proxy Server&lt;/h2&gt;

&lt;p&gt;As part of my presentation, I demo’ed a simple proxy service using Sunny and
Node. I took the v0.0.1 HTML documentation for Sunny, and uploaded all of it
to an Amazon &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;S3&lt;/a&gt; bucket, being careful to preserve paths from the original
documentation files when naming S3 keys. (Actually, this wasn’t hard at all –
I just used the reliable &lt;a href=&quot;http://cyberduck.ch/&quot;&gt;CyberDuck&lt;/a&gt; S3 client).&lt;/p&gt;

&lt;p&gt;I then wrote a really simple 40-line web server. It basically takes credentials
from the process environment, checks if a specified container exists, and
then translates web request GET paths to blob names in the container I set
up. The result is that I could serve my entire documentation site straight
from by Amazon S3 bucket!&lt;/p&gt;

&lt;p&gt;The source for the project, &lt;a href=&quot;https://github.com/ryan-roemer/sunny-proxy&quot;&gt;sunny-proxy&lt;/a&gt;, is available on GitHub,
and is fairly straightforward to setup for yourself using the Readme
instructions.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;For the impatient, here’s the entire relevant code for the web server:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'http'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;mime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'mime'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;sunny&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'sunny'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sunny&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fromEnv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;ADDR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ADDRESS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;PORT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PORT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;CONTAINER&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SUNNY_PROXY_CONTAINER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Get our container and create server inside to get blobs.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;CONTAINER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// We have a valid container, so let's create the server now.&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/index.html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;logResult&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[%s] %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// Header based on MIME type (re-write on error).&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'content-type'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// Get blob and pass through error or pipe to response.&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;statusCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'content-type'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text/html&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;h1&amp;gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;logResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ADDR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Server running at http://%s:%s/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ADDR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We use the magic of &lt;code class=&quot;highlighter-rouge&quot;&gt;stream.pipe(res)&lt;/code&gt; to handle all of the transfer details
of passing through our blob data chunks to the HTTP response.&lt;/p&gt;

&lt;p&gt;I even took it a step further and set up a simple &lt;a href=&quot;http://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; app to run
the application. All it took was a quick review of the
&lt;a href=&quot;http://devcenter.heroku.com/articles/node-js&quot;&gt;Node app instructions&lt;/a&gt; and a “Procfile” file (mine is available
in the GitHub repo) and I was able to get my node proxy server up and running
on the web in under a half hour! Moreover, I was pleasantly surprised at
how fast the web application was running – especially considering the web
server doesn’t use any caching logic or any intelligent optimizations that
a real web server would.&lt;/p&gt;

&lt;p&gt;The Sunny-based proxy server certainly isn’t anything amazing or
revolutionary – it just serves blobs as if they were static files for a
web server – but it does show just how easily Node.js and libraries like
Sunny can glue cloud / service-based components together for fast and
scalable applications.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Sunny.js, a Cloud Library for Node.js</title>
   <link href="http://loose-bits.com/2011/10/16/node-sunny-cloud-library.html"/>
   <updated>2011-10-16T16:00:00+00:00</updated>
   <id>http://loose-bits.com/2011/10/16/node-sunny-cloud-library</id>
   <content type="html">&lt;h2 id=&quot;sunnyjs&quot;&gt;Sunny.js&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://nodejs.org&quot;&gt;Node.js&lt;/a&gt; provides a great environment for cloud applications and
development. Node’s asynchronous design and great HTTP/REST support provide
the building blocks for architecting non-blocking and scalable applications
in the cloud.&lt;/p&gt;

&lt;p&gt;There are a lot of great Node cloud clients out there for cloud datastores.
Amazon Web Services &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;S3&lt;/a&gt; has: &lt;a href=&quot;https://github.com/LearnBoost/knox&quot;&gt;knox&lt;/a&gt;, &lt;a href=&quot;https://github.com/tricknik/node-sissy&quot;&gt;node-sissy&lt;/a&gt;,
&lt;a href=&quot;https://github.com/grippy/node-s3&quot;&gt;node-s3&lt;/a&gt;, &lt;a href=&quot;https://github.com/nuxusr/Node.js---Amazon-S3&quot;&gt;Node.js-Amazon-S3&lt;/a&gt;, 
and Rackspace &lt;a href=&quot;http://www.rackspacecloud.com/cloud_hosting_products/files/&quot;&gt;Cloud Files&lt;/a&gt; has: &lt;a href=&quot;https://github.com/nodejitsu/node-cloudfiles&quot;&gt;node-cloudfiles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After reviewing these libraries, I found a few features variously lacking that
I would like in an ideal cloud datastore client:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Event-based interface.&lt;/li&gt;
  &lt;li&gt;“One-shot” requests wherever possible.&lt;/li&gt;
  &lt;li&gt;Able to create / delete arbitrary containers (buckets).&lt;/li&gt;
  &lt;li&gt;Configurable request headers, cloud options and metadata.&lt;/li&gt;
  &lt;li&gt;SSL support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, it would be nice to have a library that worked across multiple
clouds like &lt;a href=&quot;http://www.jclouds.org/&quot;&gt;jclouds&lt;/a&gt; for Java and &lt;a href=&quot;http://libcloud.apache.org/&quot;&gt;Libcloud&lt;/a&gt; for Python.&lt;/p&gt;

&lt;p&gt;With these goals in mind, I put together a basic multi-cloud datastore client
called &lt;a href=&quot;http://sunnyjs.org&quot;&gt;Sunny.js&lt;/a&gt;. Sunny initially supports
&lt;a href=&quot;http://aws.amazon.com/&quot;&gt;Amazon Web Service&lt;/a&gt;’s &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;Simple Storage Service&lt;/a&gt; (S3) and
&lt;a href=&quot;http://code.google.com/apis/storage/&quot;&gt;Google Storage for Developers&lt;/a&gt; (&lt;a href=&quot;http://code.google.com/apis/storage/docs/reference/v1/apiversion1.html&quot;&gt;version 1&lt;/a&gt;). Sunny hits all the
points above, and also has:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A really well-documented &lt;a href=&quot;http://sunnyjs.org/api/index.html&quot;&gt;API&lt;/a&gt; and &lt;a href=&quot;http://sunnyjs.org/guide.html&quot;&gt;user guide&lt;/a&gt;.
(At least I think so!)&lt;/li&gt;
  &lt;li&gt;Full unit tests for all cloud operations and providers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some basic resources to get started with Sunny, which we’ll dive into
a little deeper in the subsequent sections of this post:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;http://sunnyjs.org&quot;&gt;Sunny.js Documentation&lt;/a&gt;: Guides and API documentation.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;http://sunnyjs.org/guide.html&quot;&gt;User Guide&lt;/a&gt;: Guide for programmers using Sunny in your
applications and library.&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;http://sunnyjs.org/development.html&quot;&gt;Developer’s Guide&lt;/a&gt;: Guide for developing, extending, and
hacking on the Sunny.js source code.&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;http://sunnyjs.org/api/index.html&quot;&gt;API&lt;/a&gt;: Full API.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://github.com/ryan-roemer/node-sunny&quot;&gt;Sunny.js GitHub Page&lt;/a&gt;: Source repository and issue tracker.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://search.npmjs.org/#/sunny&quot;&gt;Sunny.js NPM Page&lt;/a&gt;: NPM page and history.&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;

&lt;p&gt;First, install sunny from &lt;a href=&quot;http://search.npmjs.org/#/sunny&quot;&gt;npm&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;npm install sunny&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Once Sunny is installed, we need to set up some configuration information for
our Node program. We can provide cloud account and option information to
Sunny either via the process environment or a JavaScript object / file.
For simplicity, we’ll just export some environment variables (see the
&lt;a href=&quot;http://sunnyjs.org/guide.html&quot;&gt;user guide&lt;/a&gt; for more on configuration and using files).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SUNNY_PROVIDER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;aws&quot;&lt;/span&gt;|&lt;span class=&quot;s2&quot;&gt;&quot;google&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SUNNY_ACCOUNT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ACCOUNT_NAME&quot;&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SUNNY_SECRET_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ACCOUNT_SECRET_KEY&quot;&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SUNNY_SSL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;|&lt;span class=&quot;s2&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;From here, we can create our Node program, and get a live cloud datastore
&lt;a href=&quot;http://sunnyjs.org/api/symbols/base.Connection.html&quot;&gt;connection&lt;/a&gt; as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sunny&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sunny&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sunny&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fromEnv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;cloud-operations&quot;&gt;Cloud Operations&lt;/h2&gt;

&lt;p&gt;Now that we have our cloud connection object (&lt;code class=&quot;highlighter-rouge&quot;&gt;conn&lt;/code&gt;), we can perform all of
our cloud operations on containers and blobs. For those used to Amazon Web
Services parlance, a Sunny container is equivalent to an S3 “bucket”, and a
Sunny blob is an S3 “key”.&lt;/p&gt;

&lt;p&gt;Sunny cloud operations are asynchronous and event-based. The cloud methods
return either a request or a stream object, that is then used to set
listeners and callbacks on, before calling &lt;code class=&quot;highlighter-rouge&quot;&gt;end()&lt;/code&gt; and starting the
underlying real cloud network operation.&lt;/p&gt;

&lt;h3 id=&quot;requests&quot;&gt;Requests&lt;/h3&gt;

&lt;p&gt;Sunny request objects are not true Node request objects, but approximate
a subset of a Node HTTP request object events. The most common request events
are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;“error”&lt;/strong&gt;: The underlying real cloud operation failed.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“end”&lt;/strong&gt;: The operation finished and we have results.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“data”&lt;/strong&gt;: A GET request returned a data chunk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s perform a basic asynchronous operation to get a container named
“sunny” from our cloud store.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sunnyjs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;We received error: %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Retrieved container \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which gives us the output:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Retrieved container &quot;sunnyjs&quot;.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Breaking the code down, we call &lt;code class=&quot;highlighter-rouge&quot;&gt;getContainer&lt;/code&gt; with the container name
and the option &lt;code class=&quot;highlighter-rouge&quot;&gt;validate: true&lt;/code&gt;. The validate option means we actually
perform a cloud request to check the container exists before continuing.
This is a good check to start with before launching in to other code. But,
Sunny allows the programmer to choose to not validate, and wait for the first
blob request to actually perform any network operation, which is faster.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;getContainer&lt;/code&gt; method returns a &lt;code class=&quot;highlighter-rouge&quot;&gt;request&lt;/code&gt; object, which we then set
our listeners on for “error” and “end” (when we have results). Finally, the
call to &lt;code class=&quot;highlighter-rouge&quot;&gt;request.end()&lt;/code&gt; initiates the actual network operation.&lt;/p&gt;

&lt;p&gt;Our “end” callback takes a &lt;code class=&quot;highlighter-rouge&quot;&gt;results&lt;/code&gt; parameter which contains a
&lt;code class=&quot;highlighter-rouge&quot;&gt;container&lt;/code&gt; method for further use with blob methods, and a &lt;code class=&quot;highlighter-rouge&quot;&gt;meta&lt;/code&gt; object
with information from the actual cloud call (metadata, HTTP headers, etc.)
See the &lt;a href=&quot;http://sunnyjs.org/api/symbols/base.Connection.html#getContainer&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;getContainer&lt;/code&gt;&lt;/a&gt; documentation for more options,
results and uses.&lt;/p&gt;

&lt;p&gt;The other request-based Sunny methods work in a similar fashion, and include
the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Connection&lt;/strong&gt;: Get a list of containers. Get/create/delete a single
container.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Container&lt;/strong&gt;: Get/create/delete a single container. Get a list of blobs.
Head/delete a single blob. Get/put blob to/from a file.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Blob&lt;/strong&gt;: Head/delete a single blob. Get/put blob to/from a file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the &lt;a href=&quot;http://sunnyjs.org/api/index.html&quot;&gt;API&lt;/a&gt; for full method details.&lt;/p&gt;

&lt;h3 id=&quot;streams&quot;&gt;Streams&lt;/h3&gt;

&lt;p&gt;Sunny returns stream objects for data-based cloud operations (PUT or GET),
which are real implementations of Node &lt;a href=&quot;http://nodejs.org/docs/v0.4.9/api/streams.html&quot;&gt;streams&lt;/a&gt;. A GET blob
method returns a &lt;a href=&quot;http://nodejs.org/docs/v0.4.9/api/streams.html#readable_Stream&quot;&gt;Readable Stream&lt;/a&gt; object, and a PUT blob method
returns a &lt;a href=&quot;http://nodejs.org/docs/v0.4.9/api/streams.html#writable_Stream&quot;&gt;Writable Stream&lt;/a&gt; object.&lt;/p&gt;

&lt;p&gt;Let’s take the &lt;code class=&quot;highlighter-rouge&quot;&gt;container&lt;/code&gt; object we received from our successful
&lt;code class=&quot;highlighter-rouge&quot;&gt;getContainer&lt;/code&gt; request above, and perform a PUT blob operation with a simple
string. (Note: programmatically, this would be within the “end” callback of
the get container request). Data can be written with any number of &lt;code class=&quot;highlighter-rouge&quot;&gt;write()&lt;/code&gt;
calls and a single &lt;code class=&quot;highlighter-rouge&quot;&gt;end()&lt;/code&gt; call (which starts the data transfer and ignores
all subsequent writes).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;putBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-blob.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;We received error: %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Put blob \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;My data string.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which produces:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Put blob &quot;my-blob.txt&quot;.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, in a later operation (say, within the &lt;code class=&quot;highlighter-rouge&quot;&gt;putBlob&lt;/code&gt; “end” callback, after
we know the blob was written), we can retrieve the data:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-blob.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;We received error: %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'data'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Data chunk: \&quot;%s\&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Get blob \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which gives us:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Data chunk: &quot;My data string.&quot;
Get blob &quot;my-blob.txt&quot;.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that we’re getting the data in raw chunks from the “data” event, which
is somewhat tedious to cobble together (as strings if encoded or &lt;code class=&quot;highlighter-rouge&quot;&gt;Buffer&lt;/code&gt;
objects otherwise). Given that we have full read / write stream implementations,
Sunny really shines with the availability of &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt;‘ing data. Essentially,
you can simply connect a Sunny read / write stream to another read / write
stream, call &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt; and let Node and Sunny take care of the rest.&lt;/p&gt;

&lt;p&gt;For example, Sunny provides helper methods for getting / putting blobs to and
from files. Looking at the source code, the real gist of, for example, getting
a blob to a local file is the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createWriteStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filePath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Start the pipe and call 'end()'.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;getStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;getStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There’s a little more to it, but it is mostly this easy. Take a look at the
&lt;a href=&quot;https://github.com/ryan-roemer/node-sunny/blob/master/lib/base/blob/blob.js&quot;&gt;source code&lt;/a&gt; for the blob convenience methods &lt;code class=&quot;highlighter-rouge&quot;&gt;putFromFile&lt;/code&gt; and
&lt;code class=&quot;highlighter-rouge&quot;&gt;getToFile&lt;/code&gt; to see a full implementation that binds together file and cloud
streams.&lt;/p&gt;

&lt;p&gt;Node streams available for use with Sunny get / put &lt;code class=&quot;highlighter-rouge&quot;&gt;pipe()&lt;/code&gt; calls include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;HTTP/HTTPS requests and responses. See my &lt;a href=&quot;https://github.com/ryan-roemer/sunny-proxy&quot;&gt;sunny-proxy&lt;/a&gt; project
for a simple web server that proxies web requests to cloud blobs using Sunny
blob get streams piped to HTTP responses.&lt;/li&gt;
  &lt;li&gt;File reads and writes – &lt;a href=&quot;http://nodejs.org/docs/v0.4.9/api/fs.html#fs.ReadStream&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;fs.ReadStream&lt;/code&gt;&lt;/a&gt;,
&lt;a href=&quot;http://nodejs.org/docs/v0.4.9/api/fs.html#fs.WriteStream&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;fs.WriteStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;cloud-headers-and-metadata&quot;&gt;Cloud Headers and Metadata&lt;/h2&gt;

&lt;p&gt;Sunny abstracts common cloud provider header and metadata. For example,
AWS S3 has a metadata header prefix of “x-amz-meta-“, while Google Storage
has an analogous one of “x-goog-meta-“.&lt;/p&gt;

&lt;p&gt;Sunny cloud operations return a &lt;code class=&quot;highlighter-rouge&quot;&gt;meta&lt;/code&gt; object that looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* HTTP headers. */&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;cloudHeaders&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* Cloud-specific HTTP headers (e.g., &quot;x-amz-&quot;). */&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* Metadata headers (e.g., &quot;x-amz-meta-&quot;). */&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;with cloud-specific prefixes stripped out.  Moreover, you can &lt;em&gt;set&lt;/em&gt; cloud
headers in the same manner by passing any of the fields above (“headers”,
“cloudHeaders”, “metadata”) in the &lt;code class=&quot;highlighter-rouge&quot;&gt;options&lt;/code&gt; object of a cloud request.
In this manner, Sunny makes it easy to utilize metadata operations as part
of your application, while remaining agnostic to your actual cloud provider.
See the Sunny &lt;a href=&quot;http://sunnyjs.org/guide.html&quot;&gt;user guide&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2 id=&quot;error-handling&quot;&gt;Error Handling&lt;/h2&gt;

&lt;p&gt;For programmatic ease, and to better abstract across cloud providers, Sunny
wraps cloud-based errors with a custom class, that adds in some additional
useful attributes. The &lt;code class=&quot;highlighter-rouge&quot;&gt;CloudError&lt;/code&gt; class has the following members:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;message&lt;/code&gt;&lt;/strong&gt;: The error message (usually in XML). (from &lt;code class=&quot;highlighter-rouge&quot;&gt;Error&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;statusCode&lt;/code&gt;&lt;/strong&gt;: HTTP status code, if any.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and error type methods:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://sunnyjs.org/api/symbols/errors.CloudError.html#isNotFound&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;isNotFound&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: Container / blob does not exist.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://sunnyjs.org/api/symbols/errors.CloudError.html#isInvalidName&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;isInvalidName&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: Invalid name requested for
container / blob.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://sunnyjs.org/api/symbols/errors.CloudError.html#isNotEmpty&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;isNotEmpty&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: Container cannot be deleted if blobs
still exist within it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;./api/symbols/errors.CloudError.html#isAlreadyOwnedByYou&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;isAlreadyOwnedByYou&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: Creating a container
that is already owned by you.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;http://sunnyjs.org/api/symbols/errors.CloudError.html#isNotOwner&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;isNotOwner&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: AWS and Google Storage have a global
namespace, so if someone else owns the container name requested, your
operations will fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ultimately, Sunny “error” event listeners will get &lt;code class=&quot;highlighter-rouge&quot;&gt;CloudError&lt;/code&gt; objects
instead of raw &lt;code class=&quot;highlighter-rouge&quot;&gt;Error&lt;/code&gt; objects on cloud method errors, which can be used to
more easily programmatically react (especially since Sunny correctly handles
different error codes and XML from cloud providers that correspond to the same
type of error).&lt;/p&gt;

&lt;h2 id=&quot;putting-it-all-together-with-a-little-help-from-asyncjs&quot;&gt;Putting it all Together, with a Little Help from Async.js&lt;/h2&gt;

&lt;p&gt;Now that we’ve seen how to create a Sunny connection, get a container and
perform operations on blobs within the container, let’s put all of our code
together in a single script.  (Note: I’ve collapsed the error handling to a
single utility function).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sunny&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sunny&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sunny&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fromEnv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;errFn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Op 1: GET container.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sunnyjs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Retrieved container \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Op 2: PUT blob.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;putBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-blob.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Put blob \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Op 3: GET blob.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-blob.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errFn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'data'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Data chunk: \&quot;%s\&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Get blob \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;My data string.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Not too shabby! However, you’ll note that we have three serial cloud operations,
each of which have to complete before the next starts, so we end up repeatedly
nesting callbacks in “end” event listeners. Even with a mere three operations,
the nesting makes the code hard to understand at first blush, and an
indentation nightmare. Wouldn’t it be better if we could code our operations
to match the general outline of what we’re trying to do? That is:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Get a container.&lt;/li&gt;
  &lt;li&gt;Once we have a container, put data to a blob.&lt;/li&gt;
  &lt;li&gt;Once we have put data to the blob, get the data back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fortunately, there are many asynchronous helper libraries for Node to help in
just this situation. I like &lt;a href=&quot;https://github.com/caolan/async&quot;&gt;Async.js&lt;/a&gt;, which from the project
page “provides around 20 functions that include the usual ‘functional’ suspects
(map, reduce, filter, forEach…) as well as some common patterns for
asynchronous control flow (parallel, series, waterfall…).”&lt;/p&gt;

&lt;p&gt;In our simple three-cloud operation script, we just need &lt;code class=&quot;highlighter-rouge&quot;&gt;async.series&lt;/code&gt; to
serialize our operations for easier control flow. I am skipping over a lot
of details about using Async.js, such as callbacks at the end of functions or
errors, and passing state across serialized functions, but you can probably
intuit most of how things are working here:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;async&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;sunny&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sunny&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sunny&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fromEnv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;series&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Op 1: GET container.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sunnyjs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;validate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Retrieved container \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// Signal function &quot;done&quot; to async.series.&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Op 2: PUT blob.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;putBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-blob.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Put blob \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// Signal function &quot;done&quot; to async.series.&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;My data string.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;writeStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Op 3: GET blob.&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getBlob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-blob.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'error'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'data'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Data chunk: \&quot;%s\&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Get blob \&quot;%s\&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// Signal function &quot;done&quot; to async.series.&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;async.series is done!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which gives us our expected output:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Retrieved container &quot;sunnyjs&quot;.
Put blob &quot;my-blob.txt&quot;.
Data chunk: &quot;My data string.&quot;
Get blob &quot;my-blob.txt&quot;.
async.series is done!&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Our code now appears in a serialized, contained order that matches the
conceptual execution of the actual cloud operations, instead of a lot of
callback nesting.&lt;/p&gt;

&lt;p&gt;For more on developing with Async.js, please see the excellent Async.js
&lt;a href=&quot;https://github.com/caolan/async/blob/master/README.md&quot;&gt;Readme / tutorial&lt;/a&gt;. It is an investment well worth your time.
Sunny internally uses Async.js extensively for unit tests. Explore the source
&lt;a href=&quot;https://github.com/ryan-roemer/node-sunny/tree/master/test/live&quot;&gt;tests&lt;/a&gt; directory for a lot of good use cases (parallel and
serial).&lt;/p&gt;

&lt;h2 id=&quot;have-fun-in-the-sun&quot;&gt;Have Fun in the Sun&lt;/h2&gt;

&lt;p&gt;That wraps up our introduction to Sunny.js. Hopefully the library provides a
useful interface for your cloud programming needs.&lt;/p&gt;

&lt;p&gt;The project is still very much in an early development state. See the Sunny
&lt;a href=&quot;https://github.com/ryan-roemer/node-sunny/blob/master/TODO.md&quot;&gt;“to do” list&lt;/a&gt; to get a better idea of what features and fixes are
coming down the pipeline. The big ticket items I would really like to see
added are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;More cloud providers, notable Rackspace &lt;a href=&quot;http://www.rackspacecloud.com/cloud_hosting_products/files/&quot;&gt;Cloud Files&lt;/a&gt; and OpenStack
&lt;a href=&quot;http://openstack.org/projects/storage/&quot;&gt;Storage&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;A basic retry function wrapper to handle expected, occasional cloud
operation failures and throttling.&lt;/li&gt;
  &lt;li&gt;Advanced cloud operation support for ACL’s, policies, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Help, bug reports, and pull requests for the project are most welcome.
Please review the &lt;a href=&quot;http://sunnyjs.org/development.html&quot;&gt;developer’s guide&lt;/a&gt;, and contact me with any
questions or comments.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Pivot Faceting (Decision Trees) in Solr 1.4.</title>
   <link href="http://loose-bits.com/2011/09/20/pivot-facets-solr.html"/>
   <updated>2011-09-20T16:00:00+00:00</updated>
   <id>http://loose-bits.com/2011/09/20/pivot-facets-solr</id>
   <content type="html">&lt;h2 id=&quot;solr-pivot-facets&quot;&gt;Solr Pivot Facets&lt;/h2&gt;

&lt;p&gt;Solr &lt;a href=&quot;http://wiki.apache.org/solr/SolrFacetingOverview&quot;&gt;faceting&lt;/a&gt; breaks down searches for terms, phrases, and fields in the &lt;a href=&quot;http://lucene.apache.org/solr/&quot;&gt;Solr&lt;/a&gt; into aggregated counts by matched fields or queries.
Facets are a great way to “preview” further searches, as well as a powerful
aggregation tool in their own right.&lt;/p&gt;

&lt;p&gt;Before &lt;a href=&quot;http://wiki.apache.org/solr/Solr4.0&quot;&gt;Solr 4.0&lt;/a&gt;, facets were only available at one level, meaning
something like “counts for field ‘foo’” for a given query.  Solr 4.0
introduced &lt;a href=&quot;http://wiki.apache.org/solr/SimpleFacetParameters#Pivot_.28ie_Decision_Tree.29_Faceting&quot;&gt;pivot facets&lt;/a&gt; (also called decision trees) which
enable facet queries to return “counts for field ‘foo’ for each different field
‘bar’” – a multi-level facet across separate Solr fields.&lt;/p&gt;

&lt;p&gt;Decision trees come up a lot, and at work, we need results along multiple
axes – typically in our case “field/query by year” for a time series.
However, we use Solr 1.4.1 and are unlikely to migrate to Solr 4.0 in the
meantime. Our existing approach was to simply query for the top “n” fields for
a first query, then perform a second-level facet query by year for &lt;em&gt;each&lt;/em&gt; field
result. So, for the top 20 results, we would perform 1 + 20 queries –
clearly not optimal, when we’re trying to get this done in the context of a
blocking HTTP request in our underlying web application.&lt;/p&gt;

&lt;p&gt;Hoping to get &lt;em&gt;something&lt;/em&gt; better than our 1 + &lt;em&gt;n&lt;/em&gt; separate queries approach,
I began researching the somewhat more obscure facet features present in Solr
1.4.1. And after some investigation, experimentation and a good amount of
hackery, I was able to come up with a “faux” pivot facet scheme that
mostly approximates true pivot faceting using Solr 1.4.1.&lt;/p&gt;

&lt;p&gt;We’ll start by examining some real pivot facets in Solr 4.0, then look at the
components and full technique for simulated pivot facets in Solr 1.4.1.&lt;/p&gt;

&lt;h2 id=&quot;pivot-faceting-in-solr-40&quot;&gt;Pivot Faceting in Solr 4.0&lt;/h2&gt;

&lt;p&gt;Pivot facets were added to Solr in &lt;a href=&quot;https://issues.apache.org/jira/browse/SOLR-792#referrer=solr.pl&quot;&gt;SOLR-792&lt;/a&gt;. A good introductory
&lt;a href=&quot;http://solr.pl/en/2010/10/25/hierarchical-faceting-pivot-facets-in-trunk/&quot;&gt;article&lt;/a&gt; is available on the Solr.pl site. To see the basic
operation in action, let’s just use the “example” setup that comes with
the Solr 4.0 distribution (located at “solr_4.0_path/solr/example”).&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;Let’s start the Solr process:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Start Solr as non-daemon.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;solr_4.0_path/solr/example
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;java -jar start.jar&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, we want to upload a series of documents. We’ll take the provided
“exampledocs/books.csv” file, tweak it slightly and update via a CSV handler.
The CSV format is: first line is field names, other lines are data. Note that
I have changed some field names in the original sample “exampledocs/books.csv”
file. The following should be written out to a new file, which I am calling
“sample_books.csv”.&lt;/p&gt;

&lt;p&gt;Note that we use &lt;code class=&quot;highlighter-rouge&quot;&gt;_s&lt;/code&gt; fields for simplicity, forcing string fields for
what would ordinarily be text fields – Solr facets only return results
on &lt;em&gt;indexed&lt;/em&gt;, not &lt;em&gt;stored&lt;/em&gt; terms, and string fields are identical for both.
In a real deployment, you would use &lt;code class=&quot;highlighter-rouge&quot;&gt;copyField&lt;/code&gt; directives to copy &lt;code class=&quot;highlighter-rouge&quot;&gt;text&lt;/code&gt;
fields to &lt;code class=&quot;highlighter-rouge&quot;&gt;string&lt;/code&gt; fields for faceting.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Create CSV file.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;vim sample_books.csv
id,cat_s,name_s,price_f,inStock_b,author_s,series_s,sequence_i,genre_s
0553573403,book,A Game of Thrones,7.99,true,George R.R. Martin,A Song of Ice and Fire,1,fantasy
0553579908,book,A Clash of Kings,7.99,true,George R.R. Martin,A Song of Ice and Fire,2,fantasy
055357342X,book,A Storm of Swords,7.99,true,George R.R. Martin,A Song of Ice and Fire,3,fantasy
0553293354,book,Foundation,7.99,true,Isaac Asimov,Foundation Novels,1,scifi
0812521390,book,The Black Company,6.99,false,Glen Cook,The Chronicles of The Black Company,1,fantasy
0812550706,book,Enders Game,6.99,true,Orson Scott Card,Ender,1,scifi
0441385532,book,Jhereg,7.95,false,Steven Brust,Vlad Taltos,1,fantasy
0380014300,book,Nine Princes In Amber,6.99,true,Roger Zelazny,the Chronicles of Amber,1,fantasy
0805080481,book,The Book of Three,5.99,true,Lloyd Alexander,The Chronicles of Prydain,1,fantasy
080508049X,book,The Black Cauldron,5.99,true,Lloyd Alexander,The Chronicles of Prydain,2,fantasy&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’ll upload this file to Solr using curl:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Upload to Solr.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8983/solr/update/csv &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  --data-binary @sample_books.csv &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  -H &lt;span class=&quot;s1&quot;&gt;'Content-type:text/plain; charset=utf-8'&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Commit the upload.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8983/solr/update &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  --data-binary &lt;span class=&quot;s1&quot;&gt;'&amp;lt;commit/&amp;gt;'&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  -H &lt;span class=&quot;s1&quot;&gt;'Content-type:application/xml'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You should now be able to query the 10 sample documents at:
&lt;code class=&quot;highlighter-rouge&quot;&gt;http://localhost:8983/solr/admin/form.jsp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that we have some documents to work with, let’s do a simple pivot query
on price by genre:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Pivot query.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8983/solr/select --data &lt;span class=&quot;nv&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;on&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;json&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%3A&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;index&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.pivot&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;price_f,genre_s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;(Note that I’ve added line breaks and escapes to show the parameters more
clearly).&lt;/p&gt;

&lt;p&gt;This gives us decision tree results for the &lt;code class=&quot;highlighter-rouge&quot;&gt;facet_pivot&lt;/code&gt; field:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;facet_counts&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;facet_pivot&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;price_f,genre_s&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;price_f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;5.99&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;pivot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[{&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;genre_s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fantasy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;price_f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;6.99&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;pivot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[{&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;genre_s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fantasy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;genre_s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;scifi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;price_f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;7.95&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;pivot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[{&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;genre_s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fantasy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;price_f&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;7.99&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;pivot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[{&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;genre_s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fantasy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;field&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;genre_s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;scifi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]}]}}}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nice intuitive results, for a fairly straightforward facet query. However,
now to the bigger question – can we approximate this in Solr 1.4.1,
which doesn’t have the &lt;code class=&quot;highlighter-rouge&quot;&gt;facet.pivot&lt;/code&gt; query option?&lt;/p&gt;

&lt;h2 id=&quot;pivot-faceting-in-solr-141&quot;&gt;Pivot Faceting in Solr 1.4.1&lt;/h2&gt;

&lt;p&gt;Solr 1.4.1 has much more limited facet support than 4.0.  The building blocks
that we will use to cobble together a “faux” pivot query are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;facet.field&lt;/code&gt;&lt;/strong&gt;: The normal facet field option.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;facet.query&lt;/code&gt;&lt;/strong&gt;: The normal facet query option.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt;&lt;/strong&gt;: Basic field queries (for restrictions).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Local Params&lt;/strong&gt;: We use a couple of Solr &lt;a href=&quot;http://wiki.apache.org/solr/LocalParams&quot;&gt;local parameters&lt;/a&gt;.
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;tag&lt;/code&gt;&lt;/strong&gt;: Tags a &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt; with an arbitrary name.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;key&lt;/code&gt;&lt;/strong&gt;: Tags a facet field an arbitrary name (instead of field name).&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ex&lt;/code&gt;&lt;/strong&gt;: Excludes tagged &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt;’s from being operative on a given facet
field/query.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that either facet fields or facet queries can be used with this technique
– I’ll only show fields, but everything applies equally to queries.&lt;/p&gt;

&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;

&lt;p&gt;At this point, you should take a Solr 1.4.1 distribution and set it up exactly
as we did above for Solr 4.0 and upload our simple 10-document CSV file to
the running server. For simplicity here (and to keep my head on straight),
I ended up running my Solr 1.4.1 server on port 8984, so that I could also
keep the Solr 4.0 server running on port 8983. Here’s what I did:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Start Solr as non-daemon.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;solr_1.4.1_path/solr/example
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;java -Djetty.port&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8984 -jar start.jar

&lt;span class=&quot;c&quot;&gt;# (Copy sample_books.csv)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Upload to Solr.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/update/csv &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  --data-binary @sample_books.csv &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  -H &lt;span class=&quot;s1&quot;&gt;'Content-type:text/plain; charset=utf-8'&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Commit the upload.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/update &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  --data-binary &lt;span class=&quot;s1&quot;&gt;'&amp;lt;commit/&amp;gt;'&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  -H &lt;span class=&quot;s1&quot;&gt;'Content-type:application/xml'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;From here on, it is assumed you now have a populated Solr 1.4.1 server running
on port 8984 (switch addresses / ports as appropriate for your actual setup).&lt;/p&gt;

&lt;h3 id=&quot;excluding-restrictions-from-facets&quot;&gt;Excluding Restrictions from Facets&lt;/h3&gt;

&lt;p&gt;The starting point for our pivot facets is excluding certain query restrictions
for facets. A &lt;a href=&quot;http://wiki.apache.org/solr/SimpleFacetParameters#Multi-Select_Faceting_and_LocalParams&quot;&gt;basic example&lt;/a&gt; is provided for tagging and excluding
facets on the Solr wiki.&lt;/p&gt;

&lt;p&gt;Let’s do a simple facet query on prices with a restriction of
&lt;code class=&quot;highlighter-rouge&quot;&gt;genre_s:scifi&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Restricted facet query.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/select --data &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\i&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ndent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;on&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;json&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%3A&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;genre_s:scifi&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;index&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;price_f&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Looking to our results in &lt;code class=&quot;highlighter-rouge&quot;&gt;facet_fields&lt;/code&gt;, we see that we only have 2 hits
(&lt;code class=&quot;highlighter-rouge&quot;&gt;numFound&lt;/code&gt;), and the facet counts also add up to 2 (which represent our 2
SciFi books).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;numFound&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;facet_counts&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;facet_fields&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;price_f&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;6.99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;7.99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For situations like a drill down, Solr developers often want to run a basic
query with full restrictions for the returned records, but get more information
for facets. In this case, Solr allows tagging of &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt;’s, and keys / excludes
on facets to conditionally remove &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt;’s from a &lt;em&gt;given facet only&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So, let’s tag our &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt; as “SCIFI_FQ” and exclude it from our facet counts
with &lt;code class=&quot;highlighter-rouge&quot;&gt;ex&lt;/code&gt;, and then rename the facet results to “PRICE_KEY” using the
&lt;code class=&quot;highlighter-rouge&quot;&gt;key&lt;/code&gt; option:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Tag fq and exclude only from the field facet.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/select --data &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\i&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ndent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;on&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;json&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%3A&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;SCIFI_FQ&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;genre_s:scifi&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;index&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;PRICE_KEY&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;SCIFI_FQ&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;price_f&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that I have to escape the exclamation points and other characters for a
command line example here. Now, let’s look at the results:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;numFound&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&quot;facet_counts&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;facet_fields&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;PRICE_KEY&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;5.99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;6.99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;7.95&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;7.99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We can first see that the exclusion of the tagged “SCIFI_FQ” field query did
not affect the overall &lt;code class=&quot;highlighter-rouge&quot;&gt;numFound&lt;/code&gt;, which is still 2.  However, for the
facet field we applied the exclusion to, we now have facet results for records
in the whole set (which is the effective query after the exclusion). Finally,
our facet field has been renamed “PRICE_KEY” instead of the field name
(“price_f”).&lt;/p&gt;

&lt;h3 id=&quot;constructing-a-pivot-query&quot;&gt;Constructing a Pivot Query&lt;/h3&gt;

&lt;p&gt;With the basic tag/key/exclude technique in mind, let’s now return to our
original goal – create a pivot query on price by genre using Solr 1.4.1.
We will do this by performing two queries:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Perform a facet query for the top price results ordered by index.&lt;/li&gt;
  &lt;li&gt;Create &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt; tagged exclusions for each facet result, then create multiple
keyed facets on genre to give us each of our decision tree “leaf” results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first query is a very basic facet field query:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# First level facet field query.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/select --data &lt;span class=&quot;nv&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;on&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;json&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%3A&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;index&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;price_f&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Which gives us four individual facet results: “5.99”, “6.99”, “7.95”, “7.99”.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;numFound&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&quot;facet_counts&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;facet_fields&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;price_f&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;5.99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;6.99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;7.95&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;7.99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We take each of those results and create specific &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt; tagged restrictions:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;fq={!tag=FQ5.99}price_f:5.99
fq={!tag=FQ6.99}price_f:6.99
fq={!tag=FQ7.95}price_f:7.95
fq={!tag=FQ7.99}price_f:7.99&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Each excludes one of the components we’ll want facet results for our next
level field (genre) on. To then get the pivot facet result for each of our
four facets, we will exclude all the &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt;’s above &lt;strong&gt;except&lt;/strong&gt; the matching
one for the facet. Translating into facet parameters, this is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;facet.field={!key=5.99_GENRE ex=FQ6.99,FQ7.95,FQ7.99}genre_s
facet.field={!key=6.99_GENRE ex=FQ5.99,FQ7.95,FQ7.99}genre_s
facet.field={!key=7.95_GENRE ex=FQ5.99,FQ6.99,FQ7.99}genre_s
facet.field={!key=7.99_GENRE ex=FQ5.99,FQ6.99,FQ7.95}genre_s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The key is that we can specify &lt;em&gt;multiple&lt;/em&gt; exclusions using a comma. Thus,
looking to the facet key “5.99_GENRE”, we exclude all the &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt; restrictions
&lt;em&gt;except&lt;/em&gt; “FQ5.99”, which means that the facet results for that facet field
key will be the facet counts for “&lt;code class=&quot;highlighter-rouge&quot;&gt;fq=price_f:5.99&lt;/code&gt;” only. It’s kind of a
twisted-double-negative logic, but it all works out.&lt;/p&gt;

&lt;p&gt;Let’s put everything into our second-level query now:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Second level pivot query.&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8984/solr/select --data &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;on&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;json&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;%3A&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;facet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.mincount&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.sort&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;index&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FQ5.99&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;price_f:5.99&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FQ6.99&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;price_f:6.99&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FQ7.95&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;price_f:7.95&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FQ7.99&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;price_f:7.99&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5.99_GENRE&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FQ6.99&lt;span class=&quot;se&quot;&gt;\,&lt;/span&gt;FQ7.95&lt;span class=&quot;se&quot;&gt;\,&lt;/span&gt;FQ7.99&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;genre_s&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;6.99_GENRE&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FQ5.99&lt;span class=&quot;se&quot;&gt;\,&lt;/span&gt;FQ7.95&lt;span class=&quot;se&quot;&gt;\,&lt;/span&gt;FQ7.99&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;genre_s&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;7.95_GENRE&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FQ5.99&lt;span class=&quot;se&quot;&gt;\,&lt;/span&gt;FQ6.99&lt;span class=&quot;se&quot;&gt;\,&lt;/span&gt;FQ7.99&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;genre_s&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\&amp;amp;&lt;/span&gt;facet.field&lt;span class=&quot;o&quot;&gt;={&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;7.99_GENRE&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;FQ5.99&lt;span class=&quot;se&quot;&gt;\,&lt;/span&gt;FQ6.99&lt;span class=&quot;se&quot;&gt;\,&lt;/span&gt;FQ7.95&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;genre_s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Which gives us the “leaves” of the decision tree with our result keys:
“5.99_GENRE”, “6.99_GENRE”, “7.95_GENRE”, and “7.99_GENRE”.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;response&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;numFound&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
 &lt;span class=&quot;s2&quot;&gt;&quot;facet_counts&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;facet_fields&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;5.99_GENRE&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;fantasy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;6.99_GENRE&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;fantasy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;scifi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;7.95_GENRE&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;fantasy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;7.99_GENRE&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;fantasy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;scifi&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Looking at our original Solr 4.0 pivot query, we can cobble together our
two Solr 1.4.1 queries to get an equivalent result. In the end, both produce
the following decision tree for price by genre:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;5.99&lt;/strong&gt;: 2&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;fantasy&lt;/strong&gt;: 2&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;6.99&lt;/strong&gt;: 3&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;fantasy&lt;/strong&gt;: 2&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;scifi&lt;/strong&gt;: 1&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;7.95&lt;/strong&gt;: 1&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;fantasy&lt;/strong&gt;: 1&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;7.99&lt;/strong&gt;: 4&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;fantasy&lt;/strong&gt;: 3&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;scifi&lt;/strong&gt;: 1&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Victory!&lt;/p&gt;

&lt;h2 id=&quot;discussion-and-practical-implications&quot;&gt;Discussion and Practical Implications&lt;/h2&gt;

&lt;p&gt;Our “price by genre” example is a bit simplistic in that we can mostly get
the same results with two standard Solr 1.4.1 facet field queries. But, the
faux pivot facet technique really shines for a “foo by bar”-type query where
there are large numbers of first (“foo”) level facet results. Say, the first
level has 10 results, this would mean 11 queries (one for the top 10 “foo”’s,
then one &lt;em&gt;each&lt;/em&gt; for the 10 second-level “bar”’s for each “foo”). The faux pivot
facet technique cuts this down to 2 queries total.&lt;/p&gt;

&lt;p&gt;The method is generally applicable too. Although our examples here only use
facet fields, the technique equally works for &lt;a href=&quot;http://wiki.apache.org/solr/SimpleFacetParameters#facet.query_:_Arbitrary_Query_Faceting&quot;&gt;facet queries&lt;/a&gt;.
And &lt;a href=&quot;http://wiki.apache.org/solr/DistributedSearch&quot;&gt;distributed search&lt;/a&gt; supports the approach as well.&lt;/p&gt;

&lt;p&gt;Looking further, the technique can be applied to additional decision tree
levels. In the Solr 4.0 world, this simply means adding another field
like &lt;code class=&quot;highlighter-rouge&quot;&gt;facet.pivot=price_f,genre_s,inStock_b&lt;/code&gt; to get further breakdowns for
the “in stock” boolean field. For Solr 1.4.1, we would do a third query,
with permutations of our previous tagged &lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt;’s, as well as second-level
&lt;code class=&quot;highlighter-rouge&quot;&gt;fq&lt;/code&gt;’s. Then, we would have third-level keyed facet fields like:
“6.99_fantasy_INSTOCK”, “6.99_scifi_INSTOCK”, etc. At this level, it
certainly wouldn’t be pretty and would result in a beastly query, but
shows that the technique only adds 1 more actual Solr query for each
additional level in the faux decision tree.&lt;/p&gt;

&lt;p&gt;However, on the topic of query complexity, it is fair to point out that this
type of query hackery really should be done programmatically to ensure
correctness, and definitely not via the manual queries I provided above using
curl. It’s tough keeping track of just the 4 first-level pivots in our example
above, let alone a larger first level group, or more than 2 levels deep of
pivots. Another benefit is that you can collapse your tag / key names to
integer or other simple keys, and then have your program match things up later
for the final assembled decision tree result.&lt;/p&gt;

&lt;p&gt;As a final performance note, the faux pivot facet approach doesn’t really
lighten the Solr server load, it just collapses what would otherwise be
multiple queries into one query.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Reflecting on the above method, pivot facets are possible in Solr 1.4.1 at a
cost of &lt;em&gt;n&lt;/em&gt; separate queries, where &lt;em&gt;n&lt;/em&gt; is the number of levels in the decision
tree. So, if reducing the number of round trips between a web application
and Solr is the goal, and you need pivot facets in a pre-4.0 Solr, this may
well be the ticket.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>How I Learned to Stop Worrying and Love the Static Blog</title>
   <link href="http://loose-bits.com/2011/09/16/moving-to-jekyll.html"/>
   <updated>2011-09-16T16:00:00+00:00</updated>
   <id>http://loose-bits.com/2011/09/16/moving-to-jekyll</id>
   <content type="html">&lt;h2 id=&quot;farewell-blogger&quot;&gt;Farewell Blogger…&lt;/h2&gt;

&lt;p&gt;Blogger provides an easy, integrated blogging service, but I have not been
posting with anywhere near the frequency I had hoped for, and have recently
started thinking of the reasons why this is. The list I cam up with includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Page load times&lt;/strong&gt;: To get syntax highlighting, a nice theme, etc., my
posts have an incredible amount of download cruft.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Editing Interface&lt;/strong&gt;: While blogger’s WYSIWYG editor is fairly intuitive
and the HTML editor is the appropriate power tool, I don’t like the fact
I have to (1) be online, and (2) find a lot of situations where I want
something in between a WYSIWYG and raw HTML.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Offline Editing&lt;/strong&gt;: Expanding on the above point, I travel a lot, and it
would be great to be able to compose and view posts offline (like on a
plane).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Versioning/Backups&lt;/strong&gt;: Blogger doesn’t allow for easily versioning of
posts, which would be nice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I essentially run a programming blog, and as such, I don’t need a lot of
frills, bells or whistles. What I really want is text-based, powerful
and configurable blog engine. Enter &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;hello-jekyll-and-github&quot;&gt;Hello Jekyll and GitHub!&lt;/h2&gt;

&lt;p&gt;After a decent amount of research, I settled on &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;. Jekyll is
a static website generator, written in Ruby, supporting
&lt;a href=&quot;http://daringfireball.net/projects/markdown/&quot;&gt;Markdown&lt;/a&gt;,
&lt;a href=&quot;http://liquidmarkup.org/&quot;&gt;Liquid templates&lt;/a&gt;, and custom
&lt;a href=&quot;https://github.com/mojombo/jekyll/wiki/liquid-extensions&quot;&gt;Liquid extensions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are &lt;a href=&quot;http://www.google.com/search?q=moving+to+jekyll&quot;&gt;many&lt;/a&gt; stories of
folks moving successfully to Jekyll, and the feature set really hit my pain
points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;All Text&lt;/strong&gt;: All files are text, and either configuration, markdown,
template language, or whatnot.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Versioned&lt;/strong&gt;: It’s all source, so place your source under Git, and you’re
ready to go.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Offline&lt;/strong&gt;: The whole site can be generated or locally served without
and internet connection.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Markdown&lt;/strong&gt;: There are other pre-processing options, but I just went with
Markdown, and it’s really nice and easy to write posts now without jumping
back and forth from a WYSIWYG editor to straight HTML. I stay in Markdown
and everything (mostly) ends up looking correct.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Syntax Highlighting&lt;/strong&gt;: Jekyll uses &lt;a href=&quot;http://pygments.org/&quot;&gt;Pygments&lt;/a&gt; for
source code highlighting, which is mostly pre-processed, and not an
after-the-fact JavaScript processing step (which had previously been slowing
down my Blogger site).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;GitHub Support&lt;/strong&gt;: GitHub’s default document generator is Jekyll, and
(separately) has full website support with CNAMEs. This provides an easy
means of both site storage / versioning as well as the actual serving.&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more start --&gt;

&lt;p&gt;However, this is not to say that a Jekyll site is everyone’s cup of tea.
There are some drawbacks to this approach:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Static Site&lt;/strong&gt;: The generated site is static, which means no database or
dynamic pages for things like comments, etc. All dynamic content has to come
in via JavaScript / JavaScript-based services (like &lt;a href=&quot;http://disqus.com/&quot;&gt;Disqus&lt;/a&gt; for
comments). And, you have to implement a manual RSS feed, etc.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Manual CSS, HTML, etc.&lt;/strong&gt;: There are no built-in themes, etc., because you
are crafting a site from scratch. Really, this isn’t too bad because other
Jekyll-site authors often publicly version their source with permissive /
public domain licenses at GitHub, so you can pull source for examples.
Also, there are a lot of starter kits for static CSS / HTML sites.
For me, it’s really a nice opportunity to get re-acquainted with site design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;packing-up-and-moving-out&quot;&gt;Packing Up and Moving Out&lt;/h2&gt;

&lt;h3 id=&quot;posts&quot;&gt;Posts&lt;/h3&gt;

&lt;p&gt;The first step to migrating my Blogger site was to simply download all of the
posts in a Jekyll-friendly format.  I used a very simple
&lt;a href=&quot;https://gist.github.com/1115810&quot;&gt;script&lt;/a&gt; that a good job of downloading
basic HTML from the Blogger feed URL.&lt;/p&gt;

&lt;p&gt;I then took the HTML pages and further converted them to Markdown using
the handy &lt;a href=&quot;http://www.aaronsw.com/2002/html2text/&quot;&gt;html2text&lt;/a&gt; script.
From there, I had to manually edit the gremlins left in the conversion process,
including redoing anchor tags, removing Blogger-specific extras, and getting
local images into my source repository static media directory.&lt;/p&gt;

&lt;p&gt;Jekyll has its own default (but customizable) format for blog post names. I
like it, but it is slightly different than Blogger, which all my existing
posts already use. Fortunately, each Jekyll post has a &lt;code class=&quot;highlighter-rouge&quot;&gt;permalink&lt;/code&gt; attribute
allowing the post to escape the normal URL processing rules and use a one-time
path instead.&lt;/p&gt;

&lt;h3 id=&quot;comments&quot;&gt;Comments&lt;/h3&gt;

&lt;p&gt;After getting my posts ported over, the next step was to get my comments out
of Blogger and in to something that would work with the static Jekyll web site.
Enter &lt;a href=&quot;http://disqus.com/&quot;&gt;Disqus&lt;/a&gt;, which is inserted via JavaScript, and this is usable
for my new static website.&lt;/p&gt;

&lt;p&gt;Making things easier, Disqus has
&lt;a href=&quot;http://loose-bits.disqus.com/admin/blogger/&quot;&gt;Blogger support&lt;/a&gt;. So, I first
switched my Blogger site comments to Disqus, then used the Disqus blogger
import tool, available at:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http://&amp;lt;DISQUS SHORT NAME&amp;gt;.disqus.com/admin/tools/import/platform/blogger/
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I followed the instructions and started the Blogger comment import. I expected
the import to finish quickly, as I only have a handful of comments at the time
of the migration, but it ended up taking 6 or so hours. Searching the web,
there are reports of the Disqus comment import taking up to 2-3 days, so
the lesson is: be patient. Oh, and don’t get freaked out when your comments
disappear entirely from Blogger in the meantime.&lt;/p&gt;

&lt;p&gt;Once the import finished, I looked at my Blogger site and saw that all of the
comments had successfully been transitioned over to Disqus. I then set up
Disqus support on my post template using the Disqus
“&lt;a href=&quot;http://docs.disqus.com/developers/universal/&quot;&gt;universal&lt;/a&gt;” installation
guidelines.&lt;/p&gt;

&lt;p&gt;Before fully transitioning over to my new Jekyll/GitHub site, I wanted to
make sure that everything was good to go with Disqus. Fortunately, Disqus
allows you to specify a “developer mode” to have comments pulled down, even
when running a localhost site. With these configuration variables in the Disqus
JavaScript block, I can view the comments for this post on my laptop at
localhost:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Live page URL.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;disqus_url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'http://loose-bits.com/2011/09/16/moving-to-jekyll.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Developer mode for localhost.&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;disqus_developer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;rss-feed&quot;&gt;RSS Feed&lt;/h3&gt;

&lt;p&gt;You have to specify your own “atom.xml” file for RSS feeds. Fortunately,
there are many examples from other Jekyll sites, including Tom Preston-Werner’s
&lt;a href=&quot;https://github.com/mojombo/mojombo.github.com&quot;&gt;site&lt;/a&gt;,
as well as a good Google Groups
&lt;a href=&quot;http://groups.google.com/group/jekyll-rb/browse_thread/thread/5a8af8abb59ff9ac/914585f6e43e9b92&quot;&gt;thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is my finished &lt;a href=&quot;https://github.com/ryan-roemer/loose-bits/blob/master/atom.xml&quot;&gt;atom.xml&lt;/a&gt;
for this site for reference.&lt;/p&gt;

&lt;h3 id=&quot;post-excerpts&quot;&gt;Post Excerpts&lt;/h3&gt;

&lt;p&gt;My Blogger site displays a few recents post excerpts on the index page, which
I liked. However, there is no native “excerpt” functionality in Jekyll at
the moment. Fortunately, a very clever
&lt;a href=&quot;http://kaspa.rs/2011/04/jekyll-hacks-html-excerpts/&quot;&gt;hack&lt;/a&gt; enabled me to
add post excerpts of my most recent couple of articles to my new index page.&lt;/p&gt;

&lt;h2 id=&quot;and-were-up&quot;&gt;And, We’re Up!&lt;/h2&gt;

&lt;p&gt;And that finishes my journey from Blogger to Jekyll and GitHub. As a final
commentary Blogger does a great many things right and is a solid platform, but
my use cases favor power and extensibility over many of the conveniences that
Blogger provides.&lt;/p&gt;

&lt;p&gt;Although it took some time to put my new site together, everything feels
“right” to me. Even writing this first post for the new site has been
considerably easier – I’ve been both online and offline composing the post in
TextMate (which has Markdown syntax highlighting as well as spell check),
and it has never been as easy or fast to write up posts, so here’s to hoping
for a more consistent stream of posts in the future!&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>ConstantDict, Yet Another Python Enumeration Pattern</title>
   <link href="http://loose-bits.com/2011/06/constantdict-yet-another-python.html"/>
   <updated>2011-06-13T13:30:00+00:00</updated>
   <id>http://loose-bits.com/2011/06/constantdict-yet-another-python</id>
   <content type="html">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;The Python language does not natively provide enumerations. Rejected &lt;a href=&quot;http://www.python.org/dev/peps/pep-0354/&quot;&gt;PEP
354&lt;/a&gt; proposed a string-constructor-based enumeration, later provided as the
separate enum &lt;a href=&quot;http://pypi.python.org/pypi/enum/&quot;&gt;package&lt;/a&gt;. The web is rife with various cookbook recipes and
ad-hoc solutions – &lt;em&gt;see, e.g.&lt;/em&gt;, &lt;a href=&quot;http://code.activestate.com/recipes/413486/&quot;&gt;ActiveState&lt;/a&gt; and &lt;a href=&quot;http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python&quot;&gt;Stack Overflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is &lt;code class=&quot;highlighter-rouge&quot;&gt;ConstantDict&lt;/code&gt;, one of my preferred enumeration patterns.&lt;/p&gt;

&lt;h2 id=&quot;constantdict&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ConstantDict&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;This pattern (like most Python enumeration recipes) is quite simple and
straightforward, so I’ll actually start with the enumeration class, and then
discuss the merits / drawbacks after.&lt;/p&gt;

&lt;p&gt;The enumeration class is as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConstantDict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;An enumeration class.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_dict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@classmethod&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Dictionary of all upper-case constants.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_dict&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;getattr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_dict&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                             &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;upper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cls&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_dict&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__contains__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__iter__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;!-- more start --&gt;

&lt;p&gt;Essentially, we create an internal dictionary of all class variables that are
upper-cased (well, and methods too), and wrap up a couple of instance built-in
methods. Implementing an enumeration with the pattern is pretty
straightforward. Here is a “days of the week” enumeration contrived by
multiply inheriting two base enumeration classes:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Weekdays&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConstantDict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Weekday days.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MONDAY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Monday'&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;TUESDAY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Tuesday'&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;WEDNESDAY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Wednesday'&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;THURSDAY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Thursday'&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;FRIDAY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Friday'&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Weekend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConstantDict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Weekend days.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SATURDAY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Saturday'&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SUNDAY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'Sunday'&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DaysOfTheWeek&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Weekdays&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Weekend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Days of the week.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And a simple use case example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Get enumeration instance.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;DAYS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DaysOfTheWeek&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DAYS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MONDAY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;        &lt;span class=&quot;c&quot;&gt;# &quot;Monday&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Saturday'&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DAYS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# True&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Notaday'&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DAYS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# False&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DAYS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# [&quot;Monday&quot;, &quot;Tuesday&quot;, ...]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;discussion&quot;&gt;Discussion&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;ConstantDict&lt;/code&gt; pattern shines in a couple of key areas:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It has a terse, clean, and extensible structure. The enumerations are
inheritable, and even support multiple inheritance (as the above examples
shows). Also, the enumerations can be enhanced with helper methods, etc.,
because they are real classes.&lt;/li&gt;
  &lt;li&gt;The structure allows arbitrary enumeration values. In my example I used
strings, but anything (even including functions) are valid values.&lt;/li&gt;
  &lt;li&gt;The enumeration members are real class variables and not strings. For me this
is a key point over a pure string-based solution, as I want to be able to run
&lt;code class=&quot;highlighter-rouge&quot;&gt;pylint&lt;/code&gt; and have it throw errors on invalid enumeration names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, &lt;code class=&quot;highlighter-rouge&quot;&gt;ConstantDict&lt;/code&gt; probably isn’t the best choice for many situations.
Some of the drawbacks of the pattern include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Enumeration members are unordered. Because everything is shoved into a
dictionary, there are no order guarantees.&lt;/li&gt;
  &lt;li&gt;Despite being called &lt;code class=&quot;highlighter-rouge&quot;&gt;ConstantDict&lt;/code&gt;, the enumeration values aren’t actually
enforced constants.&lt;/li&gt;
  &lt;li&gt;Enumeration values must be manually specified and are not guaranteed to be
unique. As noted above arbitrary (including identical) enumeration values are
allowed. However, the pattern could be easily extended to raise an exception
on detection of duplicate values.&lt;/li&gt;
  &lt;li&gt;An enumeration class cannot have other upper-cased class variables or members.
This should not be too big of a deal as the enumeration class is ideally used
just as a small constant holder and not a multi-purpose class.&lt;/li&gt;
  &lt;li&gt;The caller has to instantiate an instance of the &lt;code class=&quot;highlighter-rouge&quot;&gt;ConstantDict&lt;/code&gt; class.
This is necessary for the &lt;code class=&quot;highlighter-rouge&quot;&gt;__contains__&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;__iter__&lt;/code&gt; convenience
method. In the past, I have used a different version of the &lt;code class=&quot;highlighter-rouge&quot;&gt;ConstantDict&lt;/code&gt;
class that is used only at the class variable / method level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are certainly other tweaks and enhancements that can make the
&lt;code class=&quot;highlighter-rouge&quot;&gt;ConstantDict&lt;/code&gt; pattern better for a given situation.  But, in its most basic
form as described above, it provides an easy, extensible thin-wrapper
enumeration.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Extending Django Settings with Derived Attributes and Methods</title>
   <link href="http://loose-bits.com/2011/04/extending-django-settings-with-derived.html"/>
   <updated>2011-04-20T05:07:00+00:00</updated>
   <id>http://loose-bits.com/2011/04/extending-django-settings-with-derived</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://docs.djangoproject.com/en/dev/topics/settings/&quot;&gt;Django settings&lt;/a&gt; construction and configuration is fairly straightforward.
Configure Django and installed application settings, maybe add your own
project-specific settings, and let Django take care of the rest.
As long as you import settings from django.conf all the options and defaults
are properly resolved into the ultimate settings object.&lt;/p&gt;

&lt;p&gt;However, every so often I run across a situation where I wish the Django
settings could do just a little bit more. Rather than try and wade into the
depths of Django runtime configuration and risk mucking things up, the
approach I take is to use a thin wrapper class around the already configured
settings and pass through all calls directly.&lt;/p&gt;

&lt;p&gt;What we essentially want is “pass through” wrapper class like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.conf&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_settings&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Thin settings wrapper.&quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default_settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Initializer.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_settings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default_settings&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__getattr__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Get setting.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;getattr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Fortunately, Django already has a slightly more complex version of this in the
django.conf.UserSettingsHolder class, so we’ll use that instead. I usually
have a common application in my Django projects and insert the settings
wrapper as the common.settings or common.conf module.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;From there, we can add any number of “extra” settings variables or derived
methods. For instance, if we wanted to add a settings method to tell if our
database was SQLite or PostgreSQL using knowledge of the driver names, we
could implement some fairly simple methods:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;is_sqlite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return ``True`` if default database is SQLite.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Driver: 'sqlite3'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'sqlite3'&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DATABASES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'default'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'ENGINE'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;is_postgres&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return ``True`` if default database is PostgreSQL.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Drivers: 'postgresql_psycopg2', 'postgresql'&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'postgresql'&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DATABASES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'default'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'ENGINE'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;While perhaps too simplistic and brittle for production use, the above example
illustrates how easy it is to add extra dynamic checks. To use the above
methods if very easy – just import settings from our module (here
common.settings instead of django.conf).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;common.settings&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Using SQLite?: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_sqlite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Using Postgres?: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_postgres&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which outputs:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Using SQLite?: True
Using Postgres?: False&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;for a project configured with &lt;code class=&quot;highlighter-rouge&quot;&gt;'ENGINE': 'django.db.backends.sqlite3'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a typical Django project there are several file paths that are usually
derived and hacked into the settings file – the Django project root
directory, the Django admin media directory, and sometimes the path to the
settings file itself.  Here are methods for our wrapped settings class to
intuit these paths:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;settings_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return path to project settings.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.utils&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importlib&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Have to use real settings directly because UserSettingsHolder&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# sets SETTINGS_MODULE to ``None`` as a class variable.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;import_module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SETTINGS_MODULE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Split off the extension to replace &quot;.pyc&quot; with &quot;.py&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;splitext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abspath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.py&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;project_root_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return path to project root.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# We &quot;cheat&quot; here to find project root because we have know&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# this file is ``PROJECT_ROOT/common/settings.py``.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abspath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;..&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;admin_media_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return path to Django admin media directory.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.contrib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;admin&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abspath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;admin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;media&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And we can view our results with:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;common.settings&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Settings file path: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;settings_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Project root directory path: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project_root_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Admin media directory path: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;admin_media_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which outputs:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Settings file path: /Users/ryan/demo/settings.py
Project root directory path: /Users/ryan/demo
Admin media directory path: /Users/ryan/env/demo/lib/python2.6/site-packages/django/contrib/admin/media&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Putting a full file together with our simple extension methods. In our case,
the finished “common/settings.py” file will be:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.contrib&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;admin&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.conf&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserSettingsHolder&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.utils&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importlib&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomSettings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserSettingsHolder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Thin settings wrapper.

    .. note:: &quot;Real&quot; settings object is available as
      ``.default_settings`` member.
    &quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;is_sqlite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return ``True`` if default database is SQLite.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# Driver: 'sqlite3'&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'sqlite3'&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DATABASES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'default'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'ENGINE'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;is_postgres&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return ``True`` if default database is PostgreSQL.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# Drivers: 'postgresql_psycopg2', 'postgresql'&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'postgresql'&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DATABASES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'default'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'ENGINE'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;settings_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return path to project settings.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;import_module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;default_settings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SETTINGS_MODULE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c&quot;&gt;# Split off the extension to replace &quot;.pyc&quot; with &quot;.py&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;splitext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abspath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.py&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;project_root_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return path to project root.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abspath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;..&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;admin_media_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Return path to Django admin media directory.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abspath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;admin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;media&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CustomSettings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;While these contrived example method extensions are solving problems perhaps
best solved elsewhere (like in the actual project settings file), they serve
to illustrate a simple means of how to extend the Django settings module
easily and without interfering with the runtime settings configuration
process.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Django Cloud Browser</title>
   <link href="http://loose-bits.com/2011/03/django-cloud-browser.html"/>
   <updated>2011-03-10T05:44:00+00:00</updated>
   <id>http://loose-bits.com/2011/03/django-cloud-browser</id>
   <content type="html">
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;I recently pushed the v.0.1.2 release of my new Django application, django-
cloud-browser, to &lt;a href=&quot;http://pypi.python.org/pypi&quot;&gt;PyPi&lt;/a&gt;. Cloud Browser is a simple web-based file browser
for cloud-based datastores, which so far includes &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;Amazon S3&lt;/a&gt;, Rackspace
&lt;a href=&quot;http://www.rackspace.com/cloud/cloud_hosting_products/files/&quot;&gt;Cloud Files&lt;/a&gt; (including &lt;a href=&quot;http://www.openstack.org/projects/storage/&quot;&gt;OpenStack&lt;/a&gt;) and local file system. The
application can expose read-only cloud files to users and/or administrators
with configurations for both normal and administrative deployments.&lt;/p&gt;

&lt;p&gt;The basic reference points to get started include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://ryan-roemer.github.com/django-cloud-browser/&quot;&gt;Cloud Browser API / documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ryan-roemer/django-cloud-browser/&quot;&gt;GitHub page&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://pypi.python.org/pypi/django-cloud-browser&quot;&gt;PyPi page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;At work, we have some containers with very large numbers of objects – over 5
million and counting. While objects ultimately are named in a flat namespace,
we divide up the namespace with slashes (e.g.,
“path/to/cloud/object/file.txt”). Nearly all cloud providers (Amazon S3,
Microsoft Azure, and Rackspace) include functionality in their object APIs to
return results grouped around hierarchical “implied” or “pseudo-“ directory
objects based on a separator (like the slash that we use).&lt;/p&gt;

&lt;p&gt;Despite the fact that Rackspace’s underlying &lt;a href=&quot;http://docs.rackspacecloud.com/files/api/cf-devguide-latest.pdf&quot;&gt;REST&lt;/a&gt; and &lt;a href=&quot;https://github.com/rackspace/python-cloudfiles/&quot;&gt;Python&lt;/a&gt; APIs
both support implying directories from a delimiter like a slash, Rackspace’s
own &lt;a href=&quot;https://manage.rackspacecloud.com/CloudFiles.do&quot;&gt;management console&lt;/a&gt; will only display objects using a completely flat
namespace. As an example, I have an old side project where I stored US patent
data in XML form in both Rackspace Cloud Files and Amazon S3, and broke up
each object with a slash-delimited path. (I have obscured my bucket /
container names in all of the screen shots in this post). Let’s view the
objects in the Rackspace container using their web interface:&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;[![Rackspace control panel][img_rs_th]][img_rs]
[img_rs_th]: http://loose-bits.com/media/img/2011/03/10/rackspace_thumb.png
[img_rs]: http://loose-bits.com/media/img/2011/03/10/rackspace.png&lt;/p&gt;

&lt;p&gt;Thus, you have to wade through multiple pages of flat objects to find one you
are looking for, and all of the work in sectioning out the object namespace
has basically gone to waste.&lt;/p&gt;

&lt;p&gt;By contrast, Amazon’s &lt;a href=&quot;https://console.aws.amazon.com/s3/home?&quot;&gt;web dashboard&lt;/a&gt; for S3, will imply
pseudo-directories, as we can see from the analogous view in S3:&lt;/p&gt;

&lt;p&gt;[![AWS web dashboard][img_aws_th]][img_aws]
[img_aws_th]: http://loose-bits.com/media/img/2011/03/10/aws_thumb.png
[img_aws]: http://loose-bits.com/media/img/2011/03/10/aws.png&lt;/p&gt;

&lt;p&gt;Given the hassles with trying to browse our millions of objects in the
Rackspace dashboard, I finally decided to bite the bullet and just code up a
Django application to do the job (such that I could easily integrate it into
our system at work). A few months later, I finally hacked together a first
version of the &lt;code class=&quot;highlighter-rouge&quot;&gt;django-cloud-browser&lt;/code&gt; application.&lt;/p&gt;

&lt;h2 id=&quot;django-cloud-browser&quot;&gt;Django Cloud Browser&lt;/h2&gt;

&lt;p&gt;At a high level, Cloud Browser application exposes containers and objects in a
Cloud datastore to users or administrators. Let’s look at a basic example:&lt;/p&gt;

&lt;h3 id=&quot;basic-deployment&quot;&gt;Basic Deployment&lt;/h3&gt;

&lt;p&gt;On your system, first install the application:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ pip install django-cloud-browser&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then, edit your Django “settings.py”.  Here, I’m setting up a Rackspace Cloud
Files account for browsing:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'cloud_browser'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_DATASTORE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Rackspace&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_RACKSPACE_ACCOUNT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;my_account&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_RACKSPACE_SECRET_KEY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;my_secret_key&amp;gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, add the URLs to your “urls.py”:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;urlpatterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;r'^cb/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'cloud_browser.urls'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And start up your Django project! You should be able to click through
container and object navigation links, as well as page results when they are
over the maximum number of objects per page (configurable via
&lt;code class=&quot;highlighter-rouge&quot;&gt;CLOUD_BROWSER_DEFAULT_LIST_LIMIT&lt;/code&gt;, but defaults to 20 objects). Here is my
navigation into the same Rackspace object as before, but now the objects have
implied pseudo-directories.&lt;/p&gt;

&lt;p&gt;[![Basic Cloud Browser][img_basic_th]][img_basic]
[img_basic_th]: http://loose-bits.com/media/img/2011/03/10/cb_basic_thumb.png
[img_basic]: http://loose-bits.com/media/img/2011/03/10/cb_basic.png&lt;/p&gt;

&lt;p&gt;Objects that lead to ultimate files will be downloaded / displayed when
clicked. Breadcrumb links are displayed along the top of the page as well.&lt;/p&gt;

&lt;p&gt;The application even figures out and sets appropriate headers for content type
and content encoding, which means, e.g., that compressed files will be
uncompressed on the fly by browsers that support the compression scheme. At
this point, it is worth noting that all access through Cloud Browser is
read-only, although in the future we will look to enhancements like uploads,
metadata retrieval / modification, etc.&lt;/p&gt;

&lt;h3 id=&quot;configuration-options&quot;&gt;Configuration Options&lt;/h3&gt;

&lt;h4 id=&quot;other-datastores&quot;&gt;Other Datastores&lt;/h4&gt;

&lt;p&gt;In addition to Rackspace Cloud Files, the application supports Amazon S3 as
well as the local file system (with a mock container / objects scheme imposed
on real directories and files).  Examples:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c&quot;&gt;# AWS S3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_DATASTORE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;AWS&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_AWS_ACCOUNT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;my_account&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_AWS_SECRET_KEY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;my_secret_key&amp;gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Local filesystem&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_DATASTORE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Filesystem&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_FILESYSTEM_ROOT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/usr/share/doc&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;white--black-lists&quot;&gt;White / Black Lists&lt;/h4&gt;

&lt;p&gt;By default, all containers and all objects are browsable. However, the
application supports basic white and black lists at the container level. If a
whitelist is specified, only those containers will be browsable.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_CONTAINER_WHITELIST&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'my_container'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'more_stuff'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If a blacklist is specified, only those containers are excluded from browsing.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_CONTAINER_BLACKLIST&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'secret_stuff'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'archive'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If both are specified, access is effectively only what is allowed by the
whitelist.&lt;/p&gt;

&lt;h4 id=&quot;view-decorators&quot;&gt;View Decorators&lt;/h4&gt;

&lt;p&gt;The settings variable &lt;code class=&quot;highlighter-rouge&quot;&gt;CLOUD_BROWSER_VIEW_DECORATOR&lt;/code&gt; can be set to a function
(e.g., a decorator) that wraps all browsing views.  Setting this to Django’s
&lt;code class=&quot;highlighter-rouge&quot;&gt;login_required&lt;/code&gt; decorator permits only logged-in users to use the Cloud
Browser.&lt;/p&gt;

&lt;h4 id=&quot;static-media&quot;&gt;Static Media&lt;/h4&gt;

&lt;p&gt;Cloud Browser contains a minimal amount of CSS and JavaScript. If you set up
Django to statically serve these files, then set the
&lt;code class=&quot;highlighter-rouge&quot;&gt;CLOUD_BROWSER_STATIC_MEDIA_DIR&lt;/code&gt; variable to the relative path from
&lt;code class=&quot;highlighter-rouge&quot;&gt;settings.MEDIA_ROOT&lt;/code&gt;. The application has a fallback where if this is
not set, the CSS / JavaScript will be included (inefficiently) in the HTML of
each application view. See the documentation for further details.&lt;/p&gt;

&lt;h3 id=&quot;admin-deployment&quot;&gt;Admin Deployment&lt;/h3&gt;

&lt;p&gt;Cloud Browser also allows for integration with the Django admin.  Here, we
just make a simple change to our “urls.py”:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;urlpatterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patterns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Admin URLs. Note: Include ``urls_admin`` **before** admin.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;r'^admin/cb/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'cloud_browser.urls_admin'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;r'^admin/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;admin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;urls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It is also a really good idea to manually set the “staff required” decorator
in “settings.py” to mirror the other admin view restrictions:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.contrib.admin.views.decorators&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;staff_member_required&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CLOUD_BROWSER_VIEW_DECORATOR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;staff_member_required&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And here’s the same view as above in the admin:&lt;/p&gt;

&lt;p&gt;[![Cloud Browser admin (open)][img_admin_open_th]][img_admin_open]
[img_admin_open_th]: http://loose-bits.com/media/img/2011/03/10/cb_admin_open_thumb.png
[img_admin_open]: http://loose-bits.com/media/img/2011/03/10/cb_admin_open.png&lt;/p&gt;

&lt;p&gt;The administrative views also have a closable container element (click the +/-
box in the upper right hand corner to toggle):&lt;/p&gt;

&lt;p&gt;[![Cloud Browser admin (closed)][img_admin_closed_th]][img_admin_closed]
[img_admin_closed_th]: http://loose-bits.com/media/img/2011/03/10/cb_admin_closed_thumb.png
[img_admin_closed]: http://loose-bits.com/media/img/2011/03/10/cb_admin_closed.png&lt;/p&gt;

&lt;p&gt;This work is at a very early stage and will likely change a lot, as there are
many unresolved issues: links don’t appear on the dashboard because there are
no models, there are no configurable admin permissions to the Cloud Browser
views (either all or none), etc.  But, it’s close enough for now.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Cloud Browser provides a very basic cloud file viewer for your Django
projects, but it gets the job done, particularly for Rackspace Cloud Files.
Any and all feedback and bugs are strongly encouraged – just open a ticket on
the &lt;a href=&quot;https://github.com/ryan-roemer/django-cloud-browser/issues&quot;&gt;GitHub issues page&lt;/a&gt;. Happy browsing.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Browserless AJAX Testing with Rhino and Envjs, Part 2.</title>
   <link href="http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and_16.html"/>
   <updated>2011-02-16T15:17:00+00:00</updated>
   <id>http://loose-bits.com/2011/02/browserless-ajax-testing-pt2</id>
   <content type="html">
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;This is the second of two posts looking at browserless AJAX / JavaScript
testing with &lt;a href=&quot;http://www.mozilla.org/rhino/&quot;&gt;Rhino&lt;/a&gt; and &lt;a href=&quot;http://www.envjs.com/&quot;&gt;Envjs&lt;/a&gt;. &lt;a href=&quot;/2011/02/browserless-ajax-testing-with-rhino-and.html&quot;&gt;Last time&lt;/a&gt;, we set up Rhino and
Envjs to run a basic set of &lt;a href=&quot;http://docs.jquery.com/Qunit&quot;&gt;QUnit&lt;/a&gt; tests from the command line, exercising
pure JavaScript code with no AJAX or DOM manipulation.  Today, we will add
tests for code that hacks on the DOM and makes AJAX calls.&lt;/p&gt;

&lt;p&gt;Let’s start with the files we had last time (with links to previous blog
post):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;env.rhino.1.2.js&lt;/code&gt;&lt;/strong&gt;: Envjs for Rhino.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jquery/qunit/blob/a46610796b457fab05587945e743d4e857f580b5/qunit/qunit.css&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;qunit.css&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: QUnit styles. (&lt;em&gt;Link to known working version&lt;/em&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jquery/qunit/blob/a46610796b457fab05587945e743d4e857f580b5/qunit/qunit.js&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;qunit.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: QUnit code. (&lt;em&gt;Link to known working version&lt;/em&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2011/02/browserless-ajax-testing-with-rhino-and.html#setup.js&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;setup.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Our custom hook Envjs to QUnit.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2011/02/browserless-ajax-testing-with-rhino-and.html#run-tests.js&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;run-tests.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Include our setup script and actually invoke the
tests at on our test HTML page.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2011/02/browserless-ajax-testing-with-rhino-and.html#my-lib.js&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;my-lib.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: An example JavaScript library (that we want to test).&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2011/02/browserless-ajax-testing-with-rhino-and.html#my-tests.js&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;my-tests.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: An example custom QUnit tests for my-lib.js.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2011/02/browserless-ajax-testing-with-rhino-and.html#test.html&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;test.html&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Basic QUnit HTML page (see QUnit documentation) with
script links to “my-lib.js” and “my-tests.js”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;new-files-and-tests&quot;&gt;New Files and Tests&lt;/h2&gt;

&lt;p&gt;Let’s add some new libraries and tests to our working set, to include DOM
actions and AJAX:&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;jquery.js&lt;/code&gt;&lt;/strong&gt;: The venerable &lt;a href=&quot;http://jquery.com/&quot;&gt;jQuery library&lt;/a&gt;, which we’ll use for
DOM and AJAX calls.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2011/02/browserless-ajax-testing-with-rhino-and_16.html#my-tests2.js&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;my-tests2.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Our new tests, described in full detail below.&lt;/li&gt;
  &lt;li&gt;my-lib2.js: We’ll add two functions – one which does simply DOM manipulation
with events, the other which does an AJAX call and inserts the text from
another URL into a div element.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// my-lib2.js&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/**
 * Add text and clickable elements to div.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;addTextAndClicker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;$div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'color'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rgb(0, 0, 0)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'cursor'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pointer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;$div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'color'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rgb(255, 255, 255)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'cursor'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * Dump text from URL into 'code' div.
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dumpText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ajax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'GET'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;dataType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'text'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Update the div.&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Note: The &quot;&amp;lt;pre/&amp;gt;&quot; tag has some issues with Envjs.&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;code /&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appendTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;// User callback.&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Interestingly, I originally had the &lt;code class=&quot;highlighter-rouge&quot;&gt;dumpText()&lt;/code&gt; function create a new
“&amp;lt;pre/&amp;gt;” tag, but something between Rhino and Envjs did not like it, and
Rhino would error out on running this code. So, I switched to “&amp;lt;code/&amp;gt;”
and everything worked fine. I guess the point is that nothing absolutely
substitutes for a real browser, but Envjs does a really great job
notwithstanding!&lt;/p&gt;

&lt;h2 id=&quot;serving-our-files-for-ajax&quot;&gt;Serving Our Files for AJAX&lt;/h2&gt;

&lt;p&gt;One important thing to note is that because we are doing AJAX calls now, we
quickly will run into the &lt;a href=&quot;http://en.wikipedia.org/wiki/Same_origin_policy&quot;&gt;same origin policy&lt;/a&gt;.  To easily get around this
for purposes of this demonstration, open a new terminal window, point it to
the test directory we’re working in and serve all files in the directory
directly from localhost with the python built-in web server (here I chose port
8001):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ cd /path/to/test/code
$ python -m SimpleHTTPServer 8001 .&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Check things out by navigating a real browser to:
http://127.0.0.1:8001/test.html.  Our QUnit tests from last time should run in
the window.&lt;/p&gt;

&lt;h2 id=&quot;testing-our-new-code&quot;&gt;Testing our New Code&lt;/h2&gt;

&lt;p&gt;Now that we have created our new library and are running a slim web server (so
we can actually make AJAX requests), let’s write some test code in “my-
tests2.js”.  At the module level, we make use of the QUnit setup and teardown
options to attach test div elements to the QUnit fixture element, so we can
actually do some (temporary) DOM manipulation.&lt;/p&gt;

&lt;p&gt;We use an asynchronous test pattern for the &lt;code class=&quot;highlighter-rouge&quot;&gt;dumpText&lt;/code&gt; AJAX tests to handle
latency between the call and response.  The call to &lt;code class=&quot;highlighter-rouge&quot;&gt;stop()&lt;/code&gt; stops QUnit
execution to allow the code and tests to run.  If the asynchronous callback
doesn’t finish (and invoke &lt;code class=&quot;highlighter-rouge&quot;&gt;start()&lt;/code&gt;) within the designated time period
(here 1 second), then QUnit will raise an error.  Assuming we have given
ourselves enough time, once &lt;code class=&quot;highlighter-rouge&quot;&gt;start()&lt;/code&gt; is invoked, then QUnit starts up again
for the remaining tests and a final summary.  For a good overview of QUnit
testing with asynchronous examples, see
“&lt;a href=&quot;http://net.tutsplus.com/tutorials/javascript-ajax/how-to-test-your-javascript-code-with-qunit/&quot;&gt;How to Test your JavaScript Code with QUnit&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;Here’s everything together for “my-tests2.js”:
&lt;a name=&quot;my-tests2.js&quot;&gt; &lt;/a&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-lib2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/**
   * Add new elements before each test.
   */&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Store test page fixture.&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$fixture&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'#qunit-fixture'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Create new test div's.&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;div/&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;add-text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;appendTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$fixture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$dumpText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&amp;lt;div/&amp;gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dump-text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;appendTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$fixture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

  &lt;span class=&quot;cm&quot;&gt;/**
   * Remove tests elements after.
   */&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;teardown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Remove test div's&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$dumpText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;addTextAndClicker&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Div starts empty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Add the text and clicker.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;addTextAndClicker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hi There!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hi There!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Div has text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'color'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;s2&quot;&gt;&quot;rgb(0, 0, 0)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Starting color&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'cursor'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;s2&quot;&gt;&quot;pointer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Changed cursor&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Click the clicker and check.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hi There!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Div still has text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'color'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
         &lt;span class=&quot;s2&quot;&gt;&quot;rgb(255, 255, 255)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Changed color&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$addText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'cursor'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Changed cursor&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;


&lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;dumpText&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$dumpText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Div starts empty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Stop QUnit execution to wait for AJAX call and tests.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// Wait up to 1 second.&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Perform AJAX call.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Just dump our setup script as straight text.&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;dumpText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$dumpText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:8001/setup.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Now, run tests.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$dumpText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'code'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Verify code element.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Find some known text strings.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;s2&quot;&gt;&quot;Find string \&quot;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;\&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;beforeScriptLoad&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;afterScriptLoad&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;QUnit test runner loaded&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;QUnit.log&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Restart QUnit execution.&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, lets update our “test.html” file to run both our previous and new unit
tests (and add the jQuery dependency):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;QUnit&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit.css&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/css&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;jquery.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-lib.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-tests.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-lib2.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-tests2.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-header&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;QUnit Test Suite&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-banner&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-testrunner-toolbar&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-userAgent&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;ol&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-tests&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-fixture&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Finally, we need to tweak “run-tests.js” to point to the locally served test
page instead of simply the local filesystem.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'setup.js'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Starting QUnit tests...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:8001/test.html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And, let’s run the tests!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ java -cp ~/rhino1_7R2/js.jar org.mozilla.javascript.tools.shell.Main -opt -1 run-tests.js
[  Envjs/1.6 (Rhino; U; Mac OS X x86_64 10.6.6; en-US; rv:1.7.0.rc2) Resig/20070309 PilotFish/1.2.13  ]
Starting QUnit tests...
* QUnit test runner loaded.
  * { addTwo }( 0 )[ PASS ]  Add nothing., expected: 0
  * { addTwo }( 1 )[ PASS ]  Add numbers., expected: 3
  * { addTwo }( 2 )[ PASS ]  Add negatives., expected: -3
  * { addTextAndClicker }( 3 )[ PASS ]  Div starts empty, expected: &quot;&quot;
  * { addTextAndClicker }( 4 )[ PASS ]  Div has text, expected: &quot;Hi There!&quot;
  * { addTextAndClicker }( 5 )[ PASS ]  Starting color, expected: &quot;rgb(0, 0, 0)&quot;
  * { addTextAndClicker }( 6 )[ PASS ]  Changed cursor, expected: &quot;pointer&quot;
  * { addTextAndClicker }( 7 )[ PASS ]  Div still has text, expected: &quot;Hi There!&quot;
  * { addTextAndClicker }( 8 )[ PASS ]  Changed color, expected: &quot;rgb(255, 255, 255)&quot;
  * { addTextAndClicker }( 9 )[ PASS ]  Changed cursor, expected: &quot;default&quot;
  * { dumpText }( 10 )[ PASS ]  Div starts empty, expected: &quot;&quot;
  * { dumpText }( 11 )[ PASS ]  Verify code element., expected: 1
  * { dumpText }( 12 )[ PASS ]  Find string &quot;beforeScriptLoad&quot;
  * { dumpText }( 13 )[ PASS ]  Find string &quot;afterScriptLoad&quot;
  * { dumpText }( 14 )[ PASS ]  Find string &quot;QUnit test runner loaded&quot;
  * { dumpText }( 15 )[ PASS ]  Find string &quot;QUnit.log&quot;

*****************
* QUnit Results *
*****************
* PASSED:  16
* FAILED:  0
* Completed  16  tests total in  2.981  seconds.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;From the results, we can see that we now have all of our unit tests running
from pure JavaScript code to our new DOM manipulation and AJAX calls.&lt;/p&gt;

&lt;h2 id=&quot;other-enhancements&quot;&gt;Other Enhancements&lt;/h2&gt;

&lt;p&gt;Beyond the setup we discussed today, there are several really cool additions
and improvements you can add to your browserless testing solution.&lt;/p&gt;

&lt;h3 id=&quot;use-mocks-for-ajax-calls&quot;&gt;Use Mocks for AJAX Calls&lt;/h3&gt;

&lt;p&gt;Although running a simple python web server gets us through this demonstration
pretty easily for our AJAX calls, in practice AJAX calls might rely on more
than a simple static server can provide. At that point, you either have to
deal with running your development web application server at the same time, or
switch to mocking out the AJAX calls.  A great solution is to use fantastic
&lt;a href=&quot;http://enterprisejquery.com/2010/07/mock-your-ajax-requests-with-mockjax-for-rapid-development/&quot;&gt;MockJax&lt;/a&gt; jQuery plugin, which overrides $.ajax() calls to substitute with
a configurable mock response.&lt;/p&gt;

&lt;h3 id=&quot;add-code-coverage-reports&quot;&gt;Add Code Coverage Reports&lt;/h3&gt;

&lt;p&gt;Amazingly, the &lt;a href=&quot;http://siliconforks.com/jscoverage/&quot;&gt;JSCoverage&lt;/a&gt; code coverage library does work with this
Rhino  and Envjs setup. I hooked things up by running jscoverage-server in a
similar fashion to how we did the command line python server above (which
completely replaces the need for it), and then added the following to
“setup.js” in the QUnit.done option:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;QUnit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ... previous code ...&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Code coverage report.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jscoverage_report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Writing coverage report.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;jscoverage_report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It is important that the coverage report come at the very &lt;strong&gt;end&lt;/strong&gt; of the
QUnit.done handler to ensure that all of our tests truly are finished running.
See the JSCoverage &lt;a href=&quot;http://siliconforks.com/jscoverage/manual.html&quot;&gt;manual&lt;/a&gt; for more details about running the coverage
server and writing reports.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Revisiting the original motivation for this post series, the original pain
point was that frontend tests are complicated to write and a hassle to run.
With the fully browserless testing environment we have explored here, it is
possible to create a system out of Rhino, Envjs and QUnit that runs all tests
with a single command line argument, making the former hassle into something
so easy you could add it to your pre-/post- commit hooks in your version
control software. And, by being able to write our tests in mostly straight
QUnit (with a little extra setup help), test writing for developers remains
relatively straightforward, even dealing with AJAX, etc.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Browserless AJAX Testing with Rhino and Envjs, Part 1.</title>
   <link href="http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and.html"/>
   <updated>2011-02-08T06:15:00+00:00</updated>
   <id>http://loose-bits.com/2011/02/browserless-ajax-testing-pt1</id>
   <content type="html">
&lt;h2 id=&quot;updates---5272011&quot;&gt;Updates - 5/27/2011&lt;/h2&gt;

&lt;p&gt;Since this original post, there has been a lot of good feedback surrounding
getting an Envjs + QUnit + Rhino setup going, so please read the comments at
the bottom of this post.  Here are the summarized points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;QUnit&lt;/strong&gt;: Newer versions of QUnit have issues with Envjs, adding a
&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;pre/&amp;gt;&lt;/code&gt; to output that Envjs can’t handle.  A comment from “Ryan”
(not me – I’m “Ryan Roemer” below) also points out the QUnit has changed
the function signature of the logging callbacks and links to an improved
“&lt;a href=&quot;https://github.com/zepheira/exhibit3/blob/master/scripted/lib/setup.js&quot;&gt;setup.js&lt;/a&gt;” file.  I have inserted links to a known &lt;a href=&quot;https://github.com/jquery/qunit/commit/a46610796b457fab05587945e743d4e857f580b5&quot;&gt;working version&lt;/a&gt;
of QUnit.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;jQuery 1.5&lt;/strong&gt;: jQuery 1.5+ also has issues with Envjs due to calls to a
couple of methods not currently available in either Envjs 1.2 or 1.3.
There is a &lt;a href=&quot;https://github.com/orslumen/env-js/commit/c3e702cfa84872782dd40a2c4cd8a4c8f9bac3a3&quot;&gt;patch&lt;/a&gt; on GitHub for the issue for Envjs 1.3.  I have
backported the patch (and some other fixes) to a &lt;a href=&quot;https://github.com/ryan-roemer/envjs-1.2&quot;&gt;forked version&lt;/a&gt; of
Envjs 1.2 on my GitHub account.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Envjs 1.3&lt;/strong&gt;: Envjs 1.2 was a single file, with primarily Rhino support.
&lt;a href=&quot;https://github.com/thatcher/env-js&quot;&gt;Envjs 1.3&lt;/a&gt; is massive enhancement project that supports a large number of
JavaScript engines including: Node.js, SpiderMonkey, etc. It’s still in early
development, but definitely worth taking a look at.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;This is the first of two blog posts focusing on browserless testing of AJAX /
JavaScript using Rhino and Envjs. Today, we discuss getting browserless
&lt;a href=&quot;http://docs.jquery.com/Qunit&quot;&gt;QUnit&lt;/a&gt; tests up and running from the command line. A &lt;a href=&quot;http://loose-bits.com/2011/02/browserless-ajax-testing-with-rhino-and_16.html&quot;&gt;follow-on&lt;/a&gt;
post will discuss running QUnit tests which involve DOM manipulation and AJAX
interaction.&lt;/p&gt;

&lt;p&gt;Testing web applications can be an enormous pain. Tests take time to write
correctly and usefully, the interaction between the front and back ends is
complicated, and it is often a hassle just to get people to run the tests on a
regular basis. At work as of late, we have been trying to kick start our
JavaScript unit testing – our system has a Django/Python backend, and a
JavaScript (mostly jQuery-based) frontend.&lt;/p&gt;

&lt;p&gt;On the backend, we are in good shape, test-wise – the Django &lt;a href=&quot;http://docs.djangoproject.com/en/dev/topics/testing/&quot;&gt;test case
extensions&lt;/a&gt; to the Python unit test framework make backend unit testing
easy and straightforward. Everything runs fairly quickly, and from the command
line. In fact, we have actually made a (soft) requirement that all developers
run a &lt;a href=&quot;http://fabfile.org/&quot;&gt;Fabric&lt;/a&gt; target called “precommit” that runs all of our Django unit
tests (in addition to Python and JavaScript style checkers).&lt;/p&gt;

&lt;p&gt;On the frontend, it’s a different story. Our previous test suite relied
entirely on a browser and a running development server loaded with data in a
known state. Our unit tests were written against QUnit, and for the most part
worked fine for the code they did exercise. But, even where our frontend tests
would catch newly introduced bugs, all too often the tests just weren’t run,
and we would actually discover the bug several commit cycles later.&lt;/p&gt;

&lt;p&gt;While we clearly need to integrate a fully automated infrastructure around
browser-based tests (using great tools like &lt;a href=&quot;http://seleniumhq.org/&quot;&gt;Selenium&lt;/a&gt; or &lt;a href=&quot;http://www.getwindmill.com/&quot;&gt;Windmill&lt;/a&gt;),
my thoughts turned to the idea that the heavyweight nature of full end-to-end
AJAX testing was getting in the way of us writing and running frontend tests.
More specifically, the difficulties come down to two fundamental problems –
our tests on the frontend are: (1) not easy to write and run, and (2) are not
integrated into an automated, “one-touch” running scheme.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;javascript-engines-and-the-pesky-browser-environment&quot;&gt;JavaScript Engines and the Pesky Browser Environment&lt;/h2&gt;

&lt;p&gt;Last week I decided to finally bite the bullet and push hard for the easiest
and simplest JavaScript testing solution – execution of a full suite of QUnit
tests that would execute without a browser from a single command line command.&lt;/p&gt;

&lt;p&gt;The first stop is getting a JavaScript engine to actually run JavaScript (test
and code) in lieu of a browser. Fortunately, there are lots of great open
source options including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mozilla.org/rhino/&quot;&gt;Rhino&lt;/a&gt;: Mozilla’s Java JavaScript implementation.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mozilla.org/js/spidermonkey/&quot;&gt;SpiderMonkey&lt;/a&gt;: Mozilla’s C implementation of JavaScript.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://code.google.com/p/v8/&quot;&gt;V8&lt;/a&gt;: Google’s JavaScript virtual machine for Chrome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A JavaScript engine only gets you pure JavaScript language support. The hard
part is that any nontrivial web application with a good amount of JavaScript
has a large percentage of code that manipulates the DOM and essentially needs
a browser environment for a lot of functionality. So much so, that as general
assertion, it is probably not worth trying to separate out pure and browser-
dependent JavaScript code in development solely for this purpose.&lt;/p&gt;

&lt;p&gt;Thus, the second step necessary for running QUnit tests is a simulation of the
browser environment (e.g., window, console, etc.), just without the browser.
This is a harder problem.&lt;/p&gt;

&lt;p&gt;A few years ago, while working on some prototype code for a friend’s startup,
I started researching browserless JavaScript testing, specifically looking for
browser emulation options. At the time, the only serious emulation solution I
found was in a &lt;a href=&quot;http://ejohn.org/blog/bringing-the-browser-to-the-server/&quot;&gt;blog post&lt;/a&gt; by &lt;a href=&quot;http://ejohn.org/about/&quot;&gt;John Resig&lt;/a&gt; (of &lt;a href=&quot;http://jquery.org/&quot;&gt;jQuery&lt;/a&gt; fame),
where he had successfully hacked together a pure JavaScript browser
environment (called “env.js”) that was capable of AJAX, DOM manipulation, and
even running the jQuery set of QUnit tests.&lt;/p&gt;

&lt;p&gt;Unfortunately, my buddy’s side project eventually went on hiatus, and I never
got around to rigging up a completely browserless test project. Working on
purely backend server code for the past couple of years (C++, C#, python), it
has taken me some time to get both the opportunity and desire to research a
browserless JavaScript environment.&lt;/p&gt;

&lt;p&gt;So years later, looking back into the issue, I was pleased to revisit Resig’s
proof-of-concept browser simulation environment and find that it had evolved
into a full open source project – &lt;a href=&quot;http://www.envjs.com/&quot;&gt;Envjs&lt;/a&gt; – which provides an amazingly
powerful browser-like environment, capable of DOM manipulation, AJAX
interaction, cookie setting, etc.&lt;/p&gt;

&lt;h2 id=&quot;making-rhino-envjs-qunit-and-javascript-tests-play-nicely-together&quot;&gt;Making Rhino, Envjs, QUnit, and JavaScript Tests Play Nicely Together&lt;/h2&gt;

&lt;p&gt;Rediscovering Envjs and finding it in good shape meant that I simply had to
plug in a JavaScript engine, make sure it works with QUnit, and then fire up
my test suites without a browser. I ended up choosing Rhino as my JavaScript
engine as Envjs has specific support for it, but presumably other engines can
be made to work with Envjs and get equivalent results as I will describe
below.&lt;/p&gt;

&lt;p&gt;So, let’s get started.&lt;/p&gt;

&lt;h3 id=&quot;rhino-and-envjs-installation&quot;&gt;Rhino and Envjs Installation&lt;/h3&gt;

&lt;p&gt;In terms of extra tools / libraries, all we need is Rhino and Envjs:&lt;/p&gt;

&lt;p&gt;Rhino is available from the Mozilla &lt;a href=&quot;ftp://ftp.mozilla.org/pub/mozilla.org/js&quot;&gt;FTP site&lt;/a&gt;. I ended up going with
&lt;a href=&quot;ftp://ftp.mozilla.org/pub/mozilla.org/js/rhino1_7R2.zip&quot;&gt;rhino1_7R2&lt;/a&gt; – the Envjs web site notes that it is best to go with a
recent Rhino release. The package can be installed anywhere, but we are going
to need to know the path to the file “js.jar” in the package to pass as a
class path argument to Java.&lt;/p&gt;

&lt;p&gt;After installing, let’s check that Rhino actually works:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cd /path/to/rhino
$ java -cp js.jar org.mozilla.javascript.tools.shell.Main -opt -1
Rhino 1.7 release 2 2009 03 22
js&amp;gt; print(&quot;hello world&quot;);
hello world
js&amp;gt; quit();
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Next, install Envjs. I went with &lt;a href=&quot;http://www.envjs.com/release/envjs-1.2&quot;&gt;Envjs 1.2&lt;/a&gt;. (I did end up having to
patch the library to get cookies working properly, but that’s a post for
another day – and presumably fixed in trunk). Envjs can be installed anywhere
the rest of your code can access it – I went with the same JavaScript
directory as all our other third party library code.&lt;/p&gt;

&lt;h3 id=&quot;example-libraries-and-test-code&quot;&gt;Example Libraries and Test Code&lt;/h3&gt;

&lt;p&gt;Ignoring how I actually installed and placed files, for the rest of this
discussion, let’s assume we have a simplified world view in a single working
directory, where we’ll place all our third party, test and library code. None
of these files really need (or should) be in the same directory, but it is
easier to describe things without multiple directory traversal.&lt;/p&gt;

&lt;p&gt;Here is the third party code:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.envjs.com/dist/env.rhino.1.2.js&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;env.rhino.1.2.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: The Envjs file we downloaded.  If you are
using jQuery 1.5+, you may want this &lt;a href=&quot;https://github.com/ryan-roemer/envjs-1.2&quot;&gt;patched Envjs&lt;/a&gt; instead.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jquery/qunit/blob/a46610796b457fab05587945e743d4e857f580b5/qunit/qunit.css&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;qunit.css&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: QUnit styles. (&lt;em&gt;Link to known working version&lt;/em&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jquery/qunit/blob/a46610796b457fab05587945e743d4e857f580b5/qunit/qunit.js&quot;&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;qunit.js&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: QUnit code. (&lt;em&gt;Link to known working version&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are going to create two more files in a subsequent section.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;setup.js&lt;/code&gt;&lt;/strong&gt;: Hook Envjs to QUnit.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;run-tests.js&lt;/code&gt;&lt;/strong&gt;: Include our setup script and actually invoke the tests
at on our test HTML page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, here is some library and test code files:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;my-lib.js&lt;/code&gt;&lt;/strong&gt;: An example JavaScript library (that we want to test).
&lt;a name=&quot;my-lib.js&quot;&gt; &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;addTwo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;my-tests.js&lt;/code&gt;&lt;/strong&gt;: An example custom QUnit tests for my-lib.js.
&lt;a name=&quot;my-tests.js&quot;&gt; &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;My Module&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;addTwo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addTwo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Add nothing.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addTwo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Add numbers.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addTwo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Add negatives.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;test.html&lt;/code&gt;&lt;/strong&gt;: Basic QUnit HTML page (see QUnit documentation) with
script links to “my-lib.js” and “my-tests.js”.
&lt;a name=&quot;test.html&quot;&gt; &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;QUnit&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit.css&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/css&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-lib.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-tests.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-header&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;QUnit Test Suite&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-banner&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-testrunner-toolbar&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-userAgent&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;ol&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-tests&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;qunit-fixture&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Once we’ve got all of these custom and third party files, we can just open
test.html in a browser, and the QUnit tests should all run, pass and look
something like:&lt;/p&gt;

&lt;p&gt;[![QUnit results][img_qunit_th]][img_qunit]
[img_qunit_th]: http://loose-bits.com/media/img/2011/02/08/qunit_thumb.png
[img_qunit]: http://loose-bits.com/media/img/2011/02/08/qunit.png&lt;/p&gt;

&lt;h3 id=&quot;configuring-envjs-and-qunit&quot;&gt;Configuring Envjs and QUnit&lt;/h3&gt;

&lt;p&gt;We need to hook QUnit into Envjs, so we can display output to stdout (using
&lt;code class=&quot;highlighter-rouge&quot;&gt;console.log()&lt;/code&gt;). Fortunately, QUnit explicitly provides a lot of hooks to do
everything we want, which I’ve implemented in a file named “setup.js”, placed
alongside our other JavaScript libraries.&lt;/p&gt;

&lt;p&gt;I mostly followed the excellent &lt;a href=&quot;http://www.envjs.com/doc/guides&quot;&gt;guide&lt;/a&gt; from the Envjs folks, and here’s
the gist of my configuration for “setup.js”:
&lt;a name=&quot;setup.js&quot;&gt; &lt;/a&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'env.rhino.1.2.js'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'qunit.js'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;starttime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Envjs/QUnit Bridge.&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Envjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Straight from the Envjs guide.&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;scriptTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Straight from the Envjs guide.&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;beforeScriptLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;'sharethis'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Hook QUnit logging to console.&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;afterScriptLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;'qunit'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;testName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;* QUnit test runner loaded.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// Grab current test name.&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;QUnit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;testStart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;testEnvironment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;testName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Override log to display to stdout.&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;QUnit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// Strip out HTML in results messages.&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/&amp;lt;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\/?&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;.*&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;&amp;gt;/g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;  * {%s}(%s)[%s] %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;testName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'PASS'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'FAIL'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;QUnit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;endtime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;\n&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;*****************\n&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;* QUnit Results *\n&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;*****************\n&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;* PASSED: %s\n&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;* FAILED: %s\n&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;* Completed %s tests total in %s seconds.\n&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;nx&quot;&gt;pass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;nb&quot;&gt;parseFloat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;starttime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1000.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
           &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Straight from the Envjs guide.&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;'.'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'text/envjs'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that the Envjs guide actually went further than this example, as the
guide’s setup code writes the QUnit HTML report out to a new file. The
interesting thing about running JavaScript in Rhino is that code runs with
full system permissions outside of the JavaScript sandbox, so you can also do
normal file system manipulation. (And, as the Envjs documentation notes with
big scary warnings, there are huge security issues with running outside code
within an Envjs / Rhino environment).&lt;/p&gt;

&lt;h3 id=&quot;test-runner&quot;&gt;Test Runner&lt;/h3&gt;

&lt;p&gt;The last file we have to compose is our actual test runner.  Fortunately, this
is quite simple, as shown in “run-tests.js”– just load the setup and point to
the test page:
&lt;a name=&quot;run-tests.js&quot;&gt; &lt;/a&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;nx&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'setup.js'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Starting QUnit tests...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test.html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;test-it-out&quot;&gt;Test it Out!&lt;/h3&gt;

&lt;p&gt;Now that we have Rhino, Envjs and QUnit all hooked together, it’s time to
actually run some QUnit tests. Assuming your file / directory setup matches
the above contrived example, we should be able to run the tests by invoking
Rhino with our test runner script:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ java -cp ~/rhino1_7R2/js.jar org.mozilla.javascript.tools.shell.Main -opt -1 run-tests.js
[  Envjs/1.6 (Rhino; U; Mac OS X x86_64 1 ... ]
Starting QUnit tests...
* QUnit test runner loaded.
  * { addTwo }( 0 )[ PASS ]  Add nothing., expected: 0
  * { addTwo }( 1 )[ PASS ]  Add numbers., expected: 3
  * { addTwo }( 2 )[ PASS ]  Add negatives., expected: -3

*****************
* QUnit Results *
*****************
* PASSED:  3
* FAILED:  0
* Completed  3  tests total in  1.989  seconds.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Awesome. We have QUnit testing goodness for our library, running within Rhino,
and log / results output to the console, all through a single bash command. We
can add and include more libraries and test suites to test.html, and they
automatically get picked up by the test suite.&lt;/p&gt;

&lt;h2 id=&quot;next-time----browserless-ajax-playing-with-the-dom-and-more&quot;&gt;Next Time – Browserless AJAX, Playing with the DOM and More!&lt;/h2&gt;

&lt;p&gt;Today we examined and set up basic browserless JavaScript unit tests with
Rhino and Envjs. Despite the promises of the title of this post, we’ll have to
wait until next time to extend out QUnit test suite to include actual / mocked
AJAX testing and DOM manipulation with this set up. But, just removing the
browser from the frontend test path is a great step towards more lightweight
JavaScript unit tests – and situating us for the type of tests that developers
will want to write and run.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Rackspace Cloud Files and Pseudo-Directories</title>
   <link href="http://loose-bits.com/2010/12/rackspace-cloud-files-and-pseudo.html"/>
   <updated>2010-12-28T19:57:00+00:00</updated>
   <id>http://loose-bits.com/2010/12/rackspace-cloud-files-and-pseudo</id>
   <content type="html">&lt;h2 id=&quot;cloud-providers-and-listing-storage-objects&quot;&gt;Cloud Providers and Listing Storage Objects&lt;/h2&gt;

&lt;p&gt;Rackspace’s &lt;a href=&quot;http://www.rackspacecloud.com/cloud_hosting_products/files/&quot;&gt;Cloud Files&lt;/a&gt; provide a basic distributed blob / file
abstraction.  Like &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;Amazon Web Services Simple Storage Service&lt;/a&gt; (AWS S3)
and &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/dd135733.aspx&quot;&gt;Microsoft Azure Blobs&lt;/a&gt;, Cloud Files are organized into a two level
hierarchy consisting of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Containers&lt;/strong&gt;: A collection of file objects. In AWS S3 parlance this is
called a “bucket”.  For all three cloud providers, a container is a single
collection unit that cannot be further subdivided.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Storage Objects&lt;/strong&gt;: A single file object.  In AWS S3, this is an “object”,
while in Azure this is a “blob”.  A storage object for all the providers
similarly cannot be further nested / subdivided.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, there are no further levels of hierarchy than container, then object,
and for all three providers, the storage object namespace is completely flat.&lt;/p&gt;

&lt;p&gt;As most of the world is used to nested file hierarchies much deeper than this,
cloud providers allow wide leeway in naming storage objects, most notably
allowing characters like a slash (“/”).  To provide the illusion of a nested
hierarchy, &lt;a href=&quot;http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?RESTBucketGET.html&quot;&gt;AWS S3&lt;/a&gt; and &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/dd135734.aspx&quot;&gt;Azure Blobs&lt;/a&gt; provide listing operations that
take a delimiter character to treat as a nested directory delimiter. In this
manner, calls to the list objects API will return results as if there are
intermediate directories in the (really) flat storage object namespace.&lt;/p&gt;

&lt;h2 id=&quot;the-old-way---rackspace-cloud-files-and-dummy-directory-objects&quot;&gt;The Old Way - Rackspace Cloud Files and Dummy Directory Objects&lt;/h2&gt;

&lt;p&gt;To much frustration, Rackspace Cloud Files did not originally provide a
delimiter query API for treating a delimiter as a nested directory organizer.
Instead of delimiter queries, Rackspace required that clients upload a storage
object of type “application/directory” at each level in which a nested pseudo-
directory was desired. Only then would the results start to resemble those
from AWS / Azure with delimiter queries.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;Here’s a quick example showing how the dummy directory objects work. First
let’s create a container:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;cloudfiles&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cloudfiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ACCOUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SECRET_KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my_test_container&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, put in some objects with internal slashes, and list all.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/bar/file1.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;some text.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/bar/file2.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;some text.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/diff/file3.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;some text.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;baz/file4.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;some text.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'baz/file4.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/bar/file1.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/bar/file2.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/diff/file3.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We can use the “prefix” parameter to limit files to only “foo”, but the
internal pseudo-directories are not inferred.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo/bar/file1.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/bar/file2.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/diff/file3.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The “path” parameter will infer directories, but not without internal dummy
directory objects.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So, let’s create the dummy objects for “foo”.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;application/directory&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sync_metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/bar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;application/directory&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sync_metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/diff&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;application/directory&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sync_metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dummy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And, now we get the pseudo-directory results we expect for “foo” and children
paths.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo/bar'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/diff'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/bar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo/bar/file1.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/bar/file2.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/diff&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo/diff/file3.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;While the dummy objects technically make nested pseudo-directories possible,
the onus falls to the user to create and maintain all the dummy objects. This
really becomes a pain when creating new objects that might contain nested
delimiters for which there is not already a delimiter (say adding a nested
pseudo-directory). The user then has to either check / scrub all existing
files for missing dummy objects, or create duplicate dummy directory objects
on each new object creation.&lt;/p&gt;

&lt;h2 id=&quot;the-new-way---rackspace-cloud-files-and-delimiter-queries&quot;&gt;The New Way - Rackspace Cloud Files and Delimiter Queries&lt;/h2&gt;

&lt;p&gt;Fortunately, Rackspace &lt;a href=&quot;http://www.rackspacecloud.com/blog/2010/12/21/rackspace-cloud-files-a-look-back-at-2010/&quot;&gt;finally enabled&lt;/a&gt; “real” delimiter queries in their
list storage objects API. I couldn’t actually find the date that this was
added to REST API and added server side (as the current API PDF doesn’t even
note the change, although I expect it soon will).&lt;/p&gt;

&lt;p&gt;I first noticed the new query functionality when perusing commits on the
&lt;a href=&quot;https://github.com/rackspace/python-cloudfiles&quot;&gt;Python API GitHub page&lt;/a&gt;.  On Dec. 3, 2010, a &lt;a href=&quot;https://github.com/rackspace/python-cloudfiles/commit/f37b51af1372f428918ac289e7062d7c4a9369b5&quot;&gt;change&lt;/a&gt; was pushed that
added the “delimiter” parameter to object listing queries for the Python API.
The Python API was incremented to version 1.7.4 for the change, so make sure
to pull the proper version of the library to test the new feature out:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ pip install -U https://github.com/rackspace/python-cloudfiles/tarball/1.7.4&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Interestingly, while the container list_objects() and list_objects_info()
methods picked up a delimiter parameter, the get_objects() method did not. At
any rate, this gets the job done, and we can remove our dummy objects now to
retry our examples:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/bar&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/diff&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s check we don’t have the dummy objects anymore:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'baz/file4.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/bar/file1.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/bar/file2.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/diff/file3.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And now, let’s try equivalent queries using the prefix and delimiter
parameters. (Note that you must append an extra slash (e.g. “foo/”, not “foo”)
for the queries to work.)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delimiter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo/bar/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/diff/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/bar/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delimiter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo/bar/file1.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;s&quot;&gt;'foo/bar/file2.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cont&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_objects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo/diff/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delimiter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo/diff/file3.txt'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;… and we finally get the results we want without having to track or create
any extraneous objects.&lt;/p&gt;

&lt;p&gt;At the time of this post, many clients do not take advantage of more
convenient / advanced object querying. The &lt;a href=&quot;http://cyberduck.ch/&quot;&gt;CyberDuck&lt;/a&gt; client currently
will only display pseudo-directories via the dummy directory objects method.
In fact, Rackspace’s own control panel doesn’t display any pseudo-directories
using either the old dummy directory or new delimiter methods. So, hopefully
with the new delimiter query API support, some folks out there will make some
New Year’s resolutions to improve pseudo-directory support in Cloud Files
clients.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Celery Logging with Python Logging Handlers</title>
   <link href="http://loose-bits.com/2010/12/celery-logging-with-python-logging.html"/>
   <updated>2010-12-13T15:54:00+00:00</updated>
   <id>http://loose-bits.com/2010/12/celery-logging-with-python-logging</id>
   <content type="html">&lt;h2 id=&quot;logging-in-celery&quot;&gt;Logging in Celery&lt;/h2&gt;

&lt;p&gt;We use &lt;a href=&quot;http://celeryproject.org/&quot;&gt;Celery&lt;/a&gt; as our backend messaging abstraction at work, and have lots
of disparate nodes (and across different development, test, and production
deployments).  As each system deployment now contains a large (and growing)
number of nodes, we have been making a heavy push towards consolidated logging
through a central logging server sink, using syslog (specifically
&lt;a href=&quot;http://www.rsyslog.com/&quot;&gt;rsyslog&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So, ideally, we would just use Celery’s configuration to specify a syslog
handler (or maybe use a pipe).  Unfortunately, it seems there is just not a
simple way of doing this straight from Celery, as the only logging
configuration parameters (with possible values) are:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;CELERYD_LOG_FILE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/path/to/file.log&quot;&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# File logging.&lt;/span&gt;
                                        &lt;span class=&quot;c&quot;&gt;# (OR)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CELERYD_LOG_FILE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;                 &lt;span class=&quot;c&quot;&gt;# stderr.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which means you either get a file logger (&lt;a href=&quot;http://docs.python.org/library/logging.html#filehandler&quot;&gt;filehandler&lt;/a&gt;) or a stderr logger
(&lt;a href=&quot;http://docs.python.org/library/logging.html#streamhandler&quot;&gt;streamhandler&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Hints and guidance on the web in terms of getting arbitrary logging handlers
(or syslog specifically) is sparse, as well as noisy.  A lot of the code and
discussion I found used Celery as the &lt;em&gt;backend&lt;/em&gt; for a generic logging
framework or library.  But, I found very little in terms of getting Celery
tasks and processes to actually log &lt;em&gt;to&lt;/em&gt; syslog.&lt;/p&gt;

&lt;h2 id=&quot;bringing-arbitrary-logging-to-celery&quot;&gt;Bringing Arbitrary Logging to Celery&lt;/h2&gt;

&lt;p&gt;As nothing magically appeared as the “right” solution, I considered a couple
of different ways to hook things up:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Watched Files&lt;/strong&gt;: Have Celery log to a file per usual, then add extra
configuration and scripts to watch the file for changes and submit the
changes to syslog directly. I didn’t go with this approach, as I really
prefer to have configuration for our project within generic Python settings,
and not need extra, system-specific scripts and setup.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Patch Celery&lt;/strong&gt;: The Celery logging hooks and code are fairly
straightforward.  I could have added the patch and submitted upstream.
Unfortunately, for the project at work, we are moving to fast to wait for
changes, and as we are looking forward to some upstream Celery releases,
I’d rather not maintain a private custom patch set through all of that.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Monkey Patch Celery&lt;/strong&gt;: In the same vein as the former option, the same
hooks and changes could be applied as a &lt;a href=&quot;http://en.wikipedia.org/wiki/Monkey_patch&quot;&gt;monkey patch&lt;/a&gt; instead.  This is
what I eventually went with.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;patching-logging-handlers-into-celery&quot;&gt;Patching Logging Handlers into Celery&lt;/h2&gt;

&lt;p&gt;Monkey patching is oft-controversial and generally discouraged practice, as
getting things wrong is easy, and interactions within the patched library /
code can get really messy. In our case, I reluctantly chose monkey patching,
as it was a short patch, easy to disable, and I had no good other solutions.&lt;/p&gt;

&lt;p&gt;Now that I’ve given the standard disclaimer, let’s get to the patch! The
relevant Celery function we want to patch in celery.log._setup_logger():&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_setup_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ColorFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handlers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;                 &lt;span class=&quot;c&quot;&gt;# already configured&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_detect_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;use_color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As we can see, there is only one shot at a handler, and it comes from
&lt;code class=&quot;highlighter-rouge&quot;&gt;celery.log._detect_handler()&lt;/code&gt;, which either returns a stream or file
handler. What I would ideally like is to define be able to define a Django
settings variable (we use Django and not native Celery settings at work):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;PATCH_CELERYD_LOG_HANDLERS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FileHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;path/to/another_log.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handlers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SysLogHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and define a new &lt;code class=&quot;highlighter-rouge&quot;&gt;_setup_logger()&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;celery.log&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;celery.log&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ColorFormatter&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.conf&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;old_setup_logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;celery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_setup_logger&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_setup_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ColorFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;patched_logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old_setup_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler_fn&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;getattr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;PATCH_CELERYD_LOG_HANDLERS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler_fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;use_color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;patched_logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patched_logger&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I would have preferred a pure “&lt;code class=&quot;highlighter-rouge&quot;&gt;*args, **kwargs&lt;/code&gt;” version of the patched
function, but unfortunately, we need to dig out the formatter parameter if
set, so I copied the method signature of the original.  Also, I used lambda’s
in &lt;code class=&quot;highlighter-rouge&quot;&gt;PATCH_CELERYD_LOG_HANDLERS&lt;/code&gt; and called the function in the new setup
logger so as to create a &lt;em&gt;new&lt;/em&gt; handler object each time for loggers.  It is
important to have different handler objects because formats attach to handler
objects, and Celery uses different formats for general/default/process logging
vs. task-specific logging.  If a handler has last been set to a task logger
format, that includes a format for among other things “task_name”, then you
will get a KeyError on trying to issue a logging statement.&lt;/p&gt;

&lt;p&gt;Other than that, the patched method is very easy to read – it iterates the
custom handler function list and creates new handlers for each different
logging object.  Putting this in to a patched version, I also added a marker
attribute to the logger object (to tell the patching is done), as well as the
patching code to lock, switch and mark the methods:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;logging&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;celery.log&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;celery.log&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ColorFormatter&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;django.conf&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;old_setup_logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;celery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_setup_logger&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# Store the real method.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;my_setup_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ColorFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Replacement for :method:`celery.log._setup_logger`.&quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;patched_logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old_setup_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;colorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Check if not patched.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;getattr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;patched_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_LOG_PATCH_DONE&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# Lock and patch.&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_acquireLock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;handler_fns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;getattr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;PATCH_CELERYD_LOG_HANDLERS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler_fn&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler_fns&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler_fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;use_color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colorize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;patched_logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

            &lt;span class=&quot;c&quot;&gt;# Mark logger object patched.&lt;/span&gt;
            &lt;span class=&quot;nb&quot;&gt;setattr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;patched_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_LOG_PATCH_DONE&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_releaseLock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;patched_logger&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Apply patches.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CELERY_MOD_PATCHED&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CELERY_MOD_PATCHED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_acquireLock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# Lock logging during patch.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;celery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_setup_logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_setup_logger&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# Patch old w/ new.&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;CELERY_MOD_PATCHED&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_releaseLock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With the patch applied, any number of Python standard logging handlers can now
be added to the celery task / process loggers.&lt;/p&gt;

&lt;h2 id=&quot;applying-the-patch-at-runtime&quot;&gt;Applying the Patch at Runtime&lt;/h2&gt;

&lt;p&gt;So we’ve defined the patch, but how do we apply it?  Part of the problem is
celery configuration.  If you apply the patch too early, you get the following
exception:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;.../python2.6/site-packages/celery/loaders/default.py:53: NotConfigured:
No celeryconfig.py module found! Please make sure it exists and is available to Python.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So, we have to find a proper place in the initialization process, after Celery
settings.  The best solution I found for this is to create a faux Django
“application” , that contains only the patch code.  Let’s call the application
“patch_celery”:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ cd path/to/project
$ django-admin.py startapp patch_celery&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Copy and paste all of the patch code above into the &lt;strong&gt;init&lt;/strong&gt;.py.  And then, we
simply add the patch application to the Django settings.py installed apps:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'djcelery'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;'patch_celery'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The best place to put the patch is right &lt;em&gt;after&lt;/em&gt; the djcelery application.
Now, add your patch variable, &lt;code class=&quot;highlighter-rouge&quot;&gt;PATCH_CELERYD_LOG_HANDLERS&lt;/code&gt;, to settings.py
with any designated handlers (per previous example) and you’re off with any
logging handlers for celery tasks that you want!  You can disable the patch by
simply removing the “patch_celery” application from installed applications in
settings, which enables easily toggling per appropriate context without major
code swaps.&lt;/p&gt;

&lt;h2 id=&quot;afterthoughts&quot;&gt;Afterthoughts&lt;/h2&gt;

&lt;p&gt;The monkey patch to Celery certainly gets the job done for now.  However, the
fact that the full method signature for &lt;code class=&quot;highlighter-rouge&quot;&gt;celery.log._setup_logger()&lt;/code&gt; is used
makes the patch very brittle – if a parameter changes in the underlying
method, the patched method may cease to work, or worse, give a wrong parameter
to the underlying method.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Rackspace Cloud Files and Servicenet</title>
   <link href="http://loose-bits.com/2010/10/rackspace-cloud-files-and-servicenet.html"/>
   <updated>2010-10-18T00:01:00+00:00</updated>
   <id>http://loose-bits.com/2010/10/rackspace-cloud-files-and-servicenet</id>
   <content type="html">&lt;h2 id=&quot;cloud-files-cloud-servers&quot;&gt;Cloud Files, Cloud Servers&lt;/h2&gt;

&lt;p&gt;At work, we use &lt;a href=&quot;http://www.rackspacecloud.com/&quot;&gt;Rackspace&lt;/a&gt; &lt;a href=&quot;http://www.rackspacecloud.com/cloud_hosting_products/files&quot;&gt;Cloud Files&lt;/a&gt; for bulk blob storage of large
sets of documents (in our case, patents).  We use a farm of Rackspace &lt;a href=&quot;http://www.rackspacecloud.com/cloud_hosting_products/servers&quot;&gt;Cloud
Servers&lt;/a&gt; to process and serve blobs from Cloud Files.  Rackspace’s offering
has been very solid to date (modulo some idiosyncrasies of their particular
APIs) and quite interestingly, the underlying software is being &lt;a href=&quot;http://techcrunch.com/2010/07/18/openstack-org-rackspace-open-sources-their-cloud-services-platform-and-gets-nasa-on-board/&quot;&gt;released&lt;/a&gt;
as an open source project, &lt;a href=&quot;http://openstack.org/&quot;&gt;OpenStack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the nice things building a solution on Rackspace is that bandwidth is
free between Cloud Servers and Cloud Files, if properly configured.  However,
as our recent experience bears out, there are some pitfalls and caveats
towards getting everything set up correctly.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;h2 id=&quot;public-private-interfaces-and-servicenet&quot;&gt;Public, Private Interfaces and Servicenet&lt;/h2&gt;

&lt;p&gt;Rackspace Cloud Servers are essentially Xen VMs that come with two network
interfaces: (1) a public interface on eth0 with a world-routable IP address,
and (2) a “private” interface on eth1 with an &lt;a href=&quot;http://www.faqs.org/rfcs/rfc1918.html&quot;&gt;RFC 1918&lt;/a&gt; address.  (The
private interface isn’t really “private” in the sense that any other VMs on
the same network can route to your VM, so security-wise, it should be treated
as a public, internet-facing interface).  For more details, Rackspace has
published a good &lt;a href=&quot;http://www.rackspacecloud.com/blog/2010/02/23/networking-and-cloud-servers-more-on-the-interfaces/&quot;&gt;article&lt;/a&gt; on the public and private interfaces.&lt;/p&gt;

&lt;p&gt;For billing purposes, bandwidth between Cloud Files and a Cloud Server VM on
the public interface is charged per normal, whereas bandwidth on the private
interface is free.  To enable Cloud Files communication on the private
interface, you can either set an environment variable in your application like
follows:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export RACKSPACE_SERVICENET=True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;or programmatically specify the private network, as shown here using the
&lt;a href=&quot;http://github.com/rackspace/python-cloudfiles&quot;&gt;Python Cloud Files&lt;/a&gt; client library:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;cloudfiles&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cloudfiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;acct&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;api_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;servicenet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In both cases, “Servicenet” refers to the private interface network internal
to a Rackspace data center.&lt;/p&gt;

&lt;h2 id=&quot;servicenet-gotchas&quot;&gt;Servicenet Gotchas&lt;/h2&gt;

&lt;p&gt;All seems pretty simple! However, when we finally enabled “servicenet=True” in
our Cloud Servers, we immediately hit errors talking to Cloud Files and
everything refused to run.  After a lot of back and forth with the Rackspace
folks, we finally found out the main issue: Servicenet only works between
Cloud Files and Cloud Servers in &lt;strong&gt;the same data center&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before we ran into this snag, we hadn’t even checked that our servers and blob
storage were in the same data center.  As it turned out, our Cloud Files
location was in Texas (DFW) and our Cloud Servers were in Chicago (ORD).  And,
after much hand-wringing, we learned that: (1) new Cloud Servers will most
likely be provisioned in Chicago, and (2) you can’t easily migrate Cloud Files
accounts from Texas to Chicago.&lt;/p&gt;

&lt;p&gt;So, how can one easily tell where your Cloud Servers and Cloud Files are, and
whether or not Servicenet will be available?  The answer for Cloud Servers is
straightforward: in the Rackspace &lt;a href=&quot;https://manage.rackspacecloud.com/CloudServers/ServerList.do&quot;&gt;management dashboard&lt;/a&gt;, look at your
server list and the “Datacenter” column.  In our case, all servers list as
“ORD1”, which means Chicago.  Moving on to Cloud Files, one way to figure
things out is to fire up a Python shell and issue some basic commands to see
what we have.  In all cases, looking at a connection object’s
“&lt;code class=&quot;highlighter-rouge&quot;&gt;connection_args[0]&lt;/code&gt;” value will yield a string for public interfaces like:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;storage&amp;lt;NUMBER&amp;gt;.&amp;lt;DATACENTER&amp;gt;.clouddrive.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;and on the private interface:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;snet-storage&amp;lt;NUMBER&amp;gt;.&amp;lt;DATACENTER&amp;gt;.clouddrive.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;So, on to some example code using the Python client. Here is a Chicago Cloud
Server talking to Chicago Cloud Files on the public interface:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;cloudfiles&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chicago_pub_conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cloudfiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;chicago_acct&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1234567890&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;servicenet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chicago_pub_conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'storage101.ord1.clouddrive.com'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chicago_pub_conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_containers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bar1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'baz1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;… and on the private interface:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chicago_priv_conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cloudfiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;chicago_acct&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1234567890&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;servicenet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chicago_priv_conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'snet-storage101.ord1.clouddrive.com'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chicago_priv_conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_containers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bar1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'baz1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, switching to a Chicago Cloud Server talking to Texas Cloud Files on the
public interface, we get:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;texas_pub_conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cloudfiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;texas_acct&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1234567890&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;servicenet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;texas_pub_conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'storage101.dfw1.clouddrive.com'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texas_pub_conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_containers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'foo2'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'bar2'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'baz2'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;but, trying to talk on the private interface yields the following exception:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texas_priv_conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cloudfiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;texas_acct&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1234567890&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;servicenet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texas_priv_conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;'snet-storage101.dfw1.clouddrive.com'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texas_priv_conn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_containers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Traceback&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;most&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recent&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;File&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;stdin&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;File&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/usr/local/lib/python2.6/dist-packages/cloudfiles/connection.py&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;410&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list_containers&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ResponseError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cloudfiles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ResponseError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Found&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Confusingly, the exception raised is a 404 “Not Found”, when it really should
be something more context-specific.&lt;/p&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;The last time I chatted with the Rackspace folks, they noted most new accounts
will have both Cloud Servers and Cloud Files allocated in Chicago, so using
Servicenet should be as simple as setting the appropriate variable.  In our
specific case, we got around our Texas-Chicago split by allocating an entirely
new account that has both servers and files in Chicago, and ported over our
Texas files.  Somewhat tedious, but a worthwhile long-term investment.&lt;/p&gt;

&lt;p&gt;But, if you are trying to use Servicenet and experience 404’s or other strange
failures, you probably want to go through the above exercise and identify the
data center where your servers and blobs reside.  And, if you use Cloud
Servers and Cloud Files and aren’t already using Servicenet, you definitely
should because you are paying for bandwidth that should otherwise be free!&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 
 <entry>
   <title>Distributed Task Locking in Celery</title>
   <link href="http://loose-bits.com/2010/10/distributed-task-locking-in-celery.html"/>
   <updated>2010-10-10T20:20:00+00:00</updated>
   <id>http://loose-bits.com/2010/10/distributed-task-locking-in-celery</id>
   <content type="html">&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;We use the &lt;a href=&quot;http://celeryq.org/&quot;&gt;Celery&lt;/a&gt; distributed task queue library at work, which is great
for running asynchronous tasks across multiple processes and servers.  Celery
has both user-initiated and periodic (think cron replacement) tasks, and we
have found in practice that the system distributes tasks quite nicely across
our farm of celery servers.&lt;/p&gt;

&lt;p&gt;One issue we have is that for several of our periodic tasks, we need to ensure
that only one task is running at a time, and that later instances of the same
periodic task are skipped if a previous incarnation is still running.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://celeryq.org/docs&quot;&gt;Celery documentation&lt;/a&gt; has a cookbook recipe for this scenario:
“&lt;a href=&quot;http://celeryq.org/docs/cookbook/tasks.html#ensuring-a-task-is-only-executed-one-at-a-time&quot;&gt;Ensuring a task is only executed one at a time&lt;/a&gt;”.  The crux of the
solution is to make a distributed lock using the Django cache (&lt;a href=&quot;http://memcached.org/&quot;&gt;memcached&lt;/a&gt;
in the example) with the following lambda’s:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;n&quot;&gt;lock_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;something unique&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;lock_expire&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# five minutes&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;acquire_lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_expire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;release_lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;non-persistent-locks&quot;&gt;Non-persistent Locks?&lt;/h2&gt;

&lt;p&gt;This approach works fine if the cache is shared across all celery worker nodes
and the cache is persistent.  However, if memcached (or some other non-
persistent cache) is used and (1) the cache daemon crashes or (2) the cache
key is culled before the appropriate expiration time / lock release, then you
have a race condition where two or more tasks could simultaneously acquire the
task lock.  This “distributed cache lock” approach has been discussed in
&lt;a href=&quot;http://bluxte.net/musings/2009/10/28/simple-distributed-lock-memcached&quot;&gt;various&lt;/a&gt; &lt;a href=&quot;http://www.regexprn.com/2010/05/using-memcached-as-distributed-locking.html&quot;&gt;posts&lt;/a&gt;, which all acknowledge the danger of relying on
memcached for persistent data.&lt;/p&gt;

&lt;h2 id=&quot;distributed-locks-with-redis&quot;&gt;Distributed Locks with Redis&lt;/h2&gt;

&lt;p&gt;As noted by one of the links above, the simplest solution to this problem if
you like using the cache for distributed locks is to switch to &lt;a href=&quot;http://memcachedb.org/&quot;&gt;memcachedb&lt;/a&gt;
which is not a caching solution &lt;em&gt;per se&lt;/em&gt;, but rather a persistent key-value
store that implements the memcached interface.&lt;/p&gt;

&lt;!-- more start --&gt;

&lt;p&gt;However, for our system, using memcached as our Django cache is great in its
current non-persistent form for what it is – a cache. So, I investigated more
generally to find a high performance, persistent key-value store (ideally with
a decent Python interface).  After reviewing a lot of neat and interesting
systems, I finally settled on &lt;a href=&quot;http://code.google.com/p/redis/&quot;&gt;Redis&lt;/a&gt;.  Redis provides a very high
performance key-value store (data is maintained in memory) with persistence
(using the &lt;a href=&quot;http://code.google.com/p/redis/wiki/AppendOnlyFileHowto&quot;&gt;append only file&lt;/a&gt; feature) and &lt;a href=&quot;http://code.google.com/p/redis/wiki/ReplicationHowto&quot;&gt;distribution /
replication&lt;/a&gt;.  As an added bonus, I found the server setup, installation
and CLI interaction to be very straightforward.&lt;/p&gt;

&lt;p&gt;So, back to locking… The Redis &lt;a href=&quot;http://github.com/andymccurdy/redis-py&quot;&gt;python client&lt;/a&gt; already has a lock class
with “&lt;code class=&quot;highlighter-rouge&quot;&gt;with&lt;/code&gt;” operator support:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;redis&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Redis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Got lock.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;However, the above example is a blocking lock, and for the “single task”
issue, we want a non-blocking lock, that simply exits if the lock is not
acquired:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;redis&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;have_lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;my_lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Redis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;have_lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;my_lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acquire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blocking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Got lock.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Did not acquire lock.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;my_lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Beyond this simple example, the Lock class implements key expiration (via the
Redis &lt;a href=&quot;http://code.google.com/p/redis/wiki/SetnxCommand&quot;&gt;setnx&lt;/a&gt; command) to enable timeouts in the python client.&lt;/p&gt;

&lt;h2 id=&quot;enforced-single-celery-task&quot;&gt;Enforced Single Celery Task&lt;/h2&gt;

&lt;p&gt;So, bringing this back to our celery tasks, we can use this distributed lock
to have our tasks try to acquire a non-blocking lock, and exit if the lock
isn’t acquired.  Also, we want to set a lock timeout (lasting for a generous
overestimate of task duration time) so that tasks will eventually be able to
re-acquire the lock if some task / celery node hard crashes or goes in to an
unresponsive state before releasing the lock.&lt;/p&gt;

&lt;p&gt;All of this can be put together as a decorator around a Task.run() method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;redis&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;REDIS_CLIENT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Redis&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;only_one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Enforce only one celery task at a time.&quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_dec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Decorator.&quot;&quot;&quot;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_caller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Caller.&quot;&quot;&quot;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ret_value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;have_lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;REDIS_CLIENT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;have_lock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;acquire&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blocking&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;ret_value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;have_lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret_value&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_caller&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_dec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_dec&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that this decorator preserves task return values.  If your tasks don’t
have return values, you can get rid of the &lt;code class=&quot;highlighter-rouge&quot;&gt;ret_value&lt;/code&gt; code.&lt;/p&gt;

&lt;p&gt;Using the decorator is easy – just annotate a task &lt;code class=&quot;highlighter-rouge&quot;&gt;run()&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;celery.task&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SingleTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;A task.&quot;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@only_one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SingleTask&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Run task.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Acquired lock for up to 5 minutes and ran task!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;… and your task will only ever have one running instance at any given time.&lt;/p&gt;

&lt;!-- more end --&gt;
</content>
 </entry>
 

</feed>
