Class: Tabulo::Table

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/tabulo/table.rb

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_COLUMN_WIDTH =
12
HORIZONTAL_RULE_CHARACTER =
"-"
VERTICAL_RULE_CHARACTER =
"|"
CORNER_CHARACTER =
"+"
PADDING_CHARACTER =
" "
TRUNCATION_INDICATOR =
"~"

Instance Method Summary collapse

Constructor Details

#initialize(sources, columns: [], column_width: nil, header_frequency: :start, wrap_header_cells_to: nil, wrap_body_cells_to: nil) {|_self| ... } ⇒ Table

Returns a new Table.

Parameters:

  • sources (Enumerable)

    the underlying Enumerable from which the table will derive its data

  • columns (Array[Symbol]) (defaults to: [])

    Specifies the initial columns. Each element of the Array will be used to create a column whose content is created by calling the corresponding method on each element of sources. Note the #add_column method is a much more flexible way to set up columns on the table.

  • column_width (Fixnum, nil) (defaults to: nil)

    The default column width for columns in this table, not excluding padding. If nil, then DEFAULT_COLUMN_WIDTH will be used.

  • header_frequency (:start, nil, Fixnum) (defaults to: :start)

    Controls the display of column headers. If passed :start, headers will be shown at the top of the table only. If passed nil, headers will not be shown. If passed a Fixnum N (> 0), headers will be shown at the top of the table, then repeated every N rows.

  • wrap_header_cells_to (nil, Fixnum) (defaults to: nil)

    Controls wrapping behaviour for header cells if the content thereof is longer than the column's fixed width. If passed nil (default), content will be wrapped for as many rows as required to accommodate it. If passed a Fixnum N (> 0), content will be wrapped up to N rows and then truncated thereafter.

  • wrap_body_cells_to (nil, Fixnum) (defaults to: nil)

    Controls wrapping behaviour for table cells (excluding headers), if their content is longer than the column's fixed width. If passed nil, content will be wrapped for as many rows as required to accommodate it. If passed a Fixnum N (> 0), content will be wrapped up to N rows and then truncated thereafter.

Yields:

  • (_self)

Yield Parameters:

  • _self (Tabulo::Table)

    the object that the method was called on



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/tabulo/table.rb', line 39

def initialize(sources, columns: [], column_width: nil, header_frequency: :start,
  wrap_header_cells_to: nil, wrap_body_cells_to: nil)

  @sources = sources
  @header_frequency = header_frequency
  @wrap_header_cells_to = wrap_header_cells_to
  @wrap_body_cells_to = wrap_body_cells_to

  @default_column_width = (column_width || DEFAULT_COLUMN_WIDTH)

  @columns = []
  columns.each { |item| add_column(item) }

  yield self if block_given?
end

Instance Method Details

#add_column(label, header: nil, align_header: :center, align_body: nil, width: nil, formatter: :to_s.to_proc, &extractor) ⇒ Object

Adds a column to the Table.

