Class: Cellar

Inherits:
Object show all
Defined in:
lib/cellar.rb

Constant Summary collapse

VERSION =
"0.2.1"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(obj = nil, header: true, strict: true, index: nil) ⇒ Cellar

Returns a new instance of Cellar.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/cellar.rb', line 37

def initialize(obj=nil, header: true, strict: true, index: nil)
  @fields = []
  @values = []
  @finder = {}
  @seeker = {}
  @index  = index
  @widest = 0

  if obj.is_a?(Array)
    if obj.first.is_a?(Array)
      self.fields = obj.shift if header
      @rows = obj unless obj.empty?
    elsif !obj.empty?
      header ? (self.fields = obj) : (@rows = obj)
    end
  end

  @strict = strict.nil? ? !@fields.empty? : !!strict
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(field, *args) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/cellar.rb', line 168

def method_missing(field, *args)
  field = field.to_s
  equal = field.chomp!("=")
  index = index(field)
  if equal
    index ||= add_field(field)
    value = @values[index] = args.first
  elsif index
    raise "variable lookup ignores arguments" unless args.empty?
    value = @values[index]
  else
    value = ""
  end
  value
end

Instance Attribute Details

#fieldsObject

Returns the value of attribute fields.



33
34
35
# File 'lib/cellar.rb', line 33

def fields
  @fields
end

#strictObject

Returns the value of attribute strict.



35
36
37
# File 'lib/cellar.rb', line 35

def strict
  @strict
end

#valuesObject

Returns the value of attribute values.



34
35
36
# File 'lib/cellar.rb', line 34

def values
  @values
end

Instance Method Details

#<<(data) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/cellar.rb', line 211

def <<(data)
  @rows ||= []
  @row = row = @rows.size
  if self.class === data.class
    @rows << @values = []
    self[*data.fields] = data.values
  elsif data.is_a?(Array) && !data.first.is_a?(Array)
    @rows << (@values = data)
  else
    raise "unable to << your object"
  end
  if @index
    block = @index if @index.is_a?(Proc)
    field = @index unless block
    index = index(field) or raise "unknown index #{field.inspect}" if field
    if key = block ? block.call(self) : @values[index]
      @seeker[key] and raise "duplicate index: #{key.inspect}"
      @seeker[key] = row
    end
  end
  self
end

#[](*fields) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/cellar.rb', line 113

def [](*fields)
  case fields.size
  when 0
    []
  when 1
    first = fields.first
    first.is_a?(Cellar) and return self[*first.fields]
    index = index(fields.first)
    value = @values[index] if index
  else
    fields.inject([]) do |values, field|
      index = index(field)
      value = case index
        when nil   then nil
        when Array then @values.values_at(*index)
        else            @values[index]
      end
      Array === value ? values.concat(value) : values.push(value)
    end
  end
end

#[]=(*fields) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/cellar.rb', line 135

def []=(*fields)
  values = Array(fields.pop).dup
  fields = fields.map {|field| Array(index(field) || add_field(field))}.flatten

  if fields.empty?
    @values.replace(values)
  elsif values.size > fields.size
    raise "unable to assign #{values.size} values to #{fields.size} fields for values=#{values.inspect}"
  else
    fields.each_with_index do |field, pos|
      @values[field] = values[pos]
    end
  end

  @values
end

#add_field(field) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/cellar.rb', line 57

def add_field(field)
  field = field.to_s
  index = @fields.size
  @fields << field
  finders = @finder.size
  @finder[field.downcase.gsub(/\W/,'_')] ||= index
  @finder.size == finders + 1 or warn "field clash for #{field.inspect}"
  @finder[field] ||= index
  @widest = field.length if field.length > @widest
  index
end

#cellsObject



197
198
199
# File 'lib/cellar.rb', line 197

def cells
  [@fields.dup] + (@rows || [@values])
end

#clearObject



108
109
110
111
# File 'lib/cellar.rb', line 108

def clear
  @values = []
  self
end

#eachObject



275
276
277
278
# File 'lib/cellar.rb', line 275

def each
  @rows or raise "no rows defined"
  @rows.each_with_index {|values, row| yield(row(row)) }
end

#field(pos) ⇒ Object



164
165
166
# File 'lib/cellar.rb', line 164

def field(pos)
  @fields[pos]
end

#fill!(data, &block) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/cellar.rb', line 234

def fill!(data, &block)
  self.class == data.class or raise "unable to fill with your #{data.class}"
  data.values.each_with_index do |val, idx|
    next if val.blank?
    field = data.fields[idx]
    index = index(field) || add_field(field)
    value = @values[index]
    if block && !value.blank?
      val = block.call(value, val)
      next if val.blank?
    end
    @values[index] = val
  end
  self
end

#from_array(list) ⇒ Object



285
286
287
288
289
# File 'lib/cellar.rb', line 285

def from_array(list)
  clear
  @values = list.map {|v| v.to_s.strip if v }
  self
