<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>The Daily Build</title><link href="http://blog.bstpierre.org/" rel="alternate"></link><link href="http://blog.bstpierre.org/feeds/all.atom.xml" rel="self"></link><id>http://blog.bstpierre.org/</id><updated>2014-01-29T22:50:00-05:00</updated><entry><title>Getting Scalatest and Scalacheck to Work</title><link href="http://blog.bstpierre.org/scalatest-scalacheck" rel="alternate"></link><updated>2014-01-29T22:50:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2014-01-29:scalatest-scalacheck</id><summary type="html">&lt;p&gt;&lt;a href="http://www.scalatest.org/quick_start"&gt;ScalaTest&lt;/a&gt; is a decent library for testing scala code. It integrates
with &lt;a href="https://github.com/rickynils/scalacheck/wiki/User-Guide"&gt;ScalaCheck&lt;/a&gt; for "property based testing". In this type of testing,
you specify properties of a class or method, and the library generates
test cases. I haven't done much property based testing, and it sounds
interesting, so I thought I'd give it a shot.&lt;/p&gt;
&lt;p&gt;Unfortunately, &lt;a href="https://github.com/rickynils/scalacheck/commit/f430413a516149c38a626b7ea1a26a338d14f5be"&gt;this two-year-old
commit&lt;/a&gt;
deprecated a ScalaCheck API that is still in use by the current release
of ScalaTest. This means that following the instructions on the respective
websites for integrating the code into your build.sbt are &lt;strong&gt;wrong&lt;/strong&gt;. So
the following will &lt;strong&gt;not&lt;/strong&gt; work:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;libraryDependencies&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scalacheck&lt;/span&gt;&amp;quot; &lt;span class="c"&gt;%% &amp;quot;scalacheck&amp;quot; % &amp;quot;1.11.3&amp;quot; % &amp;quot;test&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;libraryDependencies&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scalatest&lt;/span&gt;&amp;quot; &lt;span class="c"&gt;% &amp;quot;scalatest_2.10&amp;quot; % &amp;quot;2.0&amp;quot; % &amp;quot;test&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When you try to use certain parts of ScalaTest, you will get this message at compile time:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;Compiling&lt;/span&gt; 1 &lt;span class="n"&gt;Scala&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;brian&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;2&lt;span class="p"&gt;.&lt;/span&gt;10&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="n"&gt;symbolic&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;refers&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Params&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scalacheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;It&lt;/span&gt; &lt;span class="n"&gt;may&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;completely&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="n"&gt;classpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;classpath&lt;/span&gt; &lt;span class="n"&gt;might&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;incompatible&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="n"&gt;compiling&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Compilation&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There's &lt;a href="https://github.com/scalatest/scalatest/issues/181#issuecomment-27902587"&gt;an open
issue&lt;/a&gt;
on ScalaTest's github, and there's a snapshot release that works with
1.11.3. Change the scalatest line above to:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;libraryDependencies&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scalatest&lt;/span&gt;&amp;quot; &lt;span class="c"&gt;% &amp;quot;scalatest_2.10&amp;quot; % &amp;quot;2.0.1-SNAP&amp;quot; % &amp;quot;test&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now reload sbt and the test code should compile cleanly.&lt;/p&gt;</summary><category term="scala"></category></entry><entry><title>Writing a Simple SBT Task</title><link href="http://blog.bstpierre.org/writing-simple-sbt-task" rel="alternate"></link><updated>2013-12-17T12:41:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-12-17:writing-simple-sbt-task</id><summary type="html">&lt;p&gt;In a project I'm working on, I wanted to copy a fat jar to a remote
machine for testing.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/sbt/sbt-assembly"&gt;sbt-assembly&lt;/a&gt; plugin is handy for making a fat jar, but I didn't
find any convenient plugins for just copying the jar somewhere. (SBT's
built-in publish mechanism is overkill for what I need.)&lt;/p&gt;
&lt;p&gt;It turns out that this is an almost trivial task to write. Unfortunately,
figuring out exactly how to tie the pieces together isn't exactly
straightforward.&lt;/p&gt;
&lt;p&gt;I found &lt;a href="http://sbt010.lessis.me/"&gt;this set of slides&lt;/a&gt;, while not comprehensive, to be about
100x more enlightening than the official &lt;a href="http://www.scala-sbt.org/0.13.1/docs/index.html"&gt;sbt documentation&lt;/a&gt;.
&lt;a href="http://sbt010.lessis.me/#36"&gt;Starting with this slide&lt;/a&gt; especially, I "got" how the task could fall together.&lt;/p&gt;
&lt;p&gt;The main sticking point for me was trying to figure out how to get the jar filename in my task. As the slides point out, operators that begin with &lt;code&gt;&amp;lt;&lt;/code&gt; create a dependency on other values. Specifically, &lt;code&gt;&amp;lt;&amp;lt;=&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;declares a dependency and passes the right-hand value or method result
to the setting represented by the left-hand key&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I started from scratch, not worrying about the jar filename yet,
just creating a stub task. The canonical "hello world" task is easily
accomplished by adding two lines to build.sbt:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;helloTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TaskKey&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;hello&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Print&lt;/span&gt; &lt;span class="n"&gt;hello&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;helloTask&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;hello&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(Note that I'm using SBT 0.13.)&lt;/p&gt;
&lt;p&gt;Take note that &lt;code&gt;:=&lt;/code&gt; in the definition of helloTask means that it's not
dependent on any other settings. If we redefine it as follows, it will
depend on the assembly task, and the assembly task's value (a
&lt;code&gt;java.io.File&lt;/code&gt;) will be available in the hello task:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;val helloTask = TaskKey[Unit](&amp;quot;hello&amp;quot;, &amp;quot;Print hello&amp;quot;)

helloTask &lt;span class="err"&gt;&amp;lt;&amp;lt;&lt;/span&gt;= assembly map { (asm) =&amp;gt; println(s&amp;quot;hello &lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;asm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&amp;quot;) }
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now when you run &lt;code&gt;hello&lt;/code&gt; at the sbt prompt, it will run all the tasks
required to generate the assembly jar, and then print the name of the
jar in the hello message.&lt;/p&gt;
&lt;p&gt;Armed with this (still shallow!) knowledge, and cribbing from this
(obsolete) &lt;a href="http://www.gerd-riesselmann.net/development/deploying-assembly-jar-file-sbt"&gt;recipe&lt;/a&gt; to accomplish the same goal with an older SBT,
let's get dangerous and add this task to build.sbt:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;deployTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TaskKey&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;deploy&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Copies&lt;/span&gt; &lt;span class="n"&gt;assembly&lt;/span&gt; &lt;span class="n"&gt;jar&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;deployTask&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assembly&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&amp;quot; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;FIXME&lt;/span&gt;!
  &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPath&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &amp;quot;&lt;span class="p"&gt;:&lt;/span&gt;&amp;quot; &lt;span class="o"&gt;+&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&amp;quot; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;asm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;
  &lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;Copying&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; $&lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; $&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;$&lt;span class="n"&gt;remote&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;scp&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; !!
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="scala"></category></entry><entry><title>Enjoying Scala's Compactness</title><link href="http://blog.bstpierre.org/enjoying-compactness" rel="alternate"></link><updated>2013-12-11T19:55:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-12-11:enjoying-compactness</id><summary type="html">&lt;p&gt;As I slowly learn my way around the Scala APIs, I'm enjoying the
expressiveness that is possible. You can do a lot with just a little
bit of code.&lt;/p&gt;
&lt;h3&gt;Operating on Files&lt;/h3&gt;
&lt;p&gt;For example, I needed a way to perform an operation on every file in a
directory tree.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt; * Call proc(f) for each file in the directory tree rooted at dir.&lt;/span&gt;
&lt;span class="cm"&gt; *&lt;/span&gt;
&lt;span class="cm"&gt; * proc will not be called for directories, just for files.&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;dir:&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;proc:&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listFiles&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                 &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That &lt;code&gt;File&lt;/code&gt; is just a &lt;a href="http://docs.oracle.com/javase/6/docs/api/java/io/File.html"&gt;java.io.File&lt;/a&gt;, and its &lt;code&gt;listFiles&lt;/code&gt; method
returns an &lt;code&gt;Array&lt;/code&gt; of &lt;code&gt;File&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Scala's &lt;code&gt;foreach&lt;/code&gt; calls the given anonymous function for each element in the array.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Unit&lt;/code&gt; is Scala's name for &lt;code&gt;void&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Operating on Strings, Experimenting in the REPL&lt;/h3&gt;
&lt;p&gt;Related to files, but operating on strings, I needed to take a file, say
&lt;code&gt;content/topic/subtopic/post.md&lt;/code&gt; and generate the name where the output
would land, in this case &lt;code&gt;output/topic/subtopic/post.html&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This means splitting the filename by "/", dropping the first directory,
prepending the output directory, and changing the expression from ".md"
to ".html".&lt;/p&gt;
&lt;p&gt;I had to experiment in the REPL (along with heavy use of &lt;a href="https://duckduckgo.com/"&gt;ddg&lt;/a&gt;) to figure
this one out, but it ends up being a (long) one-liner.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;subtopic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;subtopic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;First, the &lt;code&gt;File&lt;/code&gt; object gives us the parent directory of the file,
and splitting on the separator gives an array of Strings representing
the path.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getParent&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;
&lt;span class="n"&gt;res37&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtopic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I want to &lt;code&gt;drop&lt;/code&gt; the first directory component:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getParent&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; 1
&lt;span class="n"&gt;res38&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtopic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;and put the output directory at the front of the list:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;output&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;

&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPath&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getParent&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;toList&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; 1&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;res42&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subtopic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;[If you're paying close attention to the REPL output, you'll notice that
I'm leaving out my mistakes, i.e. res39 through res41.]&lt;/p&gt;
&lt;p&gt;I had to convert the &lt;code&gt;Array&lt;/code&gt; returned by &lt;code&gt;split&lt;/code&gt; to a list, drop the
first element of the list, and then prepend the output directory using
Scala &lt;code&gt;List&lt;/code&gt; &lt;code&gt;::&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Now I can join the strings in the list together with a slash to get the
output directory that the file belongs in:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPath&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getParent&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;toList&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; 1&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;mkString&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;
&lt;span class="n"&gt;res44&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;subtopic&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That &lt;code&gt;java.io.File&lt;/code&gt; is kind of a hassle to keep typing out:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;

&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPath&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getParent&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;toList&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; 1&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;mkString&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;
&lt;span class="n"&gt;res45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;subtopic&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's a little better.&lt;/p&gt;
&lt;p&gt;Now let's take care of the filename, which is a trivial replacement:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;res46&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;and append it to the path we just generated:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;cala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPath&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getParent&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;toList&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; 1&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;mkString&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;res47&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;htmltopic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;htmlsubtopic&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Whoops, that's not right -- it needs parentheses so that the mkString
happens first, and an extra path separator:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPath&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getParent&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;toList&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; 1&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;mkString&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;res49&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;subtopic&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So the function that I added to my class is (note that the &lt;code&gt;out&lt;/code&gt; from
above is called &lt;code&gt;outDir&lt;/code&gt; here):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;outputName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPath&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;
        &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getParent&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;toList&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; 1&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;mkString&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Doesn't &lt;em&gt;exactly&lt;/em&gt; look like a one-liner, but it could be if I didn't
break it up for readability.&lt;/p&gt;</summary><category term="scala"></category></entry><entry><title>Building a Single File Scala Executable</title><link href="http://blog.bstpierre.org/scala-single-file-executable-jar" rel="alternate"></link><updated>2013-12-07T23:37:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-12-07:scala-single-file-executable-jar</id><summary type="html">&lt;p&gt;I'm from a C background with a hefty dose of Python, mostly on linux
systems.&lt;/p&gt;
&lt;p&gt;My preferred mode of delivering software is as a single executable
file. I hate having to explain to users that they have to install a
half-dozen support libraries. (This is less painful on modern linux
systems where I can tell them to do the equivalent of &lt;code&gt;apt-get install
library1 library2 ...&lt;/code&gt; or just ship a deb/rpm/etc that explicitly contains
the requirements.)&lt;/p&gt;
&lt;p&gt;This doesn't seem to be the normal way of doing things in the
Java world. (It's often not that simple in Python either, so I accept
that I will have to make adjustments.)&lt;/p&gt;
&lt;p&gt;The first Scala project I'm working on is a simple static site generator
-- essentially to replace my use of &lt;a href="http://docs.getpelican.com/en/3.3.0/"&gt;Pelican&lt;/a&gt; with my own program
in Scala. It turns out this is very easy to do with &lt;a href="http://planet42.github.io/Laika/"&gt;Laika&lt;/a&gt;, which
looks like it will do most of the work for me.&lt;/p&gt;
&lt;p&gt;So my first challenge was producing a file that I could actually run
like a normal system command, without messing around with a long java
command line, classpaths, etc. And without having to tell users that they
have to install Laika or whatever other dependencies I end up with. This
is fairly easy with &lt;a href="https://github.com/sbt/sbt-assembly"&gt;sbt-assembly&lt;/a&gt;; the following instructions are
largely copied from the sbt-assembly README.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create $root/project/assembly.sbt with the following content:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;addSbtPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eed3si9n&lt;/span&gt;&amp;quot; &lt;span class="c"&gt;% &amp;quot;sbt-assembly&amp;quot; % &amp;quot;0.10.1&amp;quot;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code&gt;import AssemblyKeys._&lt;/code&gt; at the top of $root/build.sbt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code&gt;mainClass in assembly := Some("my.main.Class")&lt;/code&gt; at the
   bottom of $root/build.sbt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create $root/assembly.sbt with the following content:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AssemblyKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;

&lt;span class="n"&gt;assemblySettings&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;sbt assembly&lt;/code&gt; and get a jar file in
   target/scala-2.10/my-assembly-1.0.jar&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the file with &lt;code&gt;java -jar target/scala-2.10/my-assembly-1.0.jar&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I tried to take it another step further and make the jar file
self-executable by prepending a shell script. This is possible with zip
files (jars are just zip files) because the important info is at the
end of the archive and anything can be prepended. But apparently java is
stricter about the format because this doesn't work -- java complains
"Invalid or corrupt jarfile".&lt;/p&gt;
&lt;p&gt;I'll have to settle for a (two-line) wrapper script. I consider it a
success that I was able to take the single jar from above, copy it to
a machine that didn't have Scala or Laika installed, and it ran just fine.&lt;/p&gt;</summary><category term="software-engineering"></category><category term="tools"></category><category term="scala"></category></entry><entry><title>Getting Set up for Programming in Scala on Debian</title><link href="http://blog.bstpierre.org/setting-up-scala-debian" rel="alternate"></link><updated>2013-12-06T21:01:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-12-06:setting-up-scala-debian</id><summary type="html">&lt;p&gt;Things we need to be able to use Scala on Debian (everything below will
likely work on Ubuntu as well):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scala itself, of course. &lt;code&gt;apt-get install scala&lt;/code&gt; will pull in a
    bunch of packages -- all the java stuff, etc. However, note that sbt
    will happily pull down whatever version of scala you specify in your
    build.sbt. While this step may not really be needed, I'm assuming
    that it at least ensures that scala's java-related requirements
    are met. For what it's worth, debian is currently shipping scala
    2.9.2 in "testing" (jessie), and I'm targeting 2.10, so my builds
    aren't using the system scala libraries.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sbt (simple build tool) is Scala's equivalent to
    make/Ant/Maven. Debian doesn't provide a package for sbt, but the &lt;a href="http://www.scala-sbt.org/release/docs/Getting-Started/Setup.html#installing-sbt"&gt;sbt
    project produces a deb&lt;/a&gt; as part of the official
    release. Install curl first if you don't already have it, since sbt
    depends on it, and check the download page to make sure you're
    getting the latest version of sbt:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;$ &lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;curl&lt;/span&gt;
$ &lt;span class="n"&gt;wget&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sbt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;scalasbt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sbt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;native&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;scala&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sbt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sbt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;0&lt;span class="p"&gt;.&lt;/span&gt;13&lt;span class="p"&gt;.&lt;/span&gt;0&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sbt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deb&lt;/span&gt;
$ &lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;dpkg&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;i&lt;/span&gt; &lt;span class="n"&gt;sbt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deb&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I'm planning on doing some work with &lt;a href="http://liftweb.net/"&gt;Lift&lt;/a&gt;, and deploying via
    .war, so I need tomcat. On one site I'm going to be behind apache,
    so I'll set up for testing that way:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;$ &lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;tomcat7&lt;/span&gt; &lt;span class="n"&gt;apache2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's all for now. I'll come back and update this if I disover other
