Skip to content

Commit

Permalink
Allow column widths to be configured individually
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-harvey committed May 17, 2020
1 parent 267f70b commit 4e37895
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 32 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,28 @@ table = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?, column_padding: [0,
+--------------+--------------+--------------+
```

Padding can also be configured on a column-by-column basis, using the `padding` option when calling
`add_column`:

```ruby
table = Tabulo::Table.new([1, 2, 5], :itself, :even?)
table.add_column(:odd?, padding: 2)
```

```
> puts table
+--------------+--------------+------------------+
| itself | even? | odd? |
+--------------+--------------+------------------+
| 1 | false | true |
| 2 | true | false |
| 5 | false | true |
+--------------+--------------+------------------+
```

This column-level `padding` setting always overrides any table-level `column_padding` setting, for
the column in question.

<a name="overflow-handling"></a>
#### Overflow handling [&#x2191;](#contents)

Expand Down
20 changes: 12 additions & 8 deletions lib/tabulo/cell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ def initialize(
alignment:,
cell_data:,
formatter:,
left_padding:,
padding_character:,
right_padding:,
styler:,
truncation_indicator:,
value:,
width:)

@alignment = alignment
@cell_data = cell_data
@padding_character = padding_character
@formatter = formatter
@left_padding = left_padding
@padding_character = padding_character
@right_padding = right_padding
@styler = styler
@truncation_indicator = truncation_indicator
@value = value
Expand All @@ -35,12 +39,12 @@ def height
end

# @!visibility private
def padded_truncated_subcells(target_height, padding_amount_left, padding_amount_right)
total_padding_amount = padding_amount_left + padding_amount_right
def padded_truncated_subcells(target_height)
total_padding_amount = @left_padding + @right_padding
truncated = (height > target_height)
(0...target_height).map do |subcell_index|
append_truncator = (truncated && (total_padding_amount != 0) && (subcell_index + 1 == target_height))
padded_subcell(subcell_index, padding_amount_left, padding_amount_right, append_truncator)
padded_subcell(subcell_index, append_truncator)
end
end

Expand Down Expand Up @@ -75,13 +79,13 @@ def subcells
@subcells ||= calculate_subcells
end

def padded_subcell(subcell_index, padding_amount_left, padding_amount_right, append_truncator)
lpad = @padding_character * padding_amount_left
def padded_subcell(subcell_index, append_truncator)
lpad = @padding_character * @left_padding
rpad =
if append_truncator
styled_truncation_indicator(subcell_index) + padding(padding_amount_right - 1)
styled_truncation_indicator(subcell_index) + padding(@right_padding - 1)
else
padding(padding_amount_right)
padding(@right_padding)
end
inner = subcell_index < height ? subcells[subcell_index] : padding(@width)
"#{lpad}#{inner}#{rpad}"
Expand Down
18 changes: 18 additions & 0 deletions lib/tabulo/column.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class Column
attr_accessor :width
attr_reader :header
attr_reader :index
attr_reader :left_padding
attr_reader :right_padding

def initialize(
align_body:,
Expand All @@ -15,7 +17,9 @@ def initialize(
header:,
header_styler:,
index:,
left_padding:,
padding_character:,
right_padding:,
styler:,
truncation_indicator:,
width:)
Expand All @@ -26,6 +30,8 @@ def initialize(
@formatter = formatter
@header = header
@index = index
@left_padding = left_padding
@right_padding = right_padding

@header_styler =
if header_styler
Expand Down Expand Up @@ -55,7 +61,9 @@ def header_cell
alignment: @align_header,
cell_data: cell_data,
formatter: -> (s) { s },
left_padding: @left_padding,
padding_character: @padding_character,
right_padding: @right_padding,
styler: @header_styler,
truncation_indicator: @truncation_indicator,
value: @header,
Expand All @@ -71,7 +79,9 @@ def body_cell(source, row_index:, column_index:)
alignment: @align_body,
cell_data: cell_data,
formatter: @formatter,
left_padding: @left_padding,
padding_character: @padding_character,
right_padding: @right_padding,
styler: @styler,
truncation_indicator: @truncation_indicator,
value: body_cell_value(source, row_index: row_index, column_index: column_index),
Expand All @@ -87,6 +97,14 @@ def body_cell_value(source, row_index:, column_index:)
end
end

def padded_width
width + total_padding
end

def total_padding
@left_padding + @right_padding
end

private

def body_cell_data_required?
Expand Down
67 changes: 44 additions & 23 deletions lib/tabulo/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ class Table
# either side of each column. If passed an Integer, then the given amount of padding is
# applied to each side of each column. If passed a two-element Array, then the first element of the
# Array indicates the amount of padding to apply to the left of each column, and the second
# element indicates the amount to apply to the right.
# element indicates the amount to apply to the right. This setting can be overridden for
# individual columns using the `padding` option of {#add_column}.
# @param [Integer, nil] column_width The default column width for columns in this
# table, not excluding padding. If <tt>nil</tt>, then {DEFAULT_COLUMN_WIDTH} will be used.
# @param [nil, #to_proc] formatter (:to_s.to_proc) The default formatter for columns in this
Expand Down Expand Up @@ -154,7 +155,7 @@ def initialize(sources, *columns, align_body: :auto, align_header: :center, alig
@column_padding = (column_padding || DEFAULT_COLUMN_PADDING)

@left_column_padding, @right_column_padding =
Array === @column_padding ? @column_padding : [@column_padding, @column_padding]
(Array === @column_padding ? @column_padding : [@column_padding, @column_padding])

@column_width = (column_width || DEFAULT_COLUMN_WIDTH)
@formatter = formatter
Expand Down Expand Up @@ -248,6 +249,13 @@ def initialize(sources, *columns, align_body: :auto, align_header: :center, alig
#
# Note that if the header content is truncated, then any <tt>header_styler</tt> will be applied to the
# truncation indicator character as well as to the truncated content.
# @param [nil, Integer, Array] padding (nil) Determines the amount of blank space with which to
# pad either side of the column. If passed nil, then the `column_padding` setting of the
# {Table} will determine the column's padding. (See {#initialize}.) Otherwise, this option
# overrides, for this column, the `column_padding` that was set at the table level: if passed an Integer,
# then the given amount of padding is applied to either side of the column; or if passed a two-element Array,
# then the first element of the Array indicates the amount of padding to apply to the left of the column,
# and the second element indicates the amount to apply to the right.
# @param [nil, #to_proc] styler (nil) A lambda or other callable object that will determine
# the colors or other styling applied to the formatted value of the cell. Can be passed
# <tt>nil</tt>, or can be passed a callable that takes either 2 or 3 parameters:
Expand Down Expand Up @@ -297,10 +305,17 @@ def initialize(sources, *columns, align_body: :auto, align_header: :center, alig
# Table. (This is case-sensitive, but is insensitive to whether a String or Symbol is passed
# to the label parameter.)
def add_column(label, align_body: nil, align_header: nil, before: nil, formatter: nil,
header: nil, header_styler: nil, styler: nil, width: nil, &extractor)
header: nil, header_styler: nil, padding: nil, styler: nil, width: nil, &extractor)

column_label = normalize_column_label(label)

left_padding, right_padding =
if padding
Array === padding ? padding : [padding, padding]
else
[@left_column_padding, @right_column_padding]
end

if column_registry.include?(column_label)
raise InvalidColumnLabelError, "Column label already used in this table."
end
Expand All @@ -313,7 +328,9 @@ def add_column(label, align_body: nil, align_header: nil, before: nil, formatter
header: (header || label).to_s,
header_styler: header_styler || @header_styler,
index: column_registry.count,
left_padding: left_padding,
padding_character: PADDING_CHARACTER,
right_padding: right_padding,
styler: styler || @styler,
truncation_indicator: @truncation_indicator,
width: width || @column_width,
Expand Down Expand Up @@ -413,7 +430,7 @@ def formatted_header
# It may be that `:top`, `:middle` and `:bottom` all look the same. Whether
# this is the case depends on the characters used for the table border.
def horizontal_rule(position = :bottom)
column_widths = get_columns.map { |column| column.width + total_column_padding }
column_widths = get_columns.map { |column| column.width + column.total_padding }
@border_instance.horizontal_rule(column_widths, position)
end

Expand Down Expand Up @@ -463,7 +480,13 @@ def pack(max_table_width: :auto)

if @title
border_edge_width = (@border == :blank ? 0 : 2)
expand_to(Unicode::DisplayWidth.of(@title) + total_column_padding + border_edge_width)
columns = get_columns
expand_to(
Unicode::DisplayWidth.of(@title) +
columns.first.left_padding +
columns.last.right_padding +
border_edge_width
)
end

self
Expand Down Expand Up @@ -597,12 +620,14 @@ def add_column_final(column, label)
# @visibility private
def formatted_title
columns = get_columns
num_fudged_columns = columns.count - 1
basic_width = columns.inject(0) { |total_width, column| total_width + column.width }

extra_for_internal_dividers = (@border == :blank ? 0 : 1)
extra_for_internal_padding = @left_column_padding + @right_column_padding
extra_total = num_fudged_columns * (extra_for_internal_dividers + extra_for_internal_padding)
title_cell_width = basic_width + extra_total

title_cell_width = columns.inject(0) do |total_width, column|
total_width + column.padded_width + extra_for_internal_dividers
end

title_cell_width -= (columns.first.left_padding + columns.last.right_padding + extra_for_internal_dividers)

styler =
if @title_styler
Expand All @@ -620,7 +645,9 @@ def formatted_title
alignment: @align_title,
cell_data: nil,
formatter: -> (s) { s },
left_padding: columns.first.left_padding,
padding_character: PADDING_CHARACTER,
right_padding: columns.last.right_padding,
styler: styler,
truncation_indicator: @truncation_indicator,
value: @title,
Expand All @@ -630,7 +657,7 @@ def formatted_title
max_cell_height = cells.map(&:height).max
row_height = ([nil, max_cell_height].compact.min || 1)
subcell_stacks = cells.map do |cell|
cell.padded_truncated_subcells(row_height, @left_column_padding, @right_column_padding)
cell.padded_truncated_subcells(row_height)
end
subrows = subcell_stacks.transpose.map do |subrow_components|
@border_instance.join_cell_contents(subrow_components)
Expand All @@ -653,10 +680,9 @@ def normalize_column_label(label)
def expand_to(min_table_width)
columns = get_columns
num_columns = columns.count
total_columns_width = columns.inject(0) { |sum, column| sum + column.width }
total_padding = num_columns * total_column_padding
total_columns_padded_width = columns.inject(0) { |sum, column| sum + column.padded_width }
total_borders = num_columns + 1
unadjusted_table_width = total_columns_width + total_padding + total_borders
unadjusted_table_width = total_columns_padded_width + total_borders
required_increase = Util.max(min_table_width - unadjusted_table_width, 0)

required_increase.times do
Expand All @@ -672,10 +698,10 @@ def expand_to(min_table_width)
def shrink_to(max_table_width)
columns = get_columns
num_columns = columns.count
total_columns_width = columns.inject(0) { |sum, column| sum + column.width }
total_padding = num_columns * total_column_padding
total_columns_padded_width = columns.inject(0) { |sum, column| sum + column.padded_width }
total_padding = columns.inject(0) { |sum, column| sum + column.total_padding }
total_borders = num_columns + 1
unadjusted_table_width = total_columns_width + total_padding + total_borders
unadjusted_table_width = total_columns_padded_width + total_borders

# Ensure max table width is at least wide enough to accommodate table borders and padding
# and one character of content.
Expand All @@ -692,11 +718,6 @@ def shrink_to(max_table_width)
end
end

# @!visibility private
def total_column_padding
@left_column_padding + @right_column_padding
end

# @!visibility private
#
# Formats a single header row or body row as a String.
Expand All @@ -714,7 +735,7 @@ def format_row(cells, wrap_cells_to)
max_cell_height = cells.map(&:height).max
row_height = ([wrap_cells_to, max_cell_height].compact.min || 1)
subcell_stacks = cells.map do |cell|
cell.padded_truncated_subcells(row_height, @left_column_padding, @right_column_padding)
cell.padded_truncated_subcells(row_height)
end
subrows = subcell_stacks.transpose.map do |subrow_components|
@border_instance.join_cell_contents(subrow_components)
Expand Down
6 changes: 5 additions & 1 deletion spec/cell_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
alignment: :right,
cell_data: cell_data,
formatter: formatter,
left_padding: left_padding,
padding_character: " ",
right_padding: right_padding,
styler: styler,
truncation_indicator: ".",
value: value,
Expand All @@ -16,6 +18,8 @@
let(:formatter) { -> (source) { source.to_s } }
let(:row_index) { 5 }
let(:column_index) { 3 }
let(:left_padding) { 2 }
let(:right_padding) { 3 }
let(:source) { "hi" }
let(:styler) { -> (source, str) { str } }
let(:value) { 30 }
Expand All @@ -32,7 +36,7 @@
end

describe "#padded_truncated_subcells" do
subject { cell.padded_truncated_subcells(target_height, 2, 3) }
subject { cell.padded_truncated_subcells(target_height) }
let(:value) { "ab\ncde\nfg" }

context "when the target height is greater than required to contain the wrapped cell content" do
Expand Down
2 changes: 2 additions & 0 deletions spec/column_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
formatter: -> (n) { "%.2f" % n },
header: "X10",
header_styler: nil,
left_padding: 1,
index: 3,
padding_character: " ",
right_padding: 1,
styler: nil,
truncation_indicator: "~",
width: 10)
Expand Down

0 comments on commit 4e37895

Please sign in to comment.