Sunday February 07, 2010

Toto, A Minimalist Blogging Engine

Since I’ve got minimalist blogging engines on the brain lately, in addition to Whisper and Jekyll, I thought I also ought to call attention to Toto.

Toto is dynamic, rather than a static site generator, but it’s very straightforward and simple. It works very nicely with Heroku (which is pretty awesome, by the way).

CouchDB Backend for Hx

Just for grins I had a go at implementing a CouchDB backend for Hx today. It’s pretty straightforward; the document id in couch becomes the entry path in Hx, and the actual entry Hash is taken directly from the document JSON. Configuration of a CouchDB source is also quite simple:

filter: Hx::Backend::CouchDB
options:
  couchdb_server: http://localhost:5984/
  couchdb_database: entries

The filter options could actually be omitted entirely in this case, since the example represents the defaults if the server and database options aren’t given explicitly.

Here’s a stripped-down version of the CouchDB backend to demonstrate what an Hx backend looks like:

class Hx::Backend::CouchDB
  include Hx::Source

  def initialize(source, options)
    @couchdb_server = options[:couchdb_server] ||
                      "http://localhost:5984"
    @couchdb_database = options[:couchdb_database] ||
                        "entries"
  end

  def edit_entry(path, prototype=nil)
    begin
      text = get_document(path)
    rescue HTTPError => e
      raise e unless e.code == 404
      raise Hx::NoSuchEntryError, path unless prototype
      text = prototype.to_json
    end
    text = yield text
    entry = JSON.parse(text)
    entry['updated'] = Time.now.xmlschema
    entry['created'] ||= entry['updated']
    put_document(path, entry.to_json)
    self
  end

  def each_entry
    listing = JSON.parse(get_document('_all_docs'))
    for row in listing['rows']
      path = row['id']
      entry = JSON.parse(get_document(path))
      for field in %(created updated)
        entry[field] = Time.parse(entry[field] || "")
      end
      yield path, entry
    end
    self
  end

  def get_document(id)
    # ...
  end

  def put_document(id, body)
    # ...
  end
end

Some error handling has been removed, and I’ve elided the definitions of get_document and put_document for the sake of space. The main points of interest are:

  • CouchDB.new, which takes an upstream source (ignored by most backend sources) and an options hash
  • CouchDB#edit_entry, which passes the raw (backend-dependent) entry text to its block, and replaces the entry in the database with the returned result, updating the 'updated' and 'created' fields.
  • CouchDB#each_entry, which iterates through all the entries provided by this backend instance, yielding path, entry pairs. In Hx, entries are just Ruby hashes, nothing more.

Those three methods are really all any Hx backend needs to provide.

Tuesday February 02, 2010

The Delectable Giant Tortoise

The Giant Tortoise went three hundred years without getting a Latin classification — perhaps in large part because it was so delicious that researchers kept eating their specimens.

QI: Giant Tortoise

I feel strangely hungry now.

Life Below 600px

In Life Below 600px, Paddy Donnelly suggests that we can benefit by embracing scrolling in web design. In particular, presenting content incrementally (rather than all at once in the initial screenfull) can afford better opportunities to build reader interest.

Actually, even back in 1997, Jakob Nielsen remarked that scrolling was much less of an obstacle for users than it had once been:

The change from 1994 is that scrolling is no longer a usability disaster for navigation pages. Scrolling still reduces usability, but all design involves trade-offs, and the argument against scrolling is no longer as strong as it used to be. Thus, pages that can be markedly improved with a scrolling design may be made as long as necessary, though it should be a rare exception to go beyond three screenfulls on an average monitor.

(Also, client-side imagemaps are okay now. In case you were anxious about that.)

I think it’s probably fair to say that the situation for scrolling has also improved further since then, particularly now that support for scroll wheels on mice is so widespread in browsers.

Of course, there are some caveats. When we talk about scrolling in this context, we really mean vertical scrolling. It is what users are accustomed to, and it is what input devices like mice generally support.

You also still have to make sure that the first screenfull your readers see is enough to capture reader interest. Lastly, if your content is primarily visual (rather than textual), scrolling can be a serious inconvenience. But more on that in another post.

Sunday January 31, 2010

Webcomic Navigation and Layout

I’ve been doing a great deal of thinking about navigation and layout for webcomics lately. I think I will have some concrete thoughts of my own to offer in a little while, but for now, I’d just like to put up some links that I have found particularly helpful or at least thought-provoking on the topic.