end

#from_hash(hash) ⇒ Object



291
292
293
294
295
# File 'lib/cellar.rb', line 291

def from_hash(hash)
  clear
  hash.each {|k,v| self[k] = v.to_s if v }
  self
end

#index(field) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/cellar.rb', line 82

def index(field)
  case field
  when String, Symbol
    field = field.to_s
    index = @finder[field] || @finder[field.downcase.gsub(/\W/,'_')]
    raise "no field #{field.inspect}" if !index && @strict
    index
  when Integer
    raise "no field at index #{field}" if field >= @fields.size && @strict
    field < 0 ? field % @fields.size : field
  when Range
    from = field.begin
    till = field.end
    from = from.blank? ?  0 : index(from)
    till = till.blank? ? -1 : index(till)
    case from <=> till
    when  1 then field.exclude_end? ? [*(till+1)..from].reverse : [*till..from].reverse
    when  0 then from
    when -1 then field.exclude_end? ? from...till : from..till
    else "no fields match #{field.inspect}"
    end
  else
    raise "unable to index fields by #{field.class.inspect} [#{field.inspect}]"
  end
end

#index!(field = nil, &block) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/cellar.rb', line 250

def index!(field=nil, &block)
  @rows ||= []
  @index = field || block or raise "index needs a field or a block"
  index = index(field) or raise "unknown index #{field.inspect}" if field && @rows.size > 0
  @seeker.clear
  @rows.each_with_index do |values, row|
    if key = block ? yield(row(row)) : values[index]
      @seeker[key] and raise "duplicate index: #{key.inspect}"
      @seeker[key] = row
    end
  end
  self
end

#mapObject



280
281
282
283
# File 'lib/cellar.rb', line 280

def map
  @rows or raise "no rows defined"
  @rows.map.with_index {|values, row| yield(row(row)) }
end

#rename_field(field, other) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/cellar.rb', line 69

def rename_field(field, other)
  field = field.to_s
  index = index(field) or raise "unable to rename the #{field.inspect} field"
  @finder.delete(field.downcase.gsub(/\W/,'_'))
  @finder.delete(field)
  other = other.to_s
  fields[index] = other
  @finder[other.downcase.gsub(/\W/,'_')] ||= index
  @finder[other] ||= index
  @widest = fields.map(&:size).max
  index
end

#row(row = nil) ⇒ Object



205
206
207
208
209
# File 'lib/cellar.rb', line 205

def row(row=nil)
  @rows or raise "no rows defined"
  @values = row ? @rows[@row = row] : []
  self
end

#row=(row) ⇒ Object



192
193
194
195
# File 'lib/cellar.rb', line 192

def row=(row)
  @rows or raise "no rows defined"
  row(row) # returns self
end

#rowsObject



201
202
203
# File 'lib/cellar.rb', line 201

def rows
  @rows
end

#rows=(rows) ⇒ Object

[ Row handling ]==



186
187
188
189
190
# File 'lib/cellar.rb', line 186

def rows=(rows)
  rows or raise "no rows defined"
  @rows = rows
  row(0) # returns self
end

#seek!(seek) ⇒ Object



264
265
266
267
268
269
270
271
272
273
# File 'lib/cellar.rb', line 264

def seek!(seek)
  return nil if @rows.blank? || @seeker.blank?

  if row = @seeker[seek]
    row(row)
  else
    @values = []
    @row = nil
  end
end

#show!(list = nil, output: :stdout) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/cellar.rb', line 311

def show!(list=nil, output: :stdout)
  tabs = output == :tabs
  meth = list.is_a?(Array) ? list.method(:push) : method(:puts)
  join = tabs ? "\t" : ""
  size = @fields.size
  full = cells
  full.each_with_index do |vals, i| # only when asymmetric
    miss = size - vals.size
    full[i] += [nil] * miss  if miss > 0
    full[i] = vals[0...size] if miss < 0
  end
  lens = full.map {|r| r.map {|c| c.to_s.size}}.transpose.map(&:max)
  pict = lens.map {|len| "%-#{len}.#{len}s" }.join(join)
  pict = [join, pict, join].join.strip
  line = (pict % ([""] * size)).tr("", "•─")
  seen = -1
  meth["", line] unless tabs
  full.each do |vals|
    meth[pict % vals]
    meth[line] if !tabs && ((seen += 1) == 0)
  end
  meth[line, "#{seen} row#{'s' if seen != 1} displayed", ""] unless tabs
  self
end

#show?Boolean

[ Show table ]==

Returns:

  • (Boolean)


307
308
309
# File 'lib/cellar.rb', line 307

def show?
  self
end

#to_hash!Object



297
298
299
300
301
302
303
# File 'lib/cellar.rb', line 297

def to_hash!
  @fields.size.times.inject({}) do |h, i|
    v = @values[i]
    h[@fields[i].downcase.gsub(/\W/,'_')] = v if !v.blank?
    h
  end
end