Class: Soroban::Sheet

Inherits:
Object
  • Object
show all
Defined in:
lib/soroban/sheet.rb

Overview

A container for cells.

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (Sheet) initialize(logger = nil)

Creates a new sheet.



15
16
17
18
19
20
21
# File 'lib/soroban/sheet.rb', line 15

def initialize(logger=nil)
  @logger = logger
  @cells = {}
  @compiled = {}
  @changes = Hash.new{ |h, k| h[k] = Set.new }
  @bindings = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

- (Object) method_missing(method, *args, &block)

Used for calling dynamically defined functions, and for creating new cells (via `label=`).



76
77
78
79
80
81
82
83
# File 'lib/soroban/sheet.rb', line 76

def method_missing(method, *args, &block)
  if match = /^func_(.*)$/i.match(method.to_s)
    return Soroban::call(self, match[1], *args)
  elsif match = /^([a-z][\w]*)=$/i.match(method.to_s)
    return _add(match[1], args[0])
  end
  super
end

Instance Attribute Details

- (Object) bindings (readonly)

Returns the value of attribute bindings



12
13
14
# File 'lib/soroban/sheet.rb', line 12

def bindings
  @bindings
end

Instance Method Details

- (Object) bind(options_hash)

Bind one or more named variables to a cell.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/soroban/sheet.rb', line 121

def bind(options_hash)
  options_hash.each do |name, label_or_range|
    _debug("binding '#{name}' to '#{label_or_range}'}")
    if Soroban::range?(label_or_range)
      LabelWalker.new(label_or_range).each do |label|
        next if @cells.keys.include?(label.to_sym)
        raise Soroban::UndefinedError, "Cannot bind '#{name}' to range '#{label_or_range}'; cell #{label} is not defined"
      end
      _bind_range(name, label_or_range)
    else
      unless @cells.keys.include?(label_or_range.to_sym)
        raise Soroban::UndefinedError, "Cannot bind '#{name}' to non-existent cell '#{label_or_range}'"
      end
      _bind(name, label_or_range)
    end
  end
end

- (Object) cells

Return a hash of `label => contents` for each cell in the sheet.



145
146
147
148
149
# File 'lib/soroban/sheet.rb', line 145

def cells
  labels = @cells.keys.map { |label| label.to_sym }
  contents = labels.map { |label| eval("@#{label}.excel") }
  Hash[labels.zip(contents)]
end

- (Object) factory(name)



23
24
25
26
# File 'lib/soroban/sheet.rb', line 23

def factory(name)
  eval(self.to_ruby(name), TOPLEVEL_BINDING)
  Object::const_get('Soroban').const_get('Model').const_get(name).new
end

- (Object) get(label_or_name)

Retrieve the contents of a cell.



110
111
112
113
114
115
116
117
118
# File 'lib/soroban/sheet.rb', line 110

def get(label_or_name)
  label = @bindings[label_or_name.to_sym] || label_or_name
  _debug("retrieving '#{label_or_name}' from '#{label}'}")
  if Soroban::range?(label)
    walk(label)
  else
    _get(label_or_name, eval("@#{label}", binding))
  end
end

- (Object) missing

Return a list of referenced but undefined cells.



152
153
154
# File 'lib/soroban/sheet.rb', line 152

def missing
  @cells.values.flatten.uniq - @cells.keys
end

- (Object) set(options_hash)

Set the contents of one or more cells or ranges.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/soroban/sheet.rb', line 86

def set(options_hash)
  options_hash.each do |label_or_range, contents|
    _debug("setting '#{label_or_range}' to '#{contents}'")
    unless range = Soroban::getRange(label_or_range)
      _add(label_or_range, contents)
      next
    end
    fc, fr, tc, tr = range
    if fc == tc || fr == tr
      raise ArgumentError, "Expecting an array when setting #{label_or_range}" unless contents.kind_of? Array
      cc, cr = fc, fr
      contents.each do |item|
        set("#{cc}#{cr}" => item)
        cc.next! if fr == tr
        cr.next! if fc == tc
      end
      raise Soroban::RangeError, "Supplied array doesn't match range length" if cc != tc && cr != tr
    else
      raise ArgumentError, "Can only set cells or 1-dimensional ranges of cells"
    end
  end
end

- (Object) to_ruby(class_name)

Return a string containing a ruby class that implements the sheet. You can call eval() on this string to create the class, which you can then instantiate. Set inputs on the instance and read outputs off.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/soroban/sheet.rb', line 31

def to_ruby(class_name)
  data = []
  data << "module Soroban"
  data << "module Model"
  data << "class #{class_name}"
  data << "  def initialize"
  data << "    @binds = {"
  data << bindings.map do |name, cell|
    "      '#{name}' => :#{cell}"
  end.join(",\n")
  data << "    }"
  data << "    @cache = {}"
  data << "    @cells = {"
  data << @compiled.map do |label, cell|
    "      :#{label} => lambda { @cache[:#{label}] ||= #{cell.to_compiled_ruby} }"
  end.join(",\n")
  data << "    }"
  data << "  end"
  data << "  def clear"
  data << "    @cache.clear"
  data << "  end"
  data << "  def get(name)"
  data << "    @cells[@binds[name]].call"
  data << "  end"
  data << "  def set(name, value)"
  data << "    self.clear"
  data << "    @cells[@binds[name]] = lambda { @cache[@binds[name]] ||= value }"
  data << "  end"
  bindings.each do |name, cell|
    data << "  def #{name}"
    data << "    get('#{name}')"
    data << "  end"
    data << "  def #{name}=(value)"
    data << "    set('#{name}', value)"
    data << "  end"
  end
  data << "end"
  data << "end"
  data << "end"
  puts data.join("\n")
  data.join("\n")
end

- (Object) walk(range)

Visit each cell in the supplied range, yielding its value.



140
141
142
# File 'lib/soroban/sheet.rb', line 140

def walk(range)
  ValueWalker.new(range, binding)
end