My preliminary thoughts:

  1. Readers hate scrolling; the comic should fit the browser window.
  2. If you must make the reader scroll, limit scrolling to one axis.
  3. Of the two axes, scrolling vertically is better since we have convenient UI like the mouse wheel for doing it.
  4. If the browser window is too small, downscaling can help, but good-quality image resampling is still somewhat hard to come by in the browser.
  5. Even with good-quality resampling, downscaling is still useless below a certain size, since important features will become illegible.
  6. When downscaling isn’t an option, it is probably best to chop the page into smaller units which can be shown individually when the browser window is too small to show them all at once.

Unfortunately there seems to be a big gap between these points and actual webcomic practice.

In a future post I think I’ll have more to say about why scrolling is really bad for comics, at least ones which weren’t carefully designed specifically for an “infinite canvas” presentation. (And even then…)

Now Powered by Hx

I looked at a number of different software packages to replace Hobix, but none of them really appealed. The best two candidates were Jekyll and Whisper, but Jekyll is a little too heavily focused on blogging specifically (a problem with Hobix too, though it wasn’t quite as bad), and Whisper isn’t a static site generator.

So, in classic NIH tradition, I eventually wrote my own static site generator, Hx. It’s pretty raw and not especially fast (though it does the job faster than Hobix), but it’s pretty straightforward and flexible. It has no particular baked-in knowledge of what a “blog” is: it’s just a graph of generic filters which enumerate pairs of paths and documents. Throw in liquid templates, and it’s enough to do everything I need for now.

For the curious, here’s what the hx-config.yaml for this site presently looks like:

---
require:
  - site-hacks
options:
  base_url: http://moonbase.rydia.net/
  template_dir: templates
  output_dir: site
  lib_dir: lib
  links:
    - url: http://twitter.com/mentalguy
      name: "@mentalguy"
    - url: http://inkscape.org/
      name: "Inkscape"
    - url: http://lastbus.rydia.net/
      name: "The Last Bus"
    - url: http://rydia.net/
      name: "rydia.net"
  default_author: mental
sources:
  entries:
    filter: Hx::Backend::Hobix
    options:
      entry_dir: entries
    only: mental/blog/**
    sort_by: created
    reverse: true
    cache: true
  blog: # alias for editing
    source: entries
    strip_prefix: mental/blog/
  indexes:
    source: entries
    filter: Hx::Listing::RecursiveIndex
    cache: true
  front_page:
    - source: indexes
      only: index
    - filter: Hx::Listing::Paginate
      options:
        page_size: 10
  sections:
    - source: indexes
      except: index
    - filter: Hx::Listing::Paginate
      options:
        page_size: 40
  feeds:
    source: entries
    filter: Hx::Listing::RecursiveIndex
    options:
      limit: 10
    only:
      - index
      - mental/index
      - mental/blog/index
outputs:
  - source: entries
    filter: Hx::Output::LiquidTemplate
    options:
      extension: html
      template: blog-entry.liquid
  - source: front_page
    filter: Hx::Output::LiquidTemplate
    options:
      extension: html
      template: front-page.liquid
  - source: sections
    filter: Hx::Output::LiquidTemplate
    options:
      extension: html
      template: section-index.liquid
  - source: feeds
    filter: Hx::Output::LiquidTemplate
    options:
      extension: atom
      template: atom-feed.liquid

I won’t be doing any serious public releases of Hx for a while since I’m still smoothing out a lot of rough areas and there are sure to be incompatible changes in the process, but you’re welcome to poke around on your own.

Saturday January 30, 2010

Worm Grunting

Worm Grunting is the art of luring earthworms to the surface, typically using a stake in the ground to create vibrations which simulate the digging of their natural predators.

Blogging Again!

After a long series of draining events, I’m finally getting back on my feet enough to resume blogging. The blog itself has required reworking, since Hobix master has been terminally broken for a while and I needed to find a replacement. I ended up writing my own static site generator, Hx. It is a bog simple piece of software, but it is pretty flexible and so far it is serving me well.

Tuesday April 14, 2009

Media Appearance: Linux Link Tech Show

Just a quick note that I’ll be making an appearance on tomorrow night’s Linux Link Tech Show at 8:30PM Eastern Time.

Monday March 23, 2009

Fastthread Still Considered Useful

So I’ve been getting mail to the effect that Ruby 1.8’s concurrency primitives are still all screwed up even as recently as 1.8.6p111, and that fastthread is consequently required to get things to work reliably.

I find that really frustrating, as I was hoping that once fastthread was merged with 1.8 as the new thread.rb, I could get out of the fastthread business altogether and rely on the 1.8 maintainers to deal with it, but unfortunately things don’t seem to have worked out that way.

I think what I may do this point is re-enable fastthread for all 1.8 versions, and hope that Ruby 1.8, as a Ruby implementation, goes away sooner rather than later.