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

<channel>
	<title>Elf Sternberg</title>
	<atom:link href="http://www.elfsternberg.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.elfsternberg.com</link>
	<description>Done, and gets things smart.</description>
	<lastBuildDate>Sat, 25 May 2013 15:05:30 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>The Thinkpad Yoga and Ubuntu 12.</title>
		<link>http://www.elfsternberg.com/2013/05/25/thinkpad-yoga-ubuntu-12/</link>
		<comments>http://www.elfsternberg.com/2013/05/25/thinkpad-yoga-ubuntu-12/#comments</comments>
		<pubDate>Sat, 25 May 2013 15:05:30 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1482</guid>
		<description><![CDATA[I hate being made to feel like a fool, especially by a software bug. I wasted four hours over the past two days trying to get my new Lenovo Yoga&#8217;s touchscreen to work. It was fine in desktop mode, but rotated (for tablet mode) or inverted (for media mode), and the touchscreen&#8217;s geometry would freak [...]]]></description>
				<content:encoded><![CDATA[<p>I hate being made to feel like a fool, especially by a software bug.  I wasted four hours over the past two days trying to get my new Lenovo Yoga&#8217;s touchscreen to work.  It was fine in desktop mode, but rotated (for tablet mode) or inverted (for media mode), and the touchscreen&#8217;s geometry would freak out; the touchscreen pointer wouldn&#8217;t track anymore, gestures stopped working, it was a pain.</p>
<p>I tried using the event driver (evdev) settings, both &#8220;Axes Swap&#8221; and &#8220;Axis Inversion&#8221;.  It didn&#8217;t help.  I got desperate, and tried to teach myself the <a href="http://www.x.org/wiki/XInputCoordinateTransformationMatrixUsage">Coordinate Transformation Matrix</a>, but that link is one brutally nasty &#8220;If you can&#8217;t do matrix math, I&#8217;m not stupid enough to help you&#8221; bit of writing.  I <i>am</i> knowledgeable enough to do matrix math, but his explanation is as clear as landfill, and about as useful.</p>
<p>It turns out that none of my confusion was my fault.  There&#8217;s a bug xf86-input-evdev-2.7, the version that ships with Ubuntu 12 (Quantal).  It doesn&#8217;t handle rotation or inversion well on the ELAN Touchscreens that ship with the Yoga.  It&#8217;s somewhat understandable&#8211; the Yoga is a very new piece of hardware&#8211; but damn, it&#8217;s annoying.</p>
<p>I downloaded, compiled and installed xf86-input-evdev-2.8, and now it works.  I have only keyboard-issued orientation working, but it&#8217;s a start.  The keyboard program is simple:</p>
<pre>
#!/bin/bash
ARG=$1
CMD=${ARG:0:1}
case $CMD in
    r) 
        xrandr --screen 0 -o right
        xinput set-prop --type=int --format=8 "ELAN Touchscreen" "Evdev Axes Swap" 1
        xinput set-prop --type=int --format=8 "ELAN Touchscreen" "Evdev Axis Inversion" 0 1
        ;;
    l)
        xrandr --screen 0 -o left
        xinput set-prop --type=int --format=8 "ELAN Touchscreen" "Evdev Axes Swap" 1
        xinput set-prop --type=int --format=8 "ELAN Touchscreen" "Evdev Axis Inversion" 1 0
        ;;
    i)
        xrandr --screen 0 -o inverted
        xinput set-prop --type=int --format=8 "ELAN Touchscreen" "Evdev Axes Swap" 0
        xinput set-prop --type=int --format=8 "ELAN Touchscreen" "Evdev Axis Inversion" 1 1
        ;;
    *)
        xrandr --screen 0 -o normal 
        xinput set-prop --type=int --format=8 "ELAN Touchscreen" "Evdev Axes Swap" 0
        xinput set-prop --type=int --format=8 "ELAN Touchscreen" "Evdev Axis Inversion" 0 0
        ;;
