atom-tools’ *NIX tools

atom-tools’ bin directory has several UNIXy tools that I’ve never really mentioned before.

These tools operate on “collections”. I’m using the term in a broader sense than RFC 5023; a “collection” can be an AtomPub Collection, a directory containing Atom Entries, or a feed on stdin or stdout.

atom-cp

atom-cp copies the contents of a source collection to a destination collection.

atom-grep

atom-grep prints a feed to stdout containing all the entries in the source feed that match a given regexp.

atom-purge

atom-purge DELETEs every entry in the given collection.

atom-post

atom-post POSTs the contents of a file or stdin to a given URL.

It doesn’t do anything fancy, it’s just a convenient way of getting media or an Entry created by some other tool onto the web. Eventually this will construct Entries too, but not yet.

Use Cases

Back up your blog:

atom-cp http://example.org/coll ./backup/

Restore a backup, or post several pre-created entries:

atom-cp ./backup/ http://example.org/coll

Delete spam:

atom-grep poker http://example.org/comments | atom-purge -

Plagiarise somebody else’s blog:

atom-grep "popular content" http://example.org/coll | atom-cp - http://example.com/seo

Post a picture to your media collection:

atom-post -m image/png http://example.org/media icon.png

(Disclaimer: XML is a terrible format to pass down a pipe. It’s awfully convenient though, and my pipes and my collections haven’t been long enough for it to be a problem.)

Too many Ruby Atom libraries

Ruby now has 3 Atom Publishing Protocol libraries: atom-tools, atomutil and ratom. They all use the Atom namespace, and they’re all incompatible. Not a great situation.

ratom uses libxml-ruby rather than REXML. It’s embarassingly faster than atom-tools. ratom can parse the 1100 Atom Entries in my Venus cache in 0.25s; atom-tools takes 6s.

I haven’t been able to get atomutil to work. I expect it is faster than atom-tools, too (though not nearly so dramatically); atom-tools parses an XML tree into a tree of Ruby objects (instead of just wrapping the XML tree). This may have been a mistake.

atom-tools 2.0

Published atom-tools 2.0 a few days ago.

The XML parsing and building has been completely reworked. This should make handling extensions much easier.

A big feature for people writing clients is HTTP caching support (ported from Joe Gregorio’s httplib2). I actually commited this to the darcs repository the same day I published 1.0 but never released a 1.1 version (oops!).

It’s got some new UNIX-y tools that I’ll write about later.

I’ve gotten rid of the YAML mapping entirely; it wasn’t as human-read/writeable as I had hoped. I think there’s promise in doing something with Maruku for that.

I’m slowly moving from Test::Unit to rspec (thanks to a lot of grunt work by Simon Rozet). I think the result is a lot cleaner.

I’ve tried to keep things as backwards-compatible as possible, but some things have changed since the 1.0 release. The main difference is that Atom::Collection now represents an app:collection element, instead of just being a fancy Atom::Feed.

Celebrating the New Year with Shoes

A bit late for most of the world, but I’ll post it anyways.

Finished cleaning the apartment and waiting for guests to arrive, I thought I’d play around a bit with Shoes. Turns out it’s a lot of fun!

require "time"

midnights = [ [ 5, 'Rio de Janeiro' ],
              [ 4, 'Santiago' ],
              [ 3.5, "St. John's" ],
              [ 3, 'Halifax' ],
              [ 2, 'Montréal' ],
              [ 1, 'Winnipeg' ],
              [ 0, 'Edmonton' ],
              [ -1, 'Vancouver' ],
              [ -2, 'Anchorage' ],
              [ -3, 'Honolulu' ],
              [ -4, 'Auckland' ]  ]

new_year = Time.parse "2008-01-01"

Shoes.app do
  background rgb(0,0,0)

  stack do
    seconds_left = (new_year - Time.now).to_i

    midnight_ps = midnights.map do |offset,place|
      para "#{place}: #{seconds_left - offset * 3600}", :stroke => white
    end

    animate(1) do |i|
      seconds_left = (new_year - Time.now).to_i

      midnight_ps.each_with_index do |p,i|
        offset, place = midnights[i]
        s = seconds_left - offset * 3600

        stroke = s > 0 ? white : gray(90)

        if s.abs < 500
          size = 200
        else
          size = 100_000 / s.abs
        end

        p.replace "#{place}: #{s}", :font => "Arial #{size}px", :stroke => stroke
      end
    end
  end
end

New Ruby Atom Library

A new Ruby Atom format/Publishing Protocol library, atomutil has just been released. I haven’t had a chance to look at it deeply yet (thanks to final exams), but it’s well documented, and it looks like it has good support for extensions elements (much better than atom-tools has at the moment).

Stupid Atom Tricks: XMPP Bot

Continuing my habit of writing x-to-Atom gateways, here’s a little Jabber bot that will post messages you send it to an Atom Publishing Protocol Collection.

It requires atom-tools and the xmpp4r library.

require "atom/collection"
require "xmpp4r"

$coll_user = "bct"
$coll_pass = "atom-password"
$coll_url = "http://necronomicorp.com/testatom?app"

$bot_jid = "test@atompub.necronomicorp.com"
$bot_pass = "xmpp-password"

http = Atom::HTTP.new
http.user = $coll_user
http.pass = $coll_pass

coll = Atom::Collection.new $coll_url, http

cl = Jabber::Client.new($bot_jid)
cl.connect
cl.auth($bot_pass)

cl.add_message_callback do |msg|
  e = Atom::Entry.new  
  e.content = msg.body

  res = coll.post! e

  if res.code == "201"
    cl.send(Jabber::Message.new(msg.from, "success!"))
  else
    cl.send(Jabber::Message.new(msg.from, "#{res.code} #{res.message}"))
  end
end

Thread.stop

Try it out; send a message to test@atompub.necronomicorp.com. It should appear in my test collection.

(The real version is a server component which handles several collections on different JIDs.)

atom-tools 0.9.0 on its way

The version number’s a bit of a jump, but only because I’d like to hit 1.0 at about the same time the Publishing Protocol makes it to RFC.

No real changelog I’m afraid, but here’s the big stuff:

One of these days I’m going to have to set up Trac.

data: URIs in Ruby

Anne van Kesteren’s got a cute little demonstration of <canvas/>. I particularly like the idea of saving something to a data: URI; one of those cool little things about the Web that doesn’t get much attention.

I also like favicons, and not having to use a general-purpose image manipulation program to create them. Hence, a teeny-tiny Camping mount which will convert an image/png data: URI into a favicon (example). At its heart is a class for handling data: URIs that goes something like this:

module URI
  class Data < Generic
    attr_reader :mime_type, :data

    def read; data; end

    def initialize(scheme, userinfo, host, port, 
                   registry, path, opaque, query, fragment)
      super

      notdata, data = opaque.split(",", 2)
      match = URI.decode(notdata).match(/^(.*?)(;base64)?$/)

      @mime_type = match[1]
      if @mime_type.empty?
        @mime_type = "text/plain;charset=US-ASCII" 
      elsif @mime_type[0] == ?;
        @mime_type = "text/plain#{@mime_type}"
      end

      @data = unless match[2].nil?
        Base64.decode64(data)
      else
        URI.decode(data)
      end
    end 
  end
  @@schemes["DATA"] = Data
end

It’s not a complete implementation of RFC 2397; some legitimate URIs (eg. data:text/plain;charset=iso-8859-7,%be%fg%be) can’t be parsed without fiddling around with uri.rb’s internals.

(BTW, the favicon is much shrunken from Paintr’s canvas; you’ll have to go over your lines several times to get something you can see.)