# frozen_string_literal: true module Helium class Console class KeyValue def row(key, value, format_key: {}, format_value: {}) rows << [key, value, { format_key: format_key, format_value: format_value }] end def rows @rows ||= [] end def as_table(console:, max_width:, level:, left: '|', between: ' ', right: '', format_keys: {}, format_values: {}) TableFormatter.( self, console: console, left: left, between: between, right: right, key_options: format_keys, value_options: format_values, max_width: max_width, level: level ) end class TableFormatter include ColorizedString::Helpers def self.call(*args, **options) new(*args, **options).() end def initialize( key_value, console:, level:, left:, between:, right:, max_width:, key_options: {}, value_options: {} ) @key_value = key_value @console = console @left = left @between = between @right = right @max_width = max_width @key_options = key_options @level = level @value_options = value_options set_width_limits end def call Formatter::LazyStringEvaluator.new do |y| key_value.rows.each do |key, value, options| format_entry(y, key, value, **options) end end end private attr_reader :key_value, :console, :left, :between, :right, :key_options, :value_options, :max_width def format_entry(yielder, key, value, format_key:, format_value:) key = format(key, merge_options(key_options, format_key)) value = format(value, merge_options(value_options, format_value)) zip_lines(key, value) do |key_line, value_line, index| yielder << [ left, rjust(key_line, key_width), index.zero? ? between : ' ' * length_of(between), value_line, right ].join end end def key_width @key_width ||= key_value.rows.first(100).map do |key, _, format_key:, **| length_of(format(key, merge_options(key_options, format_key))) end.max end def format(object, options = {}) return object unless options console.format(object, options.delete(:style), level: @level + 1, **options) end def merge_options(opt1, opt2) return false unless opt1 && opt2 opt1.merge(opt2) end def set_default(hash, key, &block) hash[key] = block.() if hash && !hash[key] end def zip_lines(str1, str2) str1 = (str1.lines.each + [nil].cycle).lazy str2 = (str2.lines.each + [nil].cycle).lazy str1.zip(str2).with_index.each do |(line1, line2), index| break unless line1 || line2 yield [line1 || '', line2 || '', index] end end def set_width_limits set_default(key_options, :max_width) { max_width / 3 } set_default(value_options, :max_width) do max_width - key_width - length_of(left) - length_of(between) - length_of(right) end end def rjust(string, length) ' ' * (length - length_of(string)) + string end end end end end