lib/tty/table/row.rb
# frozen_string_literal: true
require "forwardable"
require_relative "error"
require_relative "field"
module TTY
class Table
# Convert an Array row into Row
#
# @return [TTY::Table::Row]
#
# @api private
def to_row(row, header = nil)
Row.new(row, header)
end
# A class that represents a row in a table.
#
# Used internally by {Table} to store row represenation by converting
# {Array} into {Row} instance.
#
# @api private
class Row
include Enumerable
extend Forwardable
# The row attributes that describe each element
#
# @return [Array]
#
# @api private
attr_reader :attributes
# The row data
#
# @return [Hash]
#
# @api private
attr_reader :data
# The row fields
#
# @api public
attr_reader :fields
def_delegators :to_ary, :join
# Initialize a Row
#
# @example
# row = new TTY::Table::Row.new [1,2,3]
# row[1] # => 2
#
# row = new TTY::Table::Row.new [1,2,3], %w[a b c]
# row[0] # => 1
# row["a"] # => 1
#
# row = new TTY::Table::Row.new {"a": 1, "b": 2, "c": 3}
# row[0] # => 1
# row["a"] # => 1
#
# @param [#to_ary] data
# the row data
#
# @return [undefined]
#
# @api public
def initialize(data, header = nil)
case data
when Array
@attributes = (header || (0...data.length)).to_a
@fields = coerce_to_fields(data)
when Hash
@data = data.dup
@fields = coerce_to_fields(@data.values)
@attributes = (header || data.keys).to_a
end
@data = Hash[@attributes.zip(fields)]
end
# Coerces values to field instances
#
# @param [Array[Object]] values
#
# @return [Array[TTY::Table::Field]]
#
# @api public
def coerce_to_fields(values)
values.reduce([]) { |acc, el| acc << to_field(el) }
end
# Instantiates a new field
#
# @api public
def to_field(options = nil)
Field.new(options)
end
# Lookup a value in the row given an attribute allowing for Array or
# Hash like indexing
#
# @example
# row[1]
# row[:id]
# row.call(:id)
#
# @api public
def [](attribute)
case attribute
when Integer
data[attributes[attribute]].value
else
data.fetch(attribute) do |name|
raise UnknownAttributeError, "the attribute #{name} is unkown"
end.value
end
end
# Lookup attribute without evaluation
#
# @api public
def call(attribute)
data[attributes[attribute]]
end
# Set value at index
#
# @example
# row[attribute] = value
#
# @api public
def []=(attribute, value)
case attribute
when Integer
data[attributes[attribute]] = to_field(value)
else
data[attribute] = to_field(value)
attributes << attribute unless attributes.include?(attribute)
end
end
# Iterate over each element in the Row
#
# @example
# vec = Row.new [1,2,3], ["a","b","c"]
# vec.each { |element| ... }
#
# @return [self]
#
# @api public
def each
return to_enum unless block_given?
to_ary.each { |element| yield element }
self
end
# Number of data items in a row
#
# @return [Integer]
#
# @api public
def size
data.size
end
alias :length :size
# Check if there are no elements
#
# @return [Boolean]
#
# @api public
def empty?
to_ary.empty?
end
# Find maximum row height
#
# @return [Integer]
#
# @api public
def height
fields.map(&:height).max
end
# Convert the Row into Array
#
# @example
# array = row.to_ary
#
# @return [Array]
#
# @api public
def to_ary
to_hash.values_at(*attributes)
end
# Return the Row elements in an array.
#
# @return [Array]
#
# @api public
def to_a
to_ary.dup
end
# Convert the Row into hash
#
# @return [Hash]
#
# @api public
def to_hash
hash = data.dup
hash.update(hash) { |_, val| val.value if val }
end
# Check if this row is equivalent to another row
#
# @return [Boolean]
#
# @api public
def ==(other)
to_a == other.to_a
end
alias :eql? :==
# Provide a unique hash value. If a row contains the same data as another
# row, they will hash to the same value.
#
# @api public
def hash
to_a.hash
end
# Map field values
#
# @api public
def map!(&block)
data.values_at(*attributes).each do |field|
field.value = block.call(field)
end
end
# String representation of a row with its fields
#
# @api public
def inspect
"#<#{self.class.name} fields=#{to_a}>"
end
end # Row
end # Table
end # TTY