Parameters:

  • label (Symbol, String)

    A unique identifier for this column, which by default will also be used as the column header text (see also the header param). If the extractor argument is not also provided, then the label argument should correspond to a method to be called on each item in the table sources to provide the content for this column.

  • header (nil, #to_s) (defaults to: nil)

    (nil) Text to be displayed in the column header. If passed nil, the column's label will also be used as its header text.

  • align_header (:left, :center, :right) (defaults to: :center)

    (:center) Specifies how the header text should be aligned.

  • align_body (:left, :center, :right, nil) (defaults to: nil)

    (nil) Specifies how the cell body contents should be aligned. Possible If nil is passed, then the alignment is determined by the type of the cell value, with numbers aligned right, booleans center-aligned, and other values left-aligned. Note header text alignment is configured separately using the :align_header param.

  • width (Fixnum) (defaults to: nil)

    (nil) Specifies the width of the column, excluding padding. If nil, then the column will take the width provided by the column_width param with which the Table was initialized.

  • formatter (#to_proc) (defaults to: :to_s.to_proc)

    (:to_s.to_proc) A lambda or other callable object that will be passed the calculated value of each cell to determine how it should be displayed. This is distinct from the extractor (see below). For example, if the extractor for this column generates a Date, then the formatter might format that Date in a particular way. If no formatter is provided, then .to_s will be called on the extracted value of each cell to determine its displayed content.

  • extractor (#to_proc)

    A block or other callable that will be passed each of the Table sources to determine the value in each cell of this column. If this is not provided, then the column label will be treated as a method to be called on each source item to determine each cell's value.



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/tabulo/table.rb', line 84

def add_column(label, header: nil, align_header: :center, align_body: nil,
  width: nil, formatter: :to_s.to_proc, &extractor)

  @columns << Column.new(
    label: label.to_sym,
    header: (header || label).to_s,
    align_header: align_header,
    align_body: align_body,
    width: (width || @default_column_width),
    formatter: formatter,
    extractor: (extractor || label.to_proc)
  )
end

#eachObject

Calls the given block once for each Row in the Table, passing that Row as parameter.

Note that when printed, the first row will visually include the headers (assuming these were not disabled when the Table was initialized).

Examples:

table.each do |row|
  puts row
end


117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/tabulo/table.rb', line 117

def each
  @sources.each_with_index do |source, index|
    include_header =
      case @header_frequency
      when :start
        index == 0
      when Fixnum
        index % @header_frequency == 0
      else
        @header_frequency
      end
    yield body_row(source, with_header: include_header)
  end
end

#formatted_headerString

Returns an "ASCII" graphical representation of the Table column headers.

Returns:

  • (String)

    an "ASCII" graphical representation of the Table column headers.



133
134
135
136
# File 'lib/tabulo/table.rb', line 133

def formatted_header
  cells = @columns.map(&:header_subcells)
  format_row(cells, @wrap_header_cells_to)
end

#horizontal_ruleString

Returns an "ASCII" graphical representation of a horizontal dividing line suitable for printing at any point in the table.

Examples:

Print a horizontal divider after every row:

table.each do |row|
  puts row
  puts table.horizontal_rule
end

Returns:

  • (String)

    an "ASCII" graphical representation of a horizontal dividing line suitable for printing at any point in the table.



146
147
148
149
# File 'lib/tabulo/table.rb', line 146

def horizontal_rule
  inner = @columns.map { |column| surround(column.horizontal_rule, HORIZONTAL_RULE_CHARACTER) }
  surround_join(inner, CORNER_CHARACTER)
end

#shrinkwrap!(max_table_width: nil) ⇒ Table

Reset 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.

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.

Parameters:

  • max_table_width (nil, Numeric) (defaults to: nil)

    (nil) If provided, stops the total table width (including padding and borders) from expanding beyond this number of characters. 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, 1 character of left padding and 1 character of right padding in each column, 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.

Returns:

  • (Table)

    the Table itself



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/tabulo/table.rb', line 172

def shrinkwrap!(max_table_width: nil)
  return self if columns.none?

  wrapped_width = -> (str) { str.split($/).map(&:length).max || 1 }

  columns.each do |column|
    column.width = wrapped_width.call(column.header)
  end

  @sources.each do |source|
    columns.each do |column|
      width = wrapped_width.call(column.formatted_cell_content(source))
      column.width = width if width > column.width
    end
  end

  if max_table_width
    total_columns_width = columns.inject(0) { |sum, column| sum + column.width }
    total_padding = columns.count * 2
    total_borders = columns.count + 1
    unadjusted_table_width = total_columns_width + total_padding + total_borders

    # Ensure max table width is at least wide enough to accommodate table borders and padding
    # and one character of content.
    min_table_width = total_padding + total_borders + columns.count
    max_table_width = min_table_width if min_table_width > max_table_width

    required_reduction = [unadjusted_table_width - max_table_width, 0].max

    required_reduction.times do
      widest_column = columns.inject(columns.first) do |widest, column|
        column.width >= widest.width ? column : widest
      end

      widest_column.width -= 1
    end
  end

  self
end

#to_sString

Returns a graphical "ASCII" representation of the Table, suitable for display in a fixed-width font.

Returns:

  • (String)

    a graphical "ASCII" representation of the Table, suitable for display in a fixed-width font.



100
101
102
103
104
105
106
# File 'lib/tabulo/table.rb', line 100

def to_s
  if @columns.any?
    join_lines(map(&:to_s))
  else
    ""
  end
end