Skip to content

Commit

Permalink
Fixes #27318 - Extract table generator into reusable component (#314)
Browse files Browse the repository at this point in the history
(cherry picked from commit 4b09b72)
  • Loading branch information
ofedoren authored and shiramax committed Dec 31, 2019
1 parent 564e22e commit 3e48734
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 139 deletions.
2 changes: 1 addition & 1 deletion lib/hammer_cli/output.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
require File.join(File.dirname(__FILE__), 'output/output')
require File.join(File.dirname(__FILE__), 'output/fields')
require File.join(File.dirname(__FILE__), 'output/formatters')
require File.join(File.dirname(__FILE__), 'output/generators')
require File.join(File.dirname(__FILE__), 'output/adapter')
require File.join(File.dirname(__FILE__), 'output/definition')
require File.join(File.dirname(__FILE__), 'output/dsl')
require File.join(File.dirname(__FILE__), 'output/record_collection')
require File.join(File.dirname(__FILE__), 'output/field_filter')

81 changes: 6 additions & 75 deletions lib/hammer_cli/output/adapter/table.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
require File.join(File.dirname(__FILE__), 'wrapper_formatter')
require 'hammer_cli/output/utils'

module HammerCLI::Output::Adapter

class Table < Abstract

MAX_COLUMN_WIDTH = 80
MIN_COLUMN_WIDTH = 5

HLINE = '-'
LINE_SEPARATOR = '-|-'
COLUMN_SEPARATOR = ' | '

def features
return %i[rich_text serialized inline] if tags.empty?

Expand All @@ -29,33 +19,15 @@ def print_collection(all_fields, collection)
compact_only: true)
.filtered_fields
formatted_collection = format_values(fields, collection)
# calculate hash of column widths (label -> width)
widths = calculate_widths(fields, formatted_collection)

header_bits = []
hline_bits = []
fields.map do |f|
header_bits << normalize_column(widths[f.label], f.label.upcase)
hline_bits << HLINE * widths[f.label]
end

line = hline_bits.join(LINE_SEPARATOR)
unless @context[:no_headers]
output_stream.puts line
output_stream.puts header_bits.join(COLUMN_SEPARATOR)
output_stream.puts line
columns = fields.each_with_object({}) do |field, result|
result[field.label] = field.parameters
end

formatted_collection.collect do |row|
row_bits = fields.map do |f|
normalize_column(widths[f.label], row[f.label] || "")
end
output_stream.puts row_bits.join(COLUMN_SEPARATOR)
end

# print closing line only when the table isn't empty
# and there is no --no-headers option
output_stream.puts line unless formatted_collection.empty? || @context[:no_headers]
table_gen = HammerCLI::Output::Generators::Table.new(
columns, formatted_collection, no_headers: @context[:no_headers]
)
output_stream.print(table_gen.result)

if collection.respond_to?(:meta) && collection.meta.pagination_set? &&
@context[:verbosity] >= collection.meta.pagination_verbosity &&
Expand All @@ -71,19 +43,6 @@ def classes_filter
super << Fields::ContainerField
end

def normalize_column(width, value)
value = value.to_s
padding = width - HammerCLI::Output::Utils.real_length(value)
if padding >= 0
value += (" " * padding)
else
value, real_length = HammerCLI::Output::Utils.real_truncate(value, width-3)
value += '...'
value += ' ' if real_length < (width - 3)
end
value
end

def format_values(fields, collection)
collection.collect do |d|
fields.inject({}) do |row, f|
Expand All @@ -92,34 +51,6 @@ def format_values(fields, collection)
end
end
end

def calculate_widths(fields, collection)
Hash[fields.map { |f| [f.label, calculate_column_width(f, collection)] }]
end

def calculate_column_width(field, collection)
if field.parameters[:width]
return [field.parameters[:width], MIN_COLUMN_WIDTH].max
end

width = HammerCLI::Output::Utils.real_length(field.label.to_s)
max_width = max_width_for(field)
collection.each do |item|
width = [HammerCLI::Output::Utils.real_length(item[field.label]), width].max
return max_width if width >= max_width
end
width
end

private

def max_width_for(field)
if field.parameters[:max_width]
[field.parameters[:max_width], MAX_COLUMN_WIDTH].min
else
MAX_COLUMN_WIDTH
end
end
end

HammerCLI::Output::Output.register_adapter(:table, Table)
Expand Down
67 changes: 11 additions & 56 deletions lib/hammer_cli/output/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,68 +59,23 @@ def field_sets
end

def sets_table
fields_col_size = max_label_length || _('Fields').size
fields_col = normalize_column(fields_col_size, _('Fields'), centralize: true)
fields_col += ' ' unless (fields_col_size - fields_col.size).zero?
header_bits = [fields_col]
hline_bits = ['-' * fields_col_size]
field_sets.map do |set|
header_bits << normalize_column(set.size, set)
hline_bits << '-' * set.size
end
rows_bits = fields_row(@fields, field_sets, fields_col_size)
line = "+-#{hline_bits.join('-+-')}-+\n"
table = line
table += "| #{header_bits.join(' | ')} |\n"
table += line
table += "#{rows_bits.join("\n")}\n"
table += line
table
columns = field_sets.unshift(_('Fields'))
data = fields_data(@fields, columns).flatten
table_gen = HammerCLI::Output::Generators::Table.new(columns, data)
table_gen.result
end

