Ruby spreadsheet hack for whyday

Like many others, my introduction to ruby was Why’s (poignant) guide to ruby. Why’s attitude towards programming emphasized fun, learning, and creativity above generally regarded “best practices”.

On august 19th, the Ruby community throws caution and best practices to the wind to celebrate Whyday.

I hoped to capture this programming spirit with this ~30 line Spreadsheet class which can have procs as cells. This is adapted from an idea I’ve seen a couple times in python. It supports access through array both array access (ss['a0']) and methods (ss.a0), ranges (ss.a[0..10]) are also supported.

class Spreadsheet
  def initialize
    @hash = Hash.new(0)
  end
  class Subset < Struct.new(:spreadsheet, :row, :col)
    def [] k
      return k.map{|x| self[x]} if k.respond_to? :map
      spreadsheet[cell k]
    end
    def []= k, v
      return k.map{|x| self[x] = v} if k.respond_to? :map
      spreadsheet[cell k] = v
    end
    def cell k; "#{self.col || k}#{self.row || k}"; end
  end
  def method_missing cell, *args
    if cell =~ /\A([a-z]+)([0-9]+)=\Z/
      @hash[[$1, $2]] = args.first
    elsif cell =~ /\A([a-z]+)([0-9]+)\Z/
      v = @hash[[$1, $2]]
      v.is_a?(Proc) ? self.instance_eval(&v) : v
    elsif cell =~ /\A([a-z]+)\Z/
      Subset.new(self, nil, $1)
    elsif cell =~ /\A_?([0-9]+)\Z/
      Subset.new(self, $1, nil)
    else
      super
    end
  end
  def []= key, value; method_missing("#{key}=", value); end
  def []  key;        method_missing(key.to_s); end
  def to_s; @hash.to_s; end
end

ss = Spreadsheet.new

ss.a1 = Proc.new { a2 + a3 }
ss['a'][2..10] = 50   # assign a2 through a10
puts ss.a1
# => 100
ss['a']['1'] = Proc.new { a[2..20].inject(:+) }
puts ss['a1']
# => 450
ss.b1 = 'hello, world'
ss.d[1..20] = 'chunky bacon'
puts ss['1']['a'..'e'].inspect  # print a1 through e1
# => [450, "hello, world", 0, "this fills row d", 0]

Hope you enjoyed.