esac
</pre>
<p>And now, <tt>yoga r</tt> rotates the screen to portrait mode &#8220;right&#8221;, <tt>yoga l</tt> is portrait mode &#8220;left&#8221;, <tt>yoga i</tt> for &#8220;inverted&#8221; (upside down, &#8220;media mode&#8221;), and anything else, even just plain <tt>yoga</tt>, will put everything back to laptop mode, keyboard and all.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/05/25/thinkpad-yoga-ubuntu-12/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Switching from Gentoo to Mint questions my commitment to technical expertise.</title>
		<link>http://www.elfsternberg.com/2013/05/23/switching-gentoo-mint-questions-commitment-technical-expertise/</link>
		<comments>http://www.elfsternberg.com/2013/05/23/switching-gentoo-mint-questions-commitment-technical-expertise/#comments</comments>
		<pubDate>Thu, 23 May 2013 16:41:43 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[chat]]></category>
		<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1479</guid>
		<description><![CDATA[I bought a new laptop recently, a Lenovo Yoga.  It&#8217;s the top-of-the line current generation: 13.3&#8243; touchscreen, 8GB RAM, 256GB SSD, quad-core Intel i7 CPU.  Buying it was a fun moment: after shopping around the various retailers, I discovered that the cheapest ones available were from The Microsoft Store.  So I drove over there, walked [...]]]></description>
				<content:encoded><![CDATA[<p>I bought a new laptop recently, a Lenovo <a href="http://shop.lenovo.com/SEUILibrary/controller/e/web/LenovoPortal/en_US/catalog.workflow:category.details?current-catalog-id=12F0696583E04D86B9B79B0FEC01C087&amp;current-category-id=1A4B81C8AC677A5FB11849005C1752B5&amp;action=init&amp;TMMClickTrack=hero_shop">Yoga</a>.  It&#8217;s the top-of-the line current generation: 13.3&#8243; touchscreen, 8GB RAM, 256GB SSD, quad-core Intel i7 CPU.  Buying it was a fun moment: after shopping around the various retailers, I discovered that the cheapest ones available were from The Microsoft Store.  So I drove over there, walked in, and asked for the laptop.  The salesman was professional, but he kept trying to (a) upsell me on Office and a customer service package, both of which I repeatedly said I didn&#8217;t want, and (b) reassure me time and again that I would enjoy Windows 8 on a laptop with a touchscreen.  Finally, after I had paid for it, he handed it to me and said, &#8220;Enjoy Windows 8.&#8221;</p>
<p>&#8220;Not for long,&#8221; I told him.</p>
<p>I got it home and decided that I was going to forgo the <a href="http://www.gentoo.org">Gentoo</a> song &amp; dance.  I will miss Gentoo, but only a little.  Instead, I installed the &#8220;user-friendliest&#8221; of all Linux distro, <a href="http://www.linuxmint.com">Linux Mint</a>.  Mint is even better than Ubuntu in a lot of ways, with its nice mix of high-end, &#8220;your mom can use it&#8221; window managers as well as the Gnome 2.0 maintenance branch known as Mate.  I installed Mate, then rehacked the core manager to give me wrap-around keyboard navigation of the multi-desktop handler.  (This is a feature that I first encountered in Solaris Motif, and am absolutely confuzzled as to why Metacity, the Gnome/Mate window manager, doesn&#8217;t support it.)</p>
<p>Installing Mint was a bit of a dance: first, you have to tell Windows you want to use &#8220;Legacy mode.&#8221;  This unlocks the security features designed to prevent you from installing anything over the base OS.  That&#8217;s painful enough, but then I put in the Mint USB stick and everything went smoothly&#8211; until I needed wireless.  The wireless for this laptop isn&#8217;t stable yet; I had to install a third-party driver from Github, but there was enough of an environment on the USB stick to support doing so, and then I had wireless.</p>
<p>Mint Just Works.  Suspend to disk, automatic media detection, the camera, the sound.  There are a few bugs related to the laptop being so new&#8211; brightness DOWN works from the keyboard, brightness UP doesn&#8217;t.  There&#8217;s a menu option to control brightness, so it&#8217;s not a fatal problem.   Most of the problems relate to Yoga&#8217;s famous &#8220;tablet mode&#8221;: The touchscreen isn&#8217;t in Grails yet so gesture support isn&#8217;t there.  I can find in the kernel the endpoints for the motion sensors and gyroscope, but there are no drivers to interpret the signals coming from those hardware components yet, so they don&#8217;t emit anything.  Screen rotation 180° works fine for movie-watching mode, but 90° in either direction and the touchscreen doesn&#8217;t track correctly with finger touches.  The system emits the same signal for &#8220;transition to/from tablet mode&#8221;, so it&#8217;s not possible (yet) to detect <em>which</em> mode you&#8217;re in.  The Windows key in tablet mode is mapped to the same one on the keyboard, so remapping it for other purposes in tablet mode depends on my being able to detect tablet mode.</p>
<p>But those are <em>challenges</em>, not merely problems.</p>
<p>Still, choosing Mint over Gentoo feels like I&#8217;m giving up something.  Some deep understanding of how all the parts are put together.  I found myself chasing down new runlevel operations and other things most people aren&#8217;t concerned with, and solving them, so I&#8217;m not hopelessly lost.  But if I&#8217;m deep-diving into writing a new Linux kernel driver to get at the gyroscope and new Grails packages for the touchscreen, pushing aside my understanding of all those other components feels a little disloyal to the tradition of difficult programming from which I come.</p>
<p>Still, as a writing tool, this machine is unbeatable.  Instant-on and six hours of battery life are nothing to complain about.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/05/23/switching-gentoo-mint-questions-commitment-technical-expertise/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Querying Splunk with Clojure</title>
		<link>http://www.elfsternberg.com/2013/05/08/querying-splunk-clojure/</link>
		<comments>http://www.elfsternberg.com/2013/05/08/querying-splunk-clojure/#comments</comments>
		<pubDate>Thu, 09 May 2013 03:43:28 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1460</guid>
		<description><![CDATA[Introduction Two months ago I was complaining that I didn&#8217;t have a job, and then suddenly I had one. I work for Splunk now, an enterprise-level machine data analysis company. Their core product is pretty magical: you can shove just about any logfile, real-time stream, script-generated datasource, or anything else at it, and then interrogate [...]]]></description>
				<content:encoded><![CDATA[<h2><a name="toc1"></a>Introduction</h2>
<p>Two months ago I was complaining that I didn&#8217;t have a job, and then suddenly <a href="http://www.elfsternberg.com/2013/03/14/gave-lightning-talk/">I had one</a>. I work for <a href="http://splunk.com">Splunk</a> now, an enterprise-level machine data analysis company. Their core product is pretty magical: you can shove just about any logfile, real-time stream, script-generated datasource, or anything else at it, and then interrogate the data to understand and monitor your networks, server farms, client connections, whatever. It does an amazing job of correlating data through little more than keyword observation. There&#8217;s even a <a href="http://www.splunk.com/download">free version</a>, which, while limited to a half-gig of data, is a good way to start off taking apart your weblog files.</p>
<p>They have me niftying up the third-party web framework, the thing large companies use to integrate the data we collect with their own network intelligence dashboards, visualization systems, whatever. It&#8217;s all my kind of thing: Python back-end, Javascript/Backbone/jQuery front-end stuff, lots of clever closures and event handling.</p>
<p>But I decided, for entertainment purposes only, to learn more about a part of the system I know almost nothing about: the Java SDK. And it wasn&#8217;t enough to work in a language I don&#8217;t know (since I don&#8217;t know Java) with SDK&#8217;s I&#8217;ve never seen before. I had to go make it work with a language that, to the best of my knowledge, no one has ever demonstrated compatibility with Splunk before. I had to make it work in <a href="http://clojure.org/">Clojure</a>.</p>
<p>Yes, I&#8217;m a Hipster Hacker. I mean, come on, if you&#8217;re not making it difficult for yourself, what is education for, anyway?</p>
<p>The complete source code for this project is <a title="Splunk Search with Closure at GitHub" href="https://github.com/elfsternberg/splunk-search-clojure.git">available at Github</a>.</p>
<h3><a name="toc2"></a>Disclaimer</h3>
<p>I am a newbie to all of this. Clojure, Splunk, even Java. This is entirely raw and beginner level stuff. I did this for my own edification. This code in no way represents the state of the art at Splunk. It&#8217;s definitely not warranted in any way by me or anyone else, and it&#8217;s licensed under the Apache Public License, V2. Use at your own risk. Don&#8217;t make me go ALL CAPS on you.</p>
<h3><a name="toc3"></a>Literate Program</h3>
<p>A note: this article was written with the <a href="http://en.wikipedia.org/wiki/Literate_programming">Literate Programming</a> toolkit <a href="http://www.cs.tufts.edu/~nr/noweb/">Noweb</a>. Where you see something that looks like this , it&#8217;s a placeholder for code described elsewhere in the document. Placeholders with an equal sign at the end of them indicate the place where that code is defined. The link (U-&gt;) indicates that the code you&#8217;re seeing is used later in the document, and (&lt;-U) indicates it was used earlier but is being defined here.</p>
<h3><a name="toc4"></a>Install everything</h3>
<p>First, install Clojure, Leiningen, the Splunk free server, and the <a href="http://dev.splunk.com/view/splunk-sdk-java/SP-CAAAECN">Splunk Java SDK</a>. Start up the free server and give it some data. I gave it the dataset from earthquake.usgs.gov&#8211; &#8220;earthquakes greater than Richter 2.5 in magnitude over the past 30 days.&#8221;) but you can give it whatever you want.</p>
<p>Completing the installation is a bit tricky because this example relies on both the Splunk Java SDK, which is not in any Clojure or Maven repos, and the Splunk Java SDK&#8217;s Command utility class, which Splunk has provided as a helpful tool for unpacking the command line and for understanding the Splunk command file (.splunkrc on most Linux and Mac boxes). You&#8217;ll have to download the Splunk Java SDK yourself and install the Splunk JAR file. You&#8217;ll also have to create, from the root of this project directory, resources/com/splunk, and deposit Command.class (which can be found in the SDK&#8217;s tree somewhere) in the newly created directory.</p>
<p>I used lein localrepo to install the Splunk JAR file. It seems to have worked for me.</p>
<h3><a name="toc5"></a>Create the project</h3>
<p><a name="NWD1lBXEv-1"></a>In your workspace directory, whatever you call it, build a new project and call it <tt>splunk</tt>.</p>
<p><tt>lein new splunk</tt></p>
<p>Open the new directory and create a new directory, <tt>lib</tt>. Find the Splunk jar file in the SDK, and copy it into <tt>lib</tt>. Edit project.clj so it looks something like this:</p>
<pre><a href="#NWD1lBXEv-1" name="NW1lBXEv-qmLM7-1"><dfn>&lt;project cli rev 1&gt;=</dfn></a>
(defproject splunksearch "0.1.0"
  :description "SplunkSearch: An implementation of the Splunk Search example program in Clojure"
  :url "https://github.com/elfsternberg/splunk-search"
  :license {:name "Apache Public Licence 2.0"
            :url "http://www.apache.org/licenses/LICENSE-2.0"}
  :dependencies [
                 [org.clojure/clojure "1.4.0"] 
                 [commons-cli "1.2"] 
                 [gson "2.1"] 
                 [opencsv "2.3"]
  :plugins [[lein-localrepo "0.4.1"]]
  :main splunksearch.core)</pre>
<p><a name="NWD1lBXEv-2"></a>Run <tt>lein deps</tt> to install your dependencies.</p>
<p>Then, use the <tt>localrepo</tt> command to install the splunk jar in your Maven repository:</p>
<p><tt>lein localrepo coords splunk-1.1.jar | xargs lein localrepo install</tt></p>
<p>Now, edit the project file to show your new dependency:</p>
<pre><a href="#NWD1lBXEv-2" name="NW1lBXEv-2xv1Rv-1"><dfn>&lt;project cli rev 2&gt;=</dfn></a>
(defproject splunksearch "0.1.0"
  :description "SplunkSearch: An implementation of the Splunk Search example program in Clojure"
  :url "https://github.com/elfsternberg/splunk-search"
  :license {:name "Apache Public Licence 2.0"
            :url "http://www.apache.org/licenses/LICENSE-2.0"}
  :dependencies [
                 [org.clojure/clojure "1.4.0"] 
                 [commons-cli "1.2"] 
                 [gson "2.1"] 
                 [opencsv "2.3"]
                 [splunk "1.1"]]
  :plugins [[lein-localrepo "0.4.1"]]
  :main splunksearch.core)</pre>
<p>Now you&#8217;re ready to begin. Fun, huh?</p>
<p>[Edit: I've gotten at least one email telling me that localrepo only <em>created</em> the entry in your local Maven repository, but didn't copy the jar file there.  You might have to do that yourself.  For most of us it's in <tt>$HOME/.m2/repository/splunk/splunk/1.1</tt> Welcome to the cutting edge.]</p>
<h3><a name="toc6"></a>Create a simple query</h3>
<p>The Splunk Java SDK has its own custom methods for dealing with <a name="NWD1lBXEv-3"></a>arguments from the command line, from your run commands file (all those files in your home directory that end in &#8220;rc&#8221; on Linux and sometims Mac OS), and arguments to send to the remote Splunk server. We need two sets of arguments, one to define access to the service, and one to define the output mode of the data we expect to get back.</p>
<p>First, we have to create the namespace and import everything we&#8217;re going to need:</p>
<pre><a href="#NWD1lBXEv-3" name="NW1lBXEv-3Xe4ov-1"><dfn>&lt;create the namespace&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>
(ns splunksearch.core
  (:require [clojure.java.io :refer :all])
  (:import (com.splunk Service ServiceArgs Args ResultsReaderJson ResultsReaderCsv ResultsReaderXml Event))
  (:import (com.splunk Command))
  (:import (java.io InputStreamReader OutputStreamWriter)))</pre>
<p>I pretty much followed the code in order, a list of procedures. Remember, this document serves mostly as a stream-of-consciousness &#8220;this is what I learned about Clojure while wrestling with Splunk&#8221; history of my project.</p>
<p><a name="NWD1lBXEv-4"></a>The Splunk <tt>Command</tt> class requires a series of definitions added, and then it attempts to parse the command line. Command instantiates via a static factory method, so that&#8217;s accessed with a slash; the <tt>doto</tt> macro allows me to access the created object repeatedly, passing methods and arguments, and guarantees the object created is returned regardless of the return object of the last method. Clojure args have to be coerced via <tt>into-array</tt> into a Java array for Java-based args parsers to make sense of them.</p>
<pre><a href="#NWD1lBXEv-4" name="NW1lBXEv-2jnK8j-1"><dfn>&lt;parse command arguments&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>
(defn build-splunk-command [args]
  (let [command 
        (doto (Command/splunk "search")
          (.addRule "count" Integer 
                    "The Maximum Number of results to return (default: 100)")
          (.addRule "earliest_time" String
                    "Search earliest time")
          (.addRule "field_list" String
                    "A comma-separated list of the fields to return")
          (.addRule "latest_time" String
                    "Search latest time")
d          (.addRule "offset" Integer
                    "The first result (inclusive) from which to begin returning data. (default: 0)")
          (.addRule "output" String
                    "Which search results to output {events, results, preview, searchlog, summary, timeline} (default: results)")
          (.addRule "output_mode" String
                    "Search output format {csv, raw, json, xml} (default: xml)")
          (.addRule "reader" 
                    "Use ResultsReader")
          (.addRule "status_buckets" Integer
                    "Number of status buckets to use for search (default: 0)")
          (.addRule "verbose"  
                    "Display search progress")
          (.parse (into-array String args)))]
    (if (not= (count (.args command)) 1)
      (Command/error "Search expression required" nil))
    command))</pre>
<p><a name="NWD1lBXEv-5"></a>My example code was taken from the <tt>Search/Program.java</tt> file, provided with the SDK. That program had a ton of local variables to control search generation, stream configuration, reader and output generation. I decided that all had to go into a simple map, which I could then refer to at any time.</p>
<p>Yes, the names above are repeated here. That&#8217;s a bit of a code smell, I think.</p>
<pre><a href="#NWD1lBXEv-5" name="NW1lBXEv-27t0cY-1"><dfn>&lt;build the arguments map&gt;=</dfn></a>

(defn build-argument-map [command]
  (let [opts (.opts command)
        ruleset [["count" 100] 
                 ["earliest_time" nil ]
                 ["reader" false]
                 ["verbose" false]
                 ["field_list" nil ] 
                 ["latest_time" nil ]
                 ["offset" 0]
                 ["output" "results"]
                 ["output_mode" "xml"]]]
    doall (into {} (for [[k v] ruleset] [k (if (.containsKey opts k) (.get opts k) v)]))))</pre>
<p><a name="NWD1lBXEv-6"></a>Now that I have my argument map, I need to process it into Args objects understood by the Splunk Service class. I don&#8217;t know about you, but this all feels just a bit fiddly:</p>
<pre><a href="#NWD1lBXEv-6" name="NW1lBXEv-1KyyDy-1"><dfn>&lt;build the queryargs object&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>
(defn build-splunk-queryargs [argument-map]
  (let [rulelist ["earliest_time" "field_list" "latest_time" "status_buckets"]
        qa (Args.)]
    (doseq [fieldname rulelist]
      (if (argument-map fieldname)
        (.put args fieldname (argument-map fieldname))))
    args))</pre>
<p><a name="NWD1lBXEv-7"></a>Later, I have to build the output Args object, which the Splunk Service uses to to configure the output. Obviously. It&#8217;s the exact same code, and it wasn&#8217;t cut and paste. I&#8217;m thinking this needs an abstraction.</p>
<pre><a href="#NWD1lBXEv-7" name="NW1lBXEv-2CHxY5-1"><dfn>&lt;build the output args object&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>

(defn build-splunk-output-args [argument-map]
  (let [rulelist ["count" "offset" "output_mode"]
        args (Args.)]
    (doseq [fieldname rulelist]
      (if (argument-map fieldname)
        (.put args fieldname (argument-map fieldname))))
    args))</pre>
<p><a name="NWD1lBXEv-8"></a>Now that we have everything, <em>le sigh</em>, it&#8217;s time to pass it all to the server. I don&#8217;t even care much about the Service object once I&#8217;ve instantiated it. I&#8217;m using it for a single query, a single Job on the server. And it&#8217;s a bit redundant to pass both the command and the argument map, but I&#8217;m using the map to configure other program behaviors, so it stays as a separate copy of the command content. Between the double-dot argument and the chain operators, I&#8217;m seeing a lot of Haskellian inspiration in Clojure.</p>
<pre><a href="#NWD1lBXEv-8" name="NW1lBXEv-2PubEh-1"><dfn>&lt;send the query to the server&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>
(defn build-splunk-job [command argument-map] 
  (let [queryargs (build-splunk-queryargs argument-map)
        service (Service/connect (.opts command))
        job (.. service (getJobs) (create (first (.args command)) queryargs))]
    (while (not (.isDone job)) 
      (if (argument-map "verbose")
        (println (format "\n%03.1f%% done -- %d scanned -- %d matched -- %d results"
                         (* (.getDoneProgress job) 100.0)
                         (.getScanCount job) 
                         (.getEventCount job) 
                         (.getResultCount job))))
      (Thread/sleep 1000))
    job))</pre>
<p><a name="NWD1lBXEv-9"></a>There are a number of different things we can get from the server. The correct setting is often &#8220;preview&#8221;, meaning &#8220;show me any valid data you&#8217;ve collected, even if it&#8217;s not complete.&#8221; Preview will return everything even if the job <em>is</em> complete, so it&#8217;s safe to use at all times. In this case, it&#8217;s a one-shot, so I&#8217;ll use &#8220;results&#8221; instead.  But you can see all of the options below.  Even <em>I</em> don&#8217;t know what they all mean.</p>
<p>Here, using the argument map and the instructions to build the output args object, I ask for a stream (an actual Java <tt>InputStream</tt>) from the server of the data I want:</p>
<pre><a href="#NWD1lBXEv-9" name="NW1lBXEv-ZJpn-1"><dfn>&lt;request a stream from the server&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>
(defn get-splunk-stream [job argument-map]
  (let [outputargs (build-splunk-output-args argument-map)
        output (argument-map "output")]
    (case output
      "results" (.getResults job outputargs)
      "preview" (.getPreview job outputargs)
      "searchlog" (.getSearchLog job outputargs)
      "summary" (.getSummary job outputargs)
      "timeline" (.getTimeline job outputargs)
      )))</pre>
<p>There are two kinds of read operations: we could use the Splunk ResultsReader, which parses the content for you and turns it into a <a name="NWD1lBXEv-A"></a>hashmap of keys and values, or you can just get the raw data. I&#8217;m going to deal with the readers first.</p>
<p>Instantiating a reader irked me. I could not figure out how to make Java constructors act like first-class objects; I wanted to be able to pick a class and pass it back to the calling function, which could instantiate it on the fly after dereferencing it. I&#8217;m sure there are Clojure gurus who can help with that:</p>
<pre><a href="#NWD1lBXEv-A" name="NW1lBXEv-3gLBOn-1"><dfn>&lt;construct a reader of the appropriate type&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>

(defn construct-reader [stream output-mode]
  (case output-mode
    "xml" (ResultsReaderXml. stream)
    "json" (ResultsReaderJson. stream)
    "csv" (ResultsReaderCsv. stream)))</pre>
<p><a name="NWD1lBXEv-B"></a>This returns a reader-ready function to print out content based upon the output mode. The <tt>when</tt> macro for handling loops was a real eye-opener.</p>
<pre><a href="#NWD1lBXEv-B" name="NW1lBXEv-2UqGNX-1"><dfn>&lt;make a stream reader with a mode parser&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>

(defn get-streamtype-reader [output-mode]
  (fn [stream] 
    (with-open [reader (construct-reader stream output-mode)]
      (loop [e (.getNextEvent reader)]
        (when (not= e nil)
          (println "EVENT:********")
          (doseq [k (seq (.keySet e))]
            (printf "%s ---&gt; %s\n" k (.get e k)))
          (recur (.getNextEvent reader)))))))</pre>
<p><a name="NWD1lBXEv-C"></a>This returns a reader-ready function to print out the raw content.</p>
<pre><a href="#NWD1lBXEv-C" name="NW1lBXEv-1e2rIV-1"><dfn>&lt;make a generic stream reader&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>

(defn generic-reader []
  (fn [stream]
    (with-open [reader (InputStreamReader. stream "UTF8")
                writer (OutputStreamWriter. System/out)]
      (try
        (let [buffer (char-array 1024)]
          (while true 
            (let [count (.read reader buffer)]
              (if (== count -1) (throw (Exception. "EOF")))
              (.write writer buffer 0 count))))
        (catch Exception e nil)))))</pre>
<p><a name="NWD1lBXEv-D"></a>I&#8217;m completely sure that there&#8217;s a better way to acheive the symmetry I wanted, and that the function that takes no argument above in &#8220;make a generic stream reader&#8221; is completely gratuitous, but I kinda liked the way this one came out.</p>
<pre><a href="#NWD1lBXEv-D" name="NW1lBXEv-3Rbcox-1"><dfn>&lt;get the stream reader&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>

(defn get-reader [command argument-map]
  (let [use-reader (.. command opts (containsKey "reader"))]
    (if use-reader
      (get-streamtype-reader (argument-map "output_mode"))
      (generic-reader))))</pre>
<p><a name="NWD1lBXEv-E"></a>This has to reveal just how bleedingly new I am to Clojure, because I&#8217;ve never seen a &#8220;let cascade&#8221; like this before in anyone else&#8217;s code. But it works!</p>
<pre><a href="#NWD1lBXEv-E" name="NW1lBXEv-23uoZy-1"><dfn>&lt;main&gt;=</dfn></a> <strong>(<a href="#NWD1lBXEv-F">U-&gt;</a>)</strong>
(defn -main[&amp; args]
  (let [command (build-splunk-command args)]
    (let [argument-map (build-argument-map command)]
      (let [job (build-splunk-job command argument-map)]
        (let [stream (get-splunk-stream job argument-map)]
          (let [reader (get-reader command argument-map)]
            (reader stream)))))))</pre>
<p><a name="NWD1lBXEv-F"></a>And the entire program ends up looking like</p>
<pre><a href="#NWD1lBXEv-F" name="NW1lBXEv-44ofhW-1"><dfn>&lt;core.clj&gt;=</dfn></a>
<a href="#NWD1lBXEv-3" name="NW1lBXEv-44ofhW-1-u1"><em>&lt;create the namespace&gt;</em></a>

<a href="#NWD1lBXEv-4" name="NW1lBXEv-44ofhW-1-u2"><em>&lt;parse command arguments&gt;</em></a>

<a href="#nw@notdef" name="NW1lBXEv-44ofhW-1-u3"><em>&lt;build the argument map&gt;</em></a>

<a href="#NWD1lBXEv-6" name="NW1lBXEv-44ofhW-1-u4"><em>&lt;build the queryargs object&gt;</em></a>

<a href="#NWD1lBXEv-7" name="NW1lBXEv-44ofhW-1-u5"><em>&lt;build the output args object&gt;</em></a>

<a href="#NWD1lBXEv-8" name="NW1lBXEv-44ofhW-1-u6"><em>&lt;send the query to the server&gt;</em></a>

<a href="#NWD1lBXEv-9" name="NW1lBXEv-44ofhW-1-u7"><em>&lt;request a stream from the server&gt;</em></a>

<a href="#NWD1lBXEv-A" name="NW1lBXEv-44ofhW-1-u8"><em>&lt;construct a reader of the appropriate type&gt;</em></a>

<a href="#NWD1lBXEv-B" name="NW1lBXEv-44ofhW-1-u9"><em>&lt;make a stream reader with a mode parser&gt;</em></a>

<a href="#NWD1lBXEv-C" name="NW1lBXEv-44ofhW-1-u10"><em>&lt;make a generic stream reader&gt;</em></a>

<a href="#NWD1lBXEv-D" name="NW1lBXEv-44ofhW-1-u11"><em>&lt;get the stream reader&gt;</em></a>

<a href="#NWD1lBXEv-E" name="NW1lBXEv-44ofhW-1-u12"><em>&lt;main&gt;</em></a></pre>
<p><a name="NWD1lBXEv-G"></a>And that&#8217;s it.</p>
<p>You can now run the program with lein:</p>
<pre><a href="#NWD1lBXEv-G" name="NW1lBXEv-2SQRwt-1"><dfn>&lt;command_line&gt;=</dfn></a>
lein run --output_mode csv "search magnitude &gt; 4.5"

</pre>
<p>Assuming you have something in your Splunk database with a &#8220;magnitude,&#8221; you should get a CSV dump of all the fields related to it.</p>
<p>Now you know how to talk to the basic Splunk service using Clojure. I&#8217;m sure if you&#8217;re comfortable with Clojure none of the Java access <a name="NWD1lBXEv-H"></a>weirdness surprised you at all, but for me this was a pretty good exercise. At times I felt like I was writing in a &#8220;fantasy LISP&#8221;, a Lisp that actually, you know, had <em>real world</em> applicability, and that should have done the things I would have expected of a LISP. That fantasy LISP was pretty close to the real deal; it only took me a few hours of <tt>lein run</tt> sessions to knock out all the bugs.</p>
<h2>Index</h2>
<ul>
<li><a href="#nw@notdef"><em>&lt;build the argument map&gt;</em></a>: <a href="#NWD1lBXEv-F">U1</a></li>
<li><a href="#NWD1lBXEv-5"><em>&lt;build the arguments map&gt;</em></a>: <a href="#NWD1lBXEv-5">D1</a></li>
<li><a href="#NWD1lBXEv-7"><em>&lt;build the output args object&gt;</em></a>: <a href="#NWD1lBXEv-7">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-6"><em>&lt;build the queryargs object&gt;</em></a>: <a href="#NWD1lBXEv-6">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-G"><em>&lt;command_line&gt;</em></a>: <a href="#NWD1lBXEv-G">D1</a></li>
<li><a href="#NWD1lBXEv-A"><em>&lt;construct a reader of the appropriate type&gt;</em></a>: <a href="#NWD1lBXEv-A">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-F"><em>&lt;core.clj&gt;</em></a>: <a href="#NWD1lBXEv-F">D1</a></li>
<li><a href="#NWD1lBXEv-3"><em>&lt;create the namespace&gt;</em></a>: <a href="#NWD1lBXEv-3">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-D"><em>&lt;get the stream reader&gt;</em></a>: <a href="#NWD1lBXEv-D">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-E"><em>&lt;main&gt;</em></a>: <a href="#NWD1lBXEv-E">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-C"><em>&lt;make a generic stream reader&gt;</em></a>: <a href="#NWD1lBXEv-C">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-B"><em>&lt;make a stream reader with a mode parser&gt;</em></a>: <a href="#NWD1lBXEv-B">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-4"><em>&lt;parse command arguments&gt;</em></a>: <a href="#NWD1lBXEv-4">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-1"><em>&lt;project cli rev 1&gt;</em></a>: <a href="#NWD1lBXEv-1">D1</a></li>
<li><a href="#NWD1lBXEv-2"><em>&lt;project cli rev 2&gt;</em></a>: <a href="#NWD1lBXEv-2">D1</a></li>
<li><a href="#NWD1lBXEv-9"><em>&lt;request a stream from the server&gt;</em></a>: <a href="#NWD1lBXEv-9">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
<li><a href="#NWD1lBXEv-8"><em>&lt;send the query to the server&gt;</em></a>: <a href="#NWD1lBXEv-8">D1</a>, <a href="#NWD1lBXEv-F">U2</a></li>
</ul>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/05/08/querying-splunk-clojure/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Returning to Coffeescript, and having a good time</title>
		<link>http://www.elfsternberg.com/2013/04/27/returning-coffeescript-good-time/</link>
		<comments>http://www.elfsternberg.com/2013/04/27/returning-coffeescript-good-time/#comments</comments>
		<pubDate>Sat, 27 Apr 2013 21:26:56 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1456</guid>
		<description><![CDATA[Following on my adventures in job seeking, several weeks ago I landed a great new job with Splunk. They do big data for &#8220;internal intelligence&#8221;; monitoring your toolkit and helping you do analytics on masses of what is generally thought of as unstructured data. I help customers visualize their data, and have been doing javascript [...]]]></description>
				<content:encoded><![CDATA[<p>Following on my adventures in job seeking, several weeks ago I landed a great new job with <a href="http://www.splunk.com/">Splunk</a>.  They do big data for &#8220;internal intelligence&#8221;; monitoring your toolkit and helping you do analytics on masses of what is generally thought of as unstructured data.  I help customers visualize their data, and have been doing javascript and D3 and WebGL and a ton of fun stuff streamlining how third-party developers can visualize Splunk data with their favorite visualization tool.  </p>
<p>But there are two things that, well&#8230; one, it&#8217;s <i>javascript</i>.  My Coffee and Haskell skills are dying on the vine.  And two, it&#8217;s <i>older</i> javascript.  I&#8217;m not allowed to do anything wild.  No Bacon.  No FRP.  No Node.  This thing has to be solid and working and be usable by people who don&#8217;t have time to master the intricacies of the cutting edge, people who want answers not fanciers (okay, that&#8217;s a stretch), and whose Javascript experience might just be a couple of O&#8217;Reilly books and an API.</p>
<p>Last week I released <a href="http://www.elfsternberg.com/2013/04/17/latest-project-tumble-parserrenderer-tumblrlike-templating-language/">Tumble</a>, a little hack for parsing Tumblr-like templates and rendering their contents.  There&#8217;s a line in the source code that reads &#8220;This code <a href="http://en.wikipedia.org/wiki/Code_smell">stinks</a>!&#8221;  And it did.  It was the same logic repeated <i>three times</i>: Once in the parser when it was found, once in the parser/contexter API, and a third time in the contexter itself.</p>
<p>My first order of business was to reduce the parser to a single rule.  There are two kinds of objects in Tumbler: rendered objects and blocks.  A rendered objects looks like {this} and tells the rendered &#8220;replace this with whatever this name refers to.&#8221;  Usually, that&#8217;s just a string from the database; sometimes, though, it can be the result of a function.  A block looks like {key:something} &#8230; a bunch of stuff &#8230; {/key:something}.  Blocks have three different rules: They can be conditional (render this block <i>if</i> this data is true), descendent (render this block <i>with</i> this other source of data), or iterative (render this block <i>many</i> times, once for each item in this list of data).   I stared out with rules like &#8220;just text&#8221;, &#8220;variable&#8221;, &#8220;ifblock&#8221;, &#8220;descendblock&#8221;, &#8220;manyblock&#8221;, and corresponding rules to find {many:foo}{/many:foo}, etc. things.  </p>
<p>That turned out to stink.  So I reduced the rule to a simple {blocktype:blockname}{/blocktype:blockname} finder rule.</p>
<p>The Contexter would <i>do the right thing</i> with each kind of blocktype, using data refernced by blockname.</p>
<p>The problem was the glue between contexter and parser.  I was using the parser to derive the blocktype, then propagating that up to the contexter through a very crude set of manually written code paths.  There had to be a better way.</p>
<p>Since I&#8217;d thrown out the various block types from the parser, the javascript for initial handling had also been thrown out.  I found it in git.  It looked like this:
<pre>

    // TODO: Yeah, this code stinks.

    conditional = function(t, ps) {
        return function(content) {
            return content.if(t, function(c) {
                return sections(ps, content);
            });
        }
    };
    
    descendant = function(t, ps) {
        return function(content) {
            return content.descend(t, function(c) {
                return sections(ps, content);
            });
        }
    };
</pre>
<p> You can tell.  It&#8217;s the same damn thing, over and over.  And there&#8217;s a lot of visual clutter there.  </p>
<p>I decided to re-write the glue in coffeescript.  Here&#8217;s the whole of it now:
<pre>
module.exports = (ast, data) ->
    context = new Contexter(data)
    cmd = (o) ->
        switch o.unit
            when 'variable' then  (context) -> context.get(o.name)
            when 'text'     then  (context) -> o.content
            when 'block'    then  (context) -> context[o.type] o.name, (context) ->
                (cmd(p)(context) for p in o.content).join("")
    (cmd(o)(context) for o in ast.content).join("")
</pre>
<p>This is way smaller.  And you can see where my Haskell learning has paid off.  I&#8217;m actually <i>reasoning</i> about the kinds of data, the kinds of closures I want the interpreter to work on as it descends the parse tree.  The last line is pure magic.  It says &#8220;For the current point in the AST for our template, get a function that can handle the current context for our data.&#8221;  And it does.  Perfectly.  Even more beautiful is the way the &#8216;block&#8217; handler automagically repeats that line in order to descend further down the AST tree.  That last &#8216;context&#8217; in that &#8216;block&#8217; handler isn&#8217;t the current context&#8211; it&#8217;s whatever context the internal corresponding rule requires, and <i>I don&#8217;t have to care</i> about what that rule is, because this is just code-as-data.  Sweet.</p>
<p>It&#8217;s been years since I&#8217;ve worked with lex and yacc.  Hell, I even worked in Antlr briefly, at F5 Networks.  But Haskell, Coffeescript, and PEG have made writing something like this blazingly easy.</p>
<p>I pushed the latest rev up to github at <a href="https://github.com/elfsternberg/tumble">ElfSternberg/Tumble</a>.  It&#8217;s proving to be an interesting experiment.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/04/27/returning-coffeescript-good-time/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>An &#8220;Ah-hah!&#8221; moment</title>
		<link>http://www.elfsternberg.com/2013/04/20/ahhah-moment/</link>
		<comments>http://www.elfsternberg.com/2013/04/20/ahhah-moment/#comments</comments>
		<pubDate>Sun, 21 Apr 2013 05:49:59 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[chat]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1452</guid>
		<description><![CDATA[There are moments when the lightbulb goes off in your head with the power of a thousand white-hot suns.  For me, it happened while I was reading the WordPress Database Schema back to back with Henri Bergius&#8217;s highly influentials (well, it was a Hell of an influence on me, at any rate) Decoupling Content Management. Take a close [...]]]></description>
				<content:encoded><![CDATA[<p>There are moments when the lightbulb goes off in your head with the power of a thousand white-hot suns.  For me, it happened while I was reading the <a href="http://codex.wordpress.org/Database_Description">WordPress Database Schema</a> back to back with Henri Bergius&#8217;s highly influentials (well, it was a Hell of an influence on <em>me</em>, at any rate) <em><a href="http://bergie.iki.fi/blog/decoupling_content_management/">Decoupling Content Management</a>.</em></p>
<p>Take a close look at the WordPress Database Schema diagram and tell me what&#8217;s&#8230; off&#8230; about it.  It should leap right out at you.  Absolutely everything in the WordPress Database Schema is related&#8211; posts, users, and comments are all interrelated to one another.  And off in the lower right-hand corner, there&#8217;s Options, all by itself, unrelated to anything else.</p>
<p>And then I realized: Everything on that page is about the content.  It describes the <em>Content Layer.</em>  Except Options.  Options isn&#8217;t about the Content at all.  Options in WordPress controls a <em>completely independent program</em> hiding inside WordPress: The <em>Presentation Layer</em>.  Actually, Options is a grab-bag of controls over everything: settings for the authentication layer, settings for the presentation layer, settings for the administration layer, settings for presenting the administration editor, etc., etc.  The one thing Options does absolutely nothing, and I mean <em>nothing</em>, for, is the Content layer.</p>
<p>Huh.</p>
<p>Maybe French Press is the next project after all.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/04/20/ahhah-moment/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Post-Project Blues</title>
		<link>http://www.elfsternberg.com/2013/04/19/postproject-blues/</link>
		<comments>http://www.elfsternberg.com/2013/04/19/postproject-blues/#comments</comments>
		<pubDate>Sat, 20 Apr 2013 04:59:21 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1449</guid>
		<description><![CDATA[I wonder if anyone else has this problem. This week, I finally wrapped my head around Tumble, the basic parser engine I&#8217;ve been working my way up to since about January or so.  It really shouldn&#8217;t have taken so long, but I had a lot to learn, a lot of false starts, a change of [...]]]></description>
				<content:encoded><![CDATA[<p>I wonder if anyone else has this problem.</p>
<p>This week, I finally wrapped my head around Tumble, the basic parser engine I&#8217;ve been working my way up to since about January or so.  It really shouldn&#8217;t have taken so long, but I had a lot to learn, a lot of false starts, a change of job, and a generally busy life.</p>
<p><a href="https://github.com/elfsternberg/tumble">Tumble</a> is one part of a project that I&#8217;ve been working on for the past year.  The other parts are named Crest, Dragon, Candy, and French Press&#8211; they&#8217;re all part of a <a href="http://http://bergie.iki.fi/blog/decoupling_content_management/">decoupled CMS</a> written in NodeJS mostly meant for fiction writers.  Candy is a meta-system on top of Tumble for theme management.  French Press is a back-end.  Crest is an intake engine and Content-Editable aware piece for fixing stories after they&#8217;re done.  Dragon is a template package for Tumble that spits out the stories in EPUB and LaTeX formats for alternative distribution channels.</p>
<p>I should work on any one of those.  Probably French Press next, as it would help get the back-end toolkit rolling, and it must be working before Crest or any of the other toolkits make sense.  But I don&#8217;t wanna.  I&#8217;m not even particularly unhappy with Tumble; it&#8217;s a good piece of software, all told, and it has alternative output mechanisms that make it suitable to Candy or Dragon, but it&#8217;s&#8230; I&#8217;m just glad it&#8217;s done, and it&#8217;ll be a few days before I want to get back into that sort of thing.</p>
<p>Do you ever have post-project blues?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/04/19/postproject-blues/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Latest project: Tumble, a parser/renderer for a Tumblr-like templating language</title>
		<link>http://www.elfsternberg.com/2013/04/17/latest-project-tumble-parserrenderer-tumblrlike-templating-language/</link>
		<comments>http://www.elfsternberg.com/2013/04/17/latest-project-tumble-parserrenderer-tumblrlike-templating-language/#comments</comments>
		<pubDate>Wed, 17 Apr 2013 17:41:29 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1447</guid>
		<description><![CDATA[I really like Tumblr&#8217;s templating language.  It&#8217;s simple, it makes sense for small contexts, and it&#8217;s easily extensible.  So I decided to make my own.  This will be the core of the next generation of the Narrator engine, the thing I use to render my stories and series.  It&#8217;s also not restricted to HTML; it [...]]]></description>
				<content:encoded><![CDATA[<p>I really like<a href="http://www.tumblr.com/docs/en/custom_themes"> Tumblr&#8217;s templating language</a>.  It&#8217;s simple, it makes sense for small contexts, and it&#8217;s easily extensible.  So I decided to make my own.  This will be the core of the next generation of the Narrator engine, the thing I use to render my stories and series.  It&#8217;s also not restricted to HTML; it actually doesn&#8217;t care much what it&#8217;s embedded in, so I can use this to render LaTeX documents as well, meaning that I might be getting close to a story engine that produces EPUB and printed matter automatically.</p>
<p>It&#8217;s fairly simple.  This is just version 0.0.1, so don&#8217;t expect it to be safe like<a href="https://github.com/Shopify/liquid"> liquid</a> (although that&#8217;s ultimately a goal), and I&#8217;ve already had an idea for refining it further.  Check it out:</p>
<p><a href="https://github.com/elfsternberg/tumble">ElfSternberg/Tumble</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/04/17/latest-project-tumble-parserrenderer-tumblrlike-templating-language/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>I gave a lightning talk!</title>
		<link>http://www.elfsternberg.com/2013/03/14/gave-lightning-talk/</link>
		<comments>http://www.elfsternberg.com/2013/03/14/gave-lightning-talk/#comments</comments>
		<pubDate>Fri, 15 Mar 2013 05:21:03 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[chat]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1437</guid>
		<description><![CDATA[I don&#8217;t know if I&#8217;ve ever been more terrified.  I need to do that again.  I need to get better at it.  I need to talk slower. I went to the SeattleJS meetup this evening.  The presentation format is two hours long: a little schmoozing at the front, an introduction, then 20 minutes of lightning [...]]]></description>
				<content:encoded><![CDATA[<p>I don&#8217;t know if I&#8217;ve ever been more terrified.  I need to do that again.  I need to get better at it.  I need to talk slower.</p>
<p>I went to the SeattleJS meetup this evening.  The presentation format is two hours long: a little schmoozing at the front, an introduction, then 20 minutes of lightning talks from the audience, followed by two 20 minute presentations.  The first was on Meteor, the second on Arduino and NodeJS, and then a little more schmoozing at the end.  The introduction had high points; he asked &#8220;What do people want to talk about?&#8221;  There was a desultory &#8220;jQuery,&#8221; from the audience, then &#8220;Backbone.&#8221;  I threw out, in quick succession, &#8220;Node,&#8221; &#8220;Coffeescript,&#8221; and finally, &#8220;Functional Reactive Javascript.&#8221;   I didn&#8217;t mention Mocha or Grunt.  He asked how many people were here looking for work.  Hands went up.  How many recruiters?  Just about as many hands.  &#8221;After the presentations, recruiters and job seekers, find each other.  I recommend you move over to that side of the room to make it easy.&#8221;</p>
<p>There were lightning presentations.  The first was mindblowing, a demonstration of visual recognition in Javascript.  He could wave his hands at the computer&#8217;s camera and control the paddle in a pong game.  The next were more pedestrian, but no less professional and amazing: a bible study website (&#8220;It&#8217;s hard selling bible study in the Pacific Northwest, believe me&#8221;), a socket.io demo.</p>
<p>Completely on impulse, and because there was a lull, I stood up and said, &#8220;Hi, I&#8217;m Elf Sternberg, and I got laid off a month ago.  That&#8217;s important to what I&#8217;m about to show you.&#8221;  I showed them FridgeMagnets, and went into the details of its construction: Coffeescript, Cake, Haml, Less, the separation of axes theorem, as well as I why I wanted to learn it: the animation, unicode, and audio APIs.  And then I said, &#8220;As straightforward a toy as this is, it&#8217;s <em>important.  </em>I was sending out resumes every day for three weeks without getting a return call.  Then, on the advice of a friend, I put the address for this toy, and a few like it, and my GitHub with the source code for it, on my resume, and re-sent it.  The next day I got 18 phone calls.  That weekend I had three offers.  Next Monday I start my new job.&#8221;  That got applause.  &#8221;So let me encourage you, <em>please,</em> since Ryan showed us how many people here are looking for work,  get a GitHub account, get a StackOverflow account, get your own blog on WordPress or Tumblr or Livejournal, and let people know what you&#8217;re passionate about.  It doesn&#8217;t have to be big.  This is a <em>toy</em>.  But it shows people what I do, and how I do it.&#8221;  That got more applause.  &#8221;Thank you.&#8221;</p>
<p>I have never been more terrified in my life.  I talked too fast, and I could have pissed myself up there.  What the <em>hell</em> was I thinking, shooting off like that in front of a hundred people?  I should do it again.</p>
<p>After the other two presentations&#8211; one of which, I helped the presenter figure out how to determine his own IP address, since local DNS wasn&#8217;t resolving correctly&#8211; I had <em>recruiters</em> coming up to me and asking me to explain to them how this GitHub thing worked.  They knew, in an abstract sense, that open source meant that some programs were available in some arcane &#8220;raw&#8221; source, freely available, somewhere on the Internet, but they were only now hearing about GitHub and StackOverflow.  I explained it as best I could, and some of them nodded and others just looked puzzled.</p>
<p>So let me re-iterate: if you want a job as a programmer, get yourself a GitHub and a StackOverflow account.  Especially if you&#8217;re a woman.  The head of hiring at Etsy said that whiteboarding, the common practice in interviews of making you write code on a whiteboard to prove you&#8217;re &#8220;smart&#8230; and quick!&#8221; is stupid.  Women are generally more deliberate and careful than men, less likely to shoot from the hip, more likely to be complete and correct.  Very few jobs in programming require the &#8220;quick&#8221; part.  Not in the sense of knowing the answer up front and immediately.  A GitHub or StackOverflow account shows what you&#8217;re capable of, and the interview should be about personality and cultural fit, and it&#8217;s a much better, and gender-neutral proving ground, than the hyper-aggressive &#8220;Skate&#8230; or Die!&#8221; of whiteboarding.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/03/14/gave-lightning-talk/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>A WordPress Plug-In: Illustrated Recent Posts</title>
		<link>http://www.elfsternberg.com/2013/03/13/wordpress-plugin-illustrated-posts/</link>
		<comments>http://www.elfsternberg.com/2013/03/13/wordpress-plugin-illustrated-posts/#comments</comments>
		<pubDate>Thu, 14 Mar 2013 00:39:55 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1432</guid>
		<description><![CDATA[I&#8217;ll hang my head in shame later. So, I wrote this for my wife, it&#8217;s a hack on the &#8220;recent_posts&#8221; function from default-widgets.php, which comes with WordPress. It&#8217;s magic is that it scans each post for an IMG tag and, if it finds one, inserts that as the background of the list object. A little [...]]]></description>
				<content:encoded><![CDATA[<p>I&#8217;ll hang my head in shame later. So, I wrote this for my wife, it&#8217;s a hack on the &#8220;recent_posts&#8221; function from default-widgets.php, which comes with WordPress. It&#8217;s magic is that it scans each post for an IMG tag and, if it finds one, inserts that as the background of the list object. A little CSS magic drops the opacity down of the image down a dark grey, so it makes a nicely textured list. Since she&#8217;s a podcaster who comments on video games, I also provided the capacity to pick a category (&#8220;podcasts&#8221;) and show only those in the list. She includes screenshots with her podcast announcements, and the combination turns out to be quite lovely. We&#8217;ll be rolling out the final website later this month, so you&#8217;ll have to take my word for it.</p>
<p>But, <b>to make it fun</b>, I decided to hack the recent posts scripts to use as much functional programming as possible. I couldn&#8217;t do it with WordPress&#8217;s theLoop(), because that doesn&#8217;t convert nicely into an iterator, but I could down in the category form, which <i>does</i> have an array of categories.</p>
<p>The first part was to get the list of categories, and to pull out the base for each input field:</p>
<pre>    $cats_instance = $instance['cats'];
    $option_base ='&lt;input type="checkbox" id="'. $this-&gt;get_field_id('cats') .'[]" name="'. $this-&gt;get_field_name('cats') .'[]"';</pre>
<p>The second part takes any given category we want to show and know if it&#8217;s checked. This is a pure function; we want to transform a category into a boolean which asserts that it&#8217;s in the list of categories picked for this instance. We need to pass the instance into the main function, and PHP provides the <tt>use</tt> keyword to enclose variable in an anonymous function. Then, when this function is called, we need to enclose the passed-in variable to our <tt>is_a_cat</tt> function so we can scan it against <tt>cats_instance</tt> (which, now that I think about it, should really be named <tt>instance_cats</tt>, but whatever):</p>
<pre>    $isChecked = function($c) use ($cats_instance) {
      $is_a_cat = function($ic) use ($c) { return ($ic == $c-&gt;term_id); };
      return (count(array_filter($cats_instance, $is_a_cat)) &gt; 0);
    };</pre>
<p>And finally, we&#8217;re going to reduce the list of all categories to an HTML string representing the form. We need to pass in <tt>$isChecked</tt> and <tt>$option_base</tt>.  Because of a bug in array_reduce, the container type declaration only ever takes a NULL or an integer, never a string or array, so the if statement there works around that.  Sadly, it&#8217;s a work-around, not a proper guard condition.</p>
<pre>    $reduce_categories = function($result, $c) use ($isChecked, $option_base) {
      $checked = ($isChecked($c)) ? ' checked="checked"' : '';
      if ($result == NULL) {
        return $option_base . $checked . ' value="' . $c-&gt;term_id.'" /&gt; ' . $c-&gt;cat_name . '&lt;br /&gt;';
      }
      return $result . $option_base . $checked . ' value="' . $c-&gt;term_id.'" /&amp;t; ' . $c-&gt;cat_name . '&lt;br /&gt;';
    };</pre>
<p>This is echoed immediately afterward to the screen.  Note the almost complete lack of variables.  Even <tt>$reduce_categories()</tt> is effectively invariant for this call.</p>
<pre>&lt;?php echo array_reduce(get_categories('hide_empty=0'), $reduce_categories, NULL); ?&gt;</pre>
<p>In all, this is a nicely functional routine in which we describe how the result is built, isolate exactly those elements necessary to build the result, and describe with some precision the enclosures necessary for each function to make sense.</p>
<p>I don&#8217;t know PHP at all.  PHP5 looks like some unholy archaic Perl crossover with some weird modern additions tacked on.  There&#8217;s still too many variables and references in this code and whether or not PHP is pass-by-reference or pass-by-value, or the oddly intuitive hybrid that lives in Java, Python and Javascript, still escapes me.  There&#8217;s no equivalent of the <tt>in_array()</tt> function that takes a callback as its truth function, so <tt>isChecked()</tt> scans the entire array on every iteration.</p>
<p>But this was fun. And it works on PHP 5.3 and above. And I can claim, with some truthiness, that it&#8217;s both functional and less error-prone. And that makes me happy.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/03/13/wordpress-plugin-illustrated-posts/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Projects I will never finish.</title>
		<link>http://www.elfsternberg.com/2013/03/05/projects-finish/</link>
		<comments>http://www.elfsternberg.com/2013/03/05/projects-finish/#comments</comments>
		<pubDate>Tue, 05 Mar 2013 20:44:46 +0000</pubDate>
		<dc:creator>Elf Sternberg</dc:creator>
				<category><![CDATA[chat]]></category>

		<guid isPermaLink="false">http://www.elfsternberg.com/?p=1424</guid>
		<description><![CDATA[Tag this under The Ghost of Done.&#8221; These are projects that, for one reason or another, will never get done because my skillset has drifted so far away from the skills necessary to produce them that I&#8217;m unlikely to have the time, bandwidth, or skillset necessary to go back and revisit them. TOXIC &#8220;Terabytes Of [...]]]></description>
				<content:encoded><![CDATA[<p>Tag this under <a href="http://www.brepettis.com/blog/2009/3/3/the-cult-of-done-manifesto.html">The Ghost of Done</a>.&#8221; These are projects that, for one reason or another, will never get done because my skillset has drifted so far away from the skills necessary to produce them that I&#8217;m unlikely to have the time, bandwidth, or skillset necessary to go back and revisit them.</p>
<h3>TOXIC</h3>
<p>&#8220;Terabytes Of XML, Indexed &amp; Compressed.&#8221; I almost got hired for this idea alone, once upon a time, but sadly I was working already and had no desire to move to Boston. TOXIC is a simple idea: Very large stores of text, like those used for publishing corporations, libraries, governments and the like, live in gigantic SGML and XML databases.  There exists <a href="http://ww2.cs.mu.oz.au/~alistair/mg/">a known set of tools</a> for indexing large corpora of texts, and the most common is to use Huffman compression on both an index of terms and their location in the library.  The idea is that pulling the index off-disk and decompressing it, caching the result as needed, is faster than pulling an already uncompressed index off disk.  Likewise, because Huffman encoding can be resumed at arbitrary points in a compressed library, it&#8217;s faster to seek to a known compression point, uncompressing the block there, and seeking for the indexed term.  What I wanted was to sandwich a second index between the first and the corpus, with an equally compressed representation of the XPaths to desired sections of the corpora.  It&#8217;s such an obvious schtick that I&#8217;m surprised no one has done it for an open-source solution.</p>
<h3>A C-Robots/Ogre-GEV world-conquering mashup.</h3>
<p>I was a big <a href="http://www.sjgames.com/ogre/">Ogre-GEV</a> fan way back in the day, and I enjoyed the Rivets parody as well.  What I wanted was a web-capable version of the game that could be played multi-player, but could also be run automatically, <a href="http://en.wikipedia.org/wiki/Crobots">C-Robots</a> style, with the programming language being something sensible and accessible, like Python or Javascript.  Sadly, when I concocted this cockamamie plan, it was only the late 90&#8242;s, and nobody had the bandwidth or browser capabilities to make it happen.  Since then, the browser has the power, but I don&#8217;t.</p>
<h3>A painfully cute first-person shooter</h3>
<p> One of my stories involves a sentient doll, only 30cms tall, who starts out as the companion to a little girl on a planet where the infrastructure experiences a massive and complete systemic breakdown.  The doll, the last artificial intelligence on the planet, has to fight her way through bugs, mice, rats and nastier things in her goal of saving her person and restarting her planet&#8217;s infrastructure.  I wanted to write a first-person shooter on the same theme, but lacked the skills or time at the time.  I wanted her to have the squeaky voice of an anime character, to be dressed in pink and frills, and to show how that costume became more and more ragged and dirty, as she grew into her role.  There&#8217;s a similar PS/2 game, although it&#8217;s third-person and poorly done, <i>Hoi-Hoi San</i>, and it was never released in the US.  I wanted something <i>good</i>.</p>
<h3>A Seam-Carving tool for PAM</h3>
<p>  <a href="http://en.wikipedia.org/wiki/Seam_carving">Seam-Carving</a>, also known as &#8220;content-aware resizing,&#8221; is the algorithmic reduction of an image by creating maps, either horizontal or vertical, describing paths from top-to-bottom or left-to-right, in which, for each path, the neighboring pixel differential is added up into an overlain entropy map, and then the path with the lowest entropy is removed.  This is done systematically until the image has been cropped down to the desired size, with the pixels that represent the least amount of information loss being removed and the seam smoothed over.  While it&#8217;s not perfect, as you can see in the wikipedia example, it might be useful in a number of settings.  I still use the <a href="http://netpbm.sourceforge.net/">Portable Anymap</a> programs on a regular basis, and adding seam-carving to the manipulations would be a useful addition. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.elfsternberg.com/2013/03/05/projects-finish/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