private

def max_label_length
field_labels(@fields, full_labels: true).map(&:size).max
end
def fields_data(fields, sets)
fields.each_with_object([]) do |field, data|
next data << fields_data(field.fields, sets) if field.respond_to?(:fields)

def normalize_column(width, col, centralize: false)
padding = width - HammerCLI::Output::Utils.real_length(col)
if padding >= 0
if centralize
padding /= 2
col.prepend(' ' * padding)
data << sets.each_with_object({}) do |set, res|
mark = field.sets.include?(set) ? 'x' : ''
value = set == sets.first ? field.full_label : mark
res.update(set => value)
end
col += (' ' * padding)
else
col, real_len = HammerCLI::Output::Utils.real_truncate(col, width - 3)
col += '...'
col += ' ' if real_len < (width - 3)
end
col
end

def fields_row(fields, sets, fields_col_size)
fields.each_with_object([]) do |field, rows|
next rows << fields_row(field.fields, sets, fields_col_size) if field.respond_to?(:fields)

row = [normalize_column(fields_col_size, field.full_label)]
sets.each do |set|
mark = field.sets.include?(set) ? 'x' : ' '
column = normalize_column(set.size, mark, centralize: true)
column += ' ' unless (set.size - column.size).zero?
row << column
end
rows << "| #{row.join(' | ')} |"
end
end

def field_labels(fields, full_labels: false)
fields.each_with_object([]) do |field, labels|
label = full_labels ? field.full_label : field.label
next labels << label unless field.respond_to?(:fields)

labels.concat(field_labels(field.fields, full_labels: full_labels))
end
end

Expand Down
1 change: 1 addition & 0 deletions lib/hammer_cli/output/generators.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'hammer_cli/output/generators/table'
123 changes: 123 additions & 0 deletions lib/hammer_cli/output/generators/table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
require 'hammer_cli/output/utils'

module HammerCLI
module Output
module Generators
class Table
class Column
attr_reader :label, :params

def initialize(label, params = nil)
@label = label.to_s
@params = params || {}
end
end

MAX_COLUMN_WIDTH = 80
MIN_COLUMN_WIDTH = 5

HLINE = '-'
LINE_SEPARATOR = '-|-'
COLUMN_SEPARATOR = ' | '

attr_reader :header, :body, :footer, :result

def initialize(columns, data, options = {})
@columns = columns.map { |label, params| Column.new(label, params) }
@data = data
@options = options
create_table
end

private

def create_header(header_bits, line)
result = StringIO.new
unless @options[:no_headers]
result.puts(line)
result.puts(header_bits.join(COLUMN_SEPARATOR))
result.puts(line)
end
result.string
end

def create_body(widths)
result = StringIO.new
@data.collect do |row|
row_bits = @columns.map do |col|
normalize_column(widths[col.label], row[col.label] || '')
end
result.puts(row_bits.join(COLUMN_SEPARATOR))
end
result.string
end

def create_footer(line)
result = StringIO.new
result.puts(line) unless @data.empty? || @options[:no_headers]
result.string
end

def create_result
result = StringIO.new
result.print(@header, @body, @footer)
result.string
end

def create_table
widths = calculate_widths(@columns, @data)
header_bits = []
hline_bits = []
@columns.map do |col|
header_bits << normalize_column(widths[col.label], col.label.upcase)
hline_bits << HLINE * widths[col.label]
end
line = hline_bits.join(LINE_SEPARATOR)
@header = create_header(header_bits, line)
@body = create_body(widths)
@footer = create_footer(line)
@result = create_result
end

def normalize_column(width, value)
value = value.to_s
padding = width - HammerCLI::Output::Utils.real_length(value)
if padding >= 0
value += (' ' * padding)
else
value, real_length = HammerCLI::Output::Utils.real_truncate(value, width - 3)
value += '...'
value += ' ' if real_length < (width - 3)
end
value
end

def calculate_widths(columns, data)
Hash[columns.map { |c| [c.label, calculate_column_width(c, data)] }]
end

def calculate_column_width(column, data)
if column.params[:width]
return [column.params[:width], MIN_COLUMN_WIDTH].max
end

width = HammerCLI::Output::Utils.real_length(column.label)
max_width = max_width_for(column)
data.each do |item|
width = [HammerCLI::Output::Utils.real_length(item[column.label]), width].max
return max_width if width >= max_width
end
width
end

def max_width_for(column)
if column.params[:max_width]
[column.params[:max_width], MAX_COLUMN_WIDTH].min
else
MAX_COLUMN_WIDTH
end
end
end
end
end
end
14 changes: 7 additions & 7 deletions test/unit/output/definition_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,13 @@
end
definition.fields += [new_field, cont_field]

sets_table = "+----------+-----+---------+-----+
| Fields | ALL | DEFAULT | SET |
+----------+-----+---------+-----+
| newfield | x | x | |
| cf/abc | | | x |
| cf/bca | | | x |
+----------+-----+---------+-----+\n"
sets_table = "---------|-----|---------|----\n" \
"FIELDS | ALL | DEFAULT | SET\n" \
"---------|-----|---------|----\n" \
"newfield | x | x | \n" \
"cf/abc | | | x \n" \
"cf/bca | | | x \n" \
"---------|-----|---------|----\n"

definition.sets_table.must_equal sets_table
end
Expand Down

0 comments on commit 3e48734

Please sign in to comment.