setup items I'd have rather had at the beginning.&lt;/p&gt;</summary><category term="software-engineering"></category><category term="tools"></category><category term="scala"></category></entry><entry><title>What Makes a Nice Programming Language</title><link href="http://blog.bstpierre.org/what-makes-a-nice-programming-language" rel="alternate"></link><updated>2013-12-05T19:15:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-12-05:what-makes-a-nice-programming-language</id><summary type="html">&lt;p&gt;(I started this post about halfway through the Year of New Programming
Languages, and wrapped it up in December.)&lt;/p&gt;
&lt;p&gt;At the midway point I was most excited by Common Lisp, but with doubts
I'd be using it for anything real. By the end of the year my favorite
new language was probably D.&lt;/p&gt;
&lt;p&gt;Icon was fun, but impractical: no real libraries, no ecosystem, no way
to deploy anything, no tools, etc. It's dead. Nothing more than a
historical interest.&lt;/p&gt;
&lt;p&gt;I don't see what the big deal is about Go. To be fair, I was trying to
do a lot of string processing, which seems to be cumbersome in
Go. There are probably other areas where it shines.&lt;/p&gt;
&lt;p&gt;Scala is neat, but it's big -- it needs much more than a month to be
able to do anything worthwhile with it, and you have to learn your way
around the Java ecosystem if you aren't already familiar with it. And
in the end, you're on the Java ecosystem, which... well, it is what it
is, you take the bad with the good. (I'm prejudiced against it.)&lt;/p&gt;
&lt;p&gt;It's hard to get excited about Java, which I'm only using to make a
little game for my Android tablet. I'm trying to keep an open mind and
fight the prejudice. On the bright side, there are lots of tools,
which brings me to the point of this post.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Your language can suck (at least somewhat), but if it has a lot
     of high quality tools, it might be more usable than a
     theoretically superior language with crappy tools. In my opinion,
     this is Java: I don't care much for writing Java, but it doesn't
     completely suck and those guys have some really nice tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My goal when I start a programming project is to deliver some
     value to someone. Frequently, this is ultra-short-term value for
     myself (e.g. a one-off bash script). I also hack a lot on
     longer-term projects for personal use or by groups which I have a
     relationship with (e.g. small Flask/Python website). If I can't
     &lt;em&gt;deliver&lt;/em&gt; the value, then what is the point?&lt;/p&gt;
&lt;p&gt;I'm looking at you, Common Lisp: I couldn't figure out a way to
 reliably deliver a simple desktop app. Icon is in the same
 boat. On the other side of the coin, Java and Python &lt;em&gt;can be&lt;/em&gt;
 really easy to deliver -- there are tools to generate desktop
 installers, and deploying web apps can be really simple. In the
 old days of the web, this was the beauty of Perl CGI scripts: to
 deliver it, you just drop &lt;code&gt;myscript.pl&lt;/code&gt; into your &lt;code&gt;cgi-bin&lt;/code&gt;. (I
 figure it's also why PHP, which is a crime against humanity, is
 so popular: deployment is trivial on almost any web host.)
 And Javascript has historically been more or less the only obvious
 choice if you want something to run in a browser, though with
 Dart and some of the new languages that compile to JS we can see
 that changing.&lt;/p&gt;
&lt;p&gt;Languages like D or Chicken Scheme that compile to native binaries
 are easy to ship as well. (They're somewhat harder to 'ship' if
 you're a FOSS project and the 'product' is your code, only for the
 fact that your users are less likely to have the necessary compilers
 and libraries available.)&lt;/p&gt;
&lt;p&gt;So normally the first question I ask myself is, "How am I going
 to ship this?" That really drives the choice of language.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are there tons of libraries? If I can grab libraries that do 80%
     of the work my project needs, it makes me strongly consider using
     the language. This is a huge strength of Perl and Python: there
     are libraries for everything.&lt;/p&gt;
&lt;p&gt;In today's world, just having libraries isn't even enough: they have
 to be discoverable. The modern languages with which I'm familiar
 all have some kind of package discovery and download tool: Perl
 has CPAN, Python has pip, Dart has a tool that I only interacted
 with through the IDE, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What is the testing experience like? Does the language have a way
     (preferable &lt;em&gt;one&lt;/em&gt; standard way) to set up and run tests? Does it
     not suck?&lt;/p&gt;
&lt;p&gt;D has perhaps the best language support I've seen for testing --
 you can declare &lt;code&gt;unittest { ... }&lt;/code&gt; blocks right inline with the
 functions you're testing.&lt;/p&gt;
&lt;p&gt;My limited experience with Perl gave me a taste of that ecosystem's
 good handling of testing. There are established patterns for how
 to do it The Right Way, and useful tooling set up around those
 patterns.&lt;/p&gt;
&lt;p&gt;I'm most familiar with Python, where there isn't really a Right Way,
 but there is a &lt;code&gt;unittest&lt;/code&gt; module in the standard library, and some
 popular tools like Nose that make unit testing not painful.&lt;/p&gt;
&lt;p&gt;C on the other hand, is annoying for unit testing. There are
 libraries, but the quality varies, and mostly my experience has
 been that you have to roll your own support for testing, pretty
 much every time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Programs written in the language need to be debuggable. Especially
     when I'm learning the language, because I'm going to do stupid
     things. If debugging is painful (or worse, if I can't even figure
     out how to get past compiler error messages -- I'm looking at you,
     ghc), then the frustration just makes me want to walk away and find
     a better tool.&lt;/p&gt;
&lt;p&gt;Part of debuggability is making it so that I can seamlessly walk
 through library code. The Dart IDE provided a nice experience
 here. (Yes, I know it's Eclipse, but amazingly it didn't suck like
 Eclipse usually does. Not a single crash.) When I did dumb things
 in my code, I got a backtrace from the library code where I could
 see the incorrect argument I was passing down.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</summary><category term="software-engineering"></category><category term="tools"></category></entry><entry><title>New Language Wrapup</title><link href="http://blog.bstpierre.org/new-language-wrapup" rel="alternate"></link><updated>2013-12-04T14:16:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-12-04:new-language-wrapup</id><summary type="html">&lt;p&gt;Learning a new programming language each month this year was an ambitious
goal. I didn't quite make it -- other things got in the way. I did manage
to dip my toes into some interesting languages, learn some new things,
and find a couple of candidates for diving deeper into.&lt;/p&gt;
&lt;p&gt;Here's the final list of languages I learned and mostly-incomplete
projects/prototypes built with each:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Icon - markdown processor&lt;/li&gt;
&lt;li&gt;Go - markdown processor&lt;/li&gt;
&lt;li&gt;Common Lisp - make-like build tool&lt;/li&gt;
&lt;li&gt;Scala - personal finance app&lt;/li&gt;
&lt;li&gt;D - MP3 ID3 tag reader&lt;/li&gt;
&lt;li&gt;Scheme (specifically, Chicken Scheme) - photo sharing website&lt;/li&gt;
&lt;li&gt;Dart - web game&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I played with Java, Groovy, and Rust a little bit, but didn't really do
anything with them. (Java isn't new to me, though I'm far from
"experienced" with it either.)&lt;/p&gt;
&lt;p&gt;My favorite of the bunch has to be D, though I did enjoy working with Dart and Scheme.&lt;/p&gt;
&lt;p&gt;In the end, though, D seems too familiar -- too similar to the things
I know, and Scheme/Lisp aren't really new to me.&lt;/p&gt;
&lt;p&gt;I debated between Scala and Haskell (which I played with a little bit
last year), and thought about Erlang (which I experimented with a couple
of years ago) and decided to make a deep dive into Scala over the next
year. Scala offers a paradigm shift like Haskell or Erlang would, it
also forces me to learn a new platform and ecosystem, and challenges my
decade-plus JVM-hate. Maybe I'll even learn how to be effective with
Eclipse.&lt;/p&gt;
&lt;p&gt;Maybe not.&lt;/p&gt;
&lt;p&gt;I hope to record my progress here as I go along, but my track record on
hopes-of-more-frequent-blogging isn't great.&lt;/p&gt;</summary><category term="software-engineering"></category><category term="tools"></category><category term="learning"></category><category term="programming-languages"></category></entry><entry><title>Recovering Photos From a Bad Hard Drive</title><link href="http://blog.bstpierre.org/recover-photos-from-bad-hard-drive" rel="alternate"></link><updated>2013-05-03T19:50:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-05-03:recover-photos-from-bad-hard-drive</id><summary type="html">&lt;p&gt;I've had a couple of friends ask me if there's any way I could read
family photos off a dying hard drive. Obviously, the best way to do this
is to have a backup: then you can throw out the hard drive (use an
electronics recycler) and restore your backup to a new drive.&lt;/p&gt;
&lt;p&gt;But if you've arrived at this page, you probably don't have a backup...&lt;/p&gt;
&lt;p&gt;I'm writing this down mainly because I forgot the details after I did
this the first time, and I don't want to have to do the research again.&lt;/p&gt;
&lt;p&gt;This will only work if the drive is still alive enough to be mostly
readable. If it is well and truly dead, I can't help you. Even if it's
only half-dead, I still can't help you -- this is what worked for me,
there are probably better ways to accomplish this, I'm not an expert in
this area. (In other words: don't ask me for help.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connect the bad drive to a computer running Ubuntu. (Any linux will
     do, but some of the details below assume Ubuntu/Debian.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apt-get install gddrescue testdisk&lt;/code&gt; (Note that "ddrescue" is not
     the same thing.) Gddrescue provides a "ddrescue" program, and
     testdisk provides "photorec".&lt;/li&gt;
&lt;li&gt;Look in dmesg to see where the new drive lives. Mine is at &lt;code&gt;/dev/sdc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;sudo fdisk -l /dev/sdc&lt;/code&gt; and verify that the size and type are
     what you expect. In my case, the drive to be rescued is from a
     Windows box and formatted NTFS, so I know that I'm not mixing it
     up with any of the ext3 drives that are on my system.&lt;/li&gt;
&lt;li&gt;Create a directory to hold the rescue image, logfile, and
     files that will be extracted from the image. I put mine in
     &lt;code&gt;~/HDD_Rescues/Friend1/&lt;/code&gt;. Obviously, the destination directory needs
     to be big enough to hold the contents of the drive to be rescued.&lt;/li&gt;
&lt;li&gt;cd to that directory and run &lt;code&gt;ddrescue /dev/sdc hdimage
     logfile&lt;/code&gt;. This will try to read from the entire disk, saving the
     result in &lt;code&gt;hdimage&lt;/code&gt;, and keeping track of what has been done in
     &lt;code&gt;logfile&lt;/code&gt;. The advantage of the logfile is that, if an error occurs
     or you need to interrupt the recovery, it can continue where it
     left off. One drive I recovered was broken enough that it would
     die after a few 10s of MB -- and needed a reboot to recover. So
     the pattern is: run ddrescue, wait for error to occur, reboot,
     repeat until enough data has been recovered.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;photorec /d recovered_files hdimage&lt;/code&gt; and walk through the
     prompts. Recovered files will be dropped in a series of directories
     named &lt;code&gt;recovered_files.NN&lt;/code&gt;, where NN is an incrementing number.&lt;/li&gt;
&lt;/ol&gt;</summary><category term="tools"></category></entry><entry><title>Generic Requirements for Build Scripts</title><link href="http://blog.bstpierre.org/generic-requirements-makefiles" rel="alternate"></link><updated>2013-02-12T19:08:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-02-12:generic-requirements-makefiles</id><summary type="html">&lt;p&gt;A few years ago, I wrote
&lt;a href="http://blog.bstpierre.org/makefiles-are-software-too"&gt;Makefiles are Software Too&lt;/a&gt;, which laid
out some "generic requirements" for any build scripts (Make, Ant,
Scons, Fabric, etc) that you're writing. I still think those are
important, but over the past (nearly) four years I've worked with more
systems, and I think there are some additional requirements we can add
to the list.&lt;/p&gt;
&lt;p&gt;As motivation for this list, consider the consequences of skipping the
requirements below:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A build that simply rebuilds everything, every time will waste a
   lot of developer time. It's almost always a good investment to set
   up a system that will only rebuild what is needed; the developer
   time spent investing and maintaining such a system will be repaid
   through faster builds and less time spent waiting.&lt;/li&gt;
&lt;li&gt;A build that doesn't rebuild everything, but does not do a good job
   of tracking dependencies and often fails to rebuild a component
   that should have been rebuilt results in strange bugs during
   development. Developers who work with such a system will be in the
   habit of doing &lt;code&gt;make clean&lt;/code&gt; and you end up in situation #1.&lt;/li&gt;
&lt;li&gt;If you're in the horrible situation where &lt;code&gt;make clean&lt;/code&gt; doesn't even
   work, developers will be in the habit of nuking their tree and
   pulling down a fresh source tree. This takes even &lt;em&gt;more&lt;/em&gt; time and
   carries the risk of deleting work!&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;A Single Command Builds Everything&lt;/h2&gt;
&lt;p&gt;To build the "normal" product(s), it should not be necessary to do
&lt;code&gt;make setup; make the_product; make installer&lt;/code&gt;. If you find yourself
running series of commands like this, you probably need either a
higher-level target (&lt;code&gt;make all&lt;/code&gt;) or your &lt;code&gt;bundle&lt;/code&gt; target should depend
on &lt;code&gt;the_product&lt;/code&gt; (and one or both of those targets should probably
depend on &lt;code&gt;setup&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Environment Variables are Encapsulated&lt;/h2&gt;
&lt;p&gt;The build script must encapsulate all environment variables within the
script. (This is a corollary of the "single command" requirement
above.) When writing build scripts, environment variables &lt;em&gt;seem&lt;/em&gt; like
a convenient way to add flexibility (I'm guilty of this), but in the
end they add complexity for users of the script. I think it's ok if
there are settings that can be customized for developers who need to
do something different, but the script should provide reasonable
defaults that "just work" for most of the developers (and the CI
system).&lt;/p&gt;
&lt;p&gt;What's even worse than environment variables required by the script is
if there are parts of the toolchain that require variables that aren't
set to a reasonable default by the script.&lt;/p&gt;
&lt;h2&gt;Rebuilding is Minimized&lt;/h2&gt;
&lt;p&gt;The build script should not unnecessarily rebuild source code in a
directory tree that has previously been built. (This is &lt;code&gt;make&lt;/code&gt;'s
raison d'etre.)&lt;/p&gt;
&lt;p&gt;I have worked on more than one system at past employers and clients
with &lt;code&gt;make&lt;/code&gt; targets that include a long chain of commands to rebuild
that target, where there are intermediate build products created along
the way that could have been separate targets. Since those
intermediate targets often would not need to be rebuilt every time,
build times could have been reduced.&lt;/p&gt;
&lt;h2&gt;Documentation Exists&lt;/h2&gt;
&lt;p&gt;The build script &lt;em&gt;must&lt;/em&gt; be documented. If anything, the scripts I've
seen in the past few years are become more complex than those I saw in
the previous decade. In "Makefiles are Software Too", I said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It doesn't have to be elaborate, but a five-line comment at the top of
the script describing the available command-line variables would be
nice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A pattern I've seen that's even better is to provide a &lt;code&gt;make help&lt;/code&gt;
command or the equivalent. This should echo the targets that are
available for the user and what they do, as well as any variables that
are commonly used to customize the build (e.g. &lt;code&gt;DEBUG=1 for debug
mode&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Build Trees are Cleanable&lt;/h2&gt;
&lt;p&gt;I actually think this is less important -- with two important
assumptions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The version control system provides a way to get back to a clean
    tree (e.g. &lt;code&gt;git clean -f&lt;/code&gt;). (Though this can have the danger of
    removing new files that haven't yet been added to VC.)&lt;/li&gt;
&lt;li&gt;Dependencies work reliably so that cleaning the tree is never
    needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If both of those requirements are met, then &lt;code&gt;make clean&lt;/code&gt; is
optional. But if such a target &lt;em&gt;is&lt;/em&gt; provided, then it &lt;em&gt;must&lt;/em&gt; work
reliably. And if there are multiple levels of "clean"
(e.g. &lt;a href="https://www.gnu.org/prep/standards/html_node/Standard-Targets.html#Standard-Targets"&gt;GNU specifies&lt;/a&gt; &lt;code&gt;clean&lt;/code&gt;, &lt;code&gt;distclean&lt;/code&gt;, &lt;code&gt;mostlyclean&lt;/code&gt;, and
&lt;code&gt;maintainer-clean&lt;/code&gt;), the behavior of each of these should be specified
in the documentation.&lt;/p&gt;
&lt;h2&gt;Cleaning Should Not be Needed&lt;/h2&gt;
&lt;p&gt;This is mentioned above, but it's really a separate generic
requirement. Cleaning (i.e. running &lt;code&gt;make clean&lt;/code&gt;, &lt;code&gt;git clean -f&lt;/code&gt;, or
the equivalent) should be a rare event. If you find yourself doing
this, your build script is defective -- there's likely a problem with
dependencies, see below.&lt;/p&gt;
&lt;h2&gt;Parallelizable&lt;/h2&gt;
&lt;p&gt;I originally listed this as a "bonus", but I think that this is more
important now than it was in the last decade. Multicore systems are
the default now, so the ability to spread a build across all the
processing power available on the system is necessary. If all of the
other generic requirements for build scripts are satisfied, it
shouldn't be difficult to run a parallelized build. (And if your tool
doesn't support building in parallel, you need to find a new tool!)&lt;/p&gt;
&lt;p&gt;This also supports spreading large builds out over multiple machines,
e.g. with distcc).&lt;/p&gt;
&lt;h2&gt;Dependencies are Automatic&lt;/h2&gt;
&lt;p&gt;Wherever possible, dependencies are handled automatically. This means
that, for example, your Makefile automatically knows that foo.o gets
built from foo.c, and it automatically generates a list of all of the
header files that foo.c includes. This is almost trivially easy to do
with &lt;a href="http://make.paulandlesley.org/autodep.html"&gt;GCC's &lt;code&gt;-MD&lt;/code&gt; flag&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that, if I have to run the equivalent of &lt;code&gt;make depend&lt;/code&gt;, then
dependency generation is (by definition) not "automatic". Dependencies
&lt;em&gt;must&lt;/em&gt; be generated on the fly and updated whenever anything changes.&lt;/p&gt;
&lt;h2&gt;Don't Implicitly Touch VC&lt;/h2&gt;
&lt;p&gt;In my opinion, the build system should not touch version control at
all, but I accept that this is just my opinion. I've seen several
builds that are based on fairly complex VC configurations, and having
support for managing multiple-repository builds as part of the build
scripts can be a useful feature.&lt;/p&gt;
&lt;p&gt;As a requirement though, I think it is reasonable to say that the
build system should not touch VC (neither getting updates nor putting
changes) as part of the default target path. Any build commands that
affect VC should be obviously documented as doing so. (It's dangerous
to commit changes before you're ready. It's even more dangerous in
some VC systems to pull down/merge into your workspace before you're
ready. Yes, there are still people out there using CVS...)&lt;/p&gt;
&lt;h2&gt;Build Targets are Granular&lt;/h2&gt;
&lt;p&gt;This is complementary to the "Single Command" requirement. While we
want to enable users to be able to build the whole product all at
once, we also want to enable users to rebuild specific parts and avoid
having the overhead of creating products they don't care about.&lt;/p&gt;
&lt;p&gt;Consider the case where you've got a very large product, but your
immediate task is to fix a bug in a smaller component. You've written
a unit test that exposes the bug and/or the component can be run
standalone for testing. While you're in an edit/compile/debug loop,
you don't want the overhead of rebuilding the whole product, you just
want to rebuild your component and the tests that go along with it.&lt;/p&gt;
&lt;p&gt;The build script must provide targets so that users can rebuild
specific components instead of the entire product.&lt;/p&gt;
&lt;h2&gt;Run Tests&lt;/h2&gt;
&lt;p&gt;The build script should support running unit or other
tests. (E.g. &lt;a href="https://www.gnu.org/prep/standards/html_node/Standard-Targets.html#Standard-Targets"&gt;GNU's &lt;code&gt;make check&lt;/code&gt;&lt;/a&gt;.) Where applicable, tests must
be rebuilt automatically before being run (this is a corollary of
"Dependencies are Automatic").&lt;/p&gt;
&lt;p&gt;The reason this belongs in the build script is that unit tests are
part of the depdendency chain, and we mustn't be required to manually
determine when unit tests need to be rebuilt. Whether to include other
sorts of tests under the bailiwick of the build script is a decision
for the implementor.&lt;/p&gt;
&lt;p&gt;It would be nice to be able to run specific tests, but it doesn't seem
like this &lt;em&gt;needs&lt;/em&gt; to be provided through the build script.&lt;/p&gt;</summary><category term="tool"></category><category term="software-engineering"></category></entry><entry><title>Gebildet: Writing Ebooks in Markdown</title><link href="http://blog.bstpierre.org/gebildet-ebooks-markdown" rel="alternate"></link><updated>2013-02-03T09:24:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-02-03:gebildet-ebooks-markdown</id><summary type="html">&lt;p&gt;Last month I &lt;a href="http://blog.bstpierre.org/a-small-project-in-icon"&gt;wrote a prototype markdown processor&lt;/a&gt; as part of
learning the Icon programming language. This month I'm rewriting it as
part of learning Go, but this time with the intention of maintaining
the tool over the long term.&lt;/p&gt;
&lt;p&gt;As mentioned in the previous article, "gebildet" is intended to be a
better &lt;a href="http://www.dexy.it/"&gt;dexy&lt;/a&gt;. I want to be able to write a book (which contains
a lot of source code) in Markdown, but keep the code files
separate. The tool should allow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;inserting the code into the final product,&lt;/li&gt;
&lt;li&gt;compiling and/or running the code to verify that it is correct,
    optionally capturing command/code output into the final product,&lt;/li&gt;
&lt;li&gt;inserting the output from arbitrary commands into the final
    product,&lt;/li&gt;
&lt;li&gt;extending the tool with additional "directives" (a Markdown syntax
    extension).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gebildet will &lt;em&gt;not&lt;/em&gt; generate a "final" product (i.e. PDF or ePub) --
this is the domain of a separate tool. What it will generate is
"standard" Markdown that can be consumed by a tool like
&lt;a href="http://www.johnmacfarlane.net/pandoc/"&gt;pandoc&lt;/a&gt; to generate a final product in whatever format is
desired.&lt;/p&gt;</summary><category term="tools"></category><category term="learning"></category><category term="markdown"></category><category term="publishing"></category><category term="ebook"></category></entry><entry><title>Testing HTTP Basic Auth in Flask</title><link href="http://blog.bstpierre.org/flask-testing-auth" rel="alternate"></link><updated>2013-01-30T23:29:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-01-30:flask-testing-auth</id><summary type="html">&lt;p&gt;I created a quick &amp;amp; dirty admin page for a flask-based website I'm
working on, and I had to add HTTP authentication support for it. Doing
this is easy.&lt;/p&gt;
&lt;p&gt;In the app, I add:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;

# &lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;=[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_auth&lt;/span&gt;
&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;admin_page&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    # &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;admin.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        # &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note the &lt;code&gt;@auth.requires_auth&lt;/code&gt; decorator. This uses an auth module
(code stolen from &lt;a href="http://flask.pocoo.org/snippets/8/"&gt;this snippet&lt;/a&gt;):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;check_auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;called&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;combination&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &amp;quot;&amp;quot;&amp;quot;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;me&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;my_password&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;span class="n"&gt;Sends&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; 401 &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;enables&lt;/span&gt; &lt;span class="n"&gt;basic&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&amp;quot;&amp;quot;&amp;quot;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;&amp;#39;Could not verify your access level for that URL.\n&amp;#39;&lt;/span&gt;
    &lt;span class="s"&gt;&amp;#39;You have to login with proper credentials&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 401&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;WWW-Authenticate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Basic realm=&amp;quot;Login Required&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;requires_auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;decorated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authorization&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;check_auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorated&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Easy enough to do a one-off test, but it really ought to have an
automated test. Unfortunately, I don't see support for setting headers
in flask's built-in test client or in the Flask-Testing
extension. That's ok, we can use the test client in Werkzeug.&lt;/p&gt;
&lt;p&gt;First, the imports we need:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;werkzeug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;werkzeug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datastructures&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Note that the TestCase class we're using requires Flask-Testing (&lt;code&gt;pip
install Flask-Testing&lt;/code&gt;; I'm using version 0.4). This test class wants
us to set up the app:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;AuthTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;create_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DATABASE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;testing.sqlite3&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;query_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CREATE_TABLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then writing a test case to verify that the admin page is locked is
straightforward:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;test_admin_page_is_locked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_401&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;WWW-Authenticate&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Basic&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;WWW-Authenticate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;But actually sending an Authorization header for the client is a bit
more challenging. Note that &lt;code&gt;self.client&lt;/code&gt; is a subclass of Werkzeug's
&lt;code&gt;test.Client&lt;/code&gt;, and the &lt;code&gt;open&lt;/code&gt; method there supports passing in extra
Werkzeug &lt;code&gt;Header&lt;/code&gt; objects. This is ugly, but it works. The first two
cases below test login attempts with bad password and username,
respectively, and the final test case logs in with the correct
credentials.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;test_admin_page_rejects_bad_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Authorization&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Basic &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;me:foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_401&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;test_admin_page_rejects_bad_username&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Authorization&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="s"&gt;&amp;#39;Basic &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;foo:my_password))&lt;/span&gt;
&lt;span class="s"&gt;        rv = Client.open(self.client, path=&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;,&lt;/span&gt;
&lt;span class="s"&gt;                         headers=h)&lt;/span&gt;
&lt;span class="s"&gt;        self.assert_401(rv)&lt;/span&gt;

&lt;span class="s"&gt;    def test_admin_page_allows_valid_login(self):&lt;/span&gt;
&lt;span class="s"&gt;        h = Headers()&lt;/span&gt;
&lt;span class="s"&gt;        h.add(&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="s"&gt;&amp;#39;Basic &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;my_password&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/admin/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_200&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If I have some time when I'm done with this project, I'll probably
send a patch to Flask-Testing to add support for &lt;code&gt;headers=&lt;/code&gt; to that
test client.&lt;/p&gt;</summary><category term="testing"></category><category term="python"></category><category term="flask"></category></entry><entry><title>A Small Project in Icon</title><link href="http://blog.bstpierre.org/a-small-project-in-icon" rel="alternate"></link><updated>2013-01-26T16:30:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-01-26:a-small-project-in-icon</id><summary type="html">&lt;p&gt;(Background: &lt;a href="http://blog.bstpierre.org/12-projects-in-2013"&gt;12 Projects in 2013&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I wrote a bit about the general experience of &lt;a href="http://blog.bstpierre.org/programming-in-icon"&gt;programming in
Icon&lt;/a&gt; a couple of weeks ago. This post is a brief post-mortem
about a project that I tried to build using Icon. I'm not planning on
releasing the source, because I don't consider the results very useful
except as a personal learning experience about the Icon language and
the problem domain. (And because I plan to rewrite the software in
next month's new language: Go.)&lt;/p&gt;
&lt;p&gt;The goal is to parse a book written in slightly-augmented Markdown
into either HTML or "standard" Markdown so that other tools that
consume Markdown can be used to generate friendly formats like PDF or
ePub.&lt;/p&gt;
&lt;p&gt;I was inspired by &lt;a href="http://www.dexy.it/"&gt;dexy&lt;/a&gt;, which works reasonably well, but I
found it cumbersome to use.&lt;/p&gt;
&lt;p&gt;The limitations in standard Markdown that I intend to augment are the
ability to include files, and to include the output of shell
commands. I added support for the syntax &lt;code&gt;!!directive args...&lt;/code&gt;; where
the processing tool defines a handful of directives, e.g. &lt;code&gt;include&lt;/code&gt;,
&lt;code&gt;bash_interactive&lt;/code&gt;, etc. These could be used, as in dexy, for ensuring
that code samples are correct, and that the output from these samples
is properly reflected in the text of the book.&lt;/p&gt;
&lt;p&gt;As far as doing this in Icon goes: the language's string-handling
facilities made parsing Markdown easy. The problem I ran into that
caused the most difficulty is that Icon doesn't provide a good
mechanism for grabbing output from subprocesses. The &lt;code&gt;open()&lt;/code&gt; function
takes a "p" flag for reading/writing from/to a subprocess, but it's
nowhere near as rich as Perl, or, say, Python's &lt;code&gt;subprocess&lt;/code&gt;
module. For one thing, there's no built-in support for capturing
&lt;em&gt;both&lt;/em&gt; stdout and stderr. Also, it isn't obvious how to check the exit
status of a subprocess spawned via &lt;code&gt;open()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If I was committed to using Icon for this project, I could have
written C functions to provide better subprocess/pipe support, but the
end of the month is approaching and I think it makes more sense to
treat this as a prototype and rewrite it in Go (a language with a
strong future) next month.&lt;/p&gt;</summary><category term="software-engineering"></category><category term="tools"></category><category term="learning"></category><category term="icon-language"></category></entry><entry><title>Programming in Icon</title><link href="http://blog.bstpierre.org/programming-in-icon" rel="alternate"></link><updated>2013-01-14T20:11:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-01-14:programming-in-icon</id><summary type="html">&lt;p&gt;(Background: &lt;a href="http://blog.bstpierre.org/12-new-languages-in-2013"&gt;12 New Languages in 2013&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.cs.arizona.edu/icon/"&gt;Icon&lt;/a&gt; is a cool little programming language. It's worth learning,
even if it isn't practical for daily use.&lt;/p&gt;
&lt;p&gt;Pros:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Syntax is similar to C, which makes it easy to learn.&lt;/li&gt;
&lt;li&gt;A handful of &lt;a href="http://www.cs.arizona.edu/icon/books.htm"&gt;publication-quality Icon books&lt;/a&gt; (now out of print) are
    available for free download as PDF. I used &lt;em&gt;&lt;a href="http://www.cs.arizona.edu/icon/lb3.htm"&gt;The Icon Programming
    Language, 3rd Ed&lt;/a&gt;&lt;/em&gt; as my primary resource.&lt;/li&gt;
&lt;li&gt;Latest version was released in 2010 and has been validated against
    newer systems (Ubuntu 12.04, Mac OS 10.7).&lt;/li&gt;
&lt;li&gt;"Everything is an expression", expression failure, backtracking, and
    some of the string functions are different enough to make your brain
    exercise new pathways when solving various problems with Icon. (This is
    one of my benchmarks about whether it's worth learning a new language.)&lt;/li&gt;
&lt;li&gt;There are &lt;a href="http://packages.debian.org/source/stable/icon"&gt;debian packages&lt;/a&gt; (&lt;code&gt;apt-get install icon-ipl iconc icont iconx&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cons (some of these are just nits, really):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Documentation (at least what I found online) for the library is poor (at best).&lt;/li&gt;
&lt;li&gt;1-based indexing; this has caused me a handful of hard-to-find
    bugs. ("Positions in Icon" is kind of odd, too. It's not what you're
    used to.)&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It's basically a dead language. Newest version was released in 2010. The 
    &lt;a href="http://www.cs.arizona.edu/icon/faq.htm#futures"&gt;FAQ says&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We continue to use Icon on a daily basis, but no significant
changes are planned.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Runtime error reporting is dreadful. Better with the interpreter,
    but more or less unusable with the compiler. Not much is caught at
    compile time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Do you known any Icon fanboys? Any popular projects that use Icon? Yeah...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On balance, it's not something I'd reach for to solve a given problem, but I'm
writing a book processor with it (more in a future post), and I'm finding that
it's up to the job of mangling text.&lt;/p&gt;</summary><category term="software-engineering"></category><category term="tools"></category><category term="learning"></category><category term="icon-language"></category></entry><entry><title>12 Little Projects in 2013</title><link href="http://blog.bstpierre.org/12-projects-in-2013" rel="alternate"></link><updated>2013-01-14T19:08:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-01-14:12-projects-in-2013</id><summary type="html">&lt;p&gt;As I mentioned in &lt;a href="http://blog.bstpierre.org/12-new-languages-in-2013"&gt;12 New Languages&lt;/a&gt;, I'm learning a new programming
language each month in 2013. To go along with this effort, I need a
dozen things worth building -- one for each language.&lt;/p&gt;
&lt;p&gt;I'm going to try to play to the strengths of each language. Here's the initial lineup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.bstpierre.org/programming-in-icon"&gt;Icon&lt;/a&gt;: "&lt;a href="http://blog.bstpierre.org/a-small-project-in-icon"&gt;bucher&lt;/a&gt;" -- write books containing tons of code, using Markdown&lt;/li&gt;
&lt;li&gt;Go: "&lt;a href="http://blog.bstpierre.org/gebildet-ebooks-markdown"&gt;gebildet&lt;/a&gt;" -- better than bucher&lt;/li&gt;
&lt;li&gt;Common Lisp: yet another "make" tool; rogue-like game&lt;/li&gt;
&lt;li&gt;Scala: finance app with a Swing UI&lt;/li&gt;
&lt;li&gt;Java: a simple word game for Android&lt;/li&gt;
&lt;li&gt;Dart: rogue in "html5"&lt;/li&gt;
&lt;li&gt;D: ?&lt;/li&gt;
&lt;li&gt;Ruby: ?&lt;/li&gt;
&lt;li&gt;Groovy: scraping git (via jgit) for more-than-stats (finding buggy modules?)&lt;/li&gt;
&lt;li&gt;Fortran: ?&lt;/li&gt;
&lt;li&gt;Squirrel: ?&lt;/li&gt;
&lt;li&gt;Forth: ?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Scheduled projects subject to change at my whim...)&lt;/p&gt;</summary><category term="software-engineering"></category><category term="tools"></category><category term="learning"></category><category term="icon-language"></category></entry><entry><title>12 New Languages in 2013</title><link href="http://blog.bstpierre.org/12-new-languages-in-2013" rel="alternate"></link><updated>2013-01-05T18:26:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2013-01-05:12-new-languages-in-2013</id><summary type="html">&lt;p&gt;I'm setting a personal goal in 2013 to learn a new programming
language each month. Obviously, I won't be able to learn any of them
in depth, but I plan on completing a &lt;a href="http://blog.bstpierre.org/12-projects-in-2013"&gt;small project&lt;/a&gt; in each language.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;January: &lt;a href="http://blog.bstpierre.org/programming-in-icon"&gt;Icon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;February: Go&lt;/li&gt;
&lt;li&gt;March: Common Lisp&lt;/li&gt;
&lt;li&gt;April: Scala&lt;/li&gt;
&lt;li&gt;May: Java (oh the horror!)&lt;/li&gt;
&lt;li&gt;June: Dart&lt;/li&gt;
&lt;li&gt;July: D&lt;/li&gt;
&lt;li&gt;August: Ruby&lt;/li&gt;
&lt;li&gt;September: Groovy&lt;/li&gt;
&lt;li&gt;October: Fortran&lt;/li&gt;
&lt;li&gt;November: Squirrel&lt;/li&gt;
&lt;li&gt;December: Forth&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Scheduled languages subject to change at my whim...)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt;: 26-Jan, swapped Groovy and Go in February.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt;: 18-Feb, drop Clojure for Common Lisp, swap Groovy with Lisp in March.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt;: 1-May, drop Pike for Java (to enable an Android project), move Scala to April.&lt;/p&gt;</summary><category term="software-engineering"></category><category term="tools"></category><category term="learning"></category><category term="icon-language"></category></entry><entry><title>Get notification when long running commands are done</title><link href="http://blog.bstpierre.org/notification-long-running-commands" rel="alternate"></link><updated>2012-06-22T18:44:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-06-22:notification-long-running-commands</id><summary type="html">&lt;p&gt;I often launch a long running command (e.g. a build or firmware
upgrade), then wander off to another virtual desktop to take care of a
few small tasks while I'm waiting for the command to complete.
Unfortunately those "little tasks" tend to suck me in and I forget that
I was just filling in time while waiting for something.&lt;/p&gt;
&lt;p&gt;Adding the function below to my zshrc makes it so that I can do things
like &lt;code&gt;lr make check&lt;/code&gt; and have a dialog pop up in my face when it's done
-- even when I'm ssh'd to a VM or a remote machine. (Assuming I've used
&lt;code&gt;ssh -X remote&lt;/code&gt;.)&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    $&lt;span class="o"&gt;*&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt; $? &lt;span class="o"&gt;==&lt;/span&gt; 0 &lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;then&lt;/span&gt;
      &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;succeeded&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;
    &lt;span class="n"&gt;fi&lt;/span&gt;
    &lt;span class="n"&gt;zenity&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;command&lt;/span&gt; $&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; $1&amp;quot;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="zsh"></category><category term="tools"></category><category term="tip"></category><category term="linux"></category></entry><entry><title>Offline RSS Readers on Linux</title><link href="http://blog.bstpierre.org/offline-rss-readers-on-linux" rel="alternate"></link><updated>2012-06-22T07:24:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-06-22:offline-rss-readers-on-linux</id><summary type="html">&lt;p&gt;Building an RSS reader must be some kind of rite of passage. Everybody
has probably started one at one point. (Hey, I've done it too.) It seems
easy enough, right? Unfortunately, it &lt;em&gt;is&lt;/em&gt; pretty easy to get
&lt;em&gt;something&lt;/em&gt; sort-of working. Which means that there's a lot of junk
available. And it's a double-edged sword that Ubuntu has quality bar of
virtually zero: tons of packages very easily available, but YMMV.&lt;/p&gt;
&lt;p&gt;What follows is a quick rundown of the available RSS readers in Ubuntu,
so that you, dear reader, don't have to experiment with all the same
junk I did. (For what it's worth, I was running Ubuntu 10.04 LTS when I
did this review, so it's possible that some of these projects have
improved in the last 20 months. At some point I will update this with
results for Ubuntu 12.04 LTS.)&lt;/p&gt;
&lt;p&gt;My evaluation is relatively shallow. I'm installing the package via apt,
running it (from the console, so I see whatever turds get printed to the
console), importing three OPML files from Google Reader, and then
reading items in a handful of feeds. I also attempt to add a couple of
feeds manually, including at least one podcast.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;blam: &lt;strong&gt;JUNK&lt;/strong&gt; (1.8.7-1ubuntu1)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spits out exceptions on the console when, for example, name
    resolution fails. This happened during import of my feeds.&lt;/li&gt;
&lt;li&gt;About half of my feeds won't update -- no posts are displayed
    for the feed. Refreshing manually doesn't help. The URLs are
    valid and there should be posts.&lt;/li&gt;
&lt;li&gt;Removing the example feeds is annoying. I can't just push the
    delete key while a feed is highlighted, and there's no keyboard
    shortcut. If you use keys to navigate the menu, &lt;em&gt;three&lt;/em&gt; of the
    menu items use "R" as the accelerator key.&lt;/li&gt;
&lt;li&gt;In one of the feeds that does have posts, clicking on any post
    to read it causes blam to open a Nautilus window on
    /usr/share/themes/blam. WTF? I stopped here -- this application
    is clearly unusable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;liferea: &lt;strong&gt;GOOD&lt;/strong&gt; (1.6.2-1ubuntu6.1)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removing the example feeds is slightly less annoying than blam.
    Delete key doesn't work, but at least the menu uses a single
    accelerator key. The confirmation dialog is annoying because
    "Cancel" is the default action instead of "Delete". A minor nit:
    they mix usage of "Remove" and "Delete".&lt;/li&gt;
&lt;li&gt;Double-clicking the feed doesn't bring up an "Edit Properties"
    dialog -- you have to right-click to get there. At least blam
    got &lt;em&gt;this&lt;/em&gt; right.&lt;/li&gt;
&lt;li&gt;Fails to update if you provide an URL that serves a webpage with
    a link rel=alternate tag specifying an itpc: URL, even though
    the second link rel=alternate is an http: URL. Manually looking
    at the webpage source and specifying the correct feed URL does
    the trick.&lt;/li&gt;
&lt;li&gt;When adding a new feed URL, you aren't presented with the box to
    "automatically download enclosures". You first have to add the
    feed and then edit properties to tick this checkbox. This means
    that the first download won't fetch the enclosures.&lt;/li&gt;
&lt;li&gt;A podcast feed that includes the same URL as both an "enclosure"
    and "media:content" causes liferea to spawn &lt;em&gt;two&lt;/em&gt; instances of
    wget, each downloading the same URL into the same local file.
    The media file is also shown twice at the bottom of the post,
    though this is minor.&lt;/li&gt;
&lt;li&gt;Double-clicking the downloaded enclosure in the post window
    prompts to spawn a program to play the MP3 file. Given that
    liferea is a GNOME-aware application, it seems reasonable that
    it should use my preferred application for MP3s, just as if I
    had double-clicked the file in Nautilus.&lt;/li&gt;
&lt;li&gt;When I specify /usr/bin/mplayer as the application for MP3s,
    there's a long delay -- possibly because the file is still
    downloading? I double-clicked it again, thinking that I might
    have mis-clicked. When it finally started, there were &lt;em&gt;two&lt;/em&gt;
    players playing the same file, and no apparent means of killing
    it through liferea. (Indeed, when I look at the process list, I
    can see that mplayer doesn't show up as a child of liferea.)
    Another nit: when liferea spawns the player, it doesn't send
    stdout/stderr to /dev/null. This means that mplayer spews all
    kind of noise onto the console. (Turns out that totem is a much
    better choice -- so that you have a GUI to pause/stop/kill.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rssowl: (2.1.2-1\~getdeb1)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Best installation experience so far. When starting up for the
    first time, I'm presented with a tutorial and a sort of import
    wizard instead of a lame list of "sample feeds" that I have to
    delete.&lt;/li&gt;
&lt;li&gt;The import process is the best seen so far. It displays the
    feeds from the OPML file, offers to flatten the hierarchy and
    asks where in the existing folder hierarchy to place the new
    feeds.&lt;/li&gt;
&lt;li&gt;Rssowl has the same "itpc: URL" issue mentioned for liferea
    above.&lt;/li&gt;
&lt;li&gt;Podcast support is interesting. When an enclosure is present, an
    icon is shown in the post. You can click the icon and it will
    offer to download the file or create a "filter". Creating a
    filter is like a GMail filter -- it will be performed on every
    post in the feed. In this case, the filter is automatically
    constructed to download the enclosures to my \~/Podcast
    directory. Then, in the window listing all filters, I have the
    option of running this new filter -- which downloads all of the
    enclosures.&lt;/li&gt;
&lt;li&gt;When the enclosure is downloaded, I can click on the icon in the
    post and am presented with "Open abc.mp3". Clicking this
    automatically opens totem -- the application that is registered
    with GNOME to play MP3 files.&lt;/li&gt;
&lt;li&gt;Doesn't leave any turds on the console when run from the command
    line. (To me, this is a sign of polish. Turds aren't really a
    "bug", but they just don't belong there.)&lt;/li&gt;
&lt;li&gt;Searching/filtering is far superior to liferea.&lt;/li&gt;
&lt;li&gt;Paging through articles was a little clunky, though this may
    have been operator error. On the plus side, there are reasonable
    keyboard shortcuts for the common things I tried -- next
    article, next unread article, next feed with unread news, etc.&lt;/li&gt;
&lt;li&gt;Odd behavior: whenever I switch virtual desktops, Rssowl
    minimizes itself to the tray. &lt;em&gt;Really&lt;/em&gt; annoying when you flip
    back and forth between the app and the browser to write a review
    like this...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;yarssr: &lt;strong&gt;Meh&lt;/strong&gt; (0.2.2-5)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is a simple app that displays headlines in the tray.&lt;/li&gt;
&lt;li&gt;Didn't import OPML with nested folders.&lt;/li&gt;
&lt;li&gt;Locks up when updating -- no UI interaction permitted.&lt;/li&gt;
&lt;li&gt;Once updated, you can click to see headlines. Choosing a
    headline opens browser window on the post.&lt;/li&gt;
&lt;li&gt;Doesn't do what I need, but for what it is, it doesn't
    completely suck.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rssreader.app: &lt;strong&gt;DOA&lt;/strong&gt; (0.6.2.dfsg-4build2)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DOA: Probably because it's based on GNUstep and I'm not running
    GNUstep. I installed via apt and it pulled in several packages.
    When I tried to run it, I got a segfault. Didn't do anything
    further, just purged it from my system.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rawdog: &lt;strong&gt;Underpowered&lt;/strong&gt; (2.12.dfsg.1-2)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Except for the rssreader.app segfault, possibly the most
    braindead installation of anything. Running rawdog, you're
    greeted with "No /home/brian/.rawdog directory". Grr. If I
    weren't writing a review, I'd just uninstall it here and never
    look back.&lt;/li&gt;
&lt;li&gt;This is a batch / command line reader. You add feeds via command
    line, run an update command, and run a command to write all of
    the fetched posts into an HTML file. Then you can read the HTML
    file in your browser.&lt;/li&gt;
&lt;li&gt;Doesn't do what I need, but for what it is, it seems to do a
    good job.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;</summary><category term="rss"></category><category term="tools"></category><category term="linux"></category></entry><entry><title>Quick &amp; Dirty “sniffer” for when you don’t have tcpdump</title><link href="http://blog.bstpierre.org/logging-packet-when-you-dont-have-tcpdump" rel="alternate"></link><updated>2012-06-08T19:55:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-06-08:logging-packet-when-you-dont-have-tcpdump</id><summary type="html">&lt;p&gt;I recently needed a way to test if packets were reaching a linux device
I'm testing. The device didn't have tcpdump installed, so I didn't have
a real sniffer. Fortunately all I really needed was confirmation that
packets were arriving. iptables to the rescue! Assuming there are no
existing rules to interfere (i.e. use &lt;code&gt;iptables -F&lt;/code&gt; to clean out any
existing firewall), you can use this to log UDP packets arriving at port
1234:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;iptables&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="n"&gt;INPUT&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;udp&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;dport&lt;/span&gt; 1234 &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;j&lt;/span&gt; &lt;span class="n"&gt;LOG&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then tail your syslog and you'll see a message when traffic arrives on
UDP 1234.&lt;/p&gt;</summary><category term="tip"></category><category term="network"></category><category term="linux"></category></entry><entry><title>Don't Forget dmenu</title><link href="http://blog.bstpierre.org/dont-forget-dmenu" rel="alternate"></link><updated>2012-05-18T19:03:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-05-18:dont-forget-dmenu</id><summary type="html">&lt;p&gt;When you install i3 on Ubuntu via &lt;code&gt;apt-get install i3-wm&lt;/code&gt;, it &lt;em&gt;should&lt;/em&gt;
come with dmenu, but it doesn't.&lt;/p&gt;
&lt;p&gt;Dmenu is a handy launcher that you can (by default) invoke with &lt;code&gt;mod-d&lt;/code&gt;,
then start typing the command you want to run (with dynamic completion)
and press enter when the command you want is highlighted. But it won't
work if you don't have dmenu installed, so be sure to do
&lt;code&gt;apt-get install dmenu&lt;/code&gt;.&lt;/p&gt;</summary><category term="i3"></category><category term="tip"></category><category term="linux"></category></entry><entry><title>Moving Through i3 Workspaces</title><link href="http://blog.bstpierre.org/moving-through-i3-workspaces" rel="alternate"></link><updated>2012-05-16T07:07:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-05-16:moving-through-i3-workspaces</id><summary type="html">&lt;p&gt;The default i3 config file only has commands to move to a specific
numbered workspace (e.g. mod-1, mod-2). For better or worse, I'm in the
habit of "traveling" through the workspace list using Ctrl-Alt-Up and
Ctrl-Alt-Down (or Left and Right, if you prefer a horizontal feel for
your workspaces). This is trivial to do in your &lt;code&gt;~/.i3/config&lt;/code&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;# Use mod-Control-Up and Down to rotate through the workspace list.&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; $&lt;span class="nb"&gt;mod&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Up&lt;/span&gt; &lt;span class="n"&gt;workspace&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; $&lt;span class="nb"&gt;mod&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Down&lt;/span&gt; &lt;span class="n"&gt;workspace&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;

&lt;span class="c"&gt;# Use mod-Shift-Control-Up and Down to move the selected window to an&lt;/span&gt;
&lt;span class="c"&gt;# adjacent workspace. (This does *not* change your view to that workspace,&lt;/span&gt;
&lt;span class="c"&gt;# it just &amp;quot;zaps&amp;quot; the window there.)&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; $&lt;span class="nb"&gt;mod&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Shift&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Up&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="n"&gt;workspace&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; $&lt;span class="nb"&gt;mod&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Shift&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Control&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;Down&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="n"&gt;workspace&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="i3"></category><category term="tip"></category><category term="linux"></category></entry><entry><title>Getting dnsmasq, virtual machines (libvirt), and vpn to inter-operate</title><link href="http://blog.bstpierre.org/getting-dnsmasq-virtual-machines-libvirt-and-vpn-to-inter-operate" rel="alternate"></link><updated>2012-05-15T06:43:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-05-15:getting-dnsmasq-virtual-machines-libvirt-and-vpn-to-inter-operate</id><summary type="html">&lt;h2&gt;Fixing Dnsmasq to Work With Libvirt&lt;/h2&gt;
&lt;p&gt;There's &lt;a href="https://bugs.launchpad.net/ubuntu/+source/libvirt/+bug/231060"&gt;a bug written in the Ubuntu tracker&lt;/a&gt; (and in Fedora too, I
think) about running dnsmasq and libvirt at the same time. Basically,
libvirt runs an instance of dnsmasq, and if you install the system
dnsmasq it tries to bind to all interfaces by default. The "easy"
workaround is to use the interface and bind-interfaces configuration
options in dnsmasq.conf. On my machine, I've got:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eth0&lt;/span&gt;
&lt;span class="n"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;br0&lt;/span&gt;
&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;interfaces&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This makes it so that the system dnsmasq only listens on eth0 and br0,
and does not bind to the wildcard address. Now you can run two instances
of dnsmasq: the system instance, and libvirt's instance.&lt;/p&gt;
&lt;h2&gt;Getting The System Dnsmasq to Answer for the VMs&lt;/h2&gt;
&lt;p&gt;I wish I knew the answer for this: I'd like for my system dnsmasq to
respond to queries for "fedora16" and "winxp" (hostnames of two VMs I
use). I tried a couple of things that didn't work, and settled for a
subpar solution.&lt;/p&gt;
&lt;p&gt;First, I thought I might be able to use a fake domain ".vm" and tell the
system dnsmasq to forward queries for that domain to libvirt's dnsmasq.
This may be a workable solution; it didn't work when I tried it, but I
was fighting other issues with libvirt at the time and I haven't had a
chance to go back and try it again. The problem I ran into is that
libvirt's dnsmasq was receiving queries for "fedora16.vm" but it didn't
have ".vm" configured as a local domain. You can't monkey directly with
libvirt's dnsmasq config (at least not that I saw), or this would be an
easy fix. I tried changing the names assigned in the libvirt XML file
dealing with DHCP names and addresses, but didn't get anywhere. Again,
more experimentation here might win the day. (Definitely let me know if
you know how to solve this.)&lt;/p&gt;
&lt;p&gt;I also flailed around with some other config options trying to get those
names forwarded to libvirt's dnamasq but couldn't come up with anything
clean.&lt;/p&gt;
&lt;p&gt;What I finally settled on is adding the names of the VMs to my local
host file:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;# Virtual Machines; this should match up with the DHCP assignments in&lt;/span&gt;
&lt;span class="c"&gt;# /etc/libvirt/qemu/networks/default.xml&lt;/span&gt;
192&lt;span class="p"&gt;.&lt;/span&gt;168&lt;span class="p"&gt;.&lt;/span&gt;122&lt;span class="p"&gt;.&lt;/span&gt;22 &lt;span class="n"&gt;fedora16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;home&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bstpierre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="n"&gt;fedora16&lt;/span&gt;
192&lt;span class="p"&gt;.&lt;/span&gt;168&lt;span class="p"&gt;.&lt;/span&gt;122&lt;span class="p"&gt;.&lt;/span&gt;23 &lt;span class="n"&gt;winxp1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;home&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bstpierre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt; &lt;span class="n"&gt;winxp1&lt;/span&gt;
&lt;span class="c"&gt;# etc...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This file is referenced by dnsmasq.conf via
&lt;code&gt;addn-hosts=/etc/dnsmasq.hosts&lt;/code&gt;. It's not perfect, since I have to
remember to enter the addresses in two places (default.xml and
dnsmasq.hosts) when I create a new VM, but I don't do that often so it
won't be a huge burden.&lt;/p&gt;
&lt;h2&gt;Getting Names from the VPN&lt;/h2&gt;
&lt;p&gt;When I connect to $DAYJOB VPN, I need to be able resolve names for that
domain from the corporate (internal) DNS servers. Dnsmasq has a config
option that allows you to tie a certain domain to a certain set of DNS
servers:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;172&lt;span class="p"&gt;.&lt;/span&gt;18&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="p"&gt;.&lt;/span&gt;1
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;172&lt;span class="p"&gt;.&lt;/span&gt;18&lt;span class="p"&gt;.&lt;/span&gt;12&lt;span class="p"&gt;.&lt;/span&gt;1
&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;vpnserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;169&lt;span class="p"&gt;.&lt;/span&gt;254&lt;span class="p"&gt;.&lt;/span&gt;123&lt;span class="p"&gt;.&lt;/span&gt;210
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The first two lines set the DNS servers when I'm on the VPN. The last
line makes it so that I have the public address for the VPN server when
I'm not yet connected -- since the domain matches, dnsmasq will try to
forward the request to the internal DNS servers, which is guaranteed not
to work. This is slightly clumsy since the address is hardcoded, but it
doesn't change in practice and I can always query it directly from
upstream DNS (e.g. &lt;code&gt;dig vpnserver.example.com @192.168.1.1&lt;/code&gt; to get it
from my router).&lt;/p&gt;
&lt;p&gt;Finally, I don't want to be making requests to those 172.18 addresses
when I'm not on the VPN, so I set up this interface definition in
/etc/network/interfaces:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;iface&lt;/span&gt; &lt;span class="n"&gt;tun0&lt;/span&gt; &lt;span class="n"&gt;inet&lt;/span&gt; &lt;span class="n"&gt;manual&lt;/span&gt;
&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt; 172&lt;span class="p"&gt;.&lt;/span&gt;18&lt;span class="p"&gt;.&lt;/span&gt;0&lt;span class="p"&gt;.&lt;/span&gt;0&lt;span class="o"&gt;/&lt;/span&gt;16 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="n"&gt;openvpn&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="n"&gt;openvpn&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;blackhole&lt;/span&gt; 172&lt;span class="p"&gt;.&lt;/span&gt;18&lt;span class="p"&gt;.&lt;/span&gt;0&lt;span class="p"&gt;.&lt;/span&gt;0&lt;span class="o"&gt;/&lt;/span&gt;16 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the VPN is taken down, this will add a blackhole route for
172.18.0.0/16, which is where those DNS servers live. Just before the
VPN is started, it removes the blackhole route so that I can get to
those servers once the VPN is ready.&lt;/p&gt;</summary><category term="virtual-machine"></category><category term="network"></category><category term="dns"></category><category term="linux"></category></entry><entry><title>Controlling Audio with i3</title><link href="http://blog.bstpierre.org/controlling-audio-with-i3" rel="alternate"></link><updated>2012-05-12T07:52:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-05-12:controlling-audio-with-i3</id><summary type="html">&lt;p&gt;As I mentioned in the &lt;a href="http://blog.bstpierre.org/making-caps-lock-an-additional-control-in-xwindows"&gt;last post&lt;/a&gt;, I've switched to the i3 window
manager. After fixing the Caps Lock key, the next order of business is
getting the audio keys on my multimedia keyboard to control audio
output. (It's nice to be able to quickly mute or pause when the phone
rings, for example.)&lt;/p&gt;
&lt;p&gt;This turns out to be pretty easy. I used xev to figure out the keysyms
for the audio keys, and then added bindings to ~/.i3/config:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;bindsym&lt;/span&gt; &lt;span class="n"&gt;XF86AudioRaiseVolume&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;pactl&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt; 0 &lt;span class="o"&gt;+&lt;/span&gt;5&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; &lt;span class="n"&gt;XF86AudioLowerVolume&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;pactl&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt; 0 &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;5&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; &lt;span class="n"&gt;XF86AudioMute&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;toggle&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mute&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; &lt;span class="n"&gt;XF86AudioPlay&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;clementine&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pause&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; &lt;span class="n"&gt;XF86AudioStop&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;clementine&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; &lt;span class="n"&gt;XF86AudioPrev&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;clementine&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt;
&lt;span class="n"&gt;bindsym&lt;/span&gt; &lt;span class="n"&gt;XF86AudioNext&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;clementine&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As you can see, I prefer clementine as an audio player. If you have
something different that you like, look up the command line method for
controlling the player.&lt;/p&gt;
&lt;p&gt;The pactl command controls pulse audio. The toggle script is something I
threw together because pactl doesn't support toggling the mute, you have
to tell it mute or unmute:&lt;/p&gt;
&lt;table class="codehilitetable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;toggle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;pactl list sinks &lt;span class="o"&gt;|&lt;/span&gt; grep &lt;span class="o"&gt;-&lt;/span&gt;q Mute:.no &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
pactl &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;sink-mute &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;$toggle&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;

&lt;p&gt;I only have one sink, so if you have more than one, you'll need to
adjust the script.&lt;/p&gt;</summary><category term="i3"></category><category term="tip"></category><category term="linux"></category></entry><entry><title>Making Caps Lock an Additional Control in XWindows</title><link href="http://blog.bstpierre.org/making-caps-lock-an-additional-control-in-xwindows" rel="alternate"></link><updated>2012-05-11T06:49:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-05-11:making-caps-lock-an-additional-control-in-xwindows</id><summary type="html">&lt;p&gt;Background: I just built a new computer, and "upgraded" to Ubuntu 12.04.
I've been a fan of Ubuntu for a long time (first Debian, then Ubuntu
because of the faster / more predictable release cycle). But you can add
me to the list of people unimpressed with the "Unity" desktop
environment, and not terribly impressed by Gnome 3. Since I'm in the
middle of an upgrade and learning about new features anyway, I figured
I'd make the leap to a better window manager. I've settled on &lt;a href="http://www.i3wm.org/" title="i3 Window Manager"&gt;i3&lt;/a&gt;,
which gives me a lot of power over how things are arranged. Of course it
also means that I've got to configure a bunch of stuff the way I want
it, more so than with the default Gnome/Unity/Metacity scheme. This
potentially means some short-ish posts here about tweaks &amp;amp; tips for
working with i3.&lt;/p&gt;
&lt;p&gt;The first item of business is getting rid of Caps Lock. The example
config in xmodmap(1) will &lt;em&gt;swap&lt;/em&gt; your caps lock and control, which is
definitely not what I want. The key to the left of the "A" (which
happens to be labeled Caps Lock) should be treated the same as the left
Control key. So I created &lt;code&gt;~/.xmodmap&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="sx"&gt;!&lt;/span&gt;
&lt;span class="sx"&gt;! Make Caps_Lock an additional Control key&lt;/span&gt;
&lt;span class="sx"&gt;! (This is *DIFFERENT* than *swapping* Caps_Lock and Control, as described&lt;/span&gt;
&lt;span class="sx"&gt;! in the xmodmap(1) man page.)&lt;/span&gt;
&lt;span class="sx"&gt;!&lt;/span&gt;
&lt;span class="n"&gt;keycode&lt;/span&gt; 66 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt;
&lt;span class="n"&gt;clear&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;
&lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;Control&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Control_L&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then, in your &lt;code&gt;~/.i3/config&lt;/code&gt; add:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;exec_always&lt;/span&gt; &lt;span class="n"&gt;xmodmap&lt;/span&gt; &lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xmodmap&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;(Note that if you use the xmodmap commands provided in the xmodmap man
page, then restarting i3 will &lt;em&gt;toggle&lt;/em&gt; that key between Caps_Lock and
Control, which is probably not what you want! It seems that you also
lose the swap if you do things like run xev.)&lt;/p&gt;</summary><category term="i3"></category><category term="tip"></category><category term="linux"></category></entry><entry><title>GTK3 Example Code</title><link href="http://blog.bstpierre.org/gtk3-example-code" rel="alternate"></link><updated>2012-05-07T04:38:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-05-07:gtk3-example-code</id><summary type="html">&lt;p&gt;Over the past year or so I've been doing a fair bit of GUI programming
-- both in ${DAYJOB}s and otherwise. Until just recently most of my GUI
experience has been with wxWidgets (and most of that via wxPython).
Lately I've wandered to GTK.&lt;/p&gt;
&lt;p&gt;The documentation for both toolkits is pretty good. I've found GTK's API
reference documentation to be very thorough and mostly well detailed.
The tutorials are especially good for an experienced programmer who
knows his way around a GUI and just needs to learn how the toolkit
works.&lt;/p&gt;
&lt;p&gt;What I find lacking are a comprehensive set of tightly focused example
programs. Take the &lt;a href="http://developer.gnome.org/gtkmm-tutorial/3.0/sec-clipboard-examples.html.en"&gt;clipboard example&lt;/a&gt; from gtkmm, for instance. It's
supposed to be showing how to access the clipboard, but it's all
cluttered up with irrelevant buttons and signals and stuff. Sure, it's
not &lt;em&gt;that&lt;/em&gt; hard to read through the code to pick out the relevant
pieces, but why not produce a simple example app that's &lt;em&gt;all&lt;/em&gt; relevant
(or nearly all -- you can't avoid some of the boilerplate)?&lt;/p&gt;
&lt;p&gt;And some topics are not covered at all -- like accelerator groups, where
the API documentation says "you won't usually need these". But sometimes
you &lt;em&gt;do&lt;/em&gt;, and then it's hard to find an example of correct usage. (Yes,
I figured it out, and it didn't take too long using the API reference,
but a 25-line example program would have saved me some time.)&lt;/p&gt;
&lt;p&gt;Don't misunderstand the point of this post: I'm not complaining about
the state of the documentation, which is better than the docs for 98.3%
of FOSS projects. Instead I'm proposing a collection of &lt;a href="https://github.com/bstpierre/gtk-examples"&gt;ultra-focused
example programs&lt;/a&gt; for the breadth of the API. This will be a
complement to the tutorials and reference docs.&lt;/p&gt;
&lt;p&gt;It will ideally be against multiple language bindings (though I've found
that an example in the C API is informative on how things should work in
the Python or C++ binding, and vice versa). I've already got a few
examples (C only so far) in the GitHub repository, and if life doesn't
get in the way, I'll be adding more on a regular basis. In my dreams,
I'll post some of the examples here with more explanation, but we'll see
how that goes...&lt;/p&gt;
&lt;p&gt;If you'd like to contribute a tightly focused example on some GTK
feature, send a pull request on GitHub. If you'd like to &lt;em&gt;request&lt;/em&gt; an
example, open an issue ticket there.&lt;/p&gt;</summary><category term="gui"></category><category term="gtk"></category><category term="tutorial"></category></entry><entry><title>User Interface Hall of Fame</title><link href="http://blog.bstpierre.org/user-interface-hall-of-fame" rel="alternate"></link><updated>2012-05-05T21:38:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-05-05:user-interface-hall-of-fame</id><summary type="html">&lt;p&gt;A long, long time ago, I talked about a &lt;a href="http://blog.bstpierre.org/user-interface-hall-of-shame" title="User Interface Hall of Shame"&gt;poor user interface&lt;/a&gt;. This is
the follow-up "good user interface" post that I promised.&lt;/p&gt;
&lt;p&gt;I'd like to talk about Anki, which is a flashcard program. The user
interface is well done for several reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is uncluttered: in the main screen you are presented with the
    flash card decks that you have downloaded. You can open a deck (i.e.
    to study the deck) by clicking the button right next to the deck.
    The number of cards that you have "due" to study today is shown
    right there.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you've opened a deck, you are presented with the "Study
    Options" screen. At this point, the main thing you want to do is
    review your cards. Again, this is a big "review" button, and it is
    selected/highlighted by default so you can just press enter/spacebar
    to move into review mode.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(I will note that the Study Options screen might be
    overcomplicated. There are a number of options here, and some
    information presented that does not seem essential. I'd say it's
    a minor fault that does not detract overall from the simplicity
    of this application.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When reviewing, you are presented with one "face" of a flashcard and
    a "Show Answer" button. Again the button is selected/highlighted by
    default so you can just press spacebar to see the answer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once the answer is revealed, you click a button to say if you got
    the answer correct, and how easy it was for you to know the answer.
    So, for example, you're studying a French-&gt;English deck, and the
    face of the card is "non". Well, that's an easy one, it's "no". So
    when asked, you press the Very Easy button. Or, in a nice interface
    touch, you press "4", since the buttons are mapped to the first four
    number keys: "Again" (meaning incorrect) is 1, "Good" is 2, "Easy"
    is 3, and "Very Easy" is 4.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because of the layout of the buttons, the UI can be driven very
    quickly and easily from the keyboard: see a card, say the
    answer, press space to show the answer with the thumb, press 1-4
    with one of the fingers, and then the next card is shown.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unobtrusive status information is shown in the bottom left
    statusbar. It shows the number of failed cards that you need to
    review, the number of cards awaiting review, and the number of new
    cards today. It also shows the amount of time that it expects you
    have remaining to study, based on your average time to answer each
    card and the number of cards remaining. And then there are small
    graphs that indicate by color and size whether you've done well
    (green, &gt;80% correct) or poorly (red, &amp;lt;50% correct).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Anki also has a facility for editing cards, but I'm not going to review
that. The primary study feature is really the key piece of the
application, and it deserves a spot in the UI Hall of Fame.&lt;/p&gt;
&lt;/p&gt;</summary></entry><entry><title>User Interface Hall of Shame</title><link href="http://blog.bstpierre.org/user-interface-hall-of-shame" rel="alternate"></link><updated>2012-02-22T10:06:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-02-22:user-interface-hall-of-shame</id><summary type="html">&lt;p&gt;I'm taking &lt;a href="http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-831-user-interface-design-and-implementation-fall-2004/index.htm"&gt;MIT's 6.831: User Interface Design and Implementation&lt;/a&gt;
through their free OpenCourseWare website. One of the homework
assignments is to find two examples each for the "UI Hall of Shame" and
the "UI Hall of Fame". This post is for shame -- and since the nominee
has so many things to discuss I'm only doing one; I'll follow up with
fame in another post.&lt;/p&gt;
&lt;p&gt;My nominee for the UI Hall of Shame is &lt;strong&gt;gmusicbrowser&lt;/strong&gt;, which has a
number of UI problems. I used this app for a while last year because it
has a couple of neat features that I liked, but I got frustrated by it
(and it doesn't tie into my multimedia keyboard play/stop/forward keys),
so I moved on to something else.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://blog.bstpierre.org/static/images/Screenshot-gmusicbrowser.png"&gt;&lt;img alt="Screenshot of gmusicbrowser for UI Hall of Shame" src="http://blog.bstpierre.org/static/images/Screenshot-gmusicbrowser-300x152.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A minor point, but the "About" dialog is not on the Help menu, it's
    on "Main" (which is nonstandard; it's where you'd normally find a
    "File" menu).&lt;/li&gt;
&lt;li&gt;There isn't a "Help" menu.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The two (three? four?) icons below the menu are confusing at best.
    None of the following behaviors are what you'd expect, and not
    everything is consistent.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To the right of the icons is a pair of numbers, like
    "4972/6158". This represents the sequence number of the playing
    song and the number of total songs in the currently displayed
    playlist. Nothing wrong with this -- but we'll come back to this
    in a minute.&lt;/li&gt;
&lt;li&gt;The first icon can be clicked to toggle between in-order and
    weighted-random modes. By right-clicking you can get a context
    menu to adjust the in-order mode (e.g. Artist/Album or
    Path/Track#). The selection for weighted-random mode is on a
    submenu of that same context menu.&lt;/li&gt;
&lt;li&gt;When you select a mode, that menu item gets a little indicator
    dot. It might be my desktop color scheme, but white-on-gray is
    hard to see with a dot that small. Firefox, for example, uses a
    checkmark which is easier to see even though it's still the same
    low contrast.&lt;/li&gt;
&lt;li&gt;Choosing a weighted-random mode changes the icon to dice.&lt;/li&gt;
&lt;li&gt;You can choose "Shuffle" from the menu, in which case the icon
    changes to playing cards.&lt;/li&gt;
&lt;li&gt;Clicking (left-click) on the dice or cards icon changes it back
    to the arrow icon and sets the mode to an in-order mode.&lt;/li&gt;
&lt;li&gt;Clicking on the arrow icon changes it to whichever
    random/shuffle mode was previously in use.&lt;/li&gt;
&lt;li&gt;The second icon starts as a book. I'm not sure what this is
    supposed to mean. When you hover over it, it says "Playlist
    Filter: All". Again you can right click for a context menu of
    filters to apply to the playlist, in which case the icon changes
    to a funnel. (And if a song is playing, it will abruptly change
    to whatever is first in the selected filter.)&lt;/li&gt;
&lt;li&gt;Clicking (left-click) on the book icon doesn't seem to do
    anything.&lt;/li&gt;
&lt;li&gt;Clicking on the funnel changes it back to a book (and the "All"
    filter?).&lt;/li&gt;
&lt;li&gt;Not that it would make much sense, but for consistency I'd
    expect a left click on this icon to toggle between "All" and the
    last selected filter.&lt;/li&gt;
&lt;li&gt;Next to the book/funnel icon is a blank space... that becomes an
    arrow/triangle ("play button") icon when you hover, with the
    tooltip "Queue empty". And when you move your mouse off the
    icon, it disappears.&lt;/li&gt;
&lt;li&gt;If you enqueue a song, that queue icon appears. And the pair of
    numbers mentioned above is replaced by the text "1 song in
    queue". But the playlist doesn't change! If it's still got 50
    songs, you're still listening to, say, 12/50.&lt;/li&gt;
&lt;li&gt;Clicking on the queue icon... empties the queue! Which is very
    much not what you'd expect. Of course, since the queue is empty,
    the icon disappears and we get back our "NNN/XXX" text mentioned
    in the first bullet above.&lt;/li&gt;
&lt;li&gt;Now let's say you've got 5000 songs in your collection, but
    you're listening to a playlist that has 10 songs. You see, say,
    2/10 in the indicator, and you've got the funnel icon (not the
    book). Just for fun, you enqueue a song using the main browser
    window. After the current song is playing, your queued song will
    play -- and the funnel icon will switch to the book icon: your
    selected filter has been cancelled.&lt;/li&gt;
&lt;li&gt;There are more unpredictable behaviors buried in these icons,
    but &lt;strong&gt;what all of this boils down to&lt;/strong&gt; is that there are too
    many modes, they are not orthogonal, it's too easy to
    accidentally change from one mode to another, it's not easy to
    know what mode you're about to switch into, and it's &lt;em&gt;really&lt;/em&gt;
    easy to lose work -- by accidentally clearing a bunch of queued
    songs.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Just below those icons are a mostly standard set of
    back/stop/play/forward icons... and a volume control that is really
    hard to use. Clicking the volume icon pops up a slider that
    disappears as soon as you release the mouse button. If you hold down
    the mouse button, the slider stays displayed, but you can't move the
    slider thumb. Double clicking the icon (which I discovered
    accidentally) makes the slider stay, and then you can move the thumb
    -- as long as you don't accidentally move the pointer outside the
    slider, in which case it disappears again. Right clicking toggles
    mute, which isn't intuitive, but if you &lt;em&gt;really&lt;/em&gt; want to tie a
    behavior to right clicking on that icon, I suppose is reasonable.
    For mute, however, it's totally unusable: that icon is too small of
    a target, and when I want to mute, I'm usually trying to do it
    somewhat quickly -- so I can answer a ringing phone, for example.
    The mute button on my "multimedia" keyboard is a much easier target
    to hit quickly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;The main browser window is a mystery to me. Starting at the top,
    there are two tabs: Library and Context. The only thing in the
    Context tab is a checkbox for "Follow Playing Song"; I'm not really
    sure what this does. Assuming it does something meaningful, it would
    make much more sense to put this on a menu somewhere.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Library tab is where you can browse your music library. There
    are three listboxes at the top of the window, each with the same set
    of tabs. Those tabs change the listbox contents so you can choose
    various filters. Selecting a filter in the leftmost box (e.g.
    clicking on an album name after you've clicked the Album tab in that
    box) filters the contents of the left two listboxes. In this way you
    can drill down by choosing up to three filters. E.g. "Rock" in the
    left list from the Genre tab, then 1988 in the middle list from the
    Date tab, then Money for Nothing in the right list from the Album
    tab. This is kind of a clumsy approach to filtering, but in practice
    you don't need more than three filter selections to narrow even a
    large library significantly. The filters are a clumsy but reasonable
    approach. The difficulties start with the bells and whistles. You
    can right click on any of the three listboxes for a context menu.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There's no other way to access the features in the context menu
    except by right clicking on the list. But right clicking on the
    list selects the list item where you clicked -- a minor
    annoyance.&lt;/li&gt;
&lt;li&gt;One of the menu options is "Show Buttons", which makes a toolbar
    appear below the listbox. Seriously? Why not just show the
    toolbar all the time?&lt;/li&gt;
&lt;li&gt;A minor point: one of the buttons is a broom icon for "Clear
    Selection". When you click this, the selected item in the
    listbox above is deactivated. But the item is still selected.
    The selection should instead be removed.&lt;/li&gt;
&lt;li&gt;The buttons that are displayed depend on which tab is selected.
    This is nonstandard: normally you &lt;em&gt;disable&lt;/em&gt; a button instead of
    hiding it if the functionality is unavailable for the current
    mode.&lt;/li&gt;
&lt;li&gt;There's an "Intersection Mode" toggle button. I haven't been
    able to figure out what this does; nothing meaningful as far as
    I can tell.&lt;/li&gt;
&lt;li&gt;There's an "Options" button that displays a menu of display
    options.&lt;/li&gt;
&lt;li&gt;There are a confusing array of display options, all of which
    depend on the currently selected filter tab. When looking at the
    Date tab, for example, you can choose between a standard list or
    "cloud" mode. (I'll admit that having cloud mode available is
    kind of nice -- I can see 30+ years all at once instead of
    having to scroll.) If I switch to the Album tab, there are many
    more choices: six different sort modes; optionally displaying
    year, song count and running length of each album; five choices
    for showing album pictures and size to show; and the choice of
    cloud or "mosaic" mode. Mosaic mode is like "Icon View" in
    Nautilus -- albums are listed left-to-right with art shown where
    present and text where no image is available.&lt;/li&gt;
&lt;li&gt;I won't get into all of the behavior here, but the bottom line
    is: &lt;strong&gt;too many modes&lt;/strong&gt;, which are not orthogonal, and your
    options disappear depending on mode -- without having a clear
    path to getting back to the option you were using.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Just below the listboxes (and optionally the listbox-specific
    toolbars) is another toolbar with a filtering control, a few
    buttons, and some menus. The menus don't belong here: they belong in
    the app's main menu bar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;The "Saved List" feature is completely unusable. There's a menu
    option "Save current list as" that can only be found on the Saved
    tab in the filter listboxes -- but you have to be careful to click
    on the "Saved Lists" item in that list, because it's the only place
    you can right click that won't alter the filter contents displayed
    in the main window. I don't know if the feature is buggy or what,
    but clicking on a saved list doesn't consistently display the
    contents of that list.&lt;/li&gt;
&lt;li&gt;Overall, I'd put it down to a bad case of featuritis. I don't think
    there's a widget that hasn't been used in the UI, things are in
    nonstandard places, features are hidden in context menus, and there
    are way too many unexpected behaviors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If I were going to implement a similar music player with advanced,
highly flexible filtering; playlist construction; an ad-hoc queue; and
flexible playback sequencing (e.g. in-order with various sorts, or
weighted random according to various criteria) I would:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make three explicit playback modes: queue, filter, or playlist. You
    can edit any object regardless of which is playing, but, for
    example, changes made to the queue don't affect playback in playlist
    mode. This makes it easier for the user to figure out what he's
    currently working with -- and would actually enable new features
    like "save current queue to playlist".&lt;/li&gt;
&lt;li&gt;Similarly make three explicit editor/browser modes. You're editing a
    &lt;em&gt;specific&lt;/em&gt; (i.e. named) filter or playlist, or you're modifying the
    queue.&lt;/li&gt;
&lt;li&gt;The player and the browser/editor would be distinct. Changes in the
    browser don't implicitly change what's happening in the player
    (except for the queue -- which you expect to make fluid changes to
    the player). If you're editing the actively playing playlist, you
    have to explicitly &lt;em&gt;save&lt;/em&gt; your changes to the playlist for them to
    take effect. There are no editing controls in the player (e.g.
    moving songs up and down in the list).&lt;/li&gt;
&lt;li&gt;Retain some of the context menus where they apply to the selected
    object (e.g. "Song Properties" when you click on a song in a list),
    but I would move many options into the main menu.&lt;/li&gt;
&lt;li&gt;Use a more familiar UI for building filters. Something like what you
    see in the "Rules" window for email clients, for example -- AND/OR
    selections along with different filter rules. This would let the
    user build filters just as complex as gmusicbrowser allows but
    easier to navigate (in fact, gmusicbrowser has a window with this
    sort of interface).&lt;/li&gt;
&lt;li&gt;Retain (and expand) the status information: song index, total song
    count, total play time, remaining play time, etc. Probably in a
    status bar, but possibly in the header of the player.&lt;/li&gt;
&lt;li&gt;I didn't mention it above, but I'd retain the awesome bulk modify
    feature with some tweaks for usability. (Feature is cool, usability
    in gmusicbrowser is fair to poor.) This feature allows you, for
    example, to rename a whole batch of files according to a template,
    e.g. "&amp;lt;ALBUM&gt; - &amp;lt;TRACK#&gt; - &amp;lt;TITLE&gt;.mp3". It also allows you to
    make bulk changes to the ID3 tags on a bunch of files all at once.&lt;/li&gt;
&lt;li&gt;Integrate with mmkeys.&lt;/li&gt;
&lt;li&gt;Minimize to the tray, and possibly have a shrunken player window
    that just shows a standard set of stop/play/pause/forward/backward
    icons, title, etc -- instead of needing a big library browser and/or
    playlist window open all the time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;</summary></entry><entry><title>Maintaining "Privacy" Under the New Google Privacy Policy</title><link href="http://blog.bstpierre.org/maintaining-privacy-new-google-policy" rel="alternate"></link><updated>2012-02-02T21:59:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-02-02:maintaining-privacy-new-google-policy</id><summary type="html">&lt;p&gt;I put "privacy" in quotes, because under the scheme below you're still
giving up a lot of data, but you can (I think) still maintain "silos" of
data so that, for example, your search data isn't correlated with your
YouTube or GMail data.&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://www.eff.org/deeplinks/2012/02/what-actually-changed-google%27s-privacy-policy"&gt;What Actually Changed in Google’s Privacy Policy&lt;/a&gt; from the EFF
for a good description of the substantive changes in Google's new
privacy policy.&lt;/p&gt;
&lt;p&gt;Here's what I'm doing to try to preserve some of my data in silos. &lt;em&gt;This
is based on my understanding of the way the new privacy policy works,
which could be faulty. Remember that you're still giving up your data by
using these services.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In Firefox, I've installed &lt;a href="https://duckduckgo.com/"&gt;Duck Duck Go&lt;/a&gt;
    as a search engine, and I use it as the default. I've been using
    this for months now, and it doesn't suck. The results are &lt;em&gt;almost&lt;/em&gt;
    on par with the results I used to get through Google, and they
    have a &lt;a href="https://duckduckgo.com/privacy.html"&gt;surprisingly "private" privacy
    policy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In Firefox, I've installed &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/ghostery/"&gt;Ghostery&lt;/a&gt;. This blocks all kinds of
    trackers, &lt;em&gt;including Google Analytics&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;I've switched from using the GMail web UI to a desktop client.
    Google still sees all my email, but I don't have to remain logged in
    to that account in the browser. (See #4.) &lt;em&gt;This effectively makes
    the mail account a silo; none of the data is used elsewhere.&lt;/em&gt; I
    occasionally have the fantasy that I'm going to move my domain off
    Google Apps and go back to self-hosting, but that's unlikely to
    happen in the near future.&lt;/li&gt;
&lt;li&gt;When I'm browsing, I'm logged into a second Google account that is
    disconnected from my primary mail account. (See #3.) This means
    that, for example, videos that I watch on YouTube aren't correlated
    to the content of my mail.&lt;/li&gt;
&lt;li&gt;In Firefox, I've installed &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/adblock-plus/"&gt;Adblock Plus&lt;/a&gt;. &lt;em&gt;This cuts down on
    &lt;strong&gt;tons&lt;/strong&gt; of ads; which might avoid leaking your data to
    advertisers.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;I haven't done it yet, but I plan to find a desktop RSS reader that
    doesn't suck and delete everything from Google Reader.&lt;/li&gt;
&lt;li&gt;I've stopped checking in at Google News. Instead I pick one of CNN,
    News360, BBC, or other news outlet.&lt;/li&gt;
&lt;li&gt;I'm not sure what it would be, but if I'm browsing stuff that I
    don't want tracked, I'll fire up a Chrome Incognito session and
    proxy everything through Tor. (The only reason for using Chrome is
    that I typically don't want to interrupt a Firefox session to drop
    into Private Browsing mode there.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It's worth noting that I don't use G+, so I'm not doing anything
specific to maintain a silo there.&lt;/p&gt;
&lt;p&gt;As noted above, most of this doesn't vastly enhance your privacy. You're
still handing out a lot of data every time you browse the web; it's a
fact of life. And it is, after all, very convenient to have access to
your search history -- I often find myself trying to figure out what
that thing was that I found during a search a couple of weeks ago but
can't remember the terms I used. But I'm hoping that it cuts down on the
amount of data correlation between my various online activities.&lt;/p&gt;</summary></entry><entry><title>Turning Photos to a DVD Slideshow</title><link href="http://blog.bstpierre.org/photos-to-dvd-slideshow" rel="alternate"></link><updated>2012-01-24T10:36:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2012-01-24:photos-to-dvd-slideshow</id><summary type="html">&lt;p&gt;The task: take a bunch of photos from my digital camera and burn them to
a DVD that would play the photos as a slideshow in any DVD player.
(Actually, the task was to figure out how to do this in the simplest
possible way, and make a set of instructions for an unsophisticated user
to follow.)&lt;/p&gt;
&lt;p&gt;The result: several hours of fighting with a dozen different
applications before I actually had photos displayed on my TV. And this
post.&lt;/p&gt;
&lt;p&gt;I don't understand why what seems like a fairly simple task is so
complicated.&lt;/p&gt;
&lt;p&gt;This is what I found to be the easiest recipe on a stock Ubuntu 11.10
installation. Fair warning: this is quick &amp;amp; dirty, you don't get special
effects, you don't get music, etc.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Required software (load through the software center, or via
    apt-get): imagination, devede, brasero.&lt;/li&gt;
&lt;li&gt;Open Imagination. Click the import photos icon (it is a terrible
    choice of icon -- there's no clue that you should want to click on
    the black square to import photos) and choose your photos.&lt;/li&gt;
&lt;li&gt;Export video. Choose DVD for output; to keep it organized, drop the
    video file in the Videos directory in your home directory. This can
    take a while if you have a slow computer and/or a lot of photos.&lt;/li&gt;
&lt;li&gt;Open DeVeDe. Configure the title. Import the video you just
    generated.&lt;/li&gt;
&lt;li&gt;Generate the DVD ISO image; again save the image to your Videos
    folder.&lt;/li&gt;
&lt;li&gt;Insert a DVD in the drive. Open your Videos folder. Right click on
    the ISO image and choose Write to Disc.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Some notes, in no particular order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you want to tweak the slideshow, imagination will let you play
    around with transitions, slide duration, etc.&lt;/li&gt;
&lt;li&gt;If you want to import video clips, use Openshot instead of
    Imagination. It's harder to use (and overkill) compared to
    Imagination if all you want to do is produce a static photo
    slideshow. But if you want to do any video editing, it's a good
    combination of power and ease of use.&lt;/li&gt;
&lt;li&gt;If you want music, Openshot might also be your best bet. I didn't
    look at adding music via Imagination or DeVeDe.&lt;/li&gt;
&lt;li&gt;Don't try to use the Brasero application directly. I experienced
    crashes/lockups while using it, but it worked well when all I did
    was right click the ISO and choose Write to Disc. (It's also an
    ugly, hard to use piece of junk.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Things I tried and rejected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;K3b: After Brasero failed miserably twice, I tried K3b. Rejected it
    in favor of not pulling in a bunch of KDE infrastructure that I
    wasn't using anyway, and also in favor of the simplicity of just
    directly burning the file without having to explicitly open a
    separate app.&lt;/li&gt;
&lt;li&gt;dvd-slideshow: This command line app does appeal to my inner geek,
    but I can't recommend it for users who want a GUI. (Even the inner
    geek appeal isn't enough to make me want to use it when the
    combination outlined above works well.)&lt;/li&gt;
&lt;li&gt;Mistelix: As good as Imagination, and I would have selected it if I
    had quickly found a way to rotate a photo from within Mistelix.
    Photos that showed up auto-rotated in other apps showed up sideways
    in Mistelix and I didn't see an obvious rotate button. Imagination
    did the right thing.&lt;/li&gt;
&lt;li&gt;Bombono: Junk. Don't bother with it. I opened it, saw that the tabs
    were overlapping the menus, couldn't figure out how to import any
    photos, and purged it from my system.&lt;/li&gt;
&lt;li&gt;Videoporama: Development is dead. It wasn't immediately obvious that
    it was going to be easy to use for the simple task of creating a DVD
    from a set of photos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The software stack above would be 10x better if Imagination implemented
a feature to export a DVD ISO. It would be 100x better if it had a "Burn
DVD" button that spit out a shiny disc that I could drop into a DVD
player.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
(To be fair, the whole video situation is 1000x easier than it was 5 or
6 years ago when I tried to do something similar, realized how much work
it was going to be, and walked away in frustration. The tools have come
a long way.)&lt;/p&gt;</summary></entry><entry><title>Tightening UFW Firewall Rules to Limit SSH Access</title><link href="http://blog.bstpierre.org/ufw-firewall-limit-ssh" rel="alternate"></link><updated>2011-12-08T18:54:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-12-08:ufw-firewall-limit-ssh</id><summary type="html">&lt;p&gt;The auth.log on one of my servers (really, on &lt;em&gt;all&lt;/em&gt; of the servers I
have access to) is full of stuff like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;Dec&lt;/span&gt;  8 03&lt;span class="p"&gt;:&lt;/span&gt;19&lt;span class="p"&gt;:&lt;/span&gt;33 &lt;span class="n"&gt;localhost&lt;/span&gt; &lt;span class="n"&gt;sshd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;4718&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; 10&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="p"&gt;.&lt;/span&gt;2&lt;span class="p"&gt;.&lt;/span&gt;3 &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt;
&lt;span class="n"&gt;Dec&lt;/span&gt;  8 03&lt;span class="p"&gt;:&lt;/span&gt;19&lt;span class="p"&gt;:&lt;/span&gt;35 &lt;span class="n"&gt;localhost&lt;/span&gt; &lt;span class="n"&gt;sshd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;4721&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;Invalid&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;db2inst1&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; 10&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="p"&gt;.&lt;/span&gt;2&lt;span class="p"&gt;.&lt;/span&gt;3
&lt;span class="n"&gt;Dec&lt;/span&gt;  8 03&lt;span class="p"&gt;:&lt;/span&gt;19&lt;span class="p"&gt;:&lt;/span&gt;38 &lt;span class="n"&gt;localhost&lt;/span&gt; &lt;span class="n"&gt;sshd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;4723&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; 10&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="p"&gt;.&lt;/span&gt;2&lt;span class="p"&gt;.&lt;/span&gt;3 &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;fail2ban is configured to (temporarily) block these after a certain
number of attempts, but they keep coming back. One particular IP address
was hitting ssh constantly (except for the ban periods) for several
days, so I added a rule to drop everything from that address -- but this
strategy isn't scalable.&lt;/p&gt;
&lt;p&gt;The simple, obvious solution is to only allow access from known hosts.
It would be very rare that I need to access this server from anywhere
except a small number of addresses. I started with a ufw (Ubuntu's
"uncomplicated firewall") ruleset like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;% sudo ufw status&lt;/span&gt;
&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;activeTo&lt;/span&gt;                         &lt;span class="n"&gt;Action&lt;/span&gt;      &lt;span class="n"&gt;From&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt;                         &lt;span class="o"&gt;------&lt;/span&gt;      &lt;span class="o"&gt;----&lt;/span&gt;
&lt;span class="n"&gt;Anywhere&lt;/span&gt;                   &lt;span class="n"&gt;DENY&lt;/span&gt;        10&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="p"&gt;.&lt;/span&gt;2&lt;span class="p"&gt;.&lt;/span&gt;3
&lt;span class="n"&gt;Anywhere&lt;/span&gt;                   &lt;span class="n"&gt;DENY&lt;/span&gt;        172&lt;span class="p"&gt;.&lt;/span&gt;16&lt;span class="p"&gt;.&lt;/span&gt;99&lt;span class="p"&gt;.&lt;/span&gt;88
22                         &lt;span class="n"&gt;ALLOW&lt;/span&gt;       &lt;span class="n"&gt;Anywhere&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To avoid getting locked out, I added (temporarily) a crontab entry for
root:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="o"&gt;*/&lt;/span&gt;15 &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sbin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ufw&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;ssh&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will allow access to ssh from any un-banned IP address (the current
policy) every 15 minutes. Then I inserted a rule that allows access to
ssh from my home ISPs netblock. (You can figure out the netblock by
doing a whois lookup on your external IP address; you may need to add
multiple netblocks if your ISP has several allocated. Be careful: it's
no fun to get locked out!) This action is safe because I have not yet
removed global ssh access. Note that I'm using "insert 3" to add this
rule at a specific position in the list.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;ufw&lt;/span&gt; &lt;span class="n"&gt;insert&lt;/span&gt; 3 &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;proto&lt;/span&gt; &lt;span class="n"&gt;tcp&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; 10&lt;span class="p"&gt;.&lt;/span&gt;9&lt;span class="p"&gt;.&lt;/span&gt;8&lt;span class="p"&gt;.&lt;/span&gt;0&lt;span class="o"&gt;/&lt;/span&gt;18 &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; 22
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And I added a rule to permit access from another server with a fixed IP
I have access to (for the rare case where I need to access this server
when I'm not at home):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;ufw&lt;/span&gt; &lt;span class="n"&gt;insert&lt;/span&gt; 3 &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;proto&lt;/span&gt; &lt;span class="n"&gt;tcp&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; 172&lt;span class="p"&gt;.&lt;/span&gt;17&lt;span class="p"&gt;.&lt;/span&gt;101&lt;span class="p"&gt;.&lt;/span&gt;102 &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; 22
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now my ruleset looks like:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;activeTo&lt;/span&gt;                         &lt;span class="n"&gt;Action&lt;/span&gt;      &lt;span class="n"&gt;From&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt;                         &lt;span class="o"&gt;------&lt;/span&gt;      &lt;span class="o"&gt;----&lt;/span&gt;
&lt;span class="n"&gt;Anywhere&lt;/span&gt;                   &lt;span class="n"&gt;DENY&lt;/span&gt;        &lt;span class="mf"&gt;10.1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;2.3&lt;/span&gt;
&lt;span class="n"&gt;Anywhere&lt;/span&gt;                   &lt;span class="n"&gt;DENY&lt;/span&gt;        &lt;span class="mf"&gt;172.16&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;99.88&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt;                     &lt;span class="n"&gt;ALLOW&lt;/span&gt;       &lt;span class="mf"&gt;172.17&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;101.102&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="sr"&gt;/tcp                     ALLOW       10.9.8.0/&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt;                         &lt;span class="n"&gt;ALLOW&lt;/span&gt;       &lt;span class="n"&gt;Anywhere&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So far, so good, but ssh access is still permitted from anywhere --
because of that last rule. This is the dangerous part... you could get
locked out if you haven't set the rules correctly. (You set up that
crontab entry, right?)&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;ufw&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;ssh&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Wait for the prompt... hooray, I'm still connected! After checking that
I can access ssh from home and the other server, I know it's safe to
remove the crontab job. (If the cron job has already fired, you'll need
to rerun the &lt;code&gt;ufw delete allow ssh&lt;/code&gt; command.)&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
At this point I can delete the first two rules that ban specific IPs,
since they're outside my netblock and won't be allowed anyway.&lt;/p&gt;
&lt;p&gt;Now I can enjoy quieter logs without all those access attempts from
China and Croatia!&lt;/p&gt;</summary><category term="firewall"></category><category term="security"></category><category term="ssh"></category><category term="linux"></category></entry><entry><title>Bug Bounty Programs</title><link href="http://blog.bstpierre.org/bug-bounty-programs" rel="alternate"></link><updated>2011-11-17T19:48:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-11-17:bug-bounty-programs</id><summary type="html">&lt;p&gt;There are several companies offering "bug bounties" -- payments to
people to find security bugs and report them responsibly.&lt;/p&gt;
&lt;p&gt;I will update this page with links to the various bug bounty programs as
I become aware of them. If you know of a program that is not linked here
(or if something below becomes stale) please leave a comment below or
&lt;a href="http://blog.bstpierre.org/contact-me"&gt;send me a note&lt;/a&gt;. The notes below are a summary -- read the fine print
in each program.&lt;/p&gt;
&lt;p&gt;In no particular order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.mozilla.org/security/bug-bounty.html" title="Mozilla Security Bug Bounty Program"&gt;Mozilla&lt;/a&gt; offers $500 to $3000 (plus a t-shirt!) for serious
    security bugs in Firefox, Thunderbird, or certain Mozilla web apps.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.facebook.com/whitehat/bounty/" title="Facebook Security Bug Bounty"&gt;Facebook&lt;/a&gt; offers a "typical bounty" of $500 for responsible
    disclosure of security bugs (XSS, CSRF, injection, etc) on Facebook,
    not including third party applications.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.tarsnap.com/bounty-winners.html" title="Tarsnap Bug Bounties"&gt;Tarsnap&lt;/a&gt;, an open-source encrypted online backup service, pays a
    sliding scale of $1 for spelling errors in code comments to $1000
    for "a bug which allows someone intercepting Tarsnap traffic to
    decrypt Tarsnap users' data".&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.google.com/about/corporate/company/rewardprogram.html" title="Google Vulnerability Reward Program"&gt;Google's&lt;/a&gt; vulnerability reward program pays a base of $500, up to
    $3133.7, for "any serious bug which directly affects the
    confidentiality or integrity of user data" (e.g. XSS, CSRF,
    authentication, etc) on Google properties (youtube, orkut,
    *.google.com, etc).&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.chromium.org/Home/chromium-security/vulnerability-rewards-program" title="Chromium Vulnerability Rewards Program"&gt;Chromium&lt;/a&gt; has a vulnerability rewards program (sponsored by
    Google, of course) that pays a base of $500, up to $3133.7, for
    "&lt;a href="http://dev.chromium.org/developers/severity-guidelines"&gt;High and Critical impact bugs"&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.barracudalabs.com/bugbounty/" title="Barracuda Networks Security Bug Bounty Program"&gt;Barracuda Networks&lt;/a&gt; has a Security Bug Bounty Program that pays
    $500 to $3133.70 for bugs "that compromise confidentiality,
    availability, integrity or authentication" in certain Barracuda
    products.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://piwik.org/security/" title="Piwik bug bounty"&gt;Piwik&lt;/a&gt; pays $500 for critical security bugs, and $200 for
    non-critical bugs.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ghostscript.com/Bug_bounty_program.html" title="Ghostscript Bug Bounty Program"&gt;Artifex&lt;/a&gt; has a bug bounty program for ghostscript that does not
    appear to pay for reports, but does pay $1000 for &lt;em&gt;patches&lt;/em&gt; that fix
    bugs in their tracker flagged P1 or P2 and "bountiable". They pay
    $500 for lower priority bug fixes.&lt;/li&gt;
&lt;li&gt;One of the oldest programs I'm aware of is &lt;a href="http://en.wikipedia.org/wiki/Knuth_reward_check"&gt;Donald Knuth&lt;/a&gt;'s offer
    of $2.56 for reporting errors in his publications. (Note that he's
    no longer sending out checks due to problems with check fraud. Here,
    the intrinsic reward is worth more than the money anyway.)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.hex-rays.com/bugbounty.shtml" title="Hex-Rays Security Bug Bounty Program"&gt;Hex-Rays&lt;/a&gt; pays $3000 for security bugs in the IDA or decompiler.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.whitefirdesign.com/about/wordpress-security-bug-bounty-program.html" title="White Fir Design Wordpress Security Bug Bounty Program"&gt;White Fir Design&lt;/a&gt; has a Wordpress bug bounty that pays a sliding
    scale, from $50 for privilege escalation to $500 for remote
    execution of arbitrary PHP or malicious file injection in Wordpress,
    and $50-$250 for bugs in plugins with one million downloads.&lt;/li&gt;
&lt;li&gt;White Fir Design has a &lt;a href="http://www.whitefirdesign.com/about/drupal-security-bug-bounty-program.html" title="White Fir Design Drupal Security Bug Bounty Program"&gt;Drupal bug bounty&lt;/a&gt; that pays the same
    sliding scale as their Wordpress program ($50-500 for Drupal core
    code, $50-250 for contributed modules).&lt;/li&gt;
&lt;li&gt;TippingPoint's &lt;a href="http://www.zerodayinitiative.com/about/benefits/" title="TippingPoint Zero Day Initiative"&gt;Zero Day Initiative&lt;/a&gt; pays reports of
    vulnerabilities in third party products. Whether they pay depends on
    how widely deployed the product is, and the severity of the
    vulnerability. Their program is designed to reward repeat
    vulnerability reporters. (E.g. if you report 10 bugs worth $1000
    each, you get a $1000 bonus and 10% bonus on all subsequent reports
    for a year.)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.millsapisd.net/BugReport.cfm"&gt;Harry Tenant and Associates&lt;/a&gt; pays $5 for bugs reported in School
    Site Manager.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ricebridge.com/bugs.htm"&gt;Ricebridge&lt;/a&gt; offers $15 per bug reported in any of their products.&lt;/li&gt;
&lt;li&gt;Microsoft is running a &lt;a href="http://www.microsoft.com/security/bluehatprize/rules.aspx" title="Microsoft BlueHat Prize Contest"&gt;BlueHat Prize Contest&lt;/a&gt; from 2011-08-03 to
    2012-04-01. It's a contest, not a bounty, and only two entrants will
    receive cash: $200k and $50k for first and second place,
    respectively.&lt;/li&gt;
&lt;/ul&gt;</summary></entry><entry><title>Using SSH for IPv6-enabled HTTP Proxying</title><link href="http://blog.bstpierre.org/using-ssh-for-ipv6-enabled-http-proxying" rel="alternate"></link><updated>2011-11-11T11:50:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-11-11:using-ssh-for-ipv6-enabled-http-proxying</id><summary type="html">&lt;p&gt;My ISP has apparently made no progress whatsoever with IPv6, but I've
got an IPv6 enabled VPS.&lt;/p&gt;
&lt;p&gt;SSH makes it trivial to use that VPS as a SOCKS5 proxy. Just do:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssh -D 8080 myvps.example.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then set your browser's SOCKS proxy to localhost:8080. In Firefox on
Linux, this is Edit &gt; Preferences &gt; Advanced &gt; Network tab &gt;
(Connection) Settings &gt; Manual Proxy Configuration. Leave &lt;em&gt;all fields
blank&lt;/em&gt; except for SOCKS Host and Port -- localhost and 8080,
respectively. Choose SOCKS5. Then browse to about:config and change
&lt;code&gt;change network.proxy.socks_remote_dns&lt;/code&gt; to true. This tells Firefox to
ask the proxy to resolve names instead of trying to resolve them using
your ISP. Chrome worked for me without hassle.&lt;/p&gt;
&lt;p&gt;Go to &lt;a href="http://test-ipv6.com/"&gt;test-ipv6.com&lt;/a&gt; to test that it works. If your results from
test-ipv6.com indicate that IPv6 name lookups are failing, make sure
you've got that about:config setting mentioned above changed.&lt;/p&gt;</summary><category term="linux"></category><category term="ssh"></category><category term="ipv6"></category></entry><entry><title>Fixing Certificate Errors with Cisco AnyConnect</title><link href="http://blog.bstpierre.org/fixing-certificate-errors-with-cisco-anyconnect" rel="alternate"></link><updated>2011-11-06T10:50:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-11-06:fixing-certificate-errors-with-cisco-anyconnect</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;"AnyConnect cannot confirm it is connected to your secure gateway. The
local network may not be trustworthy. Please try another network."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There may be several reasons for this error, which you'll find on other
pages that hit for a search on this string.&lt;/p&gt;
&lt;p&gt;The reason that I encountered seems to be unique. What I found by
digging into a wireshark capture is that AnyConnect sends a TLS alert to
the server, disconnecting the session. The alert message says "Unknown
CA".&lt;/p&gt;
&lt;p&gt;It could have something to do with installing the firefox plugin
"Certificate Patrol" recently. AnyConnect apparently uses firefox's
certificate store. Perhaps Certificate Patrol does something to the
store that makes it so that AnyConnect can no longer use it?&lt;/p&gt;
&lt;p&gt;In case it matters, I'm on Ubuntu 10.04.&lt;/p&gt;
&lt;p&gt;Whatever the cause, you can fix it by doing the following (&lt;strong&gt;Edit&lt;/strong&gt;: see
also a &lt;a href="#comment-8"&gt;simpler method&lt;/a&gt; in a comment by Nathan below):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Figure out the CA that signs your VPN server's certificate. (Hat tip
    to &lt;a href="http://blog.didierstevens.com/2007/12/23/quickpost-retrieving-an-ssl-certificate/"&gt;Didier Stevens&lt;/a&gt; for the easy way to do this.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fill in your server, I'm going to use www.google.com.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openssl s_client -connect www.google.com:443 &amp;gt;! /tmp/google&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Hit ctrl-C.&lt;/li&gt;
&lt;li&gt;You saw "Thawte Consulting (Pty) Ltd.".&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now search for "thawte root certificate". (Obviously this will vary
    depending on who signs your server's certificate.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Go to the page where they list certificates. Download them all. (I
    tried downloading the one that looked like it matched the signature
    on my server's certificate, but I think you need everything down to
    the root in order to fully verify it. It's easier to just download
    everything than try to figure out exactly which ones are needed.)&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Convert to PEM format. (Hat tip to &lt;a href="http://moze.koze.net/?p=81"&gt;Mozekoze for this recipe&lt;/a&gt;.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;openssl x509 -in input.crt -out input.der -outform DER ## (if
    the certs are in .crt format)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openssl x509 -in input.der -inform DER -out output.pem -outform
    PEM&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy all the PEM files to /opt/.cisco/certificates/ca. (Or to
    ~/.cisco/certificates/ca.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Alternatively, you could copy the certs out of the keystore on your
machine, convert to PEM, and then copy the PEMs into the directory
mentioned above. (On ubuntu, you can see the certificates in the package
ca-certificates. You could copy just the thawte certs by doing something
like &lt;code&gt;cp $(dpkg -L ca-certificates | grep -i thawte)
/tmp/certificate-conversion/&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;Three cheers for wireshark, strace, openssl, and google for help
figuring out what was going on. Cisco &lt;em&gt;could&lt;/em&gt; make things a little bit
easier to figure out -- a decent diagnostic message would have been
great -- just tell the user "Unknown CA", it's already buried in the
wireshark trace.&lt;/p&gt;</summary></entry><entry><title>Preserve Your Sanity When Dealing with Unicode in Python</title><link href="http://blog.bstpierre.org/simple-python-unicode" rel="alternate"></link><updated>2011-10-05T11:09:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-10-05:simple-python-unicode</id><summary type="html">&lt;p&gt;I have not yet played with python 3, which sounds like it makes working
with Unicode easier.&lt;/p&gt;
&lt;p&gt;In python 2.x, working with Unicode can be annoying, but when you
remember this rule of thumb, it's &lt;em&gt;much&lt;/em&gt; easier and allows you to keep
your sanity:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Always use unicode strings internally. Decode whatever you
read/receive. Encode whatever you write/send.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've recently done some work on a project to generate ReStructuredText
(as an intermediate form on the way to generating HTML). The input data
has Unicode sprinkled throughout (in UTF-8). I kept getting
&lt;code&gt;UnicodeDecodeError: 'ascii' codec can't decode byte&lt;/code&gt; exceptions in
various places, until I applied that rule everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When reading, decode from UTF-8.&lt;/li&gt;
&lt;li&gt;Use Unicode strings
    &lt;code&gt;u'E.g. this formatted %s string' % (decoded_string1, decoded_string2)&lt;/code&gt;
    internally -- &lt;em&gt;everywhere&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;When writing, encode to UTF-8.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hat tip to &lt;a href="http://stackoverflow.com/q/492483/67022#492711"&gt;nosklo on StackOverflow&lt;/a&gt; for mentioning this simple rule.&lt;/p&gt;</summary></entry><entry><title>My First Android Adventure</title><link href="http://blog.bstpierre.org/my-first-android-adventure" rel="alternate"></link><updated>2011-09-26T21:43:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-09-26:my-first-android-adventure</id><summary type="html">&lt;p&gt;Here are my notes about getting the Android SDK up and running using
only the command line tools that come in the SDK.&lt;/p&gt;
&lt;p&gt;The following worked for me on Ubuntu 10.04 LTS as of September 2011. I
find Eclipse to be an excruciating form of torture, so I'm not following
the Eclipse-based instructions. Everything below can be done from vi or
emacs and the command line.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://developer.android.com/sdk/index.html"&gt;Download the SDK&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Unpack the SDK.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;android-sdk-linux_x86/tools/android&lt;/code&gt; to bring up the AVD
    Manager. (AVD is "Android Virtual Device" -- it will run in the
    emulator.)&lt;/li&gt;
&lt;li&gt;Select "Available Packages", and expand "Android Repository". It
    will fetch a list of packages.&lt;/li&gt;
&lt;li&gt;Select "Android SDK Platform-Tools, Version 7". (You need this.)&lt;/li&gt;
&lt;li&gt;Select "SDK Platform Android 2.2, API 8, revision 3". (You need at
    least one platform. My development phone has Android 2.2, so that's
    what I'm initially targeting. You can add more platforms later.)&lt;/li&gt;
&lt;li&gt;I also selected Documentation and Samples, for convenience.&lt;/li&gt;
&lt;li&gt;Install the packages you selected. Restart ADB when prompted.&lt;/li&gt;
&lt;li&gt;Then create a new virtual device. In the AVD Manager, click "Virtual
    Devices", then "New...". Give your device a name, choose the
    platform, click Create.&lt;/li&gt;
&lt;li&gt;Install the ubuntu package "ant1.8". Don't install "ant" -- this is
    version 1.7, which won't work with the android SDK.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Create a New Project&lt;/h3&gt;
&lt;p&gt;I unpacked the tools under ~/projects/android.&lt;/p&gt;
&lt;p&gt;From that directory, I can give this command:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;linux_x86&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;hello_android&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;8 &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;HelloAndroid&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blakitasoftware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hello_android&lt;/span&gt;&amp;quot;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This creates ~/projects/android/hello_android/ containing the new
project.&lt;/p&gt;
&lt;h3&gt;Build the Project&lt;/h3&gt;
&lt;p&gt;Change into the hello_android directory. Run &lt;code&gt;ant debug&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;(&lt;a href="http://developer.android.com/guide/developing/building/building-cmdline.html#DebugMode"&gt;Read this for info on package signing and building in debug
mode&lt;/a&gt;.)&lt;/p&gt;
&lt;h3&gt;Run the Application&lt;/h3&gt;
&lt;p&gt;Since we built in debug mode, we don't need to worry about signing -- we
can run it in the emulator right away.&lt;/p&gt;
&lt;p&gt;First start the virtual device: run android with no arguments, find your
device, click "Start...", "Launch".&lt;/p&gt;
&lt;p&gt;Install the application by running (assuming you're still in the
hello_android directory):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="cp"&gt;../android-sdk-linux_x86/platform-tools/adb \&lt;/span&gt;
&lt;span class="cp"&gt;  install ./bin/HelloAndroid-debug.apk&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then go to the emulator, click the Launcher. (This is the grid of little
gray squares in the bottom of the screen. Yeah, it took me a few seconds
to figure this out -- I've never even used an Android phone, this really
is an adventure...) Find your HelloAndroid app, click it, and you'll see
the greeting ("Hello World, HelloAndroid").&lt;/p&gt;
&lt;h3&gt;Make a Change&lt;/h3&gt;
&lt;p&gt;To change that greeting, edit res/layout/main.xml.&lt;/p&gt;
&lt;p&gt;Assuming you only have one emulator running, the build and install
sequence given above can be abbreviated to &lt;code&gt;ant install&lt;/code&gt;. This
reinstalls your app with the new greeting, find your app in the launcher
again and rerun it to see the change.&lt;/p&gt;</summary><category term="android"></category><category term="howto"></category><category term="sdk"></category></entry><entry><title>Change directories faster with zsh</title><link href="http://blog.bstpierre.org/save-three-keystrokes-with-zsh" rel="alternate"></link><updated>2011-07-25T10:23:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-07-25:save-three-keystrokes-with-zsh</id><summary type="html">&lt;p&gt;Something I didn't know until recently:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;zsh does not require &lt;code&gt;cd&lt;/code&gt; to change directories.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using the directory as a command implies "cd". For example, instead of
doing:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;me&lt;/span&gt; $ &lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;
&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt; $
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can just do:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;me&lt;/span&gt; $ &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;
&lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt; $
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's three whole keystrokes (nearly half the command shown above), all
day long.&lt;/p&gt;</summary><category term="zsh"></category><category term="tip"></category></entry><entry><title>Bringing up a New Cloud Server</title><link href="http://blog.bstpierre.org/bringing-up-a-new-cloud-server" rel="alternate"></link><updated>2011-07-21T10:08:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-07-21:bringing-up-a-new-cloud-server</id><summary type="html">&lt;p&gt;This is a brain dump of my task list when bringing up a new server. (For
my purposes, a linode running some version of Ubuntu.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a user account.&lt;ol&gt;
&lt;li&gt;Add user to wheel.&lt;/li&gt;
&lt;li&gt;Set up sudo for wheel.&lt;/li&gt;
&lt;li&gt;Set up ssh authorized-ids.&lt;/li&gt;
&lt;li&gt;Change shell to zsh.&lt;/li&gt;
&lt;li&gt;Copy dot-files.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;SSH config file tweaks.&lt;ol&gt;
&lt;li&gt;Disable SSH root logins.&lt;/li&gt;
&lt;li&gt;Disable SSH password logins (key only).&lt;/li&gt;
&lt;li&gt;Disable access for non-ssh-group users.&lt;/li&gt;
&lt;li&gt;Disable DNS lookups (&lt;code&gt;UseDNS no&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Other?&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Move sshd listening port to nonstandard high port. &lt;strong&gt;Test that
    logins still work!&lt;/strong&gt; (I'm not convinced this buys much -- see
    firewall &amp;amp; fail2ban at number 8 below.)&lt;/li&gt;
&lt;li&gt;Change root password.&lt;/li&gt;
&lt;li&gt;Set the hostname.&lt;/li&gt;
&lt;li&gt;Set up DNS entry(-ies) (forward &amp;amp; reverse).&lt;/li&gt;
&lt;li&gt;Download OS updates. (Configure repos / mirrors as needed first.)&lt;/li&gt;
&lt;li&gt;Activate firewall (ufw).&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.bstpierre.org/ufw-firewall-limit-ssh"&gt;Add rule for SSH&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.vigilcode.com/2011/05/ufw-with-fail2ban-quick-secure-setup-part-ii/"&gt;Install fail2ban and configure to work with ufw&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Set the time zone &lt;code&gt;(dpkg-reconfigure tzdata)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set up an offsite backup. (rsnapshot or other)&lt;/li&gt;
&lt;li&gt;Set locale.&lt;/li&gt;
&lt;li&gt;Reboot -- make sure ssh logins work after rebooting, etc. (Easier to
    fix problems now than when you &lt;em&gt;need&lt;/em&gt; to log in at some later time.)&lt;/li&gt;
&lt;li&gt;Install packages for whatever purpose the machine is going to be
    used. (Apache, git, etc.)&lt;/li&gt;
&lt;li&gt;Portscan the server (externally is preferred) to make sure there are
    no leaks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'll come back to this list occasionally to revise it. Please leave a
comment if you think something important is missing...&lt;/p&gt;
&lt;/p&gt;</summary></entry><entry><title>Use Linux to build win32 installers for Python apps</title><link href="http://blog.bstpierre.org/linux-win32-python-installer" rel="alternate"></link><updated>2011-07-20T10:00:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-07-20:linux-win32-python-installer</id><summary type="html">&lt;p&gt;A python-based project I'm working on has a win32 user that I need to
support. Until yesterday I've been moving to a win32 laptop in order to
run &lt;code&gt;python setup.py bdist_wininst&lt;/code&gt; so I can produce a self-installing
executable. Then I discovered how trivial it is to use wine to do the
job:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install wine. (&lt;code&gt;sudo aptitude install wine&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Install python into the wine environment. (Download an msi from
    python.org and run &lt;code&gt;msiexec /i python-x.x.x.msi&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;Install whatever prerequisite packages you need (e.g. wxPython)
    using &lt;code&gt;wine&lt;/code&gt; or &lt;code&gt;msiexec&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When you've got everything ready to build, just do
    &lt;code&gt;wine c:/Python27/python.exe setup.py bdist_wininst&lt;/code&gt; and look in
    ./dist/ for your exe!&lt;/li&gt;
&lt;/ol&gt;</summary><category term="python"></category><category term="howto"></category><category term="linux"></category></entry><entry><title>Making perl unit tests easier to run in emacs</title><link href="http://blog.bstpierre.org/perl-unit-tests-easier-in-emacs" rel="alternate"></link><updated>2011-07-07T19:57:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-07-07:perl-unit-tests-easier-in-emacs</id><summary type="html">&lt;p&gt;It's a hassle to have to switch from emacs to the shell, run unit tests,
pick out the failure, switch back to emacs, navigate to the line, forget
where you were supposed to go, switch back, etc etc.&lt;/p&gt;
&lt;p&gt;The whole process is much easier with &lt;a href="http://user42.tuxfamily.org/compilation-perl/index.html" title="compilation-perl.el"&gt;compilation-perl.el&lt;/a&gt;. This
handy bit of code sets up patterns for compilation-mode so that test
failures (among other things) are recognized. Just add
compilation-perl.el to your emacs lisp directory and follow the
directions at the top of the file for installation instructions.&lt;/p&gt;
&lt;p&gt;So my test process is now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Hit &lt;code&gt;F4&lt;/code&gt; (which I have mapped to &lt;code&gt;compile&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If necessary, change the command to either &lt;code&gt;make test&lt;/code&gt; or to run a
    specific test I'm working on (e.g. &lt;code&gt;perl t/MyModule.t&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If there's a failure, press &lt;code&gt;C-F4&lt;/code&gt; (which I have mapped to
    &lt;code&gt;next-error&lt;/code&gt;) and it brings me right to the failure.&lt;/li&gt;
&lt;li&gt;Repeat from step 1 (but the second time around I don't need to do
    step 2 since the command is already set properly).&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;</summary><category term="tool"></category><category term="emacs"></category><category term="unit-test"></category><category term="perl"></category></entry><entry><title>Make apt-get use an alternate sources.list</title><link href="http://blog.bstpierre.org/make-apt-get-use-an-alternate-sources-list" rel="alternate"></link><updated>2011-01-06T11:18:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2011-01-06:make-apt-get-use-an-alternate-sources-list</id><summary type="html">&lt;p&gt;Since my [former] company's product is based on ubuntu, we use &lt;a href="http://wiki.debian.org/Apt"&gt;apt&lt;/a&gt; to
distribute upgrades. We don't normally want customers to pull upgrades
from the upstream distribution, so we've pruned back the main
/etc/apt/sources.list to include just our server. We rename the original
default sources.list so that it doesn't get picked up.&lt;/p&gt;
&lt;p&gt;But sometimes I want to be able to pull from upstream, so I want to
reactivate the original sources.list. Up to now, I've been having to do
an awkward dance that involves renaming our file, copying the old
default to the active, and then doing the reverse when I'm done.&lt;/p&gt;
&lt;p&gt;(As an aside, &lt;a href="http://fedoraproject.org/wiki/Tools/yum"&gt;yum&lt;/a&gt;-based systems like Centos or Fedora make this
easy. You can say &lt;code&gt;enable=0&lt;/code&gt; in the list file, and then use
&lt;a href="http://www.fedoraforum.org/forum/showthread.php?t=26925" title="How to selectively enable yum repositories."&gt;--enablerepo=myrepo&lt;/a&gt; on the command line. There doesn't seem to be
a way to do this with apt, but the command below is almost as good.)&lt;/p&gt;
&lt;p&gt;To avoid this renaming dance, here's a better solution.&lt;/p&gt;
&lt;p&gt;First, create a directory to put the original sources.list:
&lt;code&gt;/etc/apt/sources.list.ubuntu&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then copy the original default sources.list into that directory. I
called it &lt;code&gt;/etc/apt/sources.list.ubuntu/jaunty.list&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When you want to pull from upstream, run:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Etc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SourceParts&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ubuntu&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then you'll want to run &lt;code&gt;apt-get update&lt;/code&gt; to reset your repository list
back to the trimmed-down set.&lt;/p&gt;</summary></entry><entry><title>An Interesting pid File Race</title><link href="http://blog.bstpierre.org/pid-file-race" rel="alternate"></link><updated>2010-05-26T11:08:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-05-26:pid-file-race</id><summary type="html">&lt;p&gt;ISC's dhcpd uses &lt;a href="http://google.com/codesearch/p?hl=en#5KTrgOW2hXs/pub/nslu2/sources/dhcp-3.0.4.tar.gz|9nqObdv7Xcs/dhcp-3.0.4/server/dhcpd.c&amp;amp;q=_PATH_DHCPD_CONF&amp;amp;d=3&amp;amp;l=539"&gt;this code&lt;/a&gt; to check for an already-running daemon:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="cm"&gt;/* Read previous pid file. */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_dhcpd_pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;O_RDONLY&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pbuf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt; &lt;span class="n"&gt;pbuf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;close&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pbuf&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;atoi&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pbuf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="cm"&gt;/* If the previous server process is not still running,&lt;/span&gt;
&lt;span class="cm"&gt;           write a new pid file immediately. */&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;unlink&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_dhcpd_pid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_dhcpd_pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="no"&gt;O_WRONLY&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;O_CREAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mh"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;sprintf&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pbuf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;%d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;getpid&lt;/span&gt; &lt;span class="p"&gt;());&lt;/span&gt;
                &lt;span class="n"&gt;write&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pbuf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strlen&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pbuf&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="n"&gt;close&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;pidfilewritten&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;log_fatal&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;There&amp;#39;s already a DHCP server running.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The problem with this strategy is that, if the box dies, there's a stale
pid file left in /var/run/dhcpd.pid. This wouldn't be so bad -- the code
above checks [using &lt;code&gt;kill(pid, 0)&lt;/code&gt;] to see if there's a process running
with that pid. But when the box is restarting, there will be a bunch of
processes all starting in similar sequence each time. So on one boot,
you might see dhcpd with a pid of 1001 and ntpd with a pid of 1002. If
the box dies violently (e.g. power cut), the dhcpd pid file will contain
1001. On the second boot, assume ntpd starts first and gets a pid of
1001 and dhcpd is 1002. Now, the &lt;code&gt;kill(pid, 0)&lt;/code&gt; will succeed, making it
appear that dhcpd is already running, and dhcpd will exit.&lt;/p&gt;
&lt;p&gt;How to fix this?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Explicitly put the pid file under /tmp. Getting this right is fussy
    -- make sure you avoid the race conditions associated with creating
    temp files. Use dhcpd's "-pf" flag to tell it where to use the pid
    file. This avoids spurious "already running" messages, because dhcpd
    will never read a pid from an existing pid file. [You could also
    just remove the /var/run/dhcpd.pid file, but I'd rather explicitly
    provide the path in my startup script in case some dim bulb decides
    to change the compiled-in default.]&lt;/li&gt;
&lt;li&gt;Be careful in your restart code to kill any existing dhcpd (assuming
    you really want a new dhcpd), or avoid trying to start a new one
    (assuming you want to use an already running dhcpd). &lt;code&gt;pgrep(1)&lt;/code&gt; and
    &lt;code&gt;pkill(1)&lt;/code&gt; will be useful here.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In researching this, I saw this &lt;a href="http://openbsd.monkey.org/misc/200601/msg00735.html"&gt;bit of wisdom from Henning Brauer&lt;/a&gt;:
"pid files are useless.".&lt;/p&gt;
&lt;p&gt;I heartily agree...&lt;/p&gt;</summary><category term="software-engineering"></category><category term="linux"></category></entry><entry><title>Using Lenny to Build Jaunty Packages</title><link href="http://blog.bstpierre.org/lenny-jaunty-packages" rel="alternate"></link><updated>2010-05-13T10:43:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-05-13:lenny-jaunty-packages</id><summary type="html">&lt;p&gt;The process for building debian packages is actually very well
documented, though many of the tutorials you will find are aimed at
people who are packaging third-party software instead of their own.&lt;/p&gt;
&lt;p&gt;Also, I'm running on Debian's Lenny, and I need to produce packages that
install cleanly on Ubuntu's Jaunty Jackalope. It sounds worse than it
is.&lt;/p&gt;
&lt;p&gt;Take this with a grain of salt --- it works for me, but I'm certainly
not an expert in this area.&lt;/p&gt;
&lt;p&gt;Steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Debianize each package. (&lt;a href="http://wiki.debian.org/HowToPackageForDebian" title="How to Package for Debian"&gt;Here's an excellent quick and dirty guide to getting a package created.&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Build the packages:
    &lt;code&gt;(cd MYPROJECT; fakeroot ./debian/rules binary)&lt;/code&gt;. Don't distribute
    these packages, they are probably wrong! But this is the quickest
    way to get everything mostly-working.&lt;/li&gt;
&lt;li&gt;Sanity check the packages:
    &lt;code&gt;dpkg-deb -c MYPROJECT.deb; dpkg-deb -I MYPROJECT.deb; dpkg -i MYPROJECT.deb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Check in your MYPROJECT/debian/ directory to source control (e.g.
    git, svn, etc).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This is the tricky step, and one that I didn't see explicitly
    documented.You need a version of debootstrap that knows about ubuntu
    distributions. (Ubuntu debootstrap knows about Debian, but not the
    other way around.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the jaunty (or whatever ubuntu version) &lt;a href="http://packages.ubuntu.com/jaunty-backports/admin/debootstrap"&gt;debootstrap
    source package&lt;/a&gt;. Grab the .dsc and .tar.gz from
    that page.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;dpkg-source -x debootstrap_1.0.20~jaunty1.dsc&lt;/code&gt; -- this
    extracts the source.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(cd debootstrap-1.0.20~jaunty1; debuild -uc -us)&lt;/code&gt; -- this will
    build a .deb you can install on your lenny machine. Install
    package "devscripts" if you don't have debuild.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo dpkg -i debootstrap_1.0.20~jaunty1_all.deb&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install pbuilder. This will help you build the packages in a clean
    chroot -- i.e. without all the pollution you have on your machine
    that could be making the packages work even though they will break
    on your users' machines.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;Put the &lt;a href="http://wiki.rabbitvcs.org/wiki/development/pbuilder"&gt;code found here&lt;/a&gt; in your ~/.pbuilderrc.&lt;/li&gt;
&lt;li&gt;Apply &lt;a href="http://wiki.debian.org/PbuilderTricks#Howtobuildfordifferentdistributions"&gt;this sudoers change&lt;/a&gt; (optional but convenient).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo DIST=jaunty pbuilder create&lt;/code&gt; ... then wait. (If you have lame
    internet service (Hughes) and a daily bandwidth cap, you may want to
    schedule this for the free-for-all period:
    &lt;code&gt;echo sudo DIST=jaunty pbuilder create | at 2:30am tomorrow&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you see anything I'm missing or would like to add, please drop a
comment below. Thanks!&lt;/p&gt;
&lt;/p&gt;</summary></entry><entry><title>zsh history expansion</title><link href="http://blog.bstpierre.org/zsh-history-expansion" rel="alternate"></link><updated>2010-02-15T08:59:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-02-15:zsh-history-expansion</id><summary type="html">&lt;p&gt;Exploring zsh features made me want to figure out some of the
history-editing wizardry. (Bash has similar history tricks, I just never
bothered to dive too deeply into them.)&lt;/p&gt;
&lt;p&gt;If you want to experiment with history expansion a bit, you can echo the
result instead of executing it:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="c"&gt;% ls /some/long/path/to/file_0.1-2_i386.changes&lt;/span&gt;
&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="c"&gt;% echo !?ls?:s/-2/-3/&lt;/span&gt;
&lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;long&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;file_0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="o"&gt;-&lt;/span&gt;3&lt;span class="n"&gt;_i386&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changes&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In this case, what I wanted to do is repeat a long command that
referenced a file with a version number in it --- but I wanted to use a
different version number (-3 instead of the previously given version
-2).&lt;/p&gt;
&lt;p&gt;History expansion uses "!" ("bang", or exclamation point). If you want
to select the previous command to expand based on a string match,
"!?str" will find the previous command that contains "str". If you want
to substitute some part of that previous command, put another question
mark on the end, add a colon, and use the the "s" modifier. In the
example above, "!?ls?" means expand the previous command containing
"ls". The ":s/-2/-3/" means modify that expanded command to replace the
occurrence of "-2" with "-3".&lt;/p&gt;
&lt;p&gt;(Of course, to actually get all of that figured out and working, I had
to iterate the sequence of commands above about a dozen times.)&lt;/p&gt;
&lt;p&gt;Leave a comment with your favorite zsh history trick. Thanks!&lt;/p&gt;</summary><category term="tool"></category><category term="zsh"></category></entry><entry><title>Data vs Code</title><link href="http://blog.bstpierre.org/data-vs-code" rel="alternate"></link><updated>2010-02-05T11:08:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-02-05:data-vs-code</id><summary type="html">&lt;p&gt;I'll take an array over a giant switch-case statement any day.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The array definition will be more compact and easier to see all at
    once.&lt;/li&gt;
&lt;li&gt;Defining actions in an array enforces uniformity.&lt;/li&gt;
&lt;li&gt;You can put checks in the code to automatically verify that the
    array definition is complete. (I.e. verify it contains a definition
    for every item it should have.) Yes, some tools can do this for
    certain types of switch statements. Using an array-based check is
    more portable and more foolproof.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;/p&gt;
If you have a switch statement of mostly cut and paste cases, you can
probably convert it to an array very easily, and then rewrite the switch
statement to look up the value in the array and do whatever thing is
supposed to be done, either via function pointers or by using an
associated value from the array.&lt;/p&gt;
&lt;p&gt;I did this on a horrible switch statement once. Dozens of cases, 80%
were nearly identical but the few that weren't were awful to untangle.
Once I pulled the case bodies into separate functions, put function
pointers into a table, and replaced the switch body with a lookup loop
it was much cleaner. The code for the odd cases was eventually pushed
out (it was a symptom of bad design). The whole exercise enabled another
round of changes that allowed the functions for the case bodies to be
collapsed back into the table -- we ended up removing an entire
unnecessary layer of indirection and made the design much easier to
grok.&lt;/p&gt;</summary><category term="software-engineering"></category><category term="c-programming"></category></entry><entry><title>Set Your zsh Prompt</title><link href="http://blog.bstpierre.org/zsh-prompt" rel="alternate"></link><updated>2010-02-01T11:05:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-02-01:zsh-prompt</id><summary type="html">&lt;p&gt;Since the beginning of time, all the cool kids have had really cool
shell prompts. It's a great place to display helpful information, and
zsh has features that let you have a flexible, informative, unobtrusive
prompt.&lt;/p&gt;
&lt;p&gt;Set your prompt by setting &lt;code&gt;$PROMPT&lt;/code&gt;. If you do &lt;code&gt;PROMPT='foo '&lt;/code&gt;, the
shell will give you a &lt;code&gt;foo&lt;/code&gt; prompt for every command. Not terribly
useful but you get the point.&lt;/p&gt;
&lt;p&gt;There are a bunch of codes you can use in the value of PROMPT to get
useful output. For example, %m gives the name of the machine you're
running on, and %~ gives the name of the current working directory. For
a list of all the codes, check the "&lt;a href="http://www.manpagez.com/man/1/zshmisc/" title="man page for zshmisc"&gt;zshmisc&lt;/a&gt;" man page under the
section "SIMPLE PROMPT ESCAPES". Note that there are codes for boldface,
underline, and colors here too.&lt;/p&gt;
&lt;p&gt;The drawback to having all kinds of information in your prompt is that
you limit the length of commands that you can enter without scrolling.
Scrolling stinks because the command is harder to read. Enter the "right
prompt". Set RPS1 to contain the lengthy part of your prompt, and it
will be displayed on the right margin of your terminal. Then when
commands get long and encroach on the right prompt, it will conveniently
disappear. Read Quentin's comment on &lt;a href="http://who-t.blogspot.com/2009/08/case-for-zsh.html?showComment=1253567443515#c3597222540288946216"&gt;this zsh post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's a gotcha with RPS1 and fancy prompt formatting: gnome-terminal
does not behave well when these are set. I'm trying out alternatives to
see which will be better.&lt;/p&gt;
&lt;p&gt;Lastly, a trick I picked up from &lt;a href="http://justinchouinard.com/"&gt;Justin&lt;/a&gt;. Zsh provides hooks that you
can use to do things before and after a command runs. Just define the
functions preexec and precmd. This example sets the title of the xterm
while a command is running:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;function title() {
    # escape &amp;#39;%&amp;#39; chars in $1, make nonprintables visible
    local a=&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;\&lt;span class="o"&gt;%/&lt;/span&gt;\&lt;span class="o"&gt;%&lt;/span&gt;\&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;

    # Truncate command, and join lines.
    a=$(print -Pn &amp;quot;%40&amp;gt;...&amp;gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&amp;quot; | tr -d &amp;quot;\n&amp;quot;)
    case &lt;span class="nv"&gt;$TERM&lt;/span&gt; in
        screen*)
            print -Pn &amp;quot;\e]2;&lt;span class="nv"&gt;$a&lt;/span&gt; @ $2\a&amp;quot; # plain xterm title
            print -Pn &amp;quot;\ek&lt;span class="nv"&gt;$a&lt;/span&gt;\e\\&amp;quot;      # screen title (in ^A&amp;quot;)
            print -Pn &amp;quot;\e_$2   \e\\&amp;quot;   # screen location
            ;;
        xterm*)
            print -Pn &amp;quot;\e]2;&lt;span class="nv"&gt;$a&lt;/span&gt; @ $2\a&amp;quot; # plain xterm title
            ;;
    esac
}

# precmd is called just before the prompt is printed
function precmd() {
    title &amp;quot;zsh&amp;quot; &amp;quot;%m:%55&lt;span class="nt"&gt;&amp;lt;...&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;%~&amp;quot;&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;preexec&lt;/span&gt; &lt;span class="err"&gt;is&lt;/span&gt; &lt;span class="err"&gt;called&lt;/span&gt; &lt;span class="err"&gt;just&lt;/span&gt; &lt;span class="err"&gt;before&lt;/span&gt; &lt;span class="err"&gt;any&lt;/span&gt; &lt;span class="err"&gt;command&lt;/span&gt; &lt;span class="err"&gt;line&lt;/span&gt; &lt;span class="err"&gt;is&lt;/span&gt; &lt;span class="err"&gt;executed&lt;/span&gt;
&lt;span class="err"&gt;function&lt;/span&gt; &lt;span class="err"&gt;preexec()&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;title&lt;/span&gt; &lt;span class="err"&gt;&amp;quot;$1&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;&amp;quot;%m:%35&amp;lt;...&amp;lt;%~&amp;quot;&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Set variable a to local in function title above as suggested
in the comment below.&lt;/p&gt;</summary><category term="zsh"></category><category term="tools"></category></entry><entry><title>Insist on Automatic Tests</title><link href="http://blog.bstpierre.org/insist-on-tests" rel="alternate"></link><updated>2010-01-29T14:08:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-01-29:insist-on-tests</id><summary type="html">&lt;p&gt;At some point your team is going to be gone. Not all at once (well,
maybe, but in that case you won't care), but over time turnover will
completely replace your team.&lt;/p&gt;
&lt;p&gt;If you are the manager, and you outlast the team, you're going to pay
for low quality code when you try to bring in new people and they end up
breaking everything becuase there's no tests to check their bug fixes
and/or enhancements.&lt;/p&gt;
&lt;p&gt;If you are a surviving member of the team, you're going to pay with huge
headaches because you've got to fix code where you have no idea what
might be broken as a result.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Change is scary when you don't have automatic tests.&lt;/strong&gt; Just suck it up
and write the tests!&lt;/p&gt;</summary><category term="software-engineering"></category></entry><entry><title>Make the zsh zle handle "words" correctly</title><link href="http://blog.bstpierre.org/zsh-zle-words" rel="alternate"></link><updated>2010-01-27T10:40:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-01-27:zsh-zle-words</id><summary type="html">&lt;p&gt;Snippet from my .zshrc:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;# This controls what the line editor considers a word. By default it&lt;/span&gt;
&lt;span class="c"&gt;# includes &amp;#39;/&amp;#39;, which makes it so that when I M-del (attempting to erase&lt;/span&gt;
&lt;span class="c"&gt;# a directory in a path), I erase the whole path. Annoying.&lt;/span&gt;
&lt;span class="c"&gt;# WORDCHARS=&amp;#39;*?_-.[]~=/&amp;amp;;!#$%^(){}&amp;lt;&amp;gt;&amp;#39; # (default)&lt;/span&gt;
&lt;span class="n"&gt;WORDCHARS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;*?_-.[]~=&amp;amp;;!#$%^(){}&amp;lt;&amp;gt;&amp;#39;&lt;/span&gt;`
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After living with this for a while, I realize that I should probably
remove the underscore too, since that's what I was used to in bash.&lt;/p&gt;</summary><category term="zsh"></category><category term="tools"></category></entry><entry><title>Moving to zsh</title><link href="http://blog.bstpierre.org/moving-to-zsh" rel="alternate"></link><updated>2010-01-25T10:20:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-01-25:moving-to-zsh</id><summary type="html">&lt;p&gt;To get started:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;sudo aptitude install zsh&lt;/li&gt;
&lt;li&gt;chsh /bin/zsh&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's pretty simple.&lt;/p&gt;
&lt;p&gt;Of course, you're not &lt;em&gt;running&lt;/em&gt; zsh yet... either logout and log back in
or just run zsh at the prompt. You'll get a series of prompts to
configure a .zshrc. It only takes a few minutes, so run through the
options and save the file.&lt;/p&gt;
&lt;p&gt;Next up: setting a custom prompt.&lt;/p&gt;</summary><category term="zsh"></category><category term="tools"></category></entry><entry><title>9 "Must-Have" Tools for Software Teams</title><link href="http://blog.bstpierre.org/must-have-tools-software-teams" rel="alternate"></link><updated>2010-01-18T11:06:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2010-01-18:must-have-tools-software-teams</id><summary type="html">&lt;p&gt;The items below are useful systems based on my experience working with a
bunch of different software teams at a handful of companies over the
past decade-plus. I haven't bothered to list things like compilers,
interpreters, libraries, etc. If you don't have those, you aren't making
software...&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Source control.&lt;/strong&gt; This almost belongs in the "if you don't have
    it, you aren't making software" category. There are so many free and
    simple to use tools that it is ridiculous to not use source control.
    Pick one of: subversion, git, or any of the dozens of other free
    tools and use it. I guess I'd say "if you don't have it, you are
    making a mess".&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bug/issue tracker&lt;/strong&gt;. Trac is a decent tool for keeping tabs on
    development tasks and bugs. Bugzilla is ok too. If you have a
    budget, there is an apparently infinite set of commercial systems
    available of varying quality. Almost anything is better than a list
    of scribbles on your whiteboard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backup server.&lt;/strong&gt; You &lt;em&gt;can&lt;/em&gt; do software without it, but the odds
    are against you lasting long.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build system.&lt;/strong&gt; Automated nightly/continuous builds are awesome.
    Whether you DIY, install something free like hudson, or purchase a
    commercial system, this is a key piece of making sure that the
    software maintains at least a minimum level of quality (i.e. it is
    always buildable). After having worked on a couple of teams that
    didn't have such a system, and the build was always broken, I would
    never work without it again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IM / Jabber&lt;/strong&gt;. Especially with a conference server where the rooms
    are archived and available via the web. Searchable is nice, but
    making them available where they can be grepped isn't bad either. I
    didn't become convinced of the value here until last summer.It
    sounds like an extra distraction and sometimes it can be, but you
    can turn it off when you're heads-down coding. And it has the
    potential to &lt;em&gt;reduce&lt;/em&gt; the number of interruptions from people
    walking into your office with questions that could have been
    answered in an IM. It's lighter weight than email and rooms are
    broadcast by nature so you don't have to spam a huge list of people
    to find the answer you're looking for.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Email lists&lt;/strong&gt;. Must have web-browsable archives; the list without
    this has much less value. Search would be a nice bonus, though I
    haven't seen it done well. This lets teams communicate a bit more
    formally than IM and provides a record of certain discussions and
    decisions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FTP server.&lt;/strong&gt; Bonus points for a TFTP server. Maybe it's because
    of the nature of embedded software, but every team I've been on has
    always had to fling around large files and images. Email is horribly
    inefficient for this. Also, most of the products I've worked on
    needed an FTP (or TFTP) server to boot from. You're likely to
    install something on your development machine, which is fine, but
    it's convenient to have a central team/company internal server so
    you can just tell someone that a file is in your public directory on
    "the ftp server". Microsoft shops and folks that don't need to boot
    via FTP can probably get away with samba shares.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Doc server.&lt;/strong&gt; Wiki is probably the best way to go if your team
    doesn't &lt;em&gt;have&lt;/em&gt; to produce overly-formal documentation. Wikis and
    formal document control systems that I've used have had horrible
    search, which sucks but I haven't found a great solution. In the
    past I've done this with a combination of a formal doc tool plus a
    blog for the informal "howto" kind of notes that are always needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code review.&lt;/strong&gt; I can't say I've worked anywhere that had a
    dedicated code review tool. However, when all of the development
    machines are on NFS so that everyone has a view of your workspace,
    IM fits the bill reasonably well: post a review request to the chat
    room with the path to your changes and get an ad hoc on the spot
    code review. With some discipline and a supportive culture this
    works very smoothly. (This arrangement could probably be done with
    an internal pastebin, which I haven't listed here as must-have since
    I haven't really seen it used.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What would you add to this list? Any other tools or systems that, if
missing, would make you think twice about taking a job?&lt;/p&gt;
&lt;/p&gt;</summary><category term="tool"></category><category term="software-engineering"></category></entry><entry><title>Use SSH to Forward Multiple Protocols to Multiple Machines</title><link href="http://blog.bstpierre.org/ssh-forward-multiple-protocols-to-multiple-machines" rel="alternate"></link><updated>2009-12-04T11:03:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-12-04:ssh-forward-multiple-protocols-to-multiple-machines</id><summary type="html">&lt;p&gt;&lt;em&gt;(This is part five in a &lt;a href="../category/ssh"&gt;series of posts on ssh&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let's say you have a half-dozen machines at work you want to log into.
Instead of setting up a &lt;a href="http://blog.bstpierre.org/ssh-remote-port-forwarding-tunnels"&gt;remote forwarding connection&lt;/a&gt; from each of
those machines, you can have the connection from your main machine
perform multiple forwardings instead of just one. This even works if
some of the machines don't support ssh.&lt;/p&gt;
&lt;p&gt;It shouldn't surprise you at this point that you can do this with your
config file. On your work machine, you might have something like:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;Host&lt;/span&gt; &lt;span class="n"&gt;tunnel&lt;/span&gt;
  &lt;span class="n"&gt;HostName&lt;/span&gt; &lt;span class="n"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;
  &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;mycloudusername&lt;/span&gt;
  &lt;span class="n"&gt;IdentityFile&lt;/span&gt; &lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;id_dsa&lt;/span&gt;
  &lt;span class="n"&gt;Port&lt;/span&gt; 22
  &lt;span class="n"&gt;RSAAuthentication&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
  &lt;span class="n"&gt;PubkeyAuthentication&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
  &lt;span class="n"&gt;ExitOnForwardFailure&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
  # &lt;span class="n"&gt;tunnel&lt;/span&gt; &lt;span class="n"&gt;ssh&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;myworkmachine&lt;/span&gt;
  &lt;span class="n"&gt;RemoteForward&lt;/span&gt; 4022 &lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;22
  # &lt;span class="n"&gt;tunnel&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="n"&gt;desktop&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;mywindowsbox&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;myworkmachine&lt;/span&gt;
  &lt;span class="n"&gt;RemoteForward&lt;/span&gt; 5389 192&lt;span class="p"&gt;.&lt;/span&gt;168&lt;span class="p"&gt;.&lt;/span&gt;4&lt;span class="p"&gt;.&lt;/span&gt;10&lt;span class="p"&gt;:&lt;/span&gt;3389
  # &lt;span class="n"&gt;tunnel&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;mywindowsbox&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;myworkmachine&lt;/span&gt;
  &lt;span class="n"&gt;RemoteForward&lt;/span&gt; 5080 192&lt;span class="p"&gt;.&lt;/span&gt;168&lt;span class="p"&gt;.&lt;/span&gt;4&lt;span class="p"&gt;.&lt;/span&gt;10&lt;span class="p"&gt;:&lt;/span&gt;80
  # &lt;span class="n"&gt;tunnel&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="n"&gt;desktop&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;otherwindowsbox&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;myworkmachine&lt;/span&gt;
  &lt;span class="n"&gt;RemoteForward&lt;/span&gt; 6389 192&lt;span class="p"&gt;.&lt;/span&gt;168&lt;span class="p"&gt;.&lt;/span&gt;4&lt;span class="p"&gt;.&lt;/span&gt;11&lt;span class="p"&gt;:&lt;/span&gt;3389
  # &lt;span class="n"&gt;tunnel&lt;/span&gt; &lt;span class="n"&gt;ssh&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;workserver&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;myworkmachine&lt;/span&gt;
  &lt;span class="n"&gt;RemoteForward&lt;/span&gt; 7022 192&lt;span class="p"&gt;.&lt;/span&gt;168&lt;span class="p"&gt;.&lt;/span&gt;4&lt;span class="p"&gt;.&lt;/span&gt;2&lt;span class="p"&gt;:&lt;/span&gt;22
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can add a bunch of forwardings as shown above. Each entry will open
the given port on cloud and forward it to the specified port on the
specified machine. Now when you run "ssh tunnel" on your work machine,
it will connect to cloud and set up the five port forwardings specified
in your config file.&lt;/p&gt;
&lt;p&gt;Then when logged in to cloud.example.com, you can do, for example, "ssh
-p 7022 myserverlogin@localhost" to log into the machine called
workserver.&lt;/p&gt;
&lt;p&gt;If you mirror the remote forwardings in your home config file as local
forwardings, then when you "ssh work" from home you can remote desktop
to a windows machine from your home pc by doing "rdesktop -u
myworkwinuser localhost:5389" and it will use the tunnel. (The
connection will go from your home pc to cloud, to myworkmachine, to
mywindowsbox.) The windows machine does not need to know anything about
ssh.&lt;/p&gt;</summary><category term="tool"></category><category term="howto"></category><category term="ssh"></category><category term="tutorial"></category></entry><entry><title>Open an SSH Tunnel in Four Seconds or Less</title><link href="http://blog.bstpierre.org/open-an-ssh-tunnel-in-four-seconds-or-less" rel="alternate"></link><updated>2009-12-03T10:28:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-12-03:open-an-ssh-tunnel-in-four-seconds-or-less</id><summary type="html">&lt;p&gt;&lt;em&gt;(This is part four in a &lt;a href="../category/ssh"&gt;series of posts on ssh&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As I mentioned in a previous post on &lt;a href="http://blog.bstpierre.org/configure-ssh-username"&gt;ssh configuration&lt;/a&gt;, your config
file can specify a variety settings for each server.&lt;/p&gt;
&lt;p&gt;In fact, the Hosts you use don't even have to exist! (The HostName is
the important part.) Consider the following snippet in your
~/.ssh/config.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="n"&gt;Host&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;
  &lt;span class="n"&gt;HostName&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;
  &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;myworklogin&lt;/span&gt;
  &lt;span class="n"&gt;IdentityFile&lt;/span&gt; &lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;id_dsa&lt;/span&gt;
  &lt;span class="n"&gt;Port&lt;/span&gt; 4022
  &lt;span class="n"&gt;RSAAuthentication&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
  &lt;span class="n"&gt;PubkeyAuthentication&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
  &lt;span class="n"&gt;LocalForward&lt;/span&gt; 4022 &lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;4022
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I'm going to assume remote forwarding is set up and the connection is
open from work to cloud as described in &lt;a href="http://blog.bstpierre.org/ssh-remote-port-forwarding-tunnels"&gt;this post on remote
forwarding&lt;/a&gt;; and you've got local forwarding set up from home to cloud
as described in this post on &lt;a href="http://blog.bstpierre.org/local-ssh-forwarding"&gt;local port forwarding&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now you can do &lt;code&gt;ssh work&lt;/code&gt; from your home pc, and it will automatically
log you into your work pc with the right credentials using the tunnel
on cloud.example.com. And scp simplifies to &lt;code&gt;scp work:/tmp/foo.txt
~/foo.txt&lt;/code&gt; -- you don't have to remember the forwarded port numbers.&lt;/p&gt;
&lt;p&gt;Typing &lt;code&gt;ssh work&lt;/code&gt; is nine keystrokes (eight letters plus enter). If you
can type 40 wpm, that's 200 keystrokes per minute, or 3.33 keystrokes
per second, which means you can open the tunnel in four seconds!&lt;/p&gt;
&lt;p&gt;If you add &lt;code&gt;alias ssw='ssh work'&lt;/code&gt; to your ~/.bashrc, you're down to
four keystrokes.&lt;/p&gt;</summary><category term="tool"></category><category term="howto"></category><category term="ssh"></category><category term="tutorial"></category></entry><entry><title>Use Local SSH Forwarding to Reduce the Number of Manual Hops</title><link href="http://blog.bstpierre.org/local-ssh-forwarding" rel="alternate"></link><updated>2009-12-02T10:12:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-12-02:local-ssh-forwarding</id><summary type="html">&lt;p&gt;&lt;em&gt;(This is part three in a &lt;a href="../category/ssh"&gt;series of posts on ssh&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Local port forwarding is the same as &lt;a href="http://blog.bstpierre.org/ssh-remote-port-forwarding-tunnels" title="How to Use SSH Remote Port Forwarding to Set Up Secure Tunnels"&gt;remote port forwarding&lt;/a&gt; but
works in the opposite direction. An example is the clearest way to
explain.&lt;/p&gt;
&lt;p&gt;Assuming you've done the steps in the previous posts, then at home you
can run &lt;code&gt;ssh -L 4022:localhost:4022 me@cloud.example.com&lt;/code&gt;. This listens
on TCP port 4022 on your home machine. Any connections there will be
forwarded through the ssh connection to port 4022 on cloud... which, as
we recall, gets forwarded to port 22 (ssh) at work. If you leave this
connection open, you can run &lt;code&gt;ssh -p 4022 localhost&lt;/code&gt; on your home
machine and it will connect to work in just one hop. This means that you
can use scp to copy files from home to work or vice versa. For example,
&lt;code&gt;scp -P 4022 localhost:/tmp/foo.txt ~/foo.txt&lt;/code&gt; will copy a file from
work to home. (&lt;em&gt;Note: scp needs capital "-P" to give the port. I got it
wrong the first time.&lt;/em&gt;)&lt;/p&gt;</summary><category term="tool"></category><category term="howto"></category><category term="ssh"></category><category term="tutorial"></category></entry><entry><title>How to Use SSH Remote Port Forwarding to Set Up Secure Tunnels</title><link href="http://blog.bstpierre.org/ssh-remote-port-forwarding-tunnels" rel="alternate"></link><updated>2009-12-01T09:35:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-12-01:ssh-remote-port-forwarding-tunnels</id><summary type="html">&lt;p&gt;&lt;em&gt;(This is part two in a &lt;a href="../category/ssh"&gt;series of posts on ssh&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Ssh tunneling can be a bit mind bending at first, but it's simple when
you get used to it. Assume that you're trying to ssh between two sites
that do not allow incoming ssh. Maybe your IT at work is unenlightened
and doesn't have an ssh gateway. And your ISP has braindead
configuration rules that don't allow incoming ssh or they make it
difficult.&lt;/p&gt;
&lt;p&gt;What you need to get around this is a server "in the cloud" that permits
ssh logins. This could be a hosting server that you pay for, or even a
friend with an enlightened ISP who will give you a login account.&lt;/p&gt;
&lt;p&gt;On your work PC, use ssh to login to the "cloud" server. Using the "-R"
argument, you tell ssh to listen on a TCP port on the cloud server. Any
connection coming in to this server will be forwarded back through the
ssh connection to the TCP port you specify. For example, on
mymachine.work.com, &lt;code&gt;ssh -R 4022:localhost:22 me@cloud.example.com&lt;/code&gt;
tells ssh to listen on cloud's port 4022. Incoming connections to that
port on cloud will be forwarded to port 22 (ssh) on mymachine.&lt;/p&gt;
&lt;p&gt;By default, ssh will only listen to port 4022 on cloud's localhost
interface. So to log in to work, you will first need to log into cloud,
and then use &lt;code&gt;ssh -p 4022 myworklogin@localhost&lt;/code&gt; to log into work.&lt;/p&gt;
&lt;p&gt;We'll work around this limitation in the next post in this series.&lt;/p&gt;</summary><category term="tool"></category><category term="howto"></category><category term="ssh"></category><category term="tutorial"></category></entry><entry><title>How to Tell SSH Who You Are</title><link href="http://blog.bstpierre.org/configure-ssh-username" rel="alternate"></link><updated>2009-11-30T10:11:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-11-30:configure-ssh-username</id><summary type="html">&lt;p&gt;Ssh has amazing capabilities that you probably aren't using on a daily
basis.&lt;/p&gt;
&lt;p&gt;The capability that you probably aren't using, and the easiest to use,
is customizing your config file (in ~/.ssh/config) for the various
servers you log into.&lt;/p&gt;
&lt;p&gt;For example, I frequently log into about ten different servers using at
least four different usernames. By default, if I type &lt;code&gt;ssh server&lt;/code&gt; the
client will use my login name on the client machine to try to log into
the server -- which is usually wrong. Instead you can &lt;a href="http://superuser.com/q/64996/3948"&gt;tell your ssh
client which username to use on each server&lt;/a&gt;. (Thanks to &lt;a href="http://wblinks.com/"&gt;Rich
Adams&lt;/a&gt; for the tip.)&lt;/p&gt;
&lt;p&gt;You can customize a variety of settings -- not just the username. For
example, I specify a different identity file for a couple of servers.&lt;/p&gt;
&lt;p&gt;This saves a bunch of typing and occasional confusion. (By avoiding
login errrors as I try to log into a server using the wrong username and
can't figure out why my password isn't working...)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(This is the first post in &lt;a href="../category/ssh"&gt;series of posts about how to get the most
out of ssh&lt;/a&gt;. Make sure you don't miss the rest of the series:
&lt;a href="http://feeds2.feedburner.com/TheDailyBuild"&gt;subscribe to my feed&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;</summary><category term="tool"></category><category term="howto"></category><category term="ssh"></category><category term="tutorial"></category></entry><entry><title>One Simple Step for Avoiding Shallow Reviews</title><link href="http://blog.bstpierre.org/one-simple-step-for-avoiding-shallow-reviews" rel="alternate"></link><updated>2009-11-25T08:55:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-11-25:one-simple-step-for-avoiding-shallow-reviews</id><summary type="html">&lt;p&gt;We've all been guilty of giving a shallow review: "Looks ok."&lt;/p&gt;
&lt;p&gt;Given typical defect densities, any non-trivial design or code is going
to contain some errors. Even seemingly trivial maintenance fixes are
likely to be defective.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's your job as a reviewer to find as many of these defects as
possible.&lt;/strong&gt; If you're not finding defects, you're wasting your time on
reviews.&lt;/p&gt;
&lt;p&gt;That "one simple step"? Remind yourself that there are almost certainly
defects in the work product you're reviewing, and then find them. It's
all about attitude.&lt;/p&gt;</summary><category term="process"></category><category term="codereview"></category><category term="software-engineering"></category></entry><entry><title>If the comments are ugly, the code is ugly</title><link href="http://blog.bstpierre.org/if-the-comments-are-ugly-the-code-is-ugly" rel="alternate"></link><updated>2009-11-16T21:59:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-11-16:if-the-comments-are-ugly-the-code-is-ugly</id><summary type="html">&lt;p&gt;&lt;a href="http://www.itworld.com/development/84780/if-comments-are-ugly-code-ugly"&gt;If the comments are ugly, the code is ugly&lt;/a&gt; (via &lt;a href="http://slashdot.org/"&gt;slashdot&lt;/a&gt;). Amen!
I get uncomfortable whenever I have to leave a long comment, but it's
usually to document some deficiency in a lower layer that the code is
working around. Typically broken hardware. (So that someone coming
behind me doesn't say, "This is overly complicated, I can simplify it"
and then proceed to blow everything up.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Good programs do not contain spelling errors or have grammatical
mistakes."&lt;/p&gt;
&lt;/blockquote&gt;</summary></entry><entry><title>Who Else Wants Better Short Term Memory?</title><link href="http://blog.bstpierre.org/who-else-wants-better-short-term-memory" rel="alternate"></link><updated>2009-11-12T08:13:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-11-12:who-else-wants-better-short-term-memory</id><summary type="html">&lt;p&gt;In &lt;a href="http://www.amazon.com/dp/1591842247/?tag=bstpierreorg-20" title="&amp;quot;Talent is Overrated&amp;quot; by Geoff Colvin -- on Amazon"&gt;"Talent is Overrated"&lt;/a&gt;, Geoff Colvin at one point discusses how
superstars in many fields use the memory technique of "&lt;a href="http://en.wikipedia.org/wiki/Chunking_(psychology)" title="More about chunking on wikipedia."&gt;chunking&lt;/a&gt;" to
boost their short term memory.&lt;/p&gt;
&lt;p&gt;His simple example is the 13-letter word "lexicographer". To you and I
(assuming you speak English and have a decent vocabulary), it is easy to
remember. We don't have to remember 13 letters, we just remember the
whole chunk. But when presented with "trgdpxhdewfwm" for 3 seconds, you
probably can't remember more than half a dozen letters.&lt;/p&gt;
&lt;p&gt;Another example is that chess masters can recall board positions after
being shown a chess board with pieces on it for just a few seconds. They
do this by chunking sets of pieces together -- almost like "words" --
whereas novices will try to remember individual pieces ("letters").&lt;/p&gt;
&lt;p&gt;It struck me that programmers do the same thing when reading and writing
code.&lt;/p&gt;
&lt;p&gt;The coding standard helps us, by telling us where the chunks are and how
to draw the boundaries between chunks.&lt;/p&gt;
&lt;p&gt;If you have a coding standard.&lt;/p&gt;
&lt;p&gt;And apply it consistently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When you choose names at random, you destroy your short term memory.&lt;/strong&gt;
You become a crappy programmer, and you drag everyone around you down
too.&lt;/p&gt;
&lt;p&gt;Quick example in C. Assume you're writing a small module for checking
environmental status.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;isOverTemp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;isUnderTemp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="n"&gt;GetCurTemperature&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;env_isOverVoltage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;env_isUnderVoltage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Anyone writing this pile of gibberish should be
&lt;span style="text-decoration: line-through;"&gt;fired&lt;/span&gt;
excommunicated.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;For starters, I just wrote it, and I can't now remember which was camel
case and which was underscored. Also: which one was abbreviated?&lt;/p&gt;
&lt;p&gt;Before you object that this is a contrived example, two points: (1) yes,
it is contrived, that's the point of an example, and (2) I have worked
with far worse on an almost daily basis -- the example above is fairly
tame.&lt;/p&gt;
&lt;p&gt;On the other hand, if you know the coding standard has some simple rules
-- &lt;strong&gt;and they are followed uniformly&lt;/strong&gt; -- you can easily remember the
function names.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Initial 2-3 letter module prefix ("env" for this example).&lt;/li&gt;
&lt;li&gt;All functions are lowercase with underscores.&lt;/li&gt;
&lt;li&gt;No abbreviations.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then we have:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;env_is_over_temperature&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;env_is_under_temperature&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="n"&gt;env_get_cur_temperature&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;env_is_over_voltage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;BOOL&lt;/span&gt; &lt;span class="n"&gt;env_is_under_voltage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now, when you're writing, reviewing, or maintaining code, you don't need
to constantly refer to the header or documentation to get it right. A
simple three-line coding standard just boosted your memory capacity by
over 643%.&lt;/p&gt;
&lt;p&gt;(In the interest of full disclosure: I made that number up.)&lt;/p&gt;
&lt;p&gt;I realize that implementing even this simple three line standard is
controversial because all the camel case folks are pulling out big
sticks and the underscore people are grabbing rocks. To which I say:
just flip a coin and enjoy the extra brain power. Adopt a three-line
standard, &lt;strong&gt;follow it&lt;/strong&gt;, and save the curly-brace debate for the next
major coding standard revision.&lt;/p&gt;</summary><category term="process"></category><category term="codereview"></category><category term="coding-standard"></category><category term="software-engineering"></category></entry><entry><title>3 Easy Ways to Stick to a Coding Standard</title><link href="http://blog.bstpierre.org/3-easy-ways-to-stick-to-a-coding-standard" rel="alternate"></link><updated>2009-08-25T13:40:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-08-25:3-easy-ways-to-stick-to-a-coding-standard</id><summary type="html">&lt;p&gt;When you're writing python, you don't need a lot of debate over the
minutiæ of most coding standards. &lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;PEP 8&lt;/a&gt; does that for you. Even
better, there are some tools that make it really easy to stick to the
standard.&lt;/p&gt;
&lt;p&gt;Why do this? Well, for one thing it makes code reviews easier when
everyone follows the same conventions. It also makes maintenance easier.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://github.com/cburroughs/pep8.py/tree/master"&gt;pep8.py&lt;/a&gt; is a style checker that enforces the rules of PEP 8. The
    "official home" (?) &lt;a href="http://svn.browsershots.org/trunk/devtools/pep8/pep8.py"&gt;at browsershots.org&lt;/a&gt; was dead as I was writing
    this. (Thanks GitHub!) Run pep8.py on your code and it will tell you
    where you've drifted from the standard.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.logilab.org/857"&gt;pylint&lt;/a&gt; is lint for python. It provides more functionality than
    pep8.py, but is not a strict superset. (pep8.py is pickier about
    whitespace issues.) Pylint is also configurable to enforce various
    naming rules. Like most lints, the SNR is pretty low, but you can
    turn off most of the noise and get a reasonable signal for the
    things you want to check.&lt;/li&gt;
&lt;li&gt;Subversion (as well as most other tools) can be configured to run a
    script every time you check in code. Run one or both of the above
    tools in the pre-commit hook and bad code will be rejected. (I'd be
    wary of doing this with pylint unless you've got the categories of
    "noisy" warnings turned off.)&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;</summary><category term="python"></category><category term="codereview"></category><category term="tool"></category><category term="software-engineering"></category></entry><entry><title>Hassle Free Way to Kill Sudo'd Jobs</title><link href="http://blog.bstpierre.org/hassle-free-way-to-kill-sudod-jobs" rel="alternate"></link><updated>2009-08-12T19:45:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-08-12:hassle-free-way-to-kill-sudod-jobs</id><summary type="html">&lt;p&gt;Every now and then I have to run a foreground job under sudo that
doesn't want to die when I hit ^C. Then it's a hassle to ^Z, get the
pid of the sudo job, and sudo kill that pid.&lt;/p&gt;
&lt;p&gt;So I wrote a little script (or a template for scripts) that runs the
sudo job in the background (but preserves stdout/stderr) and relies on
bash to clean up the job when you ^C the script. Only gotcha with this
is that you may have to retype your sudo password when you ^C if your
authentication has timed out by the time you get around to killing it.&lt;/p&gt;
&lt;table class="codehilitetable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;cleanup&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
    sudo &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nv"&gt;$job_pid&lt;/span&gt;
    &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nv"&gt;$job_pid&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;trap &lt;/span&gt;cleanup SIGTERM
&lt;span class="nb"&gt;trap &lt;/span&gt;cleanup SIGINT
sudo long_running_foreground_process &amp;amp;
&lt;span class="nv"&gt;job_pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;
&lt;span class="nb"&gt;wait&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary><category term="script"></category><category term="tip"></category><category term="bash"></category><category term="linux"></category></entry><entry><title>Using Python's ctypes to Call Into C Libraries</title><link href="http://blog.bstpierre.org/using-pythons-ctypes-to-make-system-calls" rel="alternate"></link><updated>2009-08-05T05:35:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-08-05:using-pythons-ctypes-to-make-system-calls</id><summary type="html">&lt;p&gt;The ctypes module makes loading and calling into a dynamic library
incredibly easy:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CDLL&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;libc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CDLL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;libc.so.6&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt; &lt;span class="n"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;abcde&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
5
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As with everything else in python, it gets even better when you scratch
the surface. In the example above, CDLL returns an object that
represents the dynamic library. You can access the functions in that
library by attribute access ("libc.strlen") or item access
("libc['strlen']"). Both access mechanisms return a callable object.&lt;/p&gt;
&lt;p&gt;This callable object has an "errcheck" attribute that can be assigned a
callable. We can use this for error-checking our calls into the library.
Let's write a simple version of the "kill" command that uses the kill(2)
system call.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ctypes&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;# &lt;span class="n"&gt;Load&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;libc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CDLL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;libc.so.6&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;# &lt;span class="n"&gt;Our&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;checking&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt;
# &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;library&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt;
# &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;called&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
# &lt;span class="n"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;kill_errcheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;funcargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;&amp;#39;&amp;#39;&amp;#39;Check for error -- retval == -1.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retval&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; 0&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;kill%s failed&amp;#39;&lt;/span&gt; &lt;span class="c"&gt;% (funcargs, ))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;True&lt;/span&gt;# &lt;span class="n"&gt;Get&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;standard&lt;/span&gt; &lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;kill&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;# &lt;span class="n"&gt;Set&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;checker&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;
&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errcheck&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kill_errcheck&lt;/span&gt;# &lt;span class="n"&gt;Pass&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt;
# &lt;span class="n"&gt;SIGSEGV&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;11&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;1&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 11&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Save this as kill.py. Then, in your shell, try something like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;# Notice that the 3401 is the pid of the process&lt;/span&gt;
&lt;span class="c"&gt;# we&amp;#39;re putting into the background. Yours will&lt;/span&gt;
&lt;span class="c"&gt;# be different.&lt;/span&gt;
&lt;span class="n"&gt;bash&lt;/span&gt;$ &lt;span class="nb"&gt;sleep&lt;/span&gt; 120&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;1&lt;span class="p"&gt;]&lt;/span&gt; 3401
&lt;span class="n"&gt;bash&lt;/span&gt;$ &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="nb"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; 3401
&lt;span class="p"&gt;[&lt;/span&gt;1&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Segmentation&lt;/span&gt; &lt;span class="n"&gt;Fault&lt;/span&gt;         &lt;span class="nb"&gt;sleep&lt;/span&gt; 120
&lt;span class="n"&gt;bash&lt;/span&gt;$ &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="nb"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; 3401
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;kill.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;line&lt;/span&gt; 17&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;
    &lt;span class="nb"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 11&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;kill.py&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;line&lt;/span&gt; 10&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kill_errcheck&lt;/span&gt;
    &lt;span class="n"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;kill%s failed&amp;#39;&lt;/span&gt; &lt;span class="c"&gt;% (funcargs, ))&lt;/span&gt;
&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;3401&lt;span class="p"&gt;,&lt;/span&gt; 11&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;At line 4 of the output we run sleep in the background. At line 5 we
learn the pid of this process. At line 6 we run our kill program, giving
it the pid we just spawned, and we see the notification from bash that
the process was killed (with signal 11, segmentation fault). At line 8
we run our kill program again on pid 3401, but it doesn't exist, the
kill system call returns -1, and our error checker raises an exception
when it detects the system call failure.&lt;/p&gt;
&lt;p&gt;But wait, there's more... I'm working on a follow up post that combines
ctypes.Structure with calls into a linux system call.&lt;/p&gt;</summary><category term="python"></category><category term="howto"></category><category term="tutorial"></category><category term="linux"></category></entry><entry><title>Yet Another Python Enum Module</title><link href="http://blog.bstpierre.org/yet-another-python-enum-module" rel="alternate"></link><updated>2009-07-19T21:21:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-07-19:yet-another-python-enum-module</id><summary type="html">&lt;p&gt;I didn't like the existing enum recipes, so I cooked up what I feel is a
better way of working with enumerations in python. The result is
&lt;a href="http://github.com/bstpierre/yapyenum/tree/master"&gt;yapyenum&lt;/a&gt;, hosted on github. Rather than come up with something new
to say about it, I'll just repost the README here:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This module provides named enumerations for python.&lt;/p&gt;
&lt;p&gt;Unlike other implementations that I have seen, this is &lt;em&gt;not&lt;/em&gt; an
anonymous enumeration. To use it derive from the Enumeration class
provided in enum.py and list the names of the enum members in the
class member &lt;em&gt;enum&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The enum is itself a singleton class. It tries to be immutable via
&lt;strong&gt;slots&lt;/strong&gt; and by refusing to allow its members to be changed.&lt;/p&gt;
&lt;p&gt;Enum members are an integer subclass with a "name" property and that
knows how to pretty print itself. The enum values are interchangeable
with integers, which may or may not be what you want.&lt;/p&gt;
&lt;p&gt;The class supports membership ("FOO in MyEnum") and mapping a
non-instance-member value to a name. (I.e. if FOO uses the integer
value 1, then MyEnum.name(1) returns "FOO".)&lt;/p&gt;
&lt;p&gt;Tested on 2.4.6, 2.5.2, 2.6.2, on a combination of debian sarge, etch,
lenny, and ubuntu 9.04.&lt;/p&gt;
&lt;p&gt;There are obvious capabilities that could be added but this does all
that I need so far. Patches will be happily accepted.&lt;/p&gt;
&lt;p&gt;Permissive license (MIT). Have fun.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For other enum recipes, see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.python.org/dev/peps/pep-0354/"&gt;PEP 354&lt;/a&gt; (rejected enum proposal for python)&lt;/li&gt;
&lt;li&gt;The &lt;a href="http://pypi.python.org/pypi/enum/"&gt;enum&lt;/a&gt; module in pypi&lt;/li&gt;
&lt;li&gt;Recipe 413486: &lt;a href="http://code.activestate.com/recipes/413486/"&gt;First Class Enums in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Recipe 305271: &lt;a href="http://code.activestate.com/recipes/305271/"&gt;Enumerated values by name or number&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Recipe 67107: &lt;a href="http://code.activestate.com/recipes/67107/"&gt;Enums for Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Recipe 81098: &lt;a href="http://code.activestate.com/recipes/81098/"&gt;python enum with strings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;At Stack Overflow: &lt;a href="http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python"&gt;What's the best way to implement an 'enum' in
    Python?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Python Cookbook (at page 607 on &lt;a href="http://books.google.com/books?id=Q0s6Vgb98CQC&amp;amp;lpg=PA607&amp;amp;ots=hc1236Snoz&amp;amp;dq=python%20cookbook%20enum&amp;amp;pg=PA607"&gt;Google Books&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Several of these recipes have comments along the lines of "if you're
using one of these too-fancy enum recipes in python, you're doing
something unpythonic". This is likely true, but sometimes unavoidable
for social, political, and/or historical reasons.&lt;/p&gt;</summary><category term="python"></category></entry><entry><title>Python Exception Handling: Cleanup and Reraise</title><link href="http://blog.bstpierre.org/python-exception-handling-cleanup-and-reraise" rel="alternate"></link><updated>2009-07-15T22:35:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-07-15:python-exception-handling-cleanup-and-reraise</id><summary type="html">&lt;p&gt;I've had &lt;a href="http://gist.github.com/148135"&gt;this code&lt;/a&gt; around for a while and had an opportunity to drag
it out the other day and dust it off. The problem: Every now and again
there's a situation where you don't really want to catch an exception,
but you do want to perform some cleanup and let the exception propagate
up the stack. Sometimes there's an extra wrinkle in that the cleanup
code may itself throw an exception (that I'm simply going to assume we
can ignore).&lt;/p&gt;
&lt;p&gt;
&lt;script src="http://gist.github.com/148135.js"&gt;&lt;/script&gt;
&lt;/p&gt;

&lt;p&gt;You can run the file to see the behavior. Simply provide an integer 1-5
as a command line argument and you'll run the selected scenario and see
the output. The goal in this case is for cleanup to occur and an
exception to be reported as having occurred at line 10.&lt;/p&gt;
&lt;p&gt;The code in reraiser1 is wrong because this behaves as if a brand new
exception were thrown at line 27. That may not seem so bad, but this
code is pretty simple. If this happens and the stack trace is deep, it
will be almost impossible to diagnose what went wrong.&lt;/p&gt;
&lt;p&gt;The code in reraiser2 shows what happens when a second exception occurs
in the except block. A bare raise statement here might be an attempt to
re-raise the original exception, but python's rules about re-raising
specify that the most recent exception in the scope is what is reraised.
In this case, that's the exception thrown from the cleanup function.
Again, this makes troubleshooting difficult.&lt;/p&gt;
&lt;p&gt;In reraiser3 I worked around the problem in reraiser2 by moving the
cleanup function's exception into a separate scope by defining a local
cleanup function and calling it from within the except block. This
prevents the cleanup function's exception from polluting the scope with
an irrelevant exception and we can re-raise the original exception. This
results in a stack trace rooted at line 10.&lt;/p&gt;
&lt;p&gt;Reraiser4 takes a different approach. Instead of moving the cleanup
function's exception into a separate scope, it captures the traceback
information from the original exception and then passes it back to the
raise statement so that the reported traceback is accurate.&lt;/p&gt;
&lt;p&gt;The cleanest way to handle this is to use a finally block as shown in
reraiser5. This situation is what "finally" is meant for: it does not
trap the exception, it just gives you a chance to clean up before
control moves back up the stack to the caller. The presence of finally
clues readers in to the fact that you aren't messing with the exception,
and that the point of the block is to perform cleanup.&lt;/p&gt;
&lt;p&gt;Kindly drop me a note if I've got something wrong above, or if I'm
missing a technique (or a common anti-pattern!). Thanks.&lt;/p&gt;</summary><category term="python"></category><category term="howto"></category><category term="tutorial"></category></entry><entry><title>Makefiles are Software Too</title><link href="http://blog.bstpierre.org/makefiles-are-software-too" rel="alternate"></link><updated>2009-04-13T22:17:00-04:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-04-13:makefiles-are-software-too</id><summary type="html">&lt;p&gt;This post was inspired by recent experience with some horrible build
scripts from the open source world — but I've seen enough in-house
badness over the years that I wanted to establish some basic parameters
for build scripts.&lt;/p&gt;
&lt;p&gt;This is a really broad way to divide the world, but I see two important
categories of build scripts. (By "build script" I mean Makefiles,
SConsfiles, Ant files, and their ilk).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build scripts intended for in-house use, never seen by outsiders.&lt;/li&gt;
&lt;li&gt;Build scripts that you distribute to external users.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both sets of build scripts are important. Let's consider the build
script (or Makefile system, or whatever you're using) as a software
system independent of the software that is being built. Its primary
requirement is to reliably convert your source code into an executable.
Any decent build script has numerous other requirements, most of which
are probably implied. I have never seen a written requirement for the
numerous build scripts I've written. I have only rarely heard them
discussed — and never actually posed as "requirements".&lt;/p&gt;
&lt;p&gt;I have always taken the following meta-requirements for granted. Based
on my experience with some fairly awful build scripts, I guess these
aren't universally acknowledged.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The build script should build all the "normal" executables with a
    single command.&lt;/li&gt;
&lt;li&gt;The build script must encapsulate all environment variables within
    the script. (This is a corollary of the above.)&lt;/li&gt;
&lt;li&gt;The build script should not unnecessarily rebuild source code in a
    directory tree that has previously been built. (This is &lt;code&gt;make&lt;/code&gt;'s
    raison d'etre.)&lt;/li&gt;
&lt;li&gt;The build script should be documented. It doesn't have to be
    elaborate, but a five-line comment at the top of the script
    describing the available command-line variables would be nice.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make clean&lt;/code&gt; or its equivalent must work reliably.&lt;/li&gt;
&lt;li&gt;It should rarely be necessary to run &lt;code&gt;make clean&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bonus: the build should be parallelizable, to be able to take
    advantage of multicore machines and/or distributed builds.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Back to those two categories. If you screw up an in-house build script,
it's primarily your team that's going to suffer. I'm not aware of any
bugs from SQA or Customers on projects I've been on that were traced
back to a build script, but I can see where it could happen. Mainly what
I've seen is developers habitually wasting time rebuilding source that
should not need to be rebuilt. The worst offender in this respect was a
build script that forced a "make clean" at the beginning of every build.
In the immortal words of Dave Barry, I'm not making this up. Really.&lt;/p&gt;
&lt;p&gt;(Actually, I think it was even worse when I was dealing with a build
script that required a "make clean" every time around but didn't have it
coded into the script!)&lt;/p&gt;
&lt;p&gt;I can't say I have a completely clean conscience in this area: I wrote a
big pile of Makefiles for a past employer in which I unknowingly used
features (bugs?) of a specific version of GNU Make that were "fixed" in
a subsequent version. As time went on it became (a) a much bigger task
to fix the Makefile code that used the "feature" and (b) harder to keep
the specific version of GNU Make that worked with the Makefiles! I
imagine they're still stuck with this situation... sorry guys.&lt;/p&gt;
&lt;p&gt;In the second category, when you toss a slapped-together build script to
your unsuspecting users you do all of human society a disservice. Now
they are not only wasting their time fighting with a bad build script,
but they often have the disadvantage of being unable to contact the
author directly for support or commiserate with other experienced users
of the system.&lt;/p&gt;
&lt;p&gt;So do us all a favor: if you don't know what you're doing when you start
to write your next build script, please ask for help. Otherwise we'll
have to resort to some sort of professional licensing scheme for build
script authors.&lt;/p&gt;</summary><category term="tool"></category><category term="software-engineering"></category></entry><entry><title>Five Days to a Django Web App: Day Four, Deployment</title><link href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-four-deployment" rel="alternate"></link><updated>2009-03-02T11:48:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-03-02:five-days-to-a-django-web-app-day-four-deployment</id><summary type="html">&lt;p&gt;Thanks for your patience, and for coming back for a discussion of
deploying our Django web app.&lt;/p&gt;
&lt;p&gt;In case you missed any of the previous posts in this series, here they
are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready"&gt;Day One, Get Ready&lt;/a&gt; (Concept and prep)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups"&gt;Day Two, Mockups&lt;/a&gt; (Creating a design)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding"&gt;Day Three, Coding&lt;/a&gt; (Coding tests, views, templates, and models)&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;h2&gt;Pre-Deployment&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
First, we need to make a couple of decisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How are we going to push updates to the live site: FTP, git, svn?&lt;/li&gt;
&lt;li&gt;How are we going to handle backups?&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;h3&gt;Version Control as Distribution System&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
In my case, I'm using svn+ssh to push updates. Notice that this does not
require special setup on your server -- you do not need to install the
svn stuff that DreamHost or your host may provide. Just do svn init to
create a repository on the server (&lt;em&gt;outside&lt;/em&gt; the DocumentRoot!). Then
point your development pc to
svn+ssh://USERNAME@host.example.com/home/USERNAME/svn/PROJECT. (Git
works similarly, no support from your host required except the
binaries.)&lt;/p&gt;
&lt;p&gt;In the directory on the host where you're going to store your project
files, point to the same URL. (You could use the file:///.../ url, but I
prefer to avoid accessing the repo directly. Superstition?)&lt;/p&gt;
&lt;p&gt;Now, whenever you make a change on your development system, just "svn
ci" and then on the host "svn up" and restart your fcgi to pick up the
new code. Presto! The live site is updated with your change.&lt;/p&gt;
&lt;h3&gt;Backups&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
You must have a backup strategy: Your app will have users. Your host's
disk will burp. Your users will hate you when the disk burps and you
don't have a good backup.&lt;/p&gt;
&lt;p&gt;There are probably 374 different ways of backing up your Django app. The
two major things you need to capture are the database and your code. If
you are storing objects (e.g. uploaded files) outside the database,
you'll need to back these up too. The option I'm using is
&lt;a href="http://code.google.com/p/django-backup/"&gt;django-backup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pull the code from subversion into your project. Rename the directory to
"django_backup". Add django_backup to your INSTALLED_APPS. Verify it
works by running &lt;code&gt;./manage.py backup -c&lt;/code&gt;. Sanity check the backup by
doing &lt;code&gt;zless backups/*.gz&lt;/code&gt;. We'll set up a cron job on the host to run
this regularly when we deploy.&lt;/p&gt;
&lt;h2&gt;Deploy&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
I've previously written about &lt;a href="http://blog.bstpierre.org/deploying-django-apps-on-dreamhost"&gt;deploying Django apps on DreamHost&lt;/a&gt;, so
I'm not going to duplicate that here. Keep in mind that you want to use
the version control strategy outlined above. Read that article, deploy
your app and come back here when you're done.&lt;/p&gt;
&lt;h2&gt;Post-Deployment&lt;/h2&gt;
&lt;/p&gt;

&lt;h3&gt;Backup&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
Let's add that cron job we previously mentioned. On the host, run
&lt;code&gt;crontab -e&lt;/code&gt;. If you're on DreamHost and this if the first time you've
used cron, it will prompt you for an email address to send output to.
Then it will dump you into an editor. (Side note: "joe" is the default.
If you want something different, like vim, be sure that
EDITOR=/usr/bin/vim is set in your environment.)&lt;/p&gt;
&lt;p&gt;Set up a job something similar to the following:&lt;/p&gt;
&lt;p&gt;
    MAILTO="YOU@EXAMPLE.com"# m h  dom mon dow   command
    4 2 * * * (cd /home/PATH/TO/PROJECT; ./manage.py backup -c --email=YOU@EXAMPLE.com)

&lt;/p&gt;

&lt;p&gt;This will run a backup every day at 02:04 (AM). You will get two emails:
one with the output from the job, and one with the compressed backup
file. (When you get to the point where your database backups are too big
for email, you'll need to find another strategy.)&lt;/p&gt;
&lt;p&gt;Now we've got another problem to solve: we're going to accumulate a
bunch of backups on the disk. Let's get rid of the old backups. This is
pretty safe, since we're receiving backup files via email. Add a cron
job like this:&lt;/p&gt;
&lt;p&gt;
    14 2 * * * (cd /home/PATH/TO/PROJECT;
        touch --date=`date --iso --date='10 days ago'` .backup.oldest;
        find ./backups/ -mindepth 1 \! -newer .backup.oldest -execdir rm '{}' +)

&lt;/p&gt;

&lt;p&gt;(Formatted here for readability — you need to put that all on one line.)&lt;/p&gt;
&lt;p&gt;This will remove backup files older than 10 days. You could do this more
concisely with a tool like tmpwatch or tmpreaper, but neither is
installed on my host and this incantation should work on pretty much any
flavor and installation of linux.&lt;/p&gt;
&lt;p&gt;At this point we're deployed and the majority of the work is done.
Tomorrow we'll take a look at some maintenance issues.&lt;/p&gt;</summary><category term="python"></category><category term="howto"></category><category term="tutorial"></category><category term="django"></category></entry><entry><title>Firefox Quick Search for Google Maps</title><link href="http://blog.bstpierre.org/firefox-quick-search-for-google-maps" rel="alternate"></link><updated>2009-02-18T08:43:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-02-18:firefox-quick-search-for-google-maps</id><summary type="html">&lt;p&gt;I recently found myself wanting to quickly figure out how far (both
miles and driving time) it is to various destinations. At first I was
keeping a Firefox tab open to Google Maps and kept keying in the address
for the destination. Then I realized that I could make a "Quick Search"
and get the info faster. Add this link to your Quick Searches folder:&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;http://maps.google.com/maps?f=d&amp;amp;saddr=Elm+St,+
Manchester,+NH+03101&amp;amp;daddr=%s&amp;amp;output=html&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;[Formatted here for readability. Put it all on one line with no
spaces.]&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Edit the properties so that the "saddr=" part is your start address.
Change the keyword to "gm". This is set to only output simple HTML (no
map). If you want the full map, remove the &amp;amp;output=html on the end.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Now, when you need to get a quick idea of how long it is going to take
you to get somewhere, open a new tab and type "gm city, state".&lt;br /&gt;
&lt;/p&gt;</summary><category term="tip"></category></entry><entry><title>Five Days to a Django Web App: Day Three, Coding</title><link href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-three-coding" rel="alternate"></link><updated>2009-02-11T10:14:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-02-11:five-days-to-a-django-web-app-day-three-coding</id><summary type="html">&lt;p&gt;Thanks for coming back for Day Three!&lt;/p&gt;
&lt;p&gt;[Note: Sorry this post is a day late. It was all ready to go late
yesterday, but some of the code included below triggered a bug either in
WordPress or ScribeFire and the whole post got mangled. I managed to
resurrect it today from drafts, and I think it's coherent, but if you
find some problem with it please drop me a note.]&lt;/p&gt;
&lt;h2&gt;Progress So Far&lt;/h2&gt;
&lt;p&gt;Yesterday we &lt;a href="../five-days-to-a-django-web-app-day-two-mockups"&gt;built some mockups&lt;/a&gt;, just HTML and CSS — nothing active.
Hopefully you’ve had a chance to run your mockups past a couple of
people for feedback and ideas.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
Armed with these mockups, we’re ready to get started coding. Today’s
post is the longest in the series. Take it in chunks if you need to. (I
didn’t write it all at once either.)&lt;/p&gt;
&lt;h2&gt;Foundation&lt;/h2&gt;
&lt;/p&gt;

&lt;h3&gt;MySQL&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
First, go to your web host’s control panel and create two MySQL
databases for this project. I created "resumandb" and "test_resumandb".
This latter database is needed for running the built-in test system.&lt;/p&gt;
&lt;p&gt;You’ll get an error/warning if the test database exists when you first
run tests. However, the control panel needs to set user privileges and
it seems like the only way it knows how to do this is by creating the
database.&lt;/p&gt;
&lt;p&gt;
Set up the same databases on your local system:

    bash$ mysql -u root
    Welcome to the MySQL monitor.  Commands end with ; or \g.
    Your MySQL connection id is 1969
    Server version: 5.0.32-Debian_7etch8-log Debian etch distributionType 'help;' or '\h' for help. Type '\c' to clear the buffer.mysql&gt; CREATE DATABASE resumandb;
    Query OK, 1 row affected (0.00 sec)mysql&gt; GRANT ALL ON resumandb.* TO 'resuman'@'localhost' identified
        by 'reallY_baD_9passworD!';
    Query OK, 0 rows affected (0.11 sec)mysql&gt; GRANT ALL ON test_resumandb.* TO 'resuman'@'localhost' identified
        by 'reallY_baD_9passworD!';
    Query OK, 0 rows affected (0.0 sec)

Notice that you don't need to create the test database. As mentioned
above, this will happen when you run tests.

### Project Skeleton

What follows is a bunch of steps: do-this, do-that. At the end of this
section you should have a runnable (but empty) project that is ready to
start hanging functionality onto.

&lt;/p&gt;

&lt;p&gt;Then generate a skeleton for your project:
&lt;code&gt;django-admin.py  startproject YOURPROJECT&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then dive right in to settings.py and change a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set the admin email to your email address.&lt;/li&gt;
&lt;li&gt;Set the MySQL database according to your web host's settings.&lt;/li&gt;
&lt;li&gt;Timezone.&lt;/li&gt;
&lt;li&gt;MEDIA_ROOT, MEDIA_URL and variants — see note below.&lt;/li&gt;
&lt;li&gt;TEMPLATE_DIRS — see note below.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make it easier to use the same settings file to test both locally and
on your host, I add the following to my settings.py to set MEDIA_ROOT
and TEMPLATE_DIRS:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;osdef&lt;/span&gt; &lt;span class="n"&gt;full_path_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;&amp;#39;&amp;#39;&amp;#39;This makes this settings file relocatable.&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;full_path_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;static/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TEMPLATE_DIRS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;full_path_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;templates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then you just have to make sure to cd to the directory where your
settings.py lives whenever the app runs (more on this when we deploy to
the host). I find this easier than monkeying with PYTHONPATH.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;
Similarly, the following will change your URLs based on whether you're
running locally or on the host. (Just make sure you test for a directory
that only exists locally! My paths are a little different on the host.)&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;http://media.resuman.com/resuman/static/&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/home/brian/projects/resuman&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;MEDIA_URL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;http://localhost/apache2-default/resuman-static/&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;ADMIN_MEDIA_PREFIX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;http://media.example.com/admin_media/&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/home/brian/projects/resuman&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ADMIN_MEDIA_PREFIX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;http://localhost/apache2-default/resuman-admin/&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now it's time to generate the app: &lt;code&gt;./manage.py startapp YOURAPP&lt;/code&gt;.
Notice that the app name should be different from the project name
(otherwise it gets too confusing later on).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;
Edit your YOURPROJECT/urls.py to include a reference to YOURAPP:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;^&lt;span class="n"&gt;funnel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;$&lt;span class="s"&gt;&amp;#39;, include(&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Also uncomment the admin lines so you can use the built-in admin app.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
Now it's time to generate the app: &lt;code&gt;./manage.py startapp YOURAPP&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now edit YOURAPP/urls.py -- paste in the needed bits from the existing
urls.py, but drop the admin lines and the include().&lt;/p&gt;
&lt;p&gt;In your settings file, add the admin and admindoc apps and
"YOURPROJECT.YOURAPP" to INSTALLED_APPS.&lt;/p&gt;
&lt;p&gt;Under the YOURPROJECT directory, create a templates directory and a
static directory. Copy your HTML files from the mockup to the templates
directory. Copy the CSS file to the static directory.&lt;/p&gt;
&lt;p&gt;
My system is configured by default to serve from
/var/www/apache2-default/, and we specified above to fetch static files
from http://localhost/resuman-static/, so we need to do the following to
make this possible (substituting your paths, of course):

    bash$ ln -s /home/brian/projects/resuman/static /var/www/apache2-default/resuman-static
    bash$ ln -s /home/brian/projects/django/git/django/contrib/admin/media/   
       /var/www/apache2-default/resuman-admin

Restart apache.

&lt;/p&gt;

&lt;p&gt;Change directory to YOURPROJECT and
&lt;code&gt;./manage.py syncdb; ./manage.py runserver&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Browse to &lt;a href="http://127.0.0.1:8000/admin/"&gt;http://127.0.0.1:8000/admin/&lt;/a&gt;. Log in, and you should see
the admin app in all its glory. If the stylesheet didn't load (ie. it
looks really ugly), View Source on the page. At the top, find the URL to
that ends something like .../resuman-admin/css/base.css. Copy-paste this
into the address bar. It probably doesn't load. Verify that it is the
right URL — if not then change your settings.py to have the right URL
base. If the URL is right, then you need to fix your symlink, server
config, or permissions (I often get bit by having the wrong
permissions).&lt;/p&gt;
&lt;h3&gt;Adding Some Meat&lt;/h3&gt;
&lt;p&gt;This wouldn't be a bad time to push a snapshot into your version control
system (e.g.
&lt;code&gt;git init; git add .; git commit -m'YOURPROJECT skeleton done'&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;
Now we're finally ready to write the first view for this app. Edit
YOURAPP/urls.py. Lay out the URL map that you want to use for your app.
We're on a tight five day schedule, so don't go nuts! There's only time
to get a couple of pages done. Don't worry, you can add more later.
Here's what mine looks like:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaults&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;^$&lt;span class="s"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;^&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;$&lt;span class="s"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_job&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;^&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;$&lt;span class="s"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;edit_job&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Remember that my toplevel urls.py includes this based on the &lt;code&gt;^funnel/$&lt;/code&gt;
pattern, so each of the patterns above will have …funnel/ as a prefix in
the URL.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;
Now let’s write our first couple of tests. Edit YOURAPP/tests.py and add
something similar to the code below. You’ll have to change URLs and
logins. The class ViewTestCase is defined in viewtestcase.py, a
convenience TestCase subclass I wrote for testing Django views. Copy
that file into YOURAPPNAME directory.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;viewtestcase&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ViewTestCase&lt;/span&gt;
&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;DashboardViewTestBase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ViewTestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    # &lt;span class="n"&gt;Override&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subclass&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Must&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    # &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;defined&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;TESTMETHOD&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;get&amp;#39;&lt;/span&gt;    # &lt;span class="n"&gt;Override&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;TESTURL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;/funnel/&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;TESTARGS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;TEMPLATE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;dashboard.html&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;DashboardViewLoginTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DashboardViewTestBase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    # &lt;span class="n"&gt;We&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt; &lt;span class="n"&gt;expecting&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;TEMPLATE&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;avoid&lt;/span&gt; &lt;span class="n"&gt;getting&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;bogus&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;TEMPLATE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt;    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;test_login_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &amp;quot;&amp;quot;&amp;quot;
        &lt;span class="n"&gt;Tests&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &amp;quot;&amp;quot;&amp;quot;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expect_login_redirect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;returnclass&lt;/span&gt; &lt;span class="n"&gt;DashboardViewTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DashboardViewTestBase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    # &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;uses&lt;/span&gt; &lt;span class="n"&gt;TEMPLATE&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;    # &lt;span class="n"&gt;Set&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;automagically&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;USERNAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;brian&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;TESTLOGIN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;USERNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;test_logged_in_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now run the test: &lt;code&gt;./manage.py test&lt;/code&gt;. You should see exactly two
failures. If a bunch of stuff fails (like built-in django tests), fix
whatever is wrong before continuing.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;bash&lt;/span&gt;$ &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;django_admin_log&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;auth_permission&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;auth_group&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;auth_message&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;django_content_type&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;django_session&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;django_site&lt;/span&gt;
&lt;span class="n"&gt;Creating&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="n"&gt;jobfunnel_job&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogEntry&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permission&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Job&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;
&lt;span class="n"&gt;Installing&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="n"&gt;fixture&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;initial_data&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;/home/brian/projects/resuman/../resuman/jobfunnel/fixtures&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Installed&lt;/span&gt; 1 &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; 1 &lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;..........&lt;/span&gt;&lt;span class="n"&gt;EE&lt;/span&gt;&lt;span class="p"&gt;......&lt;/span&gt;
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_login_required&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DashboardViewLoginTest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;----------------------------------------------------------------------&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;brian&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;viewtestcase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 98&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;setUp&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TESTMETHOD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TESTURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TESTARGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;brian&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;viewtestcase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 79&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fetch_view&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testurl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testargs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 277&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 77&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 183&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;
    &lt;span class="n"&gt;sub_match&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 183&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;
    &lt;span class="n"&gt;sub_match&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 124&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 136&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_get_callback&lt;/span&gt;
    &lt;span class="n"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ViewDoesNotExist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Tried&lt;/span&gt; &lt;span class="c"&gt;%s in module %s. Error was: %s&amp;quot; % (func_name, mod_name, str(e))&lt;/span&gt;
&lt;span class="n"&gt;ViewDoesNotExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tried&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;module&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;dashboard&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;======================================================================&lt;/span&gt;
&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_logged_in_ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DashboardViewTest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;----------------------------------------------------------------------&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;brian&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;viewtestcase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 98&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;setUp&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TESTMETHOD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TESTURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TESTARGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;brian&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;viewtestcase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 79&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fetch_view&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testurl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testargs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 277&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 77&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 183&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;
    &lt;span class="n"&gt;sub_match&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 183&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;
    &lt;span class="n"&gt;sub_match&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 124&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &amp;quot;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;5&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;urlresolvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; 136&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_get_callback&lt;/span&gt;
    &lt;span class="n"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ViewDoesNotExist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Tried&lt;/span&gt; &lt;span class="c"&gt;%s in module %s. Error was: %s&amp;quot; % (func_name, mod_name, str(e))&lt;/span&gt;
&lt;span class="n"&gt;ViewDoesNotExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tried&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;resuman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;module&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;dashboard&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;----------------------------------------------------------------------&lt;/span&gt;
&lt;span class="n"&gt;Ran&lt;/span&gt; 18 &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; 4&lt;span class="p"&gt;.&lt;/span&gt;364&lt;span class="n"&gt;sFAILED&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;2&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Destroying&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let’s write the view so the test will pass. Edit views.py:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;login_required&lt;/span&gt;
&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_to_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;dashboard.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Dashboard&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                              &lt;span class="n"&gt;context_instance&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This view uses a template called dashboard.html. Let’s make that
template. Go back to the mockup for the dashboard. Copy everything into
templates/base.html, then rip out the content so all you have left is
the generic skeleton of a page in your app. Something like this (notice
that this is using the title variable passed into the context by the
view):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="n"&gt;DOCTYPE&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;PUBLIC&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;-//W3C//DTD XHTML 1.0 Strict//EN&amp;quot;&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;!--&lt;/span&gt;
&lt;span class="cp"&gt;# Resuman: keep track of job applications, cover letters, and resumes&lt;/span&gt;
&lt;span class="cp"&gt;#&lt;/span&gt;
&lt;span class="cp"&gt;# Copyright (c) 2009, Blakita Software LLC&lt;/span&gt;
&lt;span class="cp"&gt;# All rights reserved.&lt;/span&gt;
&lt;span class="o"&gt;--&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en&amp;quot;&lt;/span&gt; &lt;span class="nl"&gt;xml:&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;equiv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/html;charset=utf-8&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/css&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;{{ MEDIA_URL }}base.css&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="n"&gt;scripts&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endblock&lt;/span&gt; &lt;span class="n"&gt;scripts&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="kt"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;container&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="kt"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;menu&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;TBD&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;menu&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="kt"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;header&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="kt"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endblock&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="kt"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;footer&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;nav&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Home&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/blog/about&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;About&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/privacy.html&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Privacy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;copyright&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Copyright&lt;/span&gt; &lt;span class="err"&gt;©&lt;/span&gt; &lt;span class="mi"&gt;2009&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Blakita&lt;/span&gt; &lt;span class="n"&gt;Software&lt;/span&gt; &lt;span class="n"&gt;LLC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;footer&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now take the content you ripped out and put it into dashboard.html:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;base.html&amp;quot;&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;funnel&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase first&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Applied&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;AAA&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Confirmed&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;BBB&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;CCC&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Screen&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Fubar&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Interview&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Rabuf&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Offer&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Oof Rab&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase last&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;phase&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Start!&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Rab Oof&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Obviously this still just has dummy static data. We'll get some active
data in there very soon, but now we're ready to rerun the test. It
should pass this time. If not, fix the problem. When you get all tests
passing, celebrate!&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;
For real data, we need to write the model(s) used by this app. Let's add
another test:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="n"&gt;DashboardViewTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DashboardViewTestBase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;USERNAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;brian&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;TESTLOGIN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;USERNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;test_company_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expect_div_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;content&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Foobar Corp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Edit models.py to add the model:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contrib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;modelsclass&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;applicant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;company&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;80&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The test depends on having a job in the database. Let's set up a test
fixture. Edit YOURAPPNAME/fixtures/initial_data.json:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;pk&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 1&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;model&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;fields&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;username&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;brian&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;first_name&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;last_name&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;is_active&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 1&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;is_superuser&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 0&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;is_staff&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 0&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;last_login&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;2009&lt;span class="o"&gt;-&lt;/span&gt;02&lt;span class="o"&gt;-&lt;/span&gt;06 13&lt;span class="p"&gt;:&lt;/span&gt;50&lt;span class="p"&gt;:&lt;/span&gt;02&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;groups&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;user_permissions&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;password&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;sha1&lt;/span&gt;$0&lt;span class="n"&gt;fcc8&lt;/span&gt;$&lt;span class="n"&gt;c4cf5184f5c005f90165e782fb090e7d75b72986&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;email&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;brian&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;date_joined&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;2009&lt;span class="o"&gt;-&lt;/span&gt;02&lt;span class="o"&gt;-&lt;/span&gt;06 13&lt;span class="p"&gt;:&lt;/span&gt;44&lt;span class="p"&gt;:&lt;/span&gt;04&amp;quot;&lt;span class="p"&gt;}},&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;pk&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 2&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;model&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;fields&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;username&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;alan&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;first_name&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;last_name&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;is_active&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 1&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;is_superuser&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 0&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;is_staff&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 0&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;last_login&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;2009&lt;span class="o"&gt;-&lt;/span&gt;02&lt;span class="o"&gt;-&lt;/span&gt;06 13&lt;span class="p"&gt;:&lt;/span&gt;50&lt;span class="p"&gt;:&lt;/span&gt;02&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;groups&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;user_permissions&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;password&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;sha1&lt;/span&gt;$0&lt;span class="n"&gt;fcc8&lt;/span&gt;$&lt;span class="n"&gt;c4cf5184f5c005f90165e782fb090e7d75b72986&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;email&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;alan&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;date_joined&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;2009&lt;span class="o"&gt;-&lt;/span&gt;02&lt;span class="o"&gt;-&lt;/span&gt;06 13&lt;span class="p"&gt;:&lt;/span&gt;44&lt;span class="p"&gt;:&lt;/span&gt;04&amp;quot;&lt;span class="p"&gt;}},&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;pk&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 1&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;model&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;jobfunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;fields&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&amp;quot;&lt;span class="n"&gt;position_url&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;career&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;title&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Foobar&lt;/span&gt; &lt;span class="n"&gt;Eng&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;company_url&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;company&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Foobar&lt;/span&gt; &lt;span class="n"&gt;Corp&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;applicant&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; 1&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;phase&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Apply&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;date&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;2009&lt;span class="o"&gt;-&lt;/span&gt;02&lt;span class="o"&gt;-&lt;/span&gt;11&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;position&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Engineer&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;,&lt;/span&gt;
     &amp;quot;&lt;span class="n"&gt;notes&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;:&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;Applied&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;website&lt;/span&gt;&amp;quot;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will populate the database with a couple of users, both with
password "a", and a job before each test runs.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;
Run this test, expecting exactly one failure — the content div does not
contain the expected string. Let's grab the list of jobs in the view and
pass it into the template:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;login_required&lt;/span&gt;
&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_to_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;dashboard.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;Dashboard&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="s"&gt;&amp;#39;jobs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="p"&gt;},&lt;/span&gt;
                              &lt;span class="n"&gt;context_instance&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I won't paste all of the code here again, but we need to edit the
template to use the jobs list:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;companies&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      {% for job in jobs %}
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ job.company }}&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      {% empty %}
        &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;No jobs in this phase.&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      {% endfor %}
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;/p&gt;
Now rerun the test and expect it to pass. Hooray!&lt;/p&gt;
&lt;p&gt;One last refinement before we quit for today: users shouldn't be able to
see each others applications. The way this is coded, all jobs are going
to show up on everybody's dashboards. Not good. Here's another test that
checks that the application for Foobar Corp only shows up on Brian's
dashboard, not on Alan's.&lt;/p&gt;
&lt;p&gt;
    class PrivateDashboardViewTest(DashboardViewTestBase):
        USERNAME = 'alan'
        TESTLOGIN = (USERNAME, 'a')    def test_private_applications(self):
            assert('Foobar' not in self.get_div_content('content'))
            return

&lt;/p&gt;

&lt;p&gt;Run the test, watch it fail, and then change one line in the view:&lt;/p&gt;
&lt;p&gt;
        jobs = models.Job.objects.filter(applicant=request.user)

&lt;/p&gt;

&lt;p&gt;Now the all the tests should pass.&lt;/p&gt;
&lt;p&gt;Push a copy of your code into your version control tool. Take a break,
you deserve it.&lt;/p&gt;
&lt;p&gt;For "homework", put together your other views in the same way as this
one. We’ll look at deployment tomorrow.&lt;/p&gt;</summary><category term="python"></category><category term="howto"></category><category term="tutorial"></category><category term="django"></category></entry><entry><title>Five Days to a Django Web App: Day Two, Mockups</title><link href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups" rel="alternate"></link><updated>2009-02-10T09:40:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-02-10:five-days-to-a-django-web-app-day-two-mockups</id><summary type="html">&lt;p&gt;Welcome back.&lt;/p&gt;
&lt;p&gt;Ready for Day Two? Did you get your "hello world" app running on your
host?&lt;/p&gt;
&lt;h3&gt;Where We Stand&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready"&gt;Yesterday&lt;/a&gt; we nailed down our concept, bought a domain name and
hosting, set up our toolkit, and deployed a practice app on the host.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
Here's what I'm going to build: a web app to keep track of job
applications, cover letters, and resumes. The working name for the
project is "resuman". Unfortunately, that obvious domain name was
already registered and I'm not up for buying from the current owner. So
after trying a bunch of more-or-less obvious combinations, I grabbed
"yresu.me", and I'll deploy the app at "trackm.yresu.me".&lt;/p&gt;
&lt;h3&gt;Notes on Domain Registration&lt;/h3&gt;
&lt;p&gt;It's worth making a couple of notes here on the domain registration
process. I use &lt;a href="http://godaddy.com"&gt;GoDaddy&lt;/a&gt; for my registrations. In the past I've done
registration with my preferred host, but if you end up switching hosts
it's a hassle to move the domain registrations. Another complication:
hosting services like DreamHost only support registrations on the major
TLDs (.com, .org, .net, .info), while a full-service registrar like
GoDaddy supports alternatives like .me.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
Lastly, I like the bulk domain search service from &lt;a href="http://dotster.com/"&gt;Dotster&lt;/a&gt;. You can
search for up to 50 names at a time. So if your first choice is
unavailable, use Dotster's bulk search tool to enter a whole mess of
variations and see what's available. This is the process I used to find
yresu.me — the search included resu.me, esu.me, su.me, and myresu.me,
all of which were taken.&lt;/p&gt;
&lt;h3&gt;Make Some Mockups&lt;/h3&gt;
&lt;p&gt;Before we commit any design ideas to HTML, let's take some great advice
from 37signals: &lt;a href="http://gettingreal.37signals.com/ch06_From_Idea_to_Implementation.php"&gt;sketch a bunch of drafts on paper first&lt;/a&gt;. To back up
this idea, check out this story posted by Jeff Atwood about &lt;a href="http://www.codinghorror.com/blog/archives/001160.html"&gt;quantity
vs. quality&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The ceramics teacher announced on opening day that he was dividing the
class into two groups. All those on the left side of the studio, he
said, would be graded solely on the quantity of work they produced,
all those on the right solely on its quality. His procedure was
simple: on the final day of class he would bring in his bathroom
scales and weigh the work of the "quantity" group: fifty pound of pots
rated an "A", forty pounds a "B", and so on. Those being graded on
"quality", however, needed to produce only one pot - albeit a perfect
one - to get an "A".
&lt;/p&gt;
&lt;p&gt;
Well, came grading time and a curious fact emerged: the works of
highest quality were all produced by the group being graded for
quantity. It seems that while the "quantity" group was busily churning
out piles of work - and learning from their mistakes - the "quality"
group had sat theorizing about perfection, and in the end had little
more to show for their efforts than grandiose theories and a pile of
dead clay.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So my advice to you is to produce no fewer than five different designs
on paper. It only takes a few minutes to sketch something out and see
what it looks like. I did a handful of drawings on paper with my kids'
crayons, and then some refinements on my whiteboard once I had chosen
the general design. So use whatever media you have handy, and don't
worry about getting it perfect. Run your sketches by a couple of
innocent bystanders for some feedback.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;img alt="Mockups on my whiteboard." src="http://blog.bstpierre.org/static/images/whiteboard-mockup-300x203.jpg" title="Mockups on my whiteboard." /&gt;&lt;/p&gt;
&lt;p&gt;Once you've got a good design on paper, make mockups in HTML and CSS.
Steve Dennis at subcide.com has a great walkthrough on &lt;a href="http://www.subcide.com/tutorials/csslayout/"&gt;creating a CSS
layout from scratch&lt;/a&gt;. If you don't have an established process for
building up a design, follow his tutorial. Putting together the HTML+CSS
takes a little more time than sketches on paper, but it's still worth
doing a couple of different designs to see what grabs you. I worked up
two different CSS layouts on top of the same HTML. Run these designs by
some people — now that you've got some code, you can upload it to your
site and email a link to some friends. Pick the better of the two (or
three).&lt;/p&gt;
&lt;p&gt;Better yet, upload the mockups to your host and leave a comment here
with a link to them. Traffic here is low enough that I should be able to
reply with feedback. (Although feel free to circulate a link to this
series to a hundred of your friends, or post it to digg. I'll at least
reply to the first 50 comments...)&lt;/p&gt;
&lt;p&gt;Tomorrow we'll start writing some code.&lt;/p&gt;</summary><category term="python"></category><category term="howto"></category><category term="tutorial"></category><category term="django"></category></entry><entry><title>Five Days to a Django Web App: Day One, Get Ready</title><link href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-one-get-ready" rel="alternate"></link><updated>2009-02-09T10:15:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-02-09:five-days-to-a-django-web-app-day-one-get-ready</id><summary type="html">&lt;p&gt;This is the first in a series of posts that will walk through the steps
of designing, building, and deploying a complete web app using Django.
I'm going to assume you know the basics when it comes to Python, Django,
HTML, CSS, Javascript and some basic tools. This will be a part time
effort, so you should be able to work alongside me in a couple of hours
a day.&lt;/p&gt;
&lt;p&gt;A word about costs: you'll need to spend ten bucks or so for a domain
name, and anywhere from $30 to $150+ for hosting if you don't already
have these set up. Of course, you can just follow along on your server
at home for free, but you'll learn more if it's all live when everything
is said and done.&lt;/p&gt;
&lt;p&gt;Let's get started.&lt;/p&gt;
&lt;h2&gt;Day 1: Get Ready&lt;/h2&gt;
&lt;h3&gt;Concept&lt;/h3&gt;
&lt;p&gt;We need to figure out what we'll be building, and we need to get a few
things set up.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
First, you need an idea. It doesn't have to be the next &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt;. Try
to pick something simple that you'll be able to code up in a couple of
evenings, but useful enough that it's worth doing right.&lt;/p&gt;
&lt;p&gt;Let that simmer on the back burner, and let's get some stuff set up.&lt;/p&gt;
&lt;h3&gt;Hosting&lt;/h3&gt;
&lt;p&gt;You will need someplace to host the app. As noted above, you can do this
on your own machine at home, but it's good practice to set it up live —
the learning will be stickier too.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;a href="http://www.webfaction.com/"&gt;Webfaction&lt;/a&gt; is highly recommended for Django hosting, but it can be a
bit pricey if you're just experimenting.&lt;/p&gt;
&lt;p&gt;Deploying Django on &lt;a href="http://dreamhost.com/"&gt;Dreamhost&lt;/a&gt; will suffer a bit performance-wise,
but if you &lt;a href="http://www.google.com/search?q=dreamhost+coupon"&gt;google for a coupon&lt;/a&gt; you may be able to get up to $97 off.
So for $30 you can get a year's worth of hosting. Not bad.&lt;/p&gt;
&lt;p&gt;Go off and do this. Now. This post will still be here when you come
back.&lt;/p&gt;
&lt;h3&gt;Domain&lt;/h3&gt;
&lt;p&gt;Obviously, you're going to need a domain name where your app will live.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
How's that idea you've been simmering for the past 20 minutes? Good, now
pick a domain name to go with it. You can let this simmer for a while
too, but remember that the name can take a day or two to propagate
through DNS once you've purchased it.&lt;/p&gt;
&lt;p&gt;Not sure about Webfaction, but you can buy this through Dreamhost for
$10 or $15. Depending on what kinds of promotions they're offering, you
may even have a credit for a free domain name when you signed up for
hosting.&lt;/p&gt;
&lt;p&gt;Go ahead and snag that domain now, I'll wait patiently...&lt;/p&gt;
&lt;h3&gt;Tools&lt;/h3&gt;
&lt;p&gt;Since you don't want to develop on the hosting platform, you'll need to
install some things on your own machine so you can develop and test.
This is the minimum I've been using, please leave a comment if there's
something you think I'm missing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache with mod_python (used at Webfaction) or mod_fastcgi (used
    at Dreamhost). If you have a different host (or you're reading this
    at some future date and the options have changed), just make sure
    that what you're using matches them.&lt;/li&gt;
&lt;li&gt;Editor&lt;/li&gt;
&lt;li&gt;Firefox with the &lt;a href="http://www.getfirebug.com/"&gt;Firebug&lt;/a&gt; and &lt;a href="http://chrispederick.com/work/web-developer/"&gt;Web Developer&lt;/a&gt; add-ons.&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Django. I use the trunk. (Actually, I pull from &lt;a href="http://github.com/bstpierre/django/"&gt;my github fork&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It will be helpful if you can install the tools in the same way —
including the same paths — that you have on your web host. If this isn't
practical, it isn't a big deal, but it will make your life easier down
the road when you're trying to figure out why something works locally
but doesn't work on the live site.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
That's it for today. As homework, &lt;a href="http://blog.bstpierre.org/deploying-django-apps-on-dreamhost"&gt;set up a practice "hello world" app
on your host&lt;/a&gt; so that you know everything is working.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: continue this series with &lt;a href="http://blog.bstpierre.org/five-days-to-a-django-web-app-day-two-mockups"&gt;day two: mockups&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://blog.bstpierre.org/feed/rss2"&gt;Subscribe to The Daily Build&lt;/a&gt; to make sure you get all the posts in
this series!&lt;/p&gt;</summary><category term="python"></category><category term="howto"></category><category term="tutorial"></category><category term="django"></category></entry><entry><title>Jesse Noller on Python</title><link href="http://blog.bstpierre.org/jesse-noller-on-python" rel="alternate"></link><updated>2009-02-09T08:12:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-02-09:jesse-noller-on-python</id><summary type="html">&lt;p&gt;&lt;a href="http://jessenoller.com/category/python-magazine/"&gt;Jesse Noller&lt;/a&gt; has been republishing articles he wrote for Python
Magazine. These are very informative: I learned new things about the
&lt;a href="http://jessenoller.com/2009/02/03/get-with-the-program-as-contextmanager-completely-different/"&gt;"with" statement&lt;/a&gt; in python 2.5 and tonight I was &lt;a href="http://jessenoller.com/2009/02/05/ssh-programming-with-paramiko-completely-different/"&gt;introduced to
Paramiko&lt;/a&gt;, a library for using SSH.&lt;/p&gt;</summary><category term="python"></category></entry><entry><title>The Toolkit of a Software Engineer</title><link href="http://blog.bstpierre.org/the-toolkit-of-a-software-engineer" rel="alternate"></link><updated>2009-02-05T16:48:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-02-05:the-toolkit-of-a-software-engineer</id><summary type="html">&lt;p&gt;This is a rundown of the things you should have in your toolkit --
doesn't matter whether you call yourself a software engineer,
programmer, developer, code monkey, etc.&lt;/p&gt;
&lt;h2&gt;Editor&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
You must have an excellent editor. It should slice, dice, puree and
mince, all with minimal effort. Seriously, it should have support for
searching across files, "tags" (jumping to the definition of an
identifier), ideally provide tooltips for function calls while you're
writing, automatic template generation, and pushbutton compilation that
can step you through the list of compile errors, if any.&lt;/p&gt;
&lt;p&gt;Pick a good editor, and become an expert at using it. It's worth the
investment.&lt;/p&gt;
&lt;h2&gt;Toolchain (Compiler)&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
I can't really say compiler any more, since many of the languages we're
using aren't compiled in the traditional sense. So instead I'll call
this the toolchain. For your chosen language, you need to choose the
most appropriate toolchain. It may sometimes seem like you don't have
much choice: there's only one python, right? Wrong. You have the choice
of targeting different platforms (CPython, Jython or IronPython), and
also of choosing the version you'll use. You may need to choose an older
version so that you can match what's installed on your hosting provider.
You may even need to keep around multiple versions — maybe you're a
library writer and need to ensure portability across various versions.&lt;/p&gt;
&lt;p&gt;Things that are part of your toolchain: compiler or interpreter and
runtime. In other words, whatever it takes to make the software run on
your customers' computer.&lt;/p&gt;
&lt;h2&gt;Version Control&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
AKA VC, software configuration management (SCM), source control. This is
a tool that keeps track of the various versions of your software and
allows you to work effectively with other engineers. There are a number
of different VC tools, with a wide variety of features, complexity and
costs.&lt;/p&gt;
&lt;p&gt;If you haven't used VC before, take a look at &lt;a href="http://subversion.tigris.org/"&gt;subversion&lt;/a&gt;. It's free
(open source), widely used, easy to install and use, and stable. There
are other tools that may have better technical merits, especially for
certain environments, but I think subversion is the best place to start
from an ease-of-learning perspective.&lt;/p&gt;
&lt;h2&gt;Build&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
For your software to have value for your customers, you need to be able
to convert program text into executable software. If your software is a
one-file script, maybe all you need to do is publish it to your website.
In this is the case, you're allowed a pass on having a build tool, but
you should still consider an automated solution.&lt;/p&gt;
&lt;p&gt;If you have to deliver something more complex, an automated build tool
is required.&lt;/p&gt;
&lt;p&gt;Even if you're just publishing a handful of scripts to your website as a
tar or zip file, you still need to perform the following: gather the
scripts, tar them, name the tarball something sensible, and upload. Do
this by hand, and eventually you will skip a step and your customers
will end up downloading an unusable mess.&lt;/p&gt;
&lt;p&gt;At the opposite extreme, you may have hundreds of files that need to be
compiled into a handful of libraries and then linked into an executable.
You may have dozens of dependencies on external libraries. Translations
to other natural languages. All of this needs to be portable across a
dozen platforms.&lt;/p&gt;
&lt;p&gt;This is why "make" was invented, and why other tools with similar goals
continue to evolve to solve these complex problems. Ant, Maven, Scons,
an endless parade of Make variants, etc. Pick the build tool that best
works with your toolchain and specific situation.&lt;/p&gt;
&lt;h2&gt;Review&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
You need a way to review your work. If you're working alone, it may be
enough to simply review your work in your editor — though I'd recommend
at least printing it out. For whatever reason, it seems harder / less
effective to review a work product in the medium that produced it. A
better alternative may be to use the view / diff functionality in your
VC tool.&lt;/p&gt;
&lt;p&gt;If you're working with others, it will be helpful to &lt;a href="http://blog.bstpierre.org/code-review-tools"&gt;install a code
review tool&lt;/a&gt; that helps package your work product, distribute it to
your peers for review, and manage feedback.&lt;/p&gt;
&lt;h2&gt;Test&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
Some languages come with support for unit test built in — python
includes it in the standard library. You can find a handful of different
libraries for c++, and for &lt;a href="http://www.xprogramming.com/software.htm"&gt;seemingly any other programming language
known to man&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Your testing needs may also require a framework like selenium, which
allows automated testing in a web browser.&lt;/p&gt;
&lt;p&gt;If you're doing embedded software, you're probably going to have to jump
through a bunch of hoops to automate your testing. Unfortunately, you'll
have to figure out how big the hoops are, how many you need, how far off
the ground they need to be, whether they should be electrified, and then
you'll have to build them yourself. Annoying, but very much worth it in
the end.&lt;/p&gt;
&lt;h2&gt;Continuous Integration / Automated Build&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
Again: if you're working alone you can skip this, though I still think
it's useful. To be clear, "continuous integration" is more than a tool.
It's a way of life. However, this post is about your toolkit, and CI is
a very useful category of tool. There are a number of &lt;a href="http://blog.bstpierre.org/continuous-integration-tool-roundup"&gt;different CI
tools available&lt;/a&gt;, each with a slightly different focus and set of
features. Pick the tool that has the features you need, connect it to
your VC system, and enjoy the automation.&lt;/p&gt;
&lt;h2&gt;Lint&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
For those of you who aren't familiar, lint is an old-school tool for
finding potential trouble spots in C code. There are many tools
available for C and other programming languages that help to sniff out
trouble spots in code. Some tools can even spot design anti-patterns. C
has lint. Python has pylint. Coverity Prevent is a commercial tool
(expensive) that can find deep bugs in C, C++ and Java.&lt;/p&gt;
&lt;p&gt;I don't want to advocate sloppy programming practices: you shouldn't
&lt;em&gt;need&lt;/em&gt; to run lint on your code. But if there's a tool available that
can effectively remove defects from my software, I'm going to use it.&lt;/p&gt;
&lt;p&gt;("Effective" defined to mean costing less to remove defects than some
alternative, which is often either system testing or "customer testing".
Before you go spending tons of money on an expensive lint, are you doing
code reviews? This is an extremely cost effective practice. Spend your
time and money there first.)&lt;/p&gt;
&lt;p&gt;I'll continue this in another post. Meanwhile, what's in your toolkit?
Favorites from the categories above? Categories of software tools that
you want to make sure I don't miss?&lt;/p&gt;</summary><category term="tool"></category></entry><entry><title>Deploying Django Apps on Dreamhost</title><link href="http://blog.bstpierre.org/deploying-django-apps-on-dreamhost" rel="alternate"></link><updated>2009-02-03T10:42:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-02-03:deploying-django-apps-on-dreamhost</id><summary type="html">&lt;p&gt;The &lt;a href="http://wiki.dreamhost.com/Django"&gt;Dreamhost wiki article on Django&lt;/a&gt; helped, but all the steps
starting from scratch aren't really documented in one place. Hopefully
the list below will help, but since I'm writing it after the fact and I
had to go through a couple of iterations to get it right, there are
probably some things that aren't 100% right.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://wiki.dreamhost.com/Python"&gt;Read the python article.&lt;/a&gt; Set up virtualenv into $HOME/local.&lt;/li&gt;
&lt;li&gt;If you've already messed around with installing MySQLdb and/or other
    packages, remove them and start over.&lt;/li&gt;
&lt;li&gt;Install ez_setup (easy_install) as describe in the article.&lt;/li&gt;
&lt;li&gt;Install the MySQL egg: easy_install MySQL_python&lt;/li&gt;
&lt;li&gt;Follow the setup steps in the &lt;a href="http://wiki.dreamhost.com/Django"&gt;Django article&lt;/a&gt;. Do the "myproject"
    test using sqlite3. Really.
    It helped me find a couple of things I was doing wrong with my real
    (i.e. more complicated) MySQL project.&lt;/li&gt;
&lt;li&gt;As a deviation from the Django setup instructions, I prefer to use a
    fork of the Django codebase with some of my own patches. If you're
    in the same situation, use &lt;a href="http://github.com/bstpierre/"&gt;github&lt;/a&gt;. I forked the github
    "unofficial copy" of the subversion code, added a couple of patches
    that aren't in the trunk yet and a couple of my own, and cloned a
    copy into my dreamhost account.&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;</summary><category term="python"></category><category term="howto"></category><category term="django"></category></entry><entry><title>Five Things That Do Not Belong In A Review Checklist</title><link href="http://blog.bstpierre.org/five-things-that-do-not-belong-in-a-review-checklist" rel="alternate"></link><updated>2009-01-27T17:09:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-27:five-things-that-do-not-belong-in-a-review-checklist</id><summary type="html">&lt;p&gt;This is the second half of an article I posted about &lt;a href="http://blog.bstpierre.org/how-to-use-a-checklist-to-prevent-security-errors"&gt;using a checklist
to prevent security errors&lt;/a&gt;. There, I said that you have 15 checklist
items max, and you shouldn't waste any of those questions on silly
things like "Does the code follow the coding standard?".&lt;/p&gt;
&lt;p&gt;&lt;a href="http://blog.asmartbear.com/"&gt;Jason Cohen&lt;/a&gt; pointed to an &lt;a href="http://embedded.com/columns/guest/208803162"&gt;article of his&lt;/a&gt; in which he said
&lt;strong&gt;"List no items that can be automated."&lt;/strong&gt; (His emphasis, but I second
the motion.)&lt;/p&gt;
&lt;p&gt;In the context of the SANS paper, let's look at five items that should
be automated so that no human has to find the defects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://cwe.mitre.org/data/definitions/665.html"&gt;CWE-665&lt;/a&gt;: Improper Initialization. As the write-up for CWE-665
    suggests, you could use a programming language that forces you to
    explicitly initialize all variables before use. Or, if you're stuck
    with something like C, make sure you turn on the warnings in your
    compiler, or use a static analysis tool (e.g. lint) to verify that
    all variables are initialized before being used. Tools like Coverity
    can be very sophisticated in their static analysis.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://cwe.mitre.org/data/definitions/119.html"&gt;CWE-119&lt;/a&gt;: Failure to Constrain Operations within the Bounds of a
    Memory Buffer. Same goes for this one. First, use a programming
    language that doesn't require attention to every tiny little detail
    of string handling. Failing that, apply static analysis. It's also
    worth performing runtime analysis (e.g. electric fence, Purify) with
    appropriate test cases to verify that you've avoided buffer
    overflows.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://cwe.mitre.org/data/definitions/404.html"&gt;CWE-404&lt;/a&gt;: Improper Resource Shutdown or Release. Even garbage
    collected languages can have problems with resource release. First,
    some GC systems have problems with circular references. Second, GC
    systems are typically worried about releasing memory. You also have
    to worry about database handles, file handles, and sockets.
    Configure your static analysis tool to track resources like these so
    that you don't have to do it manually.&lt;/li&gt;
&lt;li&gt;The write-up for CWE-404 also mentions in passing that you should
    "wash your garbage before you dispose of it". This is useful for two
    reasons: first, an attacker won't have access to the contents of
    previously used memory, and second, it often makes debugging memory
    problems easier if you write a known value into freed memory. (I
    modified malloc() at my last job to write 0xcacacaca into freed
    memory so that we would know right away when someone stepped on a
    turd, um, I mean used freed memory.) Configure your libraries to
    shred objects before you free them and you won't have to worry about
    it on a line-by-line basis in the code you review.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://cwe.mitre.org/data/definitions/362.html"&gt;CWE-362&lt;/a&gt;: Race Condition. I'm typically worried less about
    security than the nightmare of isolating and debugging race
    conditions. I haven't seen any tools that detect race conditions
    well, but Coverity does a decent job of telling you when you've
    mishandled a race condition in a couple of cases: first, it warns
    you when you fail to release a lock, and second, it warns you when
    you access a resource that is statistically accessed from within a
    lock. So you still have to beware of race conditions (at the design
    level is the best place to find these), but there is an option for
    finding tedious errors in the mechanics of dealing with races.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And now I'm going to throw out everything above. Remember that the tools
are built to detect common errors. If it is really important that you
find a certain class of bug, put it on your checklist. In fact, if it's
super critical, make it a checklist of length one. For example, I've
gone over codebases with the single-minded focus of finding race
conditions. When you perform a review like this, it's amazing how many
defects you might find in areas that you never suspected.&lt;/p&gt;
&lt;/p&gt;</summary><category term="process"></category><category term="codereview"></category></entry><entry><title>Seeking Timelog Beta Testers</title><link href="http://blog.bstpierre.org/seeking-timelog-beta-testers" rel="alternate"></link><updated>2009-01-27T14:09:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-27:seeking-timelog-beta-testers</id><summary type="html">&lt;p&gt;&lt;a href="http://nutriaproject.com/"&gt;Nutria is an online timelog.&lt;/a&gt; I'm hoping to start a live beta this
week, initially light on features but with more to follow. I need some
beta testers to give it a spin and provide feedback. &lt;a href="http://nutriaproject.com/"&gt;Sign up to
test&lt;/a&gt; and you will have my undying
gratitude. Thanks!&lt;/p&gt;</summary></entry><entry><title>How To Use A Checklist to Prevent Security Errors</title><link href="http://blog.bstpierre.org/how-to-use-a-checklist-to-prevent-security-errors" rel="alternate"></link><updated>2009-01-23T12:23:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-23:how-to-use-a-checklist-to-prevent-security-errors</id><summary type="html">&lt;p&gt;The &lt;a href="http://www.sans.org/top25errors/"&gt;2009 CWE/SANS Top 25 Most Dangerous Programming Errors&lt;/a&gt; has been
out for a while now. Maybe you've already eliminated all of these errors
from your code. In case you haven't, this post will help you develop a
checklist that you can use to eliminate these errors starting at the
architecture level and moving through design, code, and testing. Before
we get to the security aspect, first let's take a quick detour through
the mechanics of a review checklist.&lt;/p&gt;
&lt;h3&gt;Why A Review Checklist is Essential&lt;/h3&gt;
&lt;p&gt;Most (all?) experts recommend the use of a checklist for performing
design and code reviews. It's more effective to ask a set of questions
about the item being reviewed than to simply stare at a bunch of code
looking for problems. The checklist focuses your attention on aspects
that are most important.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
If you want to perform a broader review, develop two or three checklists
with different foci and give the lists to separate reviewers. The defect
lists from each reviewer will have less overlap than if you gave
everyone the same list.&lt;/p&gt;
&lt;h3&gt;What Your Review Checklist Should Look Like&lt;/h3&gt;
&lt;p&gt;The following advice is partly based on advice from &lt;a href="http://www.amazon.com/Software-Inspection-Tom-Gilb/dp/0201631814/"&gt;"Software
Inspection" by Gilb and Graham&lt;/a&gt;. A good checklist is a list of
questions. For practical purposes, I'm strongly in favor of a checklist
that does not exceed one side of a printed page — at normal font size.
For me this means a checklist of about 15 items, 20 as an absolute
maximum. Remember, we're &lt;em&gt;focusing&lt;/em&gt;, which means looking harder for just
a few types of items. If you're using online checklists, I'd extend this
to mean that it should fit on the screen without scrolling. Either way,
I don't think you should have more than 20 questions or so at the max.
Fewer is probably better, more is too overwhelming.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
Checklist items are yes/no questions. They should all be phrased such
that a "no" answer means something is broken.&lt;/p&gt;
&lt;p&gt;When reviewing, proceed through the entire item under review (the entire
code listing or design) with each question. Don't work linearly, this is
a horrible way to review code. I prefer to review on paper, simply
because I can make little notes and marks all over the paper as I go
through it. For the first question, you might work mostly front-to-back.
Then the second question might send you backwards through the code.
After a couple of passes over the code you have some familiarity with
it, so the third question might send you straight to a location that has
potential problems.&lt;/p&gt;
&lt;h3&gt;How To Choose Questions for the Review Checklist&lt;/h3&gt;
&lt;p&gt;Since the whole point of the checklist (and reviews in general) is to
find defects, the questions should focus on finding the highest value
defects. Why waste valuable checklist real estate on a question like
"Does the code conform to formatting conventions?" Duh. If your team has
a formatting convention, and you look at a piece of code that doesn't
conform, it's going to jump off the page and bite you in the nose. You
don't need a checklist question for this!&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
Back to the specific problem at hand: security errors. SANS has done a
great job of sifting through tons of security errors and boiling them
down to the top 25. We could probably develop a 100 question review
checklist from their Top 25 list, but it would be too big to be usable.
So let's &lt;em&gt;focus&lt;/em&gt; on the most important for our application.&lt;/p&gt;
&lt;p&gt;For this exercise, our application is a blog like Wordpress but much
simpler: no plugins, no comments, single author. This is not a hosted
application like Blogger, so we are less concerned about vulnerabilities
like cross-site scripting, SQL injection, and OS command injection. We
do need to make sure that only the author is allowed to post, so let's
look at the "Porous Defenses" category.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CWE-285: Improper Access Control (Authorization)&lt;/li&gt;
&lt;li&gt;CWE-327: Use of a Broken or Risky Cryptographic Algorithm&lt;/li&gt;
&lt;li&gt;CWE-259: Hard-Coded Password&lt;/li&gt;
&lt;li&gt;CWE-732: Insecure Permission Assignment for Critical Resource&lt;/li&gt;
&lt;li&gt;CWE-330: Use of Insufficiently Random Values&lt;/li&gt;
&lt;li&gt;CWE-250: Execution with Unnecessary Privileges&lt;/li&gt;
&lt;li&gt;CWE-602: Client-Side Enforcement of Server-Side Security&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The entry for CWE-285: Improper Access Control lists the following for
prevention and mitigation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For web applications, make sure that the access control mechanism is
enforced correctly at the server side on every page. Users should not
be able to access any information that they are not authorized for by
simply requesting direct access to that page. One way to do this is to
ensure that all pages containing sensitive information are not cached,
and that all such pages restrict access to requests that are
accompanied by an active and authenticated session token associated
with a user who has the required permissions to access that page.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here are some straightforward checklist questions we can extract from
that description:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Does every administrative page enforce access control on the server
    side?&lt;/li&gt;
&lt;li&gt;Are unauthenticated (anonymous) users prevented from accessing
    administrative pages?&lt;/li&gt;
&lt;li&gt;Is caching disabled for administrative pages?&lt;/li&gt;
&lt;li&gt;Does every administrative page validate the session token properly?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you can go walk through your design and/or code to verify that you
can say "yes" to each of these questions.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
If you found this post helpful, &lt;a href="http://blog.bstpierre.org/feed/rss2"&gt;subscribe to The Daily Build&lt;/a&gt; to get
updates as they are posted. Thanks!&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&amp;quot;2009 &lt;span class="n"&gt;CWE&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;SANS&lt;/span&gt; &lt;span class="n"&gt;Top&lt;/span&gt; 25 &lt;span class="n"&gt;Most&lt;/span&gt; &lt;span class="n"&gt;Dangerous&lt;/span&gt; &lt;span class="n"&gt;Programming&lt;/span&gt; &lt;span class="n"&gt;Errors&lt;/span&gt;&amp;quot;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="process"></category><category term="codereview"></category><category term="howto"></category><category term="tutorial"></category></entry><entry><title>Today's Time Log Is Tomorrow's Historical Data</title><link href="http://blog.bstpierre.org/todays-time-log" rel="alternate"></link><updated>2009-01-20T08:16:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-20:todays-time-log</id><summary type="html">&lt;p&gt;The only good way to create any kind of a reliable estimate is by using
(your own) historical data.&lt;/p&gt;
&lt;p&gt;If you are trying to create an estimate for a new project and are having
a hard time, don't lament your lack of historical data. The best thing
you can do is to set up a system for tracking time on your projects.
Today's time log is tomorrow's historical data. (When I say "you" I mean
either you, singular, if you work alone or you, plural, to refer to your
project team.) Do the best you can on this estimate, recognize that it's
probably a bunch of nonsense, and move forward.&lt;/p&gt;
&lt;p&gt;Before you start working on the project you just (mis-)estimated, plan
how you're going to track time spent on it. This doesn't mean you have
to establish the Time Tracking Committee, hold a dozen meetings every
day, and publish a seventy-page document describing how time will be
tracked. Just pick a solution and spitball it past your team.
Lightweight is best for a first pass, you'll be able to get a feel for
what works and what doesn't in your situation.&lt;/p&gt;
&lt;p&gt;Some things to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You might work a nominal eight hour day, but you probably spend less
    than three actually working on your project. Meetings, support for
    previous projects, and the water cooler suck up a lot more time than
    you realize.&lt;/li&gt;
&lt;li&gt;Some meetings are directly project-related (design review, etc) and
    should probably be charged to the project. Oooh, I said "charged".
    Don't get too uptight about applying all of your time to one bucket
    or another. Keep it lightweight and easy to implement and you'll
    increase the likelihood that you'll be able to stick with it.&lt;/li&gt;
&lt;li&gt;Spreadsheets can be a convenient way to track time for individuals
    or small teams. If a team is going to use a spreadsheet-based
    solution, take a look at Google Docs and set up a shared
    spreadsheet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end of the project, you can compare the actual results to the
estimate you made. Disappointing, eh? Don't worry, you'll get better
with practice and as you build a data collection.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
See also &lt;a href="http://www.amazon.com/Software-Estimation-Demystifying-Practices-Microsoft/dp/0735605351/"&gt;Software Estimation: Demystifying the Black Art&lt;/a&gt;, by Steve
McConnell. This book is an excellent resource.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&amp;quot;&lt;span class="n"&gt;Software&lt;/span&gt; &lt;span class="n"&gt;Estimation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Demystifying&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Black&lt;/span&gt; &lt;span class="n"&gt;Art&lt;/span&gt;&amp;quot;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="tool"></category><category term="estimation"></category><category term="howto"></category></entry><entry><title>Time Tracking Input Mechanisms?</title><link href="http://blog.bstpierre.org/time-tracking-input-mechanisms" rel="alternate"></link><updated>2009-01-19T13:00:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-19:time-tracking-input-mechanisms</id><summary type="html">&lt;p&gt;Do time tracking tools have a low uptake because it's tedious to enter
your activity data?&lt;/p&gt;
&lt;p&gt;What do you use to enter time / activity data into your tracking tool?&lt;/p&gt;
&lt;p&gt;If you don't use time tracking, is there a specific reason or is it just
not something you've considered?&lt;/p&gt;
&lt;p&gt;What if you could enter activity data through twitter or similar
services?&lt;/p&gt;
&lt;p&gt;Lastly, how much does the ability to (selectively?) share your activity
data with others on related projects affect your choice of method?&lt;/p&gt;
&lt;p&gt;Thanks for your response!&lt;/p&gt;</summary><category term="tool"></category></entry><entry><title>Simpler C Development Environment</title><link href="http://blog.bstpierre.org/simpler-c-development-environment" rel="alternate"></link><updated>2009-01-19T10:14:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-19:simpler-c-development-environment</id><summary type="html">&lt;p&gt;If you're on Windows and you had problems installing Cygwin, there is
another package that includes GCC called "MinGW" (minimalist GNU for
Windows). Download and run &lt;a href="http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/mingw-get-inst-20120426/"&gt;the MinGW installer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Go through the installer and keep clicking the defaults. It will then
download and install several other packages. When the install finishes,
do Start Menu -&gt; Run... -&gt; "cmd", then:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;\\\&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="n"&gt;tutorial&lt;/span&gt;
&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;\\\&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;tutorial&lt;/span&gt;
&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;\\&lt;/span&gt;&lt;span class="n"&gt;tutorial&lt;/span&gt;&lt;span class="o"&gt;\&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;\\&lt;/span&gt;&lt;span class="n"&gt;MinGW&lt;/span&gt;&lt;span class="o"&gt;\\&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="c"&gt;%path%&lt;/span&gt;
&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;\\&lt;/span&gt;&lt;span class="n"&gt;tutorial&lt;/span&gt;&lt;span class="o"&gt;\&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gcc&lt;/span&gt; &lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And follow the rest of the directions &lt;a href="http://blog.bstpierre.org/getting-started-in-c"&gt;from the first tutorial post&lt;/a&gt;,
making the appropriate path substitutions and using \ instead of /.&lt;/p&gt;
&lt;p&gt;If you have problems downloading through the automatic installer, try
following the &lt;a href="http://www.mingw.org/wiki/Getting_Started#toc2"&gt;MinGW "Manual Download" directions&lt;/a&gt;. (Just for the
"minimum requirements".)&lt;/p&gt;</summary><category term="howto"></category><category term="tutorial"></category><category term="c-programming"></category></entry><entry><title>The Best Development Tools</title><link href="http://blog.bstpierre.org/the-best-development-tools" rel="alternate"></link><updated>2009-01-14T09:25:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-14:the-best-development-tools</id><summary type="html">&lt;p&gt;There are two kinds of &lt;em&gt;great&lt;/em&gt; software development tools:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Those that do &lt;em&gt;one&lt;/em&gt; thing exceptionally well.&lt;/li&gt;
&lt;li&gt;Those that allow you to communicate better with your coworkers.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In some rare cases, you may find that the same tool lives in both
categories.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
Without an insane focus on exactly one mission, tools get watered down
and cumbersome to use.&lt;/p&gt;
&lt;p&gt;Quick example: version control tools. When evaluating a tool, ask what
else the tool is capable of. If it can also handle bug tracking, feature
requests, mailing list management, continuous integration, and
monitoring the coffee pot, then back away slowly. Seriously. You want a
version control system that is tightly focused on its core mission and
nothing else.&lt;/p&gt;
&lt;p&gt;Yes, you need those other capabilities, but you're better off getting
them from a different tool and then integrating the two. I know, I know,
it sounds like such a great idea to have everything under one roof, one
vendor to contact, etc. The reality is that you end up with a
comprehensive, tightly integrated set of weak tools.&lt;/p&gt;</summary><category term="tool"></category></entry><entry><title>Getting Started in C</title><link href="http://blog.bstpierre.org/getting-started-in-c" rel="alternate"></link><updated>2009-01-09T12:14:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-09:getting-started-in-c</id><summary type="html">&lt;p&gt;Some may argue that C is an outdated language to learn in 2009. I
disagree. Learning C requires some degree of understanding how computers
really work. And if you can &lt;a href="http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html"&gt;understand pointers&lt;/a&gt;, you're ten steps
ahead of the game.&lt;/p&gt;
&lt;p&gt;This is the first in a series of articles designed to help the reader
learn the C language and understand how to use it to solve practical
problems. The tutorial will be based on gcc in a Cygwin or Linux
environment.&lt;/p&gt;
&lt;p&gt;If you are already running Linux or have Cygwin and GCC installed,
you're halfway done with the first lesson.&lt;/p&gt;
&lt;h2&gt;Lesson 1&lt;/h2&gt;
&lt;h3&gt;Part 1, Get GCC&lt;/h3&gt;
&lt;p&gt;If you're using linux, open a terminal window (bash prompt). Type "gcc
--version". If you get back something like:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;$ &lt;span class="n"&gt;gcc&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;
&lt;span class="n"&gt;gcc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GCC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 4&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="p"&gt;.&lt;/span&gt;2 20061115 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prerelease&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Debian&lt;/span&gt; 4&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="p"&gt;.&lt;/span&gt;1&lt;span class="o"&gt;-&lt;/span&gt;21&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Copyright&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 2006 &lt;span class="n"&gt;Free&lt;/span&gt; &lt;span class="n"&gt;Software&lt;/span&gt; &lt;span class="n"&gt;Foundation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Inc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;free&lt;/span&gt; &lt;span class="n"&gt;software&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;see&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;copying&lt;/span&gt; &lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;  &lt;span class="n"&gt;There&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;NO&lt;/span&gt;
&lt;span class="n"&gt;warranty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;even&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;MERCHANTABILITY&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;FITNESS&lt;/span&gt; &lt;span class="n"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="n"&gt;PARTICULAR&lt;/span&gt; &lt;span class="n"&gt;PURPOSE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then you're all set. If you don't have gcc installed, use your
distribution's package installation tool to install it. (For example,
Debian-based systems will use &lt;code&gt;sudo apt-get install gcc&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
If you're on Windows, install &lt;a href="http://cygwin.com/setup.exe"&gt;Cygwin&lt;/a&gt;. When the selection box pops up
to choose packages, expand the "Devel" category and select gcc:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Select GCC from the list of packages." src="http://blog.bstpierre.org/static/images/cygwin-gcc-select-300x211.png" title="Select GCC from the list of packages." /&gt;&lt;/p&gt;
&lt;p&gt;There's a &lt;a href="https://www.youtube.com/watch?v=2-YUD5r4gUw"&gt;video tutorial&lt;/a&gt; available if you want more
detail, or a &lt;a href="http://www.softpanorama.org/Unixification/Cygwin/cygwin_minitutorial.shtml"&gt;text tutorial&lt;/a&gt; if you'd rather read about it.&lt;/p&gt;
&lt;h3&gt;Part 2, "Hello World"&lt;/h3&gt;
&lt;p&gt;Now you're ready to write your first program. If you're on Windows, I
strongly recommend using a decent text editor instead of Windows Notepad
or WordPad. If you have no editor on your system, try &lt;a href="http://www.notepad-plus-plus.org/"&gt;Notepad++&lt;/a&gt;. I
haven't used it much, but it seems to work well enough. If you're on
Linux, you have a choice of whatever editors are installed on your system:
vi, emacs, pico, etc.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
Now, create a directory to store your source files. On Windows, place
this directory under your cygwin installation directory. I'm going to
assume you're using c:\cygwin\tutorials\, so adjust accordingly if
you choose a different path.&lt;/p&gt;
&lt;p&gt;
Finally, we can write a program to print the string "Hello World" to the
output when you run it. Open your text editor and *type in* the
following program. (Do not cut and paste. The act of retyping the code
presented in this tutorial will help you learn better.)

    #include &lt;stdio.h&gt;int main(int argc, char** argv)
    {
        printf("Hello World\n");
        return 0;
    }

Now, get to a bash prompt. (On cygwin, this is the cygwin icon that
should be on your desktop from part 1 above. On linux, this is a
terminal window or whatever your system happens to call it.) Change to
the directory where your files are stored.

    bash$ cd /tutorial

On cygwin, this will put you in the c:\\cygwin\\tutorial directory. Now,
compile the file.

    bash$ gcc hello.c

GCC does not give output for a successful compile, it just produces the
executable. If you do a directory listing ("ls" at the bash prompt) you
will see a file called a.out (linux) or a.exe (cygwin). For historical
reasons, this is what the compiler produces by default. You can run this
and see the output. Type "./a.exe" or (./a.out) at the bash prompt and
you should get the greeting.

&lt;/p&gt;

&lt;p&gt;
We can change the output file by telling the compiler what we want it to
name the file.

    bash$ gcc -o hello hello.c

The -o flag (think of "output") tells the compiler to produce a file
called hello.exe (cygwin) or hello (linux). Try this, and run
"./hello.exe".

### Part 3, Breakdown

Here's a rundown of what the pieces of this program mean.

&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This tells the compiler to pull the text of the file stdio.h into the
program. This is called a "header" file, because it is typically
included at the top (head) of your program. This header file contains
the standard input/output definitions and declarations, including the
declaration of the function "printf" which is used below.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;int main(int argc, char** argv)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This is the start of a function definition. "main" is the name of the
function that the operating system will call when you run the program.
Main is always declared this way: it accepts two "arguments" called argc
and argv, and it returns an integer (int). We'll come back to the argc
and argv in another lesson.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A left brace is used to indicate the start of a block. The line that
describes the type and arguments of the function is followed by a block
that contains the programming statements that actually do something
useful.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;printf("Hello World\n");&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This calls the printf function, which sends text to the output device
(in this case, your terminal window). A function call is built by
following the function name with parentheses and including arguments
inside the parentheses. The printf function is passed an argument: the
string "Hello World\n".&lt;/p&gt;
&lt;p&gt;In C, strings are surrounded by quotation marks. The sequence \n means
"newline". &lt;strong&gt;Exercise&lt;/strong&gt;: modify the program to remove the \n,
compile it, and see what happens.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;return 0;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This ends the function, returning control to the function that called it
(in this case, back to the operating system). It gives the value 0 to
the calling function. When we return 0 to the operating system, this
signifies a normal exit. Returning 1 or other values generally signifies
that an error occurred.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A right brace is used to mark the end of the function.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exercise&lt;/strong&gt;: Experiment with this. Change the string. Add another call
to printf to display a different greeting.&lt;/p&gt;
&lt;p&gt;Next lesson: the debugger.&lt;/p&gt;</summary><category term="howto"></category><category term="tutorial"></category><category term="c-programming"></category></entry><entry><title>Version Control Habits of Effective Developers</title><link href="http://blog.bstpierre.org/version-control-habits" rel="alternate"></link><updated>2009-01-07T09:55:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-07:version-control-habits</id><summary type="html">&lt;p&gt;(Apologies to &lt;a href="http://www.google.com/url?sa=t&amp;amp;source=web&amp;amp;ct=res&amp;amp;cd=3&amp;amp;url=http%3A%2F%2Fwww.amazon.com%2FHabits-Highly-Effective-People%2Fdp%2F0671708635&amp;amp;ei=qdJkSZn1A4is8gTF45XSCQ&amp;amp;usg=AFQjCNHcwbWdJxdTwjTERnp8lQ0alziYVw&amp;amp;sig2=CAkQYh3m2s0tkwADLCxGvQ"&gt;Stephen R Covey&lt;/a&gt; for the title...)&lt;/p&gt;
&lt;p&gt;Through some odd coincidence these posts crossed my path recently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.codinghorror.com/blog/archives/001165.html"&gt;Check In Early, Check In Often&lt;/a&gt; [comments here are especially
    good]&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.looplabel.net/2008/07/28/best-practices-for-version-control/"&gt;Best Practices for Version Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.iovene.com/5-svn-best-practices/"&gt;5 SVN Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jamesshore.com/Blog/Continuous-Integration-is-an-Attitude.html"&gt;Continuous Integration is an Attitude, Not a Tool&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I got to thinking about is that version control is one of the
foundation tools in your development process. It enables many other
processes and practices. The posts above make great points, but I'd like
to put them in the context of the overall process. (I've also given some
thought to the conflicting advice given, again in the context of the
forest instead of the trees.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use version control!&lt;/strong&gt; If you're not doing this, you should get
    out of the profession now.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check in often.&lt;/strong&gt; This is frequently given advice, but I feel it's
    often given out of context. [Jeff Atwood][Check In Early, Check In
    Often] includes a quote from AccuRev founder &lt;a href="http://damonpoole.blogspot.com/2005/07/check-in-early-and-check-in-often.html"&gt;Damon Poole&lt;/a&gt;: &lt;q&gt;My
    rule of thumb is "check-in early and often", but &lt;em&gt;with the caveat
    that you have access to private versioning&lt;/em&gt;. If a check-in is
    immediately visible to other users, then you run the risk of
    introducing immature changes and/or breaking the build.&lt;/q&gt; (My
    emphasis.) "Private versioning" means a private branch: you can
    check in without affecting anyone else (see #8). But even with a
    private branch, consider &lt;a href="http://www.iovene.com/5-svn-best-practices/"&gt;Salvatore Iovene&lt;/a&gt;'s
    advice: "SVN is not a backup tool". He makes the point that you
    shouldn't check in before you go home just because it's the end of
    the day. Instead check in when you have a logical unit of work (e.g.
    everything compiles, most of the tests pass but you have to make
    some experimental changes to finish the feature or perform
    debugging, etc.). The danger is that if you have to revert because
    you've made some disastrous changes, you won't have a sane state to
    revert to. Final point: Salvatore also reminds us that some tools
    don't commit to a central repository by default (git, darcs, etc),
    so checking in doesn't replace a good backup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Put everything in version control.&lt;/strong&gt; This is a key enabler for
    continuous integration (CI). &lt;a href="http://jamesshore.com/Blog/Continuous-Integration-on-a-Dollar-a-Day.html"&gt;James Shore&lt;/a&gt; has several great posts
    on CI. I think he under-emphasizes the need to check in everything
    necessary to build. First, it should be easy for a new hire to pull
    down a source tree and immediately get a good build. It makes life
    easier for them and for you. Second, your CI server should be able
    to do the same thing. If someone upgrades one of the tools locally
    and the build depends on this then the integration build should
    break. The tool upgrade must be checked in so that everyone picks up
    the change.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don't break the build.&lt;/strong&gt; [When I say "build" I mean "build and
    tests". It's unfortunate naming but I think this is the way the term
    is used by a lot of people.] This is a promise that the team has to
    make to each other: "I will not break the build." What it means is
    that every team member agrees not to check in code to the trunk that
    does not build and pass all tests. If you have jerks on your team
    that constantly break the build, ask them to play by the rules, and
    if they can't then get rid of them because they're dragging you
    down.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check in bite sized chunks.&lt;/strong&gt; Too big and you've probably been
    working too long: did you go dark? Large checkins are also harder to
    review well, so quality will suffer. If a given feature is large,
    create a branch and continue to check in bite sized chunks (see
    #6).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branch, but only when needed.&lt;/strong&gt; If you don't have bite sized
    chunks, or if more than one person needs to work on a feature that
    can't go into the trunk, create a branch. When you have a branch, it
    is critical to update (or merge, or pull, whatever terminology you
    prefer) into the branch from the trunk on a regular basis.
    Integration will be a nightmare if you don't. When the code on the
    branch is mature &lt;em&gt;enough&lt;/em&gt;, merge (or push) it back to the trunk.
    (Tip: it doesn't need to be perfect, it just shouldn't introduce
    regressions into the trunk.) If more non-bite-sized work is needed
    on the feature, create a new branch and repeat the process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check in when you're ready.&lt;/strong&gt; But not sooner or later. This should
    be obvious, but some people don't seem to get it. If you check in
    before you're ready, you break the build (and your promise to your
    team members). This can stop your entire team dead in their tracks!
    If you wait too long, you've lost the opportunity to get rapid
    feedback on your work. This practice enables continuous integration.
    It relies on having work broken down into bite sized chunks (see
    #5).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use private versioning.&lt;/strong&gt; Private versioning means that each
    developer has his own branch (or even multiple branches). With a VC
    tool like AccuRev, this is automatic: every tree has an associated
    private stream (branch) on the server. With distributed VC tools
    like git, this is also automatic: everything is private until you
    push/pull to the central repository or another developer. It can be
    simulated with other tools but requires a bit more effort. This
    allows individual developers to maintain several versions of their
    work before they are ready to share with the rest of the team. It
    also allows a developer to keep a sane version of code before
    merging in changes from the trunk. (If the merge becomes a disaster,
    he can discard the merge results and go back to the sane version
    without much effort.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update/merge/pull regularly.&lt;/strong&gt; This also seems obvious, but again
    not everybody gets it. I mentioned it above as part of branching,
    and it's especially important if you're using private branches. You
    must merge from the trunk to your private branch/workspace/sandbox
    regularly. If you have private versioning, this is low risk because
    it's easy to revert a bad merge. (These two practices create a
    self-reinforcing virtuous cycle!)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Train your users.&lt;/strong&gt; I've worked in shops where it seems like only
    half of the team understands anything beyond "checkout, update,
    checkin". &lt;em&gt;Everyone&lt;/em&gt; must know how to perform daily duties like
    checkout, checkin, update and merge from trunk, merge from private
    branch to trunk, how to navigate history, perform diffs, etc.
    Everyone should know how to perform less-frequently-needed duties
    like odd sorts of merges - cherry picking, etc, though this is maybe
    less important.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a common project structure and naming convention.&lt;/strong&gt; Thanks to
    &lt;a href="http://blog.looplabel.net/2008/07/28/best-practices-for-version-control/"&gt;Anders Sandvig&lt;/a&gt; for this bit of
    wisdom. I wouldn't have thought to include it in VC best practices
    but it makes sense to list it here. A common project structure makes
    it easier to add every project into the CI server. For example, I've
    used the CI server to enforce the rule that every project must
    provide a $BUILDROOT/build script that runs the entire build. (The
    server also enforced placing tests in $BUILDROOT/tests, list of
    images to be published in $BUILDROOT/MANIFEST, etc.) This also makes
    it easier to move staff from one project to another.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enable checkin notifications&lt;/strong&gt;. This is typically seen as an email
    from a post-commit-hook (or whatever your tool calls it). The
    checkin notification can also launch CI build. Ideally, the
    notification will tie each checkin back to whatever
    issue/defect/task/project tracker you're using.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write good checkin comments.&lt;/strong&gt; This makes notifications
    worthwhile. Remember that VC is a communication tool as much as a
    time machine! I've had several experiences where reading someone
    else's checkin comment has spurred a discussion that either causes
    them to rework their bug fix or it has caused me to change course on
    something I've got in progress.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perform atomic checkins.&lt;/strong&gt; - When it's time to check in, don't do
    ten different checkins for the ten different files you modified!
    They all belong together as a logical unit. This makes merging
    easier. It prevents the CI server from running multiple build and
    test cycles needlessly (and from reporting spurious breakages
    because it picked up half a change). This makes history easier to
    understand when others look at comments. This is part of training,
    and is reinforced if you expect all developers to merge regularly.
    (On the theory that merging is easier when checkins are atomic.)&lt;/li&gt;
&lt;li&gt;[Advanced practice.] &lt;strong&gt;Use a holding area.&lt;/strong&gt; - Instead of checking
    in to trunk, developers check in to a holding branch (call it
    "pre-integration" or something similar). You perform code review and
    run integration tests on that branch. When review has approved the
    changes and the tests pass, promote (merge) the change from the
    holding area to trunk. The rest of the team pulls (merges) from the
    trunk, which is guaranteed safe. I've also seen the use of a
    two-level holding area where the first level is for review and the
    second level is integration test. This is the default if you use
    private branches (#8) and perform code reviews out of the private
    branch before integrating (#16).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perform reviews on checked-in code.&lt;/strong&gt; This is so much better than
    manually gathering changes and emailing patches around. (Though
    tools like git have optimizations specifically for eamiling patches
    around.) This is easy if you're using private branches since you can
    request a review before integrating. Some &lt;a href="http://blog.bstpierre.org/code-review-tools"&gt;code review tools&lt;/a&gt;
    enable this.&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;</summary><category term="codereview"></category><category term="tool"></category><category term="continuousintegration"></category></entry><entry><title>Continuous Integration Tool Roundup</title><link href="http://blog.bstpierre.org/continuous-integration-tool-roundup" rel="alternate"></link><updated>2009-01-05T13:31:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-05:continuous-integration-tool-roundup</id><summary type="html">&lt;p&gt;This is a round-up of continuous integration tools, in no particular
order.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://cruisecontrol.sourceforge.net/" title="CruiseControl"&gt;CruiseControl&lt;/a&gt;: Open source, Java-based. Focused on build.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://studios.thoughtworks.com/cruise"&gt;Cruise:&lt;/a&gt; Commercial. Build/test/deploy/release. Works for most
    platforms and languages. Free for small and open source teams.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ccnet.thoughtworks.com"&gt;CruiseControl.NET&lt;/a&gt;Open source rewrite of the original
    CruiseControl for Java.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hudson.dev.java.net/" title="Hudson"&gt;Hudson&lt;/a&gt;: Open source, Java-based, runs as servlet. Supports build
    and test. Other processes supported via plugin architecture.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.electric-cloud.com/products/electriccommander.php" title="ElectricCommander"&gt;ElectricCommander&lt;/a&gt;: Commercial. Does build, test, deploy.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.anthillpro.com/" title="AnthillPro"&gt;AnthillPro&lt;/a&gt;: Commercial. Does build, test, deploy, release.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://continuum.apache.org/"&gt;Continuum&lt;/a&gt;: Open source from Apache. Focused on build.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://gump.apache.org/" title="Gump"&gt;Gump&lt;/a&gt;: Open source from Apache. Focuses on building and testing
    public open source Java projects. May not be suitable for private
    installations.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.atlassian.com/software/bamboo/" title="Bamboo"&gt;Bamboo&lt;/a&gt;: Commercial. Build and test, with other processes
    provided by plugin.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.automatedqa.com/products/abs/"&gt;Automated Build Studio&lt;/a&gt;: Commercial. Build, test and release.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://buildbot.net/trac" title="BuildBot"&gt;BuildBot&lt;/a&gt;: Open source. Build and test.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://cabie.tigris.org/" title="CABIE"&gt;CABIE&lt;/a&gt;: Open source. Focus on build.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.conifersystems.com/cascade/" title="Cascade (software)"&gt;Cascade&lt;/a&gt;: Commercial, free for personal use. Build and test; can
    be done before comitting changes.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://draconet.sourceforge.net/" title="Draco.NET"&gt;Draco.NET&lt;/a&gt;: Open source. Focus on .NET (Windows only?) builds.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.jetbrains.com/teamcity/" title="TeamCity"&gt;TeamCity&lt;/a&gt;: Commercial with free professional edition. Java and
    .NET only. Build, test, inspection (code review).&lt;/li&gt;
&lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/vstudio/ff637362.aspx" title="Team Foundation Server"&gt;Team Foundation Server&lt;/a&gt;: Commercial from Microsoft. Team Build is
    the continuous integration component, tightly integrated with the
    rest of the system.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.johnkeiser.com/mozilla/tbox3.html" title="Tinderbox"&gt;Tinderbox&lt;/a&gt;: Open source from Mozilla. Focuses on cross platform
    build and test.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.viewtier.com/products/parabuild.htm"&gt;Parabuild&lt;/a&gt;: Commercial, free for Open Source projects. Does
    build, test, deploy.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://dev.taptinder.org/"&gt;TapTinder&lt;/a&gt;: Open source. Build and test, with emphasis on test
    cases.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://zutubi.com/"&gt;Pulse&lt;/a&gt;: Commercial, free for small teams and open source.&lt;/li&gt;
&lt;li&gt;Homebrew: The pile of scripts and cron jobs you might be using to
    manage a daily/nightly/continuous build.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
If you're in the Homebrew camp, &lt;strong&gt;please leave a comment&lt;/strong&gt;: I'm curious
about what the tools above are missing that force you to DIY. (I've been
in this category in the past, for various reasons.)&lt;/p&gt;
&lt;p&gt;I'd also appreciate any comments as to the relative merits of the
systems mentioned above. I've only used a couple of them.&lt;/p&gt;
&lt;p&gt;Lastly, if you use (or sell) some tool that is not listed here, please
leave a comment and I'll update the list. Thanks!&lt;/p&gt;</summary><category term="tool"></category><category term="continuousintegration"></category></entry><entry><title>Contact Me</title><link href="http://blog.bstpierre.org/contact-me" rel="alternate"></link><updated>2009-01-04T18:50:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-04:contact-me</id><summary type="html">&lt;p&gt;
There used to be a form here to contact me, but it is no longer here.
&lt;/p&gt;

&lt;p&gt;
You can send email to the obvious username at this domain.
&lt;/p&gt;</summary></entry><entry><title>Migrated to WordPress</title><link href="http://blog.bstpierre.org/migrated-to-wordpress" rel="alternate"></link><updated>2009-01-03T20:43:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-03:migrated-to-wordpress</id><summary type="html">&lt;p&gt;Just switched from Blogger to WordPress. Several links are broken (I
will fix these soon), and it looks like subscribers are probably going
to see a bunch of duplicate posts. Sorry about that...&lt;/p&gt;</summary></entry><entry><title>Removing Defects From Django Apps</title><link href="http://blog.bstpierre.org/removing-defects-from-django-apps" rel="alternate"></link><updated>2009-01-02T07:29:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2009-01-02:removing-defects-from-django-apps</id><summary type="html">&lt;p&gt;I'm working on a set of Django apps right now. Here are some of the
things I'm doing to filter out defects as I work:&lt;/p&gt;
&lt;h4&gt;Design Review&lt;/h4&gt;
&lt;p&gt;I do a handwritten (paper) design. I like to design offline — I find
that this forces me to do two things. First, to slow down and get it
right. Second, to stay at about the right level of abstraction instead
of drilling down into too much detail. When I'm done the paper design, I
review it against a checklist that I build from mistakes found in
downstream phases. My design reviews catch about 70% of the mistakes I
make while designing. I'd like that to be higher, but it is definitely
worth taking the time to do it. This isn't specific to Django or Python,
I do this for all projects.&lt;/p&gt;
&lt;h4&gt;Written Test Plan&lt;/h4&gt;
&lt;p&gt;After design review is done, I write (on paper, informally) a unit test
plan based on the design. This is only possible when the design is at a
low enough level that you can know what the general flow of control
looks like through each method. I aim for full branch coverage in my
unit tests. This doesn't mean it will be "fully tested", but at least
I'll have exercised most of the code. I usually find (and fix) a couple
of defects while writing the test plan. This is also something I do for
all projects.&lt;/p&gt;
&lt;h4&gt;Code Review&lt;/h4&gt;
&lt;p&gt;After I'm done coding, and &lt;em&gt;before&lt;/em&gt; I run any of the code, I print it
out and review it offline. The following bash alias is helpful (prints
2-up):&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c"&gt;# usage: py2pdf OUTPUTFILE INPUTFILE(s)&lt;/span&gt;
&lt;span class="n"&gt;py2pdf&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;$1&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; $1 &lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;echo&lt;/span&gt; $1 &lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aborting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="nb"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;echo&lt;/span&gt; $&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;a2ps&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="nb"&gt;fill&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;2 &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; $&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ps2pdf&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; $&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;fi&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It's controversial, but I like the advice from Watts Humphrey in the
PSP: review code before compiling (or in this case, before running any
unit tests since there isn't really a compile phase). It forces me to be
much more thorough looking for defects since I know that python hasn't
filtered anything out yet.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
To review the code, I use my checklist which is built in the same way as
the design review checklist. I also use the test plan, walking through
the code with each test case to see if there are any defects that
testing will expose. It's better to fix these now instead of waiting for
the unit test to find them. Two reasons: First, it's faster because you
can see what the problem is. Second, you may notice something that the
unit test won't catch for whatever reason — maybe you don't end up
including the right data pattern to catch the defect. Code review is a
generic defect removal practice.&lt;/p&gt;
&lt;h4&gt;Unit Test - XHTML Validation&lt;/h4&gt;
&lt;p&gt;Again, this seems to be controversial though I don't really understand
why. Some people don't think valid XHTML is worth much. I validate
because I want to make sure that my generated content isn't horribly
broken, and — just like compiler warnings — you either validate cleanly
or you don't validate at all. As soon as you start ignoring "a couple of
silly errors" everything goes downhill.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
I wrote an &lt;a href="http://bstpierre.org/Projects/HtmlValidatorMiddleware/"&gt;XHTML validator middleware&lt;/a&gt; for Django but I haven't yet
used it on this project. Why not? Well, I &lt;em&gt;was&lt;/em&gt; trying to use Dojo as a
javascript library, and it's not possible (or at least not easy) to
write valid XHTML while using Dojo widgets. So I had to write some
special code in my test harness to strip out all of the Dojo garbage and
then validate the result.&lt;/p&gt;
&lt;p&gt;I'm using &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; now, I'm much happier, and I suspect I'll be able
to validate using the middleware on my next iteration. (Which involves
ripping out the Dojo false starts.)&lt;/p&gt;
&lt;p&gt;Validation is generic for any web project. The middleware is specific to
Django.&lt;/p&gt;
&lt;h4&gt;Unit Test - Test Plan&lt;/h4&gt;
&lt;p&gt;I implement the written test plan using PyUnit, the &lt;a href="http://docs.djangoproject.com/en/dev/topics/testing/"&gt;Django enhancements
based on PyUnit&lt;/a&gt;, and &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt; for browser-based testing.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
My test plan usually generates a lot of test cases (a recent app had
over 100 test cases for about 800 lines of Python, 700 of Javascript,
and 200 XHTML). So I like to keep them in separate files in a "tests"
directory in each app. One trick that I use is to implement a &lt;code&gt;suite()&lt;/code&gt;
function in my &lt;code&gt;APP/tests/__init__.py&lt;/code&gt;. The suite function globs
"APP_tc_*.py" and sorts them by time stamp, most recent first. This
way if a test is failing I can make sure that it runs first when I fix
the problem and rerun the suite. The other part of the trick is to add
an optional argument to the globbing/sorting function that limits the
number of test cases returned. So when I need to rerun a test a few
times to fix a tricky problem, I don't have to wait for the entire suite
to run to get the results of the test-under-development. This is all
highly specific to Django development.&lt;/p&gt;
&lt;h4&gt;Unit Test - Pylint&lt;/h4&gt;
&lt;p&gt;This is still experimental for me. Pylint generates a lot of noise. Most
of what it reports is legitimate, but about 90% are minor (i.e.
not-user-impacting) defects. I've had to spend a fair amount of time
tuning the report parameters to stifle the noise. The most time
consuming part so far has been filtering out results from the test code.
I'd like to figure out a way to make pylint skip the test files but
there doesn't seem to be a setting for this. On the bright side, it
nagged me into making some of my test code more maintainable. Obviously
pylint is specific to python, I use other lint-like tools for other
projects when the signal-to-noise ratio is high enough.&lt;/p&gt;
&lt;h4&gt;Unit Test - jslint&lt;/h4&gt;
&lt;p&gt;I'm a javascript novice, so &lt;a href="http://www.jslint.com/"&gt;jslint&lt;/a&gt; has been helpful in ferreting out
problems that I would not have caught through testing. As I let the
feedback from the tool and testing form better design and coding
habits/patterns, the signal-to-noise ratio may drop so that this is less
effective.&lt;/p&gt;
&lt;/p&gt;</summary><category term="codereview"></category></entry><entry><title>Productivity: It Comes from Software Design Rather than Software Tools</title><link href="http://blog.bstpierre.org/productivity-it-comes-from-software-design-rather-than-software-tools" rel="alternate"></link><updated>2008-12-31T11:51:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-31:productivity-it-comes-from-software-design-rather-than-software-tools</id><summary type="html">&lt;p&gt;I just read Scott Bellware's [Productivity: It Comes from Software
Design Rather than Software Tools][].&lt;/p&gt;
&lt;p&gt;
These bullet points — the core of his argument — are excellent (quoted
here):

&gt; -   Design quality is the most important factor influencing
&gt;     productivity in software development
&gt; -   The things that obstruct quality degrade productivity
&gt; -   The reductions in productivity over time that are typical to
&gt;     tool-driven software development are greater than what can be
&gt;     solved by tools
&gt; -   The application of tools to these problems exacerbates the quality
&gt;     problem, creating a vicious cycle that accelerates exponentially
&gt; -   Quality software design is the product of principles and
&gt;     practices, not tools
&gt; -   The typical degradation in a software's quality over time isn't
&gt;     due to the nature of software, it's due to the nature of the
&gt;     approaches we choose to develop and operate software

Full disclosure here: I am working on a set of software tools, and a big
part of my consulting for software teams is helping them install tools
to support their improvement initiatives.

I agree with Scott that good design is the root of all that's virtuous
in software. I guess I also agree that tools are often a distraction,
though I think he's drawing a direct line where the root causes go
deeper.

Maybe it's my embedded background, and the fact that I've worked on
teams that were woefully under-equipped in terms of tools. (Or execs
were sold a bill of goods and we got stuck with the resulting boat
anchors and paperweights.) I just don't see an over-emphasis on tools,
maybe this happens in IT more.

What I do see are many opportunities for improvement.

-   Designs can be better communicated. If designs can be communicated
    better amongst the team, they can be reviewed better, which will
    kill bad designs before they get coded — and the feedback will help
    developers improve their future designs. A tool for formatting and
    transmission could help here. But such a tool would only be helpful
    to a team that already has processes in place for creating and
    reviewing the design.
-   Requirements can be managed better. Practices like Scrum seem
    helpful, but there are still challenges. Again, a tool can help here
    but only when the team has an understanding of how the tool fits
    into an existing process.
-   Existing processes and tools are overly slanted towards managing
    &lt;span style="font-style: italic;"&gt;code&lt;/span&gt;. [By the time any
    requirement or design has been reduced to code, it's way too late to
    fix it.][] Start working on improving quality earlier in the cycle,
    the effort will pay off in a big way.

Putting software tools in the hands of unskilled developers is like
letting me go after wood with power tools: it's an expensive way to make
a lot of sawdust. Better to build skills with manual processes and then
introduce tools when manual chores become tedious.

  [Productivity: It Comes from Software Design Rather than Software
  Tools]: http://blog.scottbellware.com/2008/12/productivity-it-comes-from-software.html
  [By the time any requirement or design has been reduced to code, it's
  way too late to fix it.]: http://blog.bstpierre.org/five-reasons-to-slow-down</summary></entry><entry><title>Code Review Tools</title><link href="http://blog.bstpierre.org/code-review-tools" rel="alternate"></link><updated>2008-12-31T11:07:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-31:code-review-tools</id><summary type="html">&lt;p&gt;Yesterday I posted &lt;a href="http://blog.bstpierre.org/twenty-reasons-to-do-code-reviews"&gt;twenty reasons to do code reviews&lt;/a&gt;, and I promised
a list of code review tools. Here they are, in no particular order. I
have not used all of them, so I can't comment on their relative merits.
If there are some I missed, please leave a comment and I'll update this
list.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://codestriker.sf.net/"&gt;Codestriker&lt;/a&gt; Open source, web based, written in perl I think.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://smartbear.com/codecollab.php"&gt;Code Collaborator&lt;/a&gt; Commercial.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.review-board.org/"&gt;Review Board&lt;/a&gt; Open source, web based, appears to be
    python/Django.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/appengine/articles/rietveld.html"&gt;Rietveld&lt;/a&gt; Open source from Google. Guido van Rossum's 2nd pass at
    a code review tool. You get three guesses on the implementation
    language...&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.atlassian.com/software/crucible/"&gt;Crucible&lt;/a&gt; Commercial from Atlassian.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bottom line: don't try to do this by hand, you'll be wasting a bunch of
time packaging patches and tracking defects when a tool can do it
automagically for you.&lt;/p&gt;
&lt;/p&gt;</summary><category term="codereview"></category><category term="tool"></category></entry><entry><title>Twenty Reasons To Do Code Reviews</title><link href="http://blog.bstpierre.org/twenty-reasons-to-do-code-reviews" rel="alternate"></link><updated>2008-12-30T11:06:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-30:twenty-reasons-to-do-code-reviews</id><summary type="html">&lt;p&gt;&lt;span style="font-weight: bold;"&gt;Update [2008-12-31]: &lt;/span&gt;I posted
the list of &lt;a href="http://blog.bstpierre.org/code-review-tools"&gt;code review tools&lt;/a&gt; as promised below.&lt;/p&gt;
&lt;p&gt;I &lt;a href="http://twitter.com/bstpierre/status/1076228386"&gt;tweeted&lt;/a&gt; &lt;a href="http://www.cio.com/article/472377/_Reasons_for_Software_Developers_to_Do_Code_Reviews_Even_If_You_Think_They_re_a_Waste_of_Time_"&gt;this article on Five Reasons to Do Code Reviews&lt;/a&gt; from
CIO.com last week,and realized that there are much more than the five
reasons they give. So I came up with 20 more over the rest of the day.
This is a collection of those tweets, with a little more detail given
here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076435522"&gt;Code review reason 1&lt;/a&gt;: Feedback comes back fast enough to give you
whiplash.&lt;/strong&gt; Since code review is done after coding and before
integration or system tests, developers don't have to wait as long to
get feedback on code quality. By providing specific, timely feedback,
developers can adjust their coding practices to avoid common mistakes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076448295"&gt;Code review reason 2&lt;/a&gt;: Find more bugs than testing alone.&lt;/strong&gt; This is
obvious: Testing finds bugs. Code review finds different bugs. When
combined they find more bugs than you would otherwise remove before
shipping to customers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076643709"&gt;Code review reason 3&lt;/a&gt;: A million monkeys sometimes produce valid
syntax, even if it doesn't make any sense. Compilers can't catch
this.&lt;/strong&gt;Sometimes you'll make a syntax error that produces valid syntax
so the compiler can't catch it. The classic here is in C, = (assignment)
versus == (boolean equality operator); in this case most compilers can
issue a warning but there are other examples. A code review will catch
this error quickly, but finding this during testing can be hard to track
down.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076645011"&gt;Code review reason 4&lt;/a&gt;: Find and kill bugs where they live, instead
of finding turds and having to track them back to the nest.&lt;/strong&gt; When you
get defect reports from a code review, you have in-hand the exact line
number and problem description. When you get reports from system test or
(horrors!) a customer, you get — at best — a reproducible set of steps
to trigger the bug. It's then up to you to work backwards to the actual
defect in the code, which is often time consuming, especially since the
code may have been written weeks or months earlier. (See reason #1.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076699581"&gt;Code review reason 5&lt;/a&gt;: Indoctrinate the n00bs.&lt;/strong&gt; Code reviews are
an effective training tool. First, new software engineers who
participate in code reviews get to look at code produced by other
engineers and thus get a feel for the coding standard and documentation
style. Second, inexperienced engineers will get feedback on their code
before they have a chance to infect the system with inferior code.
(Because we all know that the code we wrote 10 years ago probably sucks
— unless we were fortunate enough to have it reviewed by more senior
engineers. If you haven't already been coding for several years, keep in
mind that everything you're writing today probably sucks but you won't
be able to acknowledge that for another few years; see reason #6.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076706526"&gt;Code review reason 6&lt;/a&gt;: Uses developer egos to boost quality of code
from the beginning.&lt;/strong&gt; The theory here is that because developers know
their code is going to be publicly scrutinized by a group of peers, they
will take extra care in coding to avoid the embarrassment caused by
having others find all kinds of bugs in their code.&lt;/p&gt;
&lt;p&gt;Guest contributor &lt;strong&gt;&lt;a href="http://twitter.com/kyletolle"&gt;@kyletolle&lt;/a&gt;: [Code review reason [7]][]:
Explaining code makes sure they understand it well. Will fix the "Uhh,
b/c I always do it that way" remarks.&lt;/strong&gt; After you have to answer this
question a few times during a review, you'll probably ask the question
of yourself while coding. (See reason #6.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076724807"&gt;Code review reason 8&lt;/a&gt;: Makes testing go faster. Heck, it makes the
entire development cycle go faster.&lt;/strong&gt; There are multiple root causes for
this. First, since code review finds and fixes defects at the site of
the defect (see reason #4), less time is spent fixing these defect.
Second, fewer bugs are found by testers so they spend less time writing
up issues and the engineering team spends less time tracking and
managing issues. Third, testers don't have to re-run regression tests so
many times because fewer release candidates will be needed.. Last, it is
less likely that testers will be blocked waiting for fixes to bugs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076737034"&gt;Code review reason 9&lt;/a&gt;: Fewer angry phone calls means happier tech
support staff.&lt;/strong&gt; Proof: Fewer bugs in shipping code (see reason #2).
Customers not disrupted by bugs as often. They don't get angry as often.
So when they call, it's with songs of praise. QED. (Ok, maybe that
singing part isn't true, but you get the picture.) To prove this from
another angle: if you're spending less time fixing bugs, you can spend
more time implementing features that customers are requesting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076763127"&gt;Code review reason 10&lt;/a&gt;: (Anti-reason) You'll pay more sales
commissions because you'll be selling more product...wait, that's a good
thing!&lt;/strong&gt; When customers sing your praises because your product is so
reliable, it's easier both to sell to new customers and to sell more
product to your existing customers. Sure, your sales commissions might
go up, but I've never really heard anyone complain about this too
loudly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076785579"&gt;Code review reason 11&lt;/a&gt;: There are tools that reduce the annoyance
factor of old school review processes.&lt;/strong&gt; After I tweeted this,
&lt;a href="http://twitter.com/kyletolle/status/1076797657"&gt;@kyletolle&lt;/a&gt;asked for some recommendations. I will follow up this
post with several tools that I've found. Stay tuned. (Look at my
timeline starting &lt;a href="http://twitter.com/bstpierre/status/1076816304"&gt;here&lt;/a&gt; for now).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076899696"&gt;Code review reason 12&lt;/a&gt;: There's zero risk. Even not-super-effective
reviews beat testing.&lt;/strong&gt; If I spent a little time I could probably dig up
some published research to back this up. But my personal data says my
personal code reviews are about four times as effective as unit testing.
So even if I perform a half-hearted review, I'm still going to be more
effective than testing. (See reasons #2 and #4, also keep in mind
reason #5 which doesn't have an immediate measurable effect.) There
might be data out there that says code reviews are a waste of time.
Flying reindeer might also exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076933029"&gt;Code review reason 13&lt;/a&gt;: (Anti-reason) Testers bored from not
finding as many bugs... Oh yeah! They could focus on stress/perf
tests.&lt;/strong&gt; This would be a good problem to have...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076933691"&gt;Code review reason 14&lt;/a&gt;: Reviewing code is good practice for
reviewing code. You'll get better.&lt;/strong&gt; Your team's review effectiveness
will improve over time. This means that the long term risk of failure is
even lower than the short term risk (see reason #12 for why the risk of
failure is zero or very close to zero).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076934530"&gt;Code review reason 15&lt;/a&gt;: It's a great way to build a really useful
code review checklist.&lt;/strong&gt; This is kind of stretching, a useful code
review checklist doesn't really matter to the customer. (I should
probably come up with a couple of bonus reasons to account for the
"artificial fillers"... Hey, I tweeted this during the 3pm slump before
my afternoon cup of tea.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076975312"&gt;Code review reason 16&lt;/a&gt;: Out on a speculative limb: better staff
retention because developers spend less time fixing annoying bugs.&lt;/strong&gt;I'm
not sure about anyone else, but I've stayed at a job that was otherwise
not-so-great because of the quality of the team I was on and the people
around me. Being part of a well-oiled machine feels good. It's
motivating to go to work everyday knowing that you're going to produce a
high quality product and your coworkers are there to back you up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076978947"&gt;Code review reason 17&lt;/a&gt;: Reduces schedule risk because product
quality is higher as it enters downstream phases.&lt;/strong&gt; Your testers won't
get blocked as much (see reason #8) and testing won't drag out. Since
you get feedback relatively early in the schedule (see reason #1) you
have a good idea of how long testing will take.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076979963"&gt;Code review reason 18&lt;/a&gt;: Feedback (especially from peers) at the
source level means you will stop making the same mistakes over and
over.&lt;/strong&gt;(See reason #6.) This also stems from the fact that each
engineer will have data in-hand about the types of defects he makes and
he can come up with ways to prevent this defect from happening to begin
with. And this is really when the magic occurs: when you can start
thinking about preventing entire classes of defects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076980274"&gt;Code review reason 19&lt;/a&gt;: Improves the quality of documentation /
comments as well as executable code.&lt;/strong&gt; (See reason #7.) Developers will
include better comments to avoid having to answer too many questions
from reviewers. If reviewers are looking for inconsistencies between
comments and code, this is extra motivation to make sure that code
documentation is up to date. Lastly, I suspect that overly complex code
will become scarce since this will be scrutinized and questioned more
than simple code passages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://twitter.com/bstpierre/status/1076980819"&gt;Code review reason 20&lt;/a&gt;: Customers won't find as many bugs in their
soup.&lt;/strong&gt; This is maybe redundant to reason #9, but helping customers is
why we exist, right? So I'll allow this one to stand and — like I said
above — I'll work on a couple of bonus reasons to add to this list.&lt;/p&gt;
&lt;p&gt;[Code review reason [7]]: http://twitter.com/kyletolle/status/1076701951&lt;/p&gt;</summary><category term="codereview"></category></entry><entry><title>The cost of (not) testing software</title><link href="http://blog.bstpierre.org/the-cost-of-not-testing-software" rel="alternate"></link><updated>2008-12-24T06:31:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-24:the-cost-of-not-testing-software</id><summary type="html">&lt;p&gt;Great post on &lt;a href="http://jessenoller.com/2008/09/17/the-cost-of-not-testing-software/"&gt;the cost of (not) testing software&lt;/a&gt;. The take-home
lesson is "find defects early".&lt;/p&gt;
&lt;p&gt;The main thing missing from the discussion is that there are better
techniques for finding defects than testing. Like design and code
reviews, and especially more attention to requirements. Catch defects as
early as possible and reduce costs even further.&lt;/p&gt;</summary></entry><entry><title>Project Team Blog: Our Projects Are Always Late</title><link href="http://blog.bstpierre.org/project-team-blog-our-projects-are-always-late" rel="alternate"></link><updated>2008-12-18T07:09:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-18:project-team-blog-our-projects-are-always-late</id><summary type="html">&lt;p&gt;Saw this post over at the Project Team Blog &lt;a href="http://www.projectteamblog.com/?p=153"&gt;Our Projects Are Always
Late&lt;/a&gt;. Newshirt asks (I'm paraphrasing) "Why would this person not use
a time tracking tool?"&lt;/p&gt;
&lt;p&gt;Two answers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Because it feels like an extra step. If you want to change a
    behavior like time tracking, you have to make it automatic.
    Developers and other project members are already too busy, you can't
    just ask them to add another step to their daily workflow unless the
    benefits are immediate and obvious. The benefits of time tracking
    are not immediate and obvious -- it takes some time (granted a small
    amount of time) to reap the benefits of time tracking.&lt;/li&gt;
&lt;li&gt;Second, they perceive a risk. Even if the software is free (they are
    providing a 30 day free download), there's the risk that you spend
    an hour or two (or more) downloading and fiddling with the software
    only to discover that it won't work in your environment for one
    reason or another.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To use concrete examples from my recent experience:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I'm developing in Python. I've used &lt;a href="http://www.logilab.org/project/pylint"&gt;Pylint&lt;/a&gt; in the past and it
    has been helpful in pointing out bugs before I start testing. I
    &lt;span style="font-style: italic;"&gt;want&lt;/span&gt; to use Pylint on my
    current project, but I keep skipping it because it
    &lt;span style="font-style: italic;"&gt;feels like an extra step&lt;/span&gt;
    and I'm trying to get work done, not fiddle with extra tools. The
    solution is for me to integrate this tool into my workflow so that
    it is automatic and not an extra step. This is what I've done for
    the current iteration and I expect that it will stick.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I've recently been using more social media sites like &lt;a href="http://twitter.com/bstpierre"&gt;twitter&lt;/a&gt;
    and I read about a toolbar for Firefox from &lt;a href="http://wapp.minggl.com/register/install"&gt;Minggl&lt;/a&gt;. Sounded
    interesting and after I saw a few mentions of it I decided to give
    it a whirl. There should be a warning: "roadblocks ahead".&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;They make you register to download.&lt;/li&gt;
&lt;li&gt;The registration form does not allow periods or spaces in your
    last name, so it took a couple of tries to register. (This
    smacks of ethnocentrism! I am being oppressed because I have a
    period &lt;span style="font-style: italic;"&gt;and&lt;/span&gt; a space in
    my last name!)&lt;/li&gt;
&lt;li&gt;It doesn't work on linux... bummer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Contrast this with the experience of signing up for something like
&lt;a href="http://twitter.com/"&gt;twitter&lt;/a&gt; or &lt;a href="http://www.mahalo.com/answers"&gt;Mahalo Answers&lt;/a&gt;. You sign up (albeit with the
gotcha of only allowing alphabetics in the name) and you start using
it immediately.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;</summary><category term="process"></category><category term="tool"></category></entry><entry><title>Five Reasons to Slow Down</title><link href="http://blog.bstpierre.org/five-reasons-to-slow-down" rel="alternate"></link><updated>2008-12-15T11:22:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-15:five-reasons-to-slow-down</id><summary type="html">&lt;p&gt;Here are five reasons you should wait before moving on to the next phase
of your software development process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Get the requirements right.&lt;/strong&gt; It's so often repeated that it's
    almost a cliché to say that requirements errors will cost 10x or
    more to fix during coding or testing. But it's true, so I can risk
    repeating it here. Spend a little more time getting the requirements
    right, and you'll &lt;em&gt;get every second back, with interest&lt;/em&gt; during the
    testing phase. The risk here is overanalysis, but if you tend to be
    the impatient type -- jumping into design or coding with very thin
    requirements -- then you're unlikely to suffer paralysis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get the design right.&lt;/strong&gt; It doesn't have to be an elaborate 500
    page document. One of the best designs for a complex system that
    I've ever seen was a state machine described in a two-page document
    with a big matrix on the first page. Remember, a written design is a
    &lt;em&gt;communication tool&lt;/em&gt;. Just like the rest of your software, it can be
    &lt;a href="http://c2.com/xp/DoTheSimplestThingThatCouldPossiblyWork.html"&gt;the simplest thing that will work&lt;/a&gt;. Marker on a whiteboard works,
    if your team decides that is an effective way to communicate. An
    extra hour spent preparing and reviewing the design will save at
    least two hours in testing. Really. I've seen too many designs that
    were rushed (and clearly broken upon review) because of an
    unexplainable urgent desire to get a document into review so that
    coding can begin. The state machine I just mentioned went through
    probably 20 man-hours of reviews in maybe three passes, and we must
    have found at least 50 defects in it. Those defects took a few
    seconds each to fix (some may have taken a few minutes), but if
    there had been code and tests based on this broken design it would
    have taken hours to fix some of them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get the code right.&lt;/strong&gt; I used an Extreme Programming mantra above
    ("the simplest thing that will work"), but I think XP overall often
    leads to bad practices -- primarily an overemphasis on testing. I
    agree that automated regression tests are a valuable tool. However,
    it is important to recognize that testing has limits. It takes an
    immense effort to achieve full branch coverage, and even this can't
    be considered "complete" testing. Testing must be used in
    conjunction with other tools: code review and static analysis are
    two such tools for removing defects from code. And before you're at
    the point where code is ready for review or even the compiler, take
    the time to consult a reference when you're unsure how to use an
    API. I don't know how many bugs I've fixed (including some of my
    own!) that were a few lines down from a
    &lt;code&gt;/*TBD: how is this supposed to work?*/&lt;/code&gt; comment. Instead of putting
    in that comment, take the time to figure out how it is supposed to
    work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get the tests right.&lt;/strong&gt; Ever waste time on a bogus bug report?
    Testers telling you a feature is broken when they're just doing the
    wrong thing? It's not their fault if you failed to communicate how
    the feature is supposed to work. In the same way your test team
    should be insisting on some written communication from you about the
    feature set, you should insist on some written communication from
    them about what they're going to test. Again, this "test plan"
    doesn't have to conform to an ISO/IEEE standard that requires it be
    1200 pages long and six inches thick. Like with the design, a
    whiteboard works just fine. Whatever communication method you use,
    if it helps head off problems then it is working. If you're finding
    too many problems late in the game, then maybe you need to have
    something a little more formal -- try posting short documents to an
    internal wiki.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are you keeping score?&lt;/strong&gt; A &lt;em&gt;little&lt;/em&gt; bit of measurement can do
    great things for you. I &lt;a href="http://blog.bstpierre.org/eight-reasons-to-use-a-time-tracking-tool"&gt;track time&lt;/a&gt;, lines of code, and defects.
    I was reviewing one of my designs this morning and this data helped
    me decide: (a) how many defects I should expect to find in my design
    and (b) how long it should take me to find those defects. All
    because I have historical data that tells me how many defects I find
    per hour of design review, and how many defects I make per hour
    while designing. I also know how much testing time this is likely to
    save... so I don't feel any particular rush to start coding from a
    buggy design. I'd rather get the design right first.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As I mentioned above, the risk to getting into the habit of waiting is
that you spend too much time analyzing the current phase and don't just
get on with it. But if you start from good requirements, it should be
obvious when you're done with design and coding. And if you're keeping
score, you'll know how much time to devote to reviews to keep them
efficient.&lt;/p&gt;
&lt;/p&gt;</summary></entry><entry><title>Amnesia - Joel on Software</title><link href="http://blog.bstpierre.org/amnesia-joel-on-software" rel="alternate"></link><updated>2008-12-10T05:49:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-10:amnesia-joel-on-software</id><summary type="html">&lt;p&gt;Joel Spolsky also thinks that &lt;a href="http://www.joelonsoftware.com/items/2008/12/10.html"&gt;using timelog data for performance
tracking is a bad idea&lt;/a&gt;.&lt;/p&gt;</summary></entry><entry><title>Jason Cohen on QA vs.QC</title><link href="http://blog.bstpierre.org/jason-cohen-on-qa-vsqc" rel="alternate"></link><updated>2008-12-09T07:08:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-09:jason-cohen-on-qa-vsqc</id><summary type="html">&lt;p&gt;Jason Cohen of Smart Bear Software has a brilliant blog. &lt;a href="http://blog.asmartbear.com/blog/qa-vs-qc.html"&gt;His post on QA
vs. QC&lt;/a&gt; is something I've talked about in the past. I love his example
of the Pringles manufacturing line discarding off-color chips -- it's a
concrete, understandable way to define the difference between QA and
QC.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Some of my best project experiences have been those that have involved
the testers starting at the requirements phase. These guys have a
different attitude and have asked some great questions that prevented us
from going down dead-ends.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Great testers find and isolate really hairy bugs. Truly great testers
not only find these bugs, but will often find a pattern of bugs and can,
for example, point to the portion of the spec that the code failed to
implement. They take a bigger picture view of the product and its
environment and how to improve the quality by both preventing defects
and figuring out how to detect defects during the test phase.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;As Jason says, we should let these guys have a little more influence on
the development process. For example, they should be insisting on design
and code reviews, even if they don't participate. Because when the
development team is in solely in charge of the development process,
these steps sometimes get skipped -- because the job is a rush, or
because the feature is small and doesn't need to be reviewed. (Even
though rush jobs are most in need of reviews, and small features always
seem to take longer because they receive less care and attention.)&lt;br /&gt;
&lt;/p&gt;
&lt;/p&gt;</summary></entry><entry><title>Eight Reasons to Use a Time Tracking Tool</title><link href="http://blog.bstpierre.org/eight-reasons-to-use-a-time-tracking-tool" rel="alternate"></link><updated>2008-12-08T08:41:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-12-08:eight-reasons-to-use-a-time-tracking-tool</id><summary type="html">&lt;p&gt;I just finished the time tracking application for Bale. This is a tool
that is key to many other practices, but is very underutilized. (I
wonder if this is because so many developers are worried about Big
Brother type intrusions into their working habits? See below for why Big
Brother is such a bad idea. If your employer is in this camp)&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Here are eight reasons why you should use a time tracking tool:&lt;br /&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Gather reliable data for future estimates.&lt;/strong&gt; You can't make
    accurate estimates without accurate historical data. If you try to
    create estimates using a simple count of days spent working on a
    project, you'll be way off because so much time is lost to
    non-project activities (like supporting the last project you were
    on). A time tracking tool keeps an accurate record of how many hours
    you &lt;em&gt;actually&lt;/em&gt; spent working on the project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eliminate time wasters.&lt;/strong&gt; When you start tracking time, you'll be
    surprised how &lt;em&gt;little&lt;/em&gt; time you spend working on your current
    project. You can begin to pay attention to the low value activities
    that come up during the day. Awareness is the first step, then you
    can start to reduce or eliminate these time wasters so you can put
    more time into your bottom line.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluate new practices.&lt;/strong&gt; Without good time data you have no idea
    if the introduction of a new practice or process step is helping or
    hurting. For example, say you move to performing code reviews online
    instead of on paper. You may have a good idea of how much money this
    will save in terms of ink and paper, but you have no idea whether it
    is actually faster. (I'll soon have a post on why defect tracking is
    important too -- evaluating new practices is a key benefit there
    too.)&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Find areas for improvement.&lt;/strong&gt; When I first started tracking time I
    realized the vast amount of time I spent in unit testing -- even
    worse, sometimes in system testing. I made a conscious effort to
    &lt;em&gt;slow down&lt;/em&gt; when doing design work and it paid off. Hasty designs
    end up requiring a lot of rework during test. Your situation may be
    different, but you won't know until you start tracking where in the
    lifecycle your time is consumed.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improve reviews.&lt;/strong&gt; An effective review finds more defects &lt;em&gt;per
    hour&lt;/em&gt; than an ineffective review. You can't tell the difference
    between effective and ineffective reviews without having the time
    component. This is sort of a subcategory of finding areas for
    improvement. For example, I know that my personal design review is
    about 4x more effective than unit testing, and slightly more
    effective than my personal code review. In a team context, knowing
    your team's defect removal rate during inspections is an excellent
    way to justify the investment in review time to upper management.
    This is why a team-wide time tracking tool is important.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluate new tools.&lt;/strong&gt; Too often, the process of deciding whether a
    new tool is going to work is based on watching the salesman
    demonstrate the tool on the projector in a darkened room, and then
    some haggling by upper management. Sometimes you get a 30 day eval,
    but in terms of a test drive, this is really just a spin around the
    block to see if it "feels nice". It's much more useful to be able to
    know that a given tool actually increased productivity by 8% during
    the eval period. This also provides leverage during the
    aforementioned haggling when the vendor is claiming 77% productivity
    gains...&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Know when to quit.&lt;/strong&gt; Ever had your manager say, "Don't spend more
    than an hour on it."? Ever blown the afternoon after hearing such a
    request because you just couldn't put it down?&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improve visibility.&lt;/strong&gt; You can generate better upwards-facing
    status reports with a good time log. Remind your manager that the
    reason you missed the Friday deadline is because the VP of
    Engineering retasked you for all of Tuesday and Wednesday on his pet
    project. She can then show your status report to the VP as a
    reminder. (And oh, by the way, she can also show it to the VP of
    Marketing and CTO/Founder who're screaming about the missed
    deadline. Yes, your organization is not the only dysfunctional group
    out there.)&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For all the pointy haired bosses out there, here are a few reasons to
&lt;strong&gt;not&lt;/strong&gt; use time tracking to compare employees based on productivity.&lt;br /&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;It's easy to game the system.&lt;/strong&gt; You can enter fake data. This
    will, of course, screw up estimates and make it impossible to use
    the data for any kind of overall improvements. Developers need every
    assurance that the data will not be used for evil or they will see
    to it that the data is worthless.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Productivity varies by task.&lt;/strong&gt; I've seen examples, on the same
    project, by the same person (me), where different tasks have
    enormously different productivity. Some problems are simply harder
    to solve. Sometimes you have to modify an existing bit of code:
    sometimes the existing code is easy to work with, sometimes it is a
    horrible mess. Sometimes you don't even know what the problem is,
    and you have to do a bit of research just to figure out what needs
    to happen to get something to work.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;People make varying contributions.&lt;/strong&gt; I've known people who appear
    to work very slowly, producing very little every week. Sometimes
    ("The Slacker") they're not actually working, instead day trading or
    playing solitaire. Sometimes ("The Craftsman") they are doing very
    solid work; you'll never see a bug come out of system test for these
    guys. Some developers ("The Enabler") don't do lots of work of their
    own, but rather spend much of their time enabling others, or perhaps
    interacting with marketing, support, or testers; this is valuable
    time spent, they are keeping your team in the loop or solving
    customer problems. You can't tell the difference with a time
    tracking tool, the slacker will simply enter fake data, and the
    enabler's contributions don't show up on paper.&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you're trying to compare using time log data, then when lean times
come you may end up firing the Enabler because the Slacker looks good.
You'll still have the Craftsman, but because he depended on the Enabler
for answering questions, keeping Customer Support off his back, and
keeping him in the loop about What Marketing Wants This Week, his
productivity will drop dramatically.&lt;/p&gt;
&lt;/p&gt;</summary></entry><entry><title>Visions of Tool Integration</title><link href="http://blog.bstpierre.org/visions-of-tool-integration" rel="alternate"></link><updated>2008-11-21T14:56:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-11-21:visions-of-tool-integration</id><summary type="html">&lt;p&gt;It's an amazing thing when your development tools work together.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;In the bad old days, we thought it was cool when the editor could work
in concert with source control. It blew my mind when we first had source
control with integrated bug tracking. (Wow, you can see the changes that
go along with each bug fix!) In some ways, we've come a long way, but I
still see a lot of integration that is within reach but still doesn't
exist.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Look at something like &lt;a href="http://trac.edgewall.org/"&gt;Trac&lt;/a&gt;, which is open source and widely used.
It is described as a "Project management and bug/issue tracking system.
Provides an interface to Subversion and an integrated wiki." If you've
seen it in action, it is an effective combination of the tools in that
description. However, it is missing a few tools that are key pieces in
an effective development process:&lt;br /&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Integration with code review tools like &lt;a href="http://codestriker.sourceforge.net/"&gt;codestriker&lt;/a&gt; or
    &lt;a href="http://smartbear.com/"&gt;CodeCollaborator&lt;/a&gt; from Smartbear.&lt;/li&gt;
&lt;li&gt;Integration with test automation.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Integration with static analysis tools --- lint-like tools or (at
    the high end) Coverity.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Integration with runtime analysis tools like valgrind.&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Imagine the following scenario with all of the above features:&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Alice finishes coding and testing the fix to bug 1257 and checks in the
fix, all from within her favorite editor. The source control backend
notifies the build server and a new build is launched. When the build
completes it automatically kicks off a regression test. Whoops! Alice
reintroduced bug 1142 with this fix. She checks in a new patch and this
time all tests pass. The backend automatically adds notes to bugs 1257
and 1142 both when the regression occurred and when it was fixed.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Bob was already selected as the reviewer for this fix within the defect
tracker, so he gets an email once the regression tests have passed to
let him know that the patch is ready for review. He opens his browser to
see the changeset diffed against the previous version. Alice added a
couple of new function calls, and he clicks through to the function
definition because the source display includes clickable
cross-references. Whoops! Alice forgot to include a check for a NULL
return from one of the functions. Bob clicks the call site to add a
note, and rejects the patch.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Alice gets a notification about the rejection and sees the note Bob
left. She adds a new test case, sees a crash from the NULL return, and
then checks in another fix. When all tests pass, Bob gets another
request for review. This time it all looks good, he approves the patch
and it is applied to the development trunk. The backend makes notes in
the appropriate issue tickets along the way.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;Bob's review approval triggered a merge to the trunk, so the backend
system automatically kicks off a trunk build and regression test. It
also launches a full reanalysis using a commercial static analysis tool,
and re-runs the regression test using valgrind. Because this is all
automated and human intervention is only needed when something goes
wrong, developers are free to focus on fixing reported bugs and adding
features.&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;We aren't quite there today, but the individual pieces do exist, and
fortunately there's no rocket science involved in putting the
functionality together.&lt;/p&gt;
&lt;/p&gt;</summary><category term="tool"></category></entry><entry><title>Five Simple Ways to Kill More Bugs Today</title><link href="http://blog.bstpierre.org/five-simple-ways-to-kill-more-bugs-today" rel="alternate"></link><updated>2008-11-21T12:42:00-05:00</updated><author><name>brian</name></author><id>tag:blog.bstpierre.org,2008-11-21:five-simple-ways-to-kill-more-bugs-today</id><summary type="html">&lt;p&gt;Here are some simple things you can do -
&lt;span style="font-weight: bold;"&gt;today&lt;/span&gt; - to find and prevent bugs
in your code. These take very little effort to implement and do not cost
any money. (And typically don't require management approval.)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Turn on warnings.&lt;/li&gt;
&lt;li&gt;Make warnings break the build.&lt;/li&gt;
&lt;li&gt;Review your code.&lt;/li&gt;
&lt;li&gt;Get a review buddy.&lt;/li&gt;
&lt;li&gt;Track all of your defects.&lt;ul&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;li&gt;Where found (file, class, and/or function/method)&lt;/li&gt;
&lt;li&gt;How found (self, compile, peer, test, qa, field)&lt;/li&gt;
&lt;li&gt;"Type" - some brief description to define the type or category
    of defect&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;</summary></entry></feed>