Class: Tabulo::Table
Overview
Represents a table primarily intended for "pretty-printing" in a fixed-width font.
A Table is also an Enumerable, of which each element is a Row.
Constant Summary collapse
- DEFAULT_BORDER =
:ascii
- DEFAULT_COLUMN_WIDTH =
12
- DEFAULT_COLUMN_PADDING =
1
- DEFAULT_TRUNCATION_INDICATOR =
"~"
Instance Attribute Summary collapse
-
#sources ⇒ Enumerable
The underlying enumerable from which the table derives its data.
Instance Method Summary collapse
-
#add_column(label, align_body: nil, align_header: nil, before: nil, formatter: nil, header: nil, header_styler: nil, padding: nil, styler: nil, width: nil, wrap_preserve: nil, &extractor) ⇒ Object
Adds a column to the Table.
-
#autosize_columns(except: nil) ⇒ Table
Resets all the column widths so that each column is just wide enough to accommodate its header text as well as the formatted content of each its cells for the entire collection, together with padding (by default 1 character either side), without wrapping.
- #each ⇒ Object
-
#formatted_header ⇒ String
A graphical representation of the Table column headers formatted with fixed width plain text, excluding any horizontal borders above or below.
-
#horizontal_rule(position = :bottom) ⇒ String
Produce a horizontal dividing line suitable for printing at the top, bottom or middle of the table.
-
#initialize(sources, *columns, align_body: :auto, align_header: :center, align_title: :center, border: nil, border_styler: nil, column_padding: nil, column_width: nil, formatter: :to_s.to_proc, header_frequency: :start, header_styler: nil, row_divider_frequency: nil, styler: nil, title: nil, title_styler: nil, truncation_indicator: nil, wrap_preserve: :rune, wrap_body_cells_to: nil, wrap_header_cells_to: nil) {|_self| ... } ⇒ Table
constructor
A new Table.
-
#pack(max_table_width: :auto, except: nil) ⇒ Table
Resets all the column widths so that each column is just wide enough to accommodate its header text as well as the formatted content of each its cells for the entire collection, together with padding (by default 1 character on either side of the column), without wrapping.
-
#remove_column(label) ⇒ true, false
Removes the column identifed by the passed label.
-
#shrink_to(max_table_width, except: nil) ⇒ Table
If
max_table_width
is passed an integer, then column widths will be adjusted downward so that the total table width is reduced to the passed target width. -
#to_s ⇒ String
A graphical "ASCII" representation of the Table, suitable for display in a fixed-width font.
-
#transpose(opts = {}) ⇒ Table
Creates a new Table from the current Table, transposed, that is rotated 90 degrees, relative to the current Table, so that the header names of the current Table form the content of left-most column of the new Table, and each column thereafter corresponds to one of the elements of the current Table's sources, with the header of that column being the String value of that element.
Constructor Details
#initialize(sources, *columns, align_body: :auto, align_header: :center, align_title: :center, border: nil, border_styler: nil, column_padding: nil, column_width: nil, formatter: :to_s.to_proc, header_frequency: :start, header_styler: nil, row_divider_frequency: nil, styler: nil, title: nil, title_styler: nil, truncation_indicator: nil, wrap_preserve: :rune, wrap_body_cells_to: nil, wrap_header_cells_to: nil) {|_self| ... } ⇒ Table
Returns a new Tabulo::Table.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/tabulo/table.rb', line 151 def initialize(sources, *columns, align_body: :auto, align_header: :center, align_title: :center, border: nil, border_styler: nil, column_padding: nil, column_width: nil, formatter: :to_s.to_proc, header_frequency: :start, header_styler: nil, row_divider_frequency: nil, styler: nil, title: nil, title_styler: nil, truncation_indicator: nil, wrap_preserve: :rune, wrap_body_cells_to: nil, wrap_header_cells_to: nil) @sources = sources @align_body = align_body @align_header = align_header @align_title = align_title @border = (border || DEFAULT_BORDER) @border_styler = border_styler @border_instance = Border.from(@border, @border_styler) @column_padding = (column_padding || DEFAULT_COLUMN_PADDING) @left_column_padding, @right_column_padding = (Array === @column_padding ? @column_padding : [@column_padding, @column_padding]) @column_width = (column_width || DEFAULT_COLUMN_WIDTH) @formatter = formatter @header_frequency = header_frequency @header_styler = header_styler @row_divider_frequency = row_divider_frequency @styler = styler @title = title @title_styler = title_styler @truncation_indicator = validate_character(truncation_indicator, DEFAULT_TRUNCATION_INDICATOR, InvalidTruncationIndicatorError, "truncation indicator") @wrap_preserve = wrap_preserve @wrap_body_cells_to = wrap_body_cells_to @wrap_header_cells_to = wrap_header_cells_to @column_registry = { } columns.each { |item| add_column(item) } yield self if block_given? end |
Instance Attribute Details
#sources ⇒ Enumerable
Returns the underlying enumerable from which the table derives its data.
31 32 33 |
# File 'lib/tabulo/table.rb', line 31 def sources @sources end |
Instance Method Details
#add_column(label, align_body: nil, align_header: nil, before: nil, formatter: nil, header: nil, header_styler: nil, padding: nil, styler: nil, width: nil, wrap_preserve: nil, &extractor) ⇒ Object
Adds a column to the Table.
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/tabulo/table.rb', line 322 def add_column(label, align_body: nil, align_header: nil, before: nil, formatter: nil, header: nil, header_styler: nil, padding: nil, styler: nil, width: nil, wrap_preserve: 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 column = Column.new( align_body: align_body || @align_body, align_header: align_header || @align_header, extractor: extractor || label.to_proc, formatter: formatter || @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, wrap_preserve: wrap_preserve || @wrap_preserve, width: width || @column_width, ) if before == nil add_column_final(column, column_label) else add_column_before(column, column_label, before) end end |
#autosize_columns(except: nil) ⇒ Table
Resets all the column widths so that each column is just wide enough to accommodate its header text as well as the formatted content of each its cells for the entire collection, together with padding (by default 1 character either side), without wrapping.
524 525 526 527 528 529 530 531 532 533 534 535 |
# File 'lib/tabulo/table.rb', line 524 def autosize_columns(except: nil) columns = get_columns(except: except) columns.each { |column| column.width = Util.wrapped_width(column.header) } @sources.each_with_index do |source, row_index| columns.each_with_index do |column, column_index| cell = column.body_cell(source, row_index: row_index, column_index: column_index) cell_width = Util.wrapped_width(cell.formatted_content) column.width = Util.max(column.width, cell_width) end end self end |
#each ⇒ Object
406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/tabulo/table.rb', line 406 def each @sources.each_with_index do |source, index| header = if (index == 0) && @header_frequency :top elsif (Integer === @header_frequency) && Util.divides?(@header_frequency, index) :middle end show_divider = @row_divider_frequency && (index != 0) && Util.divides?(@row_divider_frequency, index) yield Row.new(self, source, header: header, divider: show_divider, index: index) end end |
#formatted_header ⇒ String
Returns a graphical representation of the Table column headers formatted with fixed width plain text, excluding any horizontal borders above or below.
423 424 425 426 |
# File 'lib/tabulo/table.rb', line 423 def formatted_header cells = get_columns.map(&:header_cell) format_row(cells, @wrap_header_cells_to) end |
#horizontal_rule(position = :bottom) ⇒ String
Produce a horizontal dividing line suitable for printing at the top, bottom or middle of the table.
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.
448 449 450 451 |
# File 'lib/tabulo/table.rb', line 448 def horizontal_rule(position = :bottom) column_widths = get_columns.map { |column| column.width + column.total_padding } @border_instance.horizontal_rule(column_widths, position) end |
#pack(max_table_width: :auto, except: nil) ⇒ Table
Resets all the column widths so that each column is just wide enough to accommodate its header text as well as the formatted content of each its cells for the entire collection, together with padding (by default 1 character on either side of the column), without wrapping. Other adjustments may also be performed (see below).
In addition, if the table has a title but is not wide enough to accommodate (without wrapping) the title text (with a character of padding either side), widens the columns roughly evenly until the table as a whole is just wide enough to accommodate the title text.
Note that calling this method will cause the entire source Enumerable to be traversed and all the column extractors and formatters to be applied in order to calculate the required widths.
Note also that this method causes column widths to be fixed as appropriate to the formatted cell contents given the state of the source Enumerable at the point it is called. If the source Enumerable changes between that point, and the point when the Table is printed, then columns will not be resized yet again on printing.
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 |
# File 'lib/tabulo/table.rb', line 490 def pack(max_table_width: :auto, except: nil) autosize_columns(except: except) max_width = nil if max_table_width max_width = (max_table_width == :auto ? TTY::Screen.width : max_table_width) shrink_to(max_width, except: except) end if @title border_edge_width = (@border == :blank ? 0 : 2) all_columns = get_columns min_width = Unicode::DisplayWidth.of(@title) + all_columns.first.left_padding + all_columns.last.right_padding + border_edge_width min_width = max_width if max_width && max_width < min_width (min_width, except: except) end self end |
#remove_column(label) ⇒ true, false
Removes the column identifed by the passed label.
381 382 383 |
# File 'lib/tabulo/table.rb', line 381 def remove_column(label) !!column_registry.delete(Integer === label ? label : label.to_sym) end |
#shrink_to(max_table_width, except: nil) ⇒ Table
If max_table_width
is passed an integer, then column widths will be adjusted downward so
that the total table width is reduced to the passed target width. (If the total width of the
table exceeds the passed max_table_width
, then this method is a no-op.)
Width is deducted from columns if required to achieve this, with one character progressively deducted from the width of the widest column until the target is reached. When the table is printed, wrapping or truncation will then occur in these columns as required (depending on how they were configured).
Note that regardless of the value passed to max_table_width
, the table will always be left wide
enough to accommodate at least 1 character's width of content for each column, and the padding
configured for each column (by default 1 character either side), together with border characters
(1 on each side of the table and 1 between adjacent columns). I.e. there is a certain width below width the
Table will refuse to shrink itself.
If max_table_width
is passed the symbol :screen
, then this method will behave as if it
were passed an integer, with that integer being the width of the terminal.
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 |
# File 'lib/tabulo/table.rb', line 561 def shrink_to(max_table_width, except: nil) min_content_width_per_column = 1 min_total_column_content_width = num_columns * min_content_width_per_column min_table_width = total_padding_width + total_borders_width + min_total_column_content_width max_table_width = (max_table_width == :screen ? TTY::Screen.width : max_table_width) max_table_width = Util.max(min_table_width, max_table_width) required_reduction = Util.max(total_table_width - max_table_width, 0) shrinkable_columns = get_columns(except: except) required_reduction.times do widest_column = shrinkable_columns.inject(shrinkable_columns.first) do |widest, column| column.width >= widest.width ? column : widest end widest_column.width -= 1 end self end |
#to_s ⇒ String
Returns a graphical "ASCII" representation of the Table, suitable for display in a fixed-width font.
387 388 389 390 391 392 393 394 395 |
# File 'lib/tabulo/table.rb', line 387 def to_s if column_registry.any? bottom_edge = horizontal_rule(:bottom) rows = map(&:to_s) bottom_edge.empty? ? Util.join_lines(rows) : Util.join_lines(rows + [bottom_edge]) else "" end end |
#transpose(opts = {}) ⇒ Table
Creates a new Tabulo::Table from the current Table, transposed, that is rotated 90 degrees, relative to the current Table, so that the header names of the current Table form the content of left-most column of the new Table, and each column thereafter corresponds to one of the elements of the current Table's sources, with the header of that column being the String value of that element.
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 |
# File 'lib/tabulo/table.rb', line 627 def transpose(opts = {}) default_opts = [:align_body, :align_header, :align_title, :border, :border_styler, :column_padding, :column_width, :formatter, :header_frequency, :row_divider_frequency, :title, :title_styler, :truncation_indicator, :wrap_body_cells_to, :wrap_header_cells_to].map do |sym| [sym, instance_variable_get("@#{sym}")] end.to_h initializer_opts = default_opts.merge(Util.slice_hash(opts, *default_opts.keys)) default_extra_opts = { field_names_body_alignment: :right, field_names_header: "", field_names_header_alignment: :right, field_names_width: nil, headers: :to_s.to_proc } extra_opts = default_extra_opts.merge(Util.slice_hash(opts, *default_extra_opts.keys)) # The underlying enumerable for the new table, is the columns of the original table. fields = column_registry.values Table.new(fields, **initializer_opts) do |t| # Left hand column of new table, containing field names width_opt = extra_opts[:field_names_width] field_names_width = (width_opt.nil? ? fields.map { |f| f.header.length }.max : width_opt) t.add_column(:dummy, align_body: extra_opts[:field_names_body_alignment], align_header: extra_opts[:field_names_header_alignment], header: extra_opts[:field_names_header], width: field_names_width, &:header) # Add a column to the new table for each of the original table's sources sources.each_with_index do |source, i| t.add_column(i, header: extra_opts[:headers].call(source)) do |original_column| original_column.body_cell_value(source, row_index: i, column_index: original_column.index) end end end end |