John Hawthorn

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.