tty-table

by piotrmurach

piotrmurach / tty-table

A flexible and intuitive table generator

144 Stars 16 Forks Last release: Not found MIT License 283 Commits 12 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

TTY Toolkit logo

TTY::Table Gitter

Gem Version Build Status Build status Code Climate Coverage Status Inline docs

A flexible and intuitive table formatting.

TTY::Table provides independent table formatting component for TTY toolkit.

Features

  • Table behaves like an array with familiar API see
  • Create table once and render using custom view renderers see
  • Rendering provides many display options see
  • Easy custom border creation see
  • Supports multibyte character encodings

Installation

Add this line to your application's Gemfile:

gem "tty-table"

And then execute:

$ bundle

Or install it yourself as:

$ gem install tty-table

Contents

1. Usage

First, provide TTY::Table with data, for example, two headers and two rows:

table = TTY::Table.new(["header1","header2"], [["a1", "a2"], ["b1", "b2"]])

Then to print to the console, call the

render
method with border type as a first argument:
puts table.render(:ascii)
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |a1     |a2     |
#  +-------+-------+
#  |b1     |b2     |
#  +-------+-------+

2. Interface

2.1 Initialization

TTY::Table can be created in variety of ways. The easiest way is to pass 2-dimensional array:

table = TTY::Table[["a1", "a2"], ["b1", "b2"]]
table = TTY::Table.new([["a1", "a2"], ["b1", "b2"]])
table = TTY::Table.new(rows: [["a1", "a2"], ["b1", "b2"]])

Alternatively you can specify the rows one by one inside a block:

table = TTY::Table.new do |t|
  t << ["a1", "a2"]
  t << ["b1", "b2"]
end

You can add rows of data after initialization with

<<
operator:
table = TTY::Table.new
table << ["a1","a2"]
table << ["b1","b2"]

In addition to rows, you can specify table header:

table = TTY::Table.new(["h1", "h2"], [["a1", "a2"], ["b1", "b2"]])
table = TTY::Table.new(header: ["h1", "h2"], rows: [["a1", "a2"], ["b1", "b2"]])

You can also mix header with rows inside a hash like so:

table = TTY::Table.new([{"h1" => ["a1", "a2"], "h2" => ["b1", "b2"]}])

2.2 Iteration

Table behaves like an Array so

<<
,
each
and familiar methods can be used:
table << ["a1", "a2", "a3"]
table << ["b1", "b2", "b3"]
table << ["a1", "a2"] << ["b1", "b2"]  # chain rows assignment

In order to iterate over table rows including headers do:

table.each { |row| ... }                       # iterate over rows
table.each_with_index  { |row, index| ... }    # iterate over rows with an index

2.3 Access

In order to reference the row at

index
do:
table = TTY::Table.new [["a1","a2"], ["b1","b2"]]
table[0]                    # => ["a1","a2"]
table.row(0)                # => ["a1","a2"]
table.row(i) { |row| ... }  # return array for row(i)

Negative indices count backwards from the end of table data (

-1
is the last element):
table[-1]   # => ["b1","b2"]

To reference element at given row(i) and column(j) do:

table[i, j]   # return element at row(i) and column(j)
table[0,0]    # => "a1"

To specifically reference column(j) do:

table.column(j) { ... }   # return array for column(j)
table.column(0)           # => ["a1","b1"]
table.column(name)        # return array for column(name), name of header

An

IndexError
is raised for indexes outside of data range.

2.4 Size

In order to query the number of rows, columns or size do:

table.rows_size        # return row size
table.columns_size     # return column size
table.size             # return an array of [row_size, column_size]

2.5 Orientation

3 Rendering

TTY-Table rendering process means you can create tabular data once and then create different renderers to match your needs for formatting the data.

3.1 Render

Given a table:

table = TTY::Table.new(["header1","header2"], [["a1", "a2"], ["b1", "b2"]])

Once you have an instance of

TTY::Table
you can decorate the content using the
render
method. In order to display a basic whitespace delimited view do:
table.render(:basic)
# =>
#  header1 header2
#  a1      a2
#  b1      b2

This will use so called

:basic
renderer with default options. The other renderers are
:ascii
and
:unicode
.

The

render
method can accept as a second argument the rendering options either as hash value:
table.render(:basic, alignments: [:left, :center])

or inside a block:

