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.
- #each ⇒ Object
-
#formatted_header ⇒ String
A graphical representation of the Table column headers formatted with fixed width plain text.
-
#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
- If passed
:rune
(the default), then it will wrap at the "character" level (approximately speaking, the Unicode grapheme cluster level).
- If passed
-
#pack(max_table_width: :auto) ⇒ 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 a single character of padding on either side of the column, without any wrapping.
-
#remove_column(label) ⇒ true, false
Removes the column identifed by the passed label.
-
#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
- If passed
:rune
(the default), then it will wrap at the "character" level (approximately speaking, the Unicode grapheme cluster level). This means the maximum number of what readers usually think of as "characters" will be fit on each line, within the column's allocated width, before contininuing to a new line, even if it means splitting a word in the middle.- If passed
:word
, then it will wrap in such a way as to avoid splitting words, where "words" are defined as units of text separated by spaces or dashes (hyphens, m-dashes and n-dashes). Whitespace will be used to pad lines as required. Already-hyphenated may will be split at the hyphen, however hyphens will not be inserted in non-hyphenated words.
- If passed
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.
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 361 |
# File 'lib/tabulo/table.rb', line 323 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 |
#each ⇒ Object
407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/tabulo/table.rb', line 407 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.
424 425 426 427 |
# File 'lib/tabulo/table.rb', line 424 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.
449 450 451 452 |
# File 'lib/tabulo/table.rb', line 449 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) ⇒ 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 a single character of padding on either side of the column, without any wrapping. 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.
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 |
# File 'lib/tabulo/table.rb', line 485 def pack(max_table_width: :auto) get_columns.each { |column| column.width = Util.wrapped_width(column.header) } @sources.each_with_index do |source, row_index| get_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 shrink_to(max_table_width == :auto ? TTY::Screen.width : max_table_width) if max_table_width if @title border_edge_width = (@border == :blank ? 0 : 2) columns = get_columns ( Unicode::DisplayWidth.of(@title) + columns.first.left_padding + columns.last.right_padding + border_edge_width ) end self end |
#remove_column(label) ⇒ true, false
Removes the column identifed by the passed label.
382 383 384 |
# File 'lib/tabulo/table.rb', line 382 def remove_column(label) !!column_registry.delete(Integer === label ? label : label.to_sym) end |
#to_s ⇒ String
Returns a graphical "ASCII" representation of the Table, suitable for display in a fixed-width font.
388 389 390 391 392 393 394 395 396 |
# File 'lib/tabulo/table.rb', line 388 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.
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 |
# File 'lib/tabulo/table.rb', line 556 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 |