table.render(:basic) do |renderer|
  renderer.alignments= [:left, :center]
end

3.2 Renderer

TTY::Table has a definition of

TTY::Table::Renderer
which allows you to provide different view for your tabular data. It comes with few initial renderers built in such as
TTY::Table::Renderer::Basic
,
TTY::Table::Renderer::ASCII
and
TTY::Table::Renderer:Unicode
.

Given a table of data:

table = TTY::Table.new ["header1","header2"], [["a1", "a2"], ["b1", "b2"]]

You can create a special renderer for it:

multi_renderer = TTY::Table::Renderer::Basic.new(table, multiline: true)

and then call

render
multi_renderer.render

This way, you create tabular data once and then create different renderers to match your needs for formatting the data.

3.2.1 Basic Renderer

The basic render allows for formatting table with whitespace without any border:

renderer = TTY::Table::Renderer::Basic.new(table)
renderer.render
# =>
#  header1 header2
#  a1      a2
#  b1      b2

This is the same as calling

render
directly on table:
table.render

3.2.2 ASCII Renderer

The ascii renderer allows for formatting table with ASCII type border.

Create an instance of ASCII renderer:

renderer = TTY::Table::Renderer::ASCII.new(table)

and then call

render
to get the formatted data:
renderer.render
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |a1     |a2     |
#  |b1     |b2     |
#  +-------+-------+

This is the same as calling

render
directly on table instance with
:ascii
as the first argument:
table.render(:ascii)

3.2.3 Unicode Renderer

The uniocde renderer allows for formatting table with Unicode type border.

Create an instance of Unicode renderer:

renderer = TTY::Table::Renderer::Unicode.new(table)

and then call

render
to get the formatted data:
renderer.render
# =>
#  ┌───────┬───────┐
#  │header1│header2│
#  ├───────┼───────┤
#  │a1     │a2     │
#  │b1     │b2     │
#  └───────┴───────┘

This is the same as calling

render
directly on table instance with
:unicode
as the first argument:
table.render(:unicode)

3.3 Options

Rendering of TTY-Table includes numerous customization options:

  • :alignments
    - array of cell alignments out of
    :left
    ,
    :center
    and
    :righit
    . Defaults to
    :left
    .
  • :border
    - hash of border options out of
    :characters
    ,
    :style
    and
    :separator
  • :border_class
    - a type of border to use such as
    TTY::Table::Border::Null
    ,
    TTY::Table::Border::ASCII
    and
    TTY::Table::Border::Unicode
  • :column_widths
    - array of maximum column widths
  • :filter
    - a
    proc
    object that is applied to every field in a row
  • :indent
    - indentation applied to rendered table, by default 0
  • :multiline
    - when
    true
    will wrap text at new line or column width, when
    false
    will escape special characters
  • :padding
    - array of integers to set table fields padding. Defaults to
    [0,0,0,0]
    .
  • :resize
    - when
    true
    will expand/shrink table column sizes to match the terminal width, otherwise when
    false
    will rotate table vertically. Defaults to
    false
    .
  • :width
    - constrains the table total width. Defaults to value automatically calculated based on the content and terminal size.

The

render
method can accept as a second argument the above options either as hash value:
table.render(:basic, alignments: [:left, :center])

Or inside a block as a property:

table.render(:basic) do |renderer|
  renderer.alignments = [:left, :center]
end

3.4 Alignment

By default all columns are

:left
aligned.

You can align each column individually by passing

:alignments
option to table renderer:
table.render(:ascii, alignments: [:center, :right])
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |  a1   |     a2|
#  |  b1   |     b2|
#  +-------+-------+

Alternatively you can align all columns with

:alignment
option:
table.render(:ascii, alignment: [:center])
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |  a1   |  a2   |
#  |  b1   |  b2   |
#  +-------+-------+

If you require a more granular alignment you can align individual fields in a row by passing

:alignment
option like so:
table = TTY::Table.new(header: ["header1", "header2"])
table << [{value: "a1", alignment: :right}, "a2"]
table << ["b1", {value: "b2", alignment: :center}]

and then simply render:

table.render(:ascii)
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |     a1|a2     |
#  |b1     |  b2   |
#  +-------+-------+

3.5 Border

To print border around data table you need to specify

renderer
type out of
basic
,
ascii
,
unicode
. By default
basic
is used. For instance, to output unicode border:
table = TTY::Table.new ["header1", "header2"], [["a1", "a2"], ["b1", "b2"]]
table.render :unicode
# =>
#  ┌───────┬───────┐
#  │header1│header2│
#  ├───────┼───────┤
#  │a1     │a2     │
#  │b1     │b2     │
#  └───────┴───────┘

or by creating unicode renderer:

renderer = TTY::Table::Renderer::Unicode.new(table)
renderer.render

3.5.1 Parts

The following are available border parts:

| Part | ASCII | Unicode | | ------------- |:-----:|:-------:| | top |

-
|
| | topmid |
+
|
| | top
left |
+
|
| | topright |
+
|
| | bottom |
-
|
| | bottom
mid |
+
|
| | bottomleft |
+
|
| | bottom
right |
+
|
| | mid |
-
|
| | midmid |
+
|
| | mid
left |
+
|
| | mid_right |
+
|
| | left |
|
|
| | center |
|
|
| | right |
|
|
|

Using the above border parts you can create your own border with the

border
helper:
table = TTY::Table.new ["header1", "header2"], [["a1", "a2"], ["b1", "b2"]
table.render do |renderer|
  renderer.border do
    mid          "="
    mid_mid      " "
  end
end
# =>
#  header1 header2
#  ======= =======
#  a1      a2
#  b1      b2

3.5.2 Custom

You can also create your own custom border by subclassing

TTY::Table::Border
and implementing the
def_border
method using internal DSL methods like so:
class MyBorder < TTY::Table::Border
  def_border do
    left         "$"
    center       "$"
    right        "$"
    bottom       " "
    bottom_mid   "*"
    bottom_left  "*"
    bottom_right "*"
  end
end

Next pass the border class to your table instance

render_with
method
table = TTY::Table.new ["header1", "header2"], [["a1", "a2"], ["b1", "b2"]
table.render_with MyBorder
# =>
#  $header1$header2$
#  $a1     $a2     $
#  *       *       *

3.5.3 Separator

In addition to specifying border characters you can force the table to render a separator line on each row like:

table = TTY::Table.new ["header1", "header2"], [["a1", "a2"], ["b1", "b2"]]
table.render do |renderer|
  renderer.border.separator = :each_row
end
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |a1     |a2     |
#  +-------+-------+
#  |b1     |b2     |
#  +-------+-------+

If you want more control you can provide an array of rows after which a separator will be added:

table = TTY::Table.new ["header1", "header2"], [["a1", "a2"], ["b1", "b2"], ["c1", "c2"]]
table.render do |renderer|
  renderer.border.separator = [0, 2]
end
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |a1     |a2     |
#  |b1     |b2     |
#  +-------+-------+
#  |c1     |c2     |
#  +-------+-------+

Note: if you supply a detailed list of rows to separate, then the separator between the header and the rows will not be automatically added.

You can also give the separator option a proc to control where the separators are:

table = TTY::Table.new ["header1", "header2"],
                       [["a1", "a2"], ["b1", "b2"], ["c1", "c2"], ["d1", "d2"]]
table.render do |renderer|
  renderer.border.separator = ->(row) { row == 0 || (row+1) % 2 == 0} # separate every two rows
end
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |a1     |a2     |
#  |b1     |b2     |
#  +-------+-------+
#  |c1     |c2     |
#  |d1     |d2     |
#  +-------+-------+

Finally you can also position a separator using the

:separator
key word in place of a row:
table = TTY::Table.new ["header1", "header2"],
                       [:separator, ["a1", "a2"], ["b1", "b2"]]
table << :separator << ["c1", "c2"]  # you can push separators on too!
table.render
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |a1     |a2     |
#  |b1     |b2     |
#  +-------+-------+
#  |c1     |c2     |
#  +-------+-------+

3.5.4 Style

If you want to change the display color of your border do:

table.render do |renderer|
  renderer.border.style = :green
end

All supported colors are provided by the Pastel dependency.

3.6 Filter

You can define filters that will modify individual table field value before it is rendered. A filter can be a callable such as proc.

Here's an example that formats capitalizes each field in second column skipping the header:

table = TTY::Table.new(["header1", "header2"], [["a1", "a2"], ["b1", "b2"]])
table.render do |renderer|
  renderer.filter = ->(val, row_index, col_index) do
    if col_index == 1 and !(row_index == 0)
      val.capitalize
    else
      val
    end
  end
end
# =>
#  +-------+-------+
#  |header1|header2|
#  +-------+-------+
#  |a1     |A2     |
#  +-------+-------+
#  |b1     |B2     |
#  +-------+-------+

To color even fields red on green background add filter like so:

pastel = Pastel.new

table.render do |renderer| renderer.filter = ->(val, row_index, col_index) do col_index % 2 == 1 ? pastel.red.on_green(val) : val end end

3.7 Multiline

Renderer options may include

:multiline
parameter. When set to
true
, table fields will wrap at their natural line breaks or the column widths(if provided).
table = TTY::Table.new([["First", "1"], ["Multi\nLine\nContent", "2"], ["Third", "3"]])
table.render(:ascii, multiline: true)
# =>
#  +-------+-+
#  |First  |1|
#  |Multi  |2|
#  |Line   | |
#  |Content| |
#  |Third  |3|
#  +-------+-+

When

multiline
is set to
false
, all line break characters will be escaped. In cases when the column widths are set, the content will be truncated.
table = TTY::Table.new [["First", "1"], ["Multiline\nContent", "2"], ["Third", "3"]]
table.render :ascii, multiline: false
# =>
#  +------------------+-+
#  |First             |1|
#  |Multiline\nContent|2|
#  |Third             |3|
#  +------------------+-+

3.8 Padding

Renderer also accepts

padding
option which accepts array with arguments similar to CSS padding.
[2,2,2,2]  # => pad left and right with 2 characters, add 2 lines above and below
[1,2]      # => pad left and right with 2 characters, add 1 line above and below
1          # => pad left and right with 1 character, and 1 lines above and below

Therefore, to apply padding to the example table do:

table.render(:ascii, padding: [1,2,1,2])
# =>
#  +---------+---------+
#  |         |         |
#  | header1 | header2 |
#  |         |         |
#  +---------+---------+
#  |         |         |
#  | a1      | a2      |
#  |         |         |
#  |         |         |
#  | b1      | b2      |
#  |         |         |
#  +---------+---------+

However, when adding top or bottom padding to content with line breaks, the

multiline
option needs to be set to
true
to allow for rows to span multiple lines. For example:
table = TTY::Table.new(header: ["head1", "head2"])
table << ["Multi\nLine", "Text\nthat\nwraps"]
table << ["Some\nother\ntext", "Simple"]

This would render as:

table.render(:ascii, multiline: true, padding: [1,2,1,2])
# =>
#  +---------+----------+
#  |         |          |
#  |  h1     |  head2   |
#  |         |          |
#  +---------+----------+
#  |         |          |
#  |  Multi  |  Text    |
#  |  Line   |  that    |
#  |         |  wraps   |
#  |         |          |
#  |         |          |
#  |  Some   |  Simple  |
#  |  other  |          |
#  |  text   |          |
#  |         |          |
#  +---------+----------+

3.9 Resize

You can force table to resize to the terminal full width using the

:resize
option:
table.render(:ascii, resize: true)

3.10 Width

To control table's column sizes pass

width
,
resize
options. By default table's natural column widths are calculated from the content. If the total table width does not fit in terminal window then the table is rotated vertically to preserve content.

The

resize
property will force the table to expand/shrink to match the terminal width or custom
width
. On its own the
width
property will not resize table but only enforce table vertical rotation if content overspills.

For example, given the following table:

header = ["h1", "h2", "h3"]
rows   = [["aaa1", "aa2", "aaaaaaa3"], ["b1", "b2", "b3"]]
table = TTY::Table.new(header, rows)

The result of rending to

80
columns width will produce:
table.render(width: 80, resize: true)
# =>
#  +---------+-------+------------+
#  |h1       |h2     |h3          |
#  +---------+-------+------------+
#  |aaa1     |aa2    |aaaaaaa3    |
#  |b1       |b2     |b3          |
#  +---------+-------+------------+

Contributing

  1. Fork it ( https://github.com/piotrmurach/tty-table/fork )
  2. Create your feature branch (
    git checkout -b my-new-feature
    )
  3. Commit your changes (
    git commit -am 'Add some feature'
    )
  4. Push to the branch (
    git push origin my-new-feature
    )
  5. Create a new Pull Request

This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

Copyright

Copyright (c) 2015 Piotr Murach. See LICENSE for further details.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.