{"id":13483322,"url":"https://github.com/matt-harvey/tabulo","last_synced_at":"2025-03-27T14:31:13.438Z","repository":{"id":39521689,"uuid":"89026277","full_name":"matt-harvey/tabulo","owner":"matt-harvey","description":"Plain text table generator for Ruby, with a DRY, column-based API","archived":false,"fork":false,"pushed_at":"2025-01-19T07:50:34.000Z","size":1473,"stargazers_count":245,"open_issues_count":0,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-25T15:04:19.427Z","etag":null,"topics":["ascii-table","coverage-status","ruby","streaming","table-enumerator","terminal-table","text-table","tty-table"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/matt-harvey.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-04-21T21:49:44.000Z","updated_at":"2025-03-09T14:54:11.000Z","dependencies_parsed_at":"2024-10-30T17:40:55.341Z","dependency_job_id":null,"html_url":"https://github.com/matt-harvey/tabulo","commit_stats":{"total_commits":457,"total_committers":4,"mean_commits":114.25,"dds":"0.013129102844638973","last_synced_commit":"d863bcd3fd741e37913577c774541f261bdeef5f"},"previous_names":[],"tags_count":53,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matt-harvey%2Ftabulo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matt-harvey%2Ftabulo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matt-harvey%2Ftabulo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matt-harvey%2Ftabulo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/matt-harvey","download_url":"https://codeload.github.com/matt-harvey/tabulo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245863063,"owners_count":20684781,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ascii-table","coverage-status","ruby","streaming","table-enumerator","terminal-table","text-table","tty-table"],"created_at":"2024-07-31T17:01:10.093Z","updated_at":"2025-03-27T14:31:13.406Z","avatar_url":"https://github.com/matt-harvey.png","language":"Ruby","readme":"# Tabulo\n\n[![Gem Version][GV img]][Gem Version]\n[![Documentation][DC img]][Documentation]\n[![Build Status][BS img]][Build Status]\n[![Coverage Status][CS img]][Coverage Status]\n[![Awesome][AR img]][Awesome Ruby]\n\nTabulo is a Ruby library for generating plain text tables, also known as \u0026ldquo;ASCII tables\u0026rdquo;. It is\nboth highly configurable and very easy to use.\n\n\u003ca name=\"overview\"\u003e\u003c/a\u003e\n## Overview\n\n_Quick API:_\n\n```\n\u003e puts Tabulo::Table.new(User.all, :id, :first_name, :last_name).pack\n+----+------------+-----------+\n| id | first_name | last_name |\n+----+------------+-----------+\n|  1 | John       | Citizen   |\n|  2 | Jane       | Doe       |\n+----+------------+-----------+\n```\n\n_Full API:_\n\n```\ntable = Tabulo::Table.new(User.all) do |t|\n  t.add_column(\"ID\", \u0026:id)\n  t.add_column(\"First name\", \u0026:first_name)\n  t.add_column(\"Last name\") { |user| user.last_name.upcase }\nend\n```\n\n```\n\u003e puts table.pack\n+----+------------+-----------+\n| ID | First name | Last name |\n+----+------------+-----------+\n|  1 | John       | CITIZEN   |\n|  2 | Jane       | DOE       |\n+----+------------+-----------+\n```\n\n\u003ca name=\"features\"\u003e\u003c/a\u003e\n## Features\n\n* Presents a [DRY API](#adding-columns) that is column-based, not row-based, meaning header and body rows are\n  automatically in sync\n* Lets you set [fixed column widths](#fixed-column-widths), then either [wrap](#overflow-handling)\n  or [truncate](#overflow-handling) the overflow\n* Alternatively, [\u0026ldquo;pack\u0026rdquo;](#pack) the table so that columns are auto-sized to their\n  contents, but [without overflowing the terminal](#max-table-width)\n* Cell alignment is [configurable](#cell-alignment), but has helpful content-based defaults (numbers right, strings\n  left)\n* Tabulate any `Enumerable`: the underlying collection need not be an array\n* [Step through](#enumerator) your table a row at a time, printing as you go, without waiting for the\n  underlying collection to load. In other words, have a [streaming interface](#enumerator) for free.\n* Add an optional [title](#title) to your table\n* The header row can be [repeated](#repeating-headers) at arbitrary intervals\n* Newlines within cell content are correctly handled\n* Multibyte Unicode characters are correctly handled\n* Option to [preserve whole words](#preserve-words) when wrapping content\n* Apply [colours](#colours-and-styling) and other styling to table content and borders, without breaking the table\n* Easily [transpose](#transposition) the table, so that rows are swapped with columns\n* Choose from multiple [border configurations](#borders), including Markdown, \u0026ldquo;ASCII\u0026rdquo;, and smoothly\n  joined Unicode border characters\n\nTabulo has also been ported to Crystal (with some modifications): see [Tablo](https://github.com/hutou/tablo).\n\n\u003ca name=\"contents\"\u003e\u003c/a\u003e\n## Contents\n\n  * [Overview](#overview)\n  * [Features](#features)\n  * [Table of contents](#contents)\n  * [Installation](#installation)\n  * [Detailed usage](#detailed-usage)\n     * [Creating a table](#table-initialization)\n     * [Adding columns](#adding-columns)\n        * [Quick API](#quick-api)\n        * [Full API](#full-api)\n        * [Column labels _vs_ headers](#labels-headers)\n        * [Positioning columns](#column-positioning)\n        * [Extracting column content from a hash or array](#from-arrays-hashes)\n     * [Removing columns](#removing-columns)\n     * [Adding a title](#title)\n     * [Cell alignment](#cell-alignment)\n     * [Column width, wrapping and truncation](#column-width-wrapping-and-truncation)\n        * [Configuring fixed widths](#configuring-fixed-widths)\n        * [Automating column widths](#automating-column-widths)\n        * [Configuring padding](#configuring-padding)\n        * [Overflow handling](#overflow-handling)\n        * [Wrapping at word boundaries](#preserve-words)\n        * [Manual cell wrapping](#manual-wrapping)\n     * [Formatting cell values](#formatting-cell-values)\n     * [Colours and other styling](#colours-and-styling)\n        * [Styling cell content](#styling-cell-content)\n        * [Styling column headers](#styling-column-headers)\n        * [Styling the table title](#styling-title)\n        * [Setting default styles](#default-styles)\n        * [Styling borders](#styling-borders)\n     * [Repeating headers](#repeating-headers)\n     * [Using a Table Enumerator](#enumerator)\n     * [Accessing cell values](#accessing-cell-values)\n     * [Accessing the underlying enumerable](#accessing-sources)\n     * [Transposing rows and columns](#transposition)\n     * [Border configuration](#borders)\n     * [Row dividers](#dividers)\n     * [Using a table as a snapshot rather than as a dynamic view](#freezing-a-table)\n  * [Comparison with other libraries](#motivation)\n  * [Contributing](#contributing)\n  * [License](#license)\n\n\u003ca name=\"installation\"\u003e\u003c/a\u003e\n## Installation [\u0026#x2191;](#contents)\n\nAdd this line to your application\u0026#8217;s Gemfile:\n\n```ruby\ngem 'tabulo'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself:\n\n    $ gem install tabulo\n\nTo use the gem, you need to require it in your source code as follows:\n\n```ruby\nrequire 'tabulo'\n```\n\n\u003ca name=\"detailed-usage\"\u003e\u003c/a\u003e\n## Detailed usage [\u0026#x2191;](#contents)\n\n\u003ca name=\"table-initialization\"\u003e\u003c/a\u003e\n### Creating a table [\u0026#x2191;](#contents)\n\nYou instantiate a `Tabulo::Table` by passing it an underlying `Enumerable`, being the collection of\nthings that you want to tabulate. Each member of this collection will end up\ncorresponding to a row of the table. The collection can be any `Enumerable`, for example a Ruby\n`Array`, or an ActiveRecord relation:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5])\nother_table = Tabulo::Table.new(User.all)\n```\n\nFor the table to be useful, however, it must also contain columns\u0026hellip;\n\n\u003ca name=\"adding-columns\"\u003e\u003c/a\u003e\n### Adding columns [\u0026#x2191;](#contents)\n\n\u003ca name=\"quick-api\"\u003e\u003c/a\u003e\n#### Quick API [\u0026#x2191;](#contents)\n\nWhen the columns correspond to methods on members of the underlying enumerable, you can use\nthe \u0026ldquo;quick API\u0026rdquo;, by passing a symbol directly to `Tabulo::Table.new` for each column.\nThis symbol also provides the column header:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?)\n```\n\n```\n\u003e puts table\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            5 |     false    |     true     |\n+--------------+--------------+--------------+\n```\n\n\u003ca name=\"full-api\"\u003e\u003c/a\u003e\n#### Full API [\u0026#x2191;](#contents)\n\nColumns can also be added to the table one-by-one using `add_column`. This \u0026ldquo;full API\u0026rdquo; is\nmore verbose, but provides greater configurability:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5])\ntable.add_column(:itself)\ntable.add_column(:even?)\ntable.add_column(:odd?)\n```\n\nAlternatively, you can pass an initialization block to `new`:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5]) do |t|\n  t.add_column(:itself)\n  t.add_column(:even?)\n  t.add_column(:odd?)\nend\n```\n\nWith the full API, columns can also be initialized using a callable to which each object will be\npassed to determine the value to be displayed in the table. In this case, the first argument to\n`add_column` provides the header text:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5]) do |t|\n  t.add_column(\"N\", \u0026:itself)\n  t.add_column(\"Doubled\") { |n| n * 2 }\n  t.add_column(:odd?)\nend\n```\n\n```\n\u003e puts table\n+--------------+--------------+--------------+\n|       N      |    Doubled   |     odd?     |\n+--------------+--------------+--------------+\n|            1 |            2 |     true     |\n|            2 |            4 |     false    |\n|            5 |           10 |     true     |\n+--------------+--------------+--------------+\n```\n\nThe `add_column` method can be passed a single parameter callable, as shown in the above example,\nwith the parameter representing the member of the underyling enumerable; or it can be passed\n2-parameter callable, with the second parameter representing the (0-based) index of each row. This can be\nuseful if you want to display a row number in one of the columns:\n\n```ruby\ntable = Tabulo::Table.new([\"a\", \"b\", \"c\"]) do |t|\n  t.add_column(\"Row\") { |letter, row_index| row_index }\n  t.add_column(\"Value\", \u0026:itself)\nend\n```\n\n```\n\u003e puts table\n+--------------+--------------+\n|      Row     |     Value    |\n+--------------+--------------+\n|            0 | a            |\n|            1 | b            |\n|            2 | c            |\n+--------------+--------------+\n```\n\n\u003ca name=\"labels-headers\"\u003e\u003c/a\u003e\n#### Column labels _vs_ headers [\u0026#x2191;](#contents)\n\nThe first argument to `add_column` is the called the _label_ for that column. It serves as the\ncolumn\u0026#8217;s unique identifier: only one column may have a given label per table.\n(`String`s and `Symbol`s are interchangeable for this purpose.) The label also forms the header shown\nat the top of the column, unless a separate `header:` argument is explicitly passed:\n\n```ruby\ntable.add_column(:itself, header: \"N\")\ntable.add_column(:itself2, header: \"N\", \u0026:itself)  # header need not be unique\n# table.add_column(:itself)  # would raise Tabulo::InvalidColumnLabelError, as label must be unique\n```\n\n\u003ca name=\"column-positioning\"\u003e\u003c/a\u003e\n#### Positioning columns [\u0026#x2191;](#contents)\n\nBy default, each new column is added to the right of all the other columns so far added to the\ntable. However, if you want to insert a new column into some other position, you can use the\n`before` option, passing the label of the column to the left of which you want the new column to be added:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 3], :itself, :odd?)\ntable.add_column(:even?, before: :odd?)\n```\n\n```\n\u003e puts table\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            5 |     false    |     true     |\n+--------------+--------------+--------------+\n```\n\n\u003ca name=\"from-arrays-hashes\"\u003e\u003c/a\u003e\n#### Extracting column content from a hash or array [\u0026#x2191;](#contents)\n\nSometimes the data source for the table may be a collection of hashes or arrays. For example:\n\n```ruby\ndata = [\n  { english: \"hello\", portuguese: \"bom dia\" },\n  { english: \"goodbye\", portuguese: \"adeus\" },\n]\n```\n\nor\n\n```ruby\ndata = [\n  [\"hello\", \"bom dia\"],\n  [\"goodbye\", \"adeus\"],\n]\n```\n\nTo tabulate such a collection, simply use the same mechanism as described above, passing a block to\nthe `add_column` method to tell Tabulo how to extract the data for each column from a row. For\nexample, to tabulate the first example above, you could do something like this:\n\n```ruby\ntable = Tabulo::Table.new(data) do |t|\n  t.add_column(\"English\") { |h| h[:english] }\n  t.add_column(\"Portuguese\") { |h| h[:portuguese] }\nend\n\nputs table\n```\n\nFor the second example, you could do the following:\n\n```ruby\ntable = Tabulo::Table.new(data) do |t|\n  t.add_column(\"English\") { |a| a[0] }\n  t.add_column(\"Portuguese\") { |a| a[1] }\nend\n\nputs table\n```\n\nIn both cases, the output will be as follows:\n\n```\n+--------------+--------------+\n|    English   |  Portuguese  |\n+--------------+--------------+\n| hello        | bom dia      |\n| goodbye      | adeus        |\n+--------------+--------------+\n```\n\nIf you have previously used other terminal tabulation libraries, you may be accustomed to being _required_\nto place your data into an array of hashes or arrays before you can tabulate them. Tabulo, however,\noffers an API that is more general and flexible than this; your data source can be _any_\nenumerable collection (not just an array), and each item in that collection can be _any_ object (not\nnecessarily an array or a hash). However, as shown above, it is still straightforward to tabulate an\narray of hashes or arrays, if your data source happens to take that form.\n\n\u003ca name=\"removing-columns\"\u003e\u003c/a\u003e\n### Removing columns [\u0026#x2191;](#contents)\n\nThere is also a `#remove_column` method, for deleting an existing column from a table. Pass it\nthe label of the column you want to remove:\n\n```ruby\ntable.remove_column(:even?)\n```\n\n\u003ca name=\"title\"\u003e\u003c/a\u003e\n### Adding a title [\u0026#x2191;](#contents)\n\nYou can give your table a title, using the `title` option:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 3], :itself, :even?, :odd?, title: \"Numbers\")\n```\n\n```\n\u003e puts table\n+--------------------------------------------+\n|                   Numbers                  |\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n+--------------+--------------+--------------+\n```\n\nThere is a caveat: Using the `title` option with the `:markdown` [border type](#borders) will cause\nthe rendered table to cease being valid Markdown, as unfortunately almost no markdown engines support\nadding a captions (i.e. titles) to tables.\n\n\u003ca name=\"cell-alignment\"\u003e\u003c/a\u003e\n### Cell alignment [\u0026#x2191;](#contents)\n\nBy default, column header text is center-aligned, while the content of each body cell is aligned\naccording to its data type. Numbers are right-aligned, text is left-aligned, and booleans (`false`\nand `true`) are center-aligned.\n\nThis default behaviour can be set at the table level, by passing `:center`, `:left` or `:right`\nto the `align_header` or `align_body` options when initializing the table:\n\n```ruby\ntable = Tabulo::Table.new([1, 2], :itself, :even?, align_header: :left, align_body: :right)\n```\n\nThe table-level alignment settings can be overridden for individual columns by\npassing similarly-named options to `add_column`, e.g.:\n\n```ruby\ntable.add_column(\"Doubled\", align_header: :right, align_body: :left) { |n| n * 2 }\n```\n\nIf a table title is present, it is center-aligned by default. This can be changed using the\n`align_title` option when initializing the table:\n\n```ruby\ntable = Tabulo::Table.new([1, 2], :itself, :even?, title: \"Numbers\", align_title: :left)\n```\n\n\u003ca name=\"column-width-wrapping-and-truncation\"\u003e\u003c/a\u003e\n### Column width, wrapping and truncation [\u0026#x2191;](#contents)\n\n\u003ca name=\"fixed-column-widths\"\u003e\u003c/a\u003e\n\u003ca name=\"configuring-fixed-widths\"\u003e\u003c/a\u003e\n#### Configuring fixed widths [\u0026#x2191;](#contents)\n\nBy default, column width is fixed at 12 characters, plus 1 character of padding on either side.\nThis can be adjusted on a column-by-column basis using the `width` option of `add_column`:\n\n```ruby\ntable = Tabulo::Table.new([1, 2]) do |t|\n  t.add_column(:itself, width: 6)\n  t.add_column(:even?, width: 9)\nend\n```\n\n```\n\u003e puts table\n+--------+-----------+\n| itself |   even?   |\n+--------+-----------+\n|      1 |   false   |\n|      2 |    true   |\n+--------+-----------+\n```\n\nIf you want to set the default column width for all columns of the table to something other\nthan 12, use the `column_width` option when initializing the table:\n\n```ruby\ntable = Tabulo::Table.new([1, 2], :itself, :even?, column_width: 6)\n```\n\n```\n\u003e puts table\n+--------+--------+\n| itself |  even? |\n+--------+--------+\n|      1 |  false |\n|      2 |  true  |\n+--------+--------+\n```\n\nWidths set for individual columns always override the default column width for the table.\n\n\u003ca name=\"pack\"\u003e\u003c/a\u003e\n\u003ca name=\"automating-column-widths\"\u003e\u003c/a\u003e\n#### Automating column widths [\u0026#x2191;](#contents)\n\nInstead of setting column widths \u0026ldquo;manually\u0026rdquo;, you can tell the table to sort out the widths\nitself, so that each column is just wide enough for its header and contents (plus a character\nof padding on either side):\n\n```ruby\ntable = Tabulo::Table.new([\"short\", \"here is a longer phrase\"], :itself, :size)\ntable.pack\n```\n\n```\n\u003e puts table\n+-------------------------+------+\n|          itself         | size |\n+-------------------------+------+\n| short                   |    5 |\n| here is a longer phrase |   23 |\n+-------------------------+------+\n```\n\nIf the table [title](#title) happens to be too long to for the existing width of the table, `pack`\nwill also arrange for the table to be widened sufficiently to accommodate it without wrapping:\n\n```ruby\ntable = Tabulo::Table.new([\"a\", \"b\"], :itself, :size, title: \"Here are some letters of the alphabet\")\ntable.pack\n```\n\n```\n\u003e puts table\n+---------------------------------------+\n| Here are some letters of the alphabet |\n+-------------------+-------------------+\n|       itself      |        size       |\n+-------------------+-------------------+\n| a                 |                 1 |\n| b                 |                 1 |\n+-------------------+-------------------+\n```\n\nThe `pack` method returns the table itself, so you can \u0026ldquo;pack-and-print\u0026rdquo; in one go:\n\n```ruby\nputs Tabulo::Table.new([\"short\", \"here is a longer phrase\"], :itself, :size).pack\n```\n\n\u003ca name=\"max-table-width\"\u003e\u003c/a\u003e\nYou can manually place an upper limit on the total width of the table when packing:\n\n```ruby\nputs Tabulo::Table.new([\"short\", \"here is a longer phrase\"], :itself, :size).pack(max_table_width: 24)\n```\n\n```\n+---------------+------+\n|     itself    | size |\n+---------------+------+\n| short         |    5 |\n| here is a lon |   23 |\n| ger phrase    |      |\n+---------------+------+\n```\n\nOr if you simply call `pack` with no arguments (or if you explicitly call\n`pack(max_table_width: :auto)`), the table width will automatically be capped at the\nwidth of your terminal.\n\nIf you want the table width not to be capped at all, call `pack(max_table_width: nil)`.\n\nIf the table cannot be fit within the width of the terminal, or the specified maximum width,\nthen column widths are reduced as required, with wrapping or truncation then occuring as\nnecessary (see [Overflow handling](#overflow-handling)). Under the hood, a character of width\nis deducted column by column\u0026mdash;the widest column being targetted each time\u0026mdash;until\nthe table will fit.\n\nTo resize only specific columns, `pack` takes an `except:` argument, which can be a single column\nlabel or an Array of column labels. E.g. `pack(except: :id)` will exclude the `id` column from\nresizing and let it keep its current width. This is useful if you want to prevent the addition of\nlinebreaks in your data. When using this option, other columns might be shrunk more to still make\nthe table fit within the `max_table_width`.\n\nFor even finer-grained control over column and table resizing, see the\nfor the [`#autosize_columns`](https://www.rubydoc.info/gems/tabulo/3.0.3/Tabulo/Table#autosize_columns-instance_method)\nand [`#shrink_to`](https://www.rubydoc.info/gems/tabulo/3.0.3/Tabulo/Table#shrink_to-instance_method) methods.\n\nNote that `pack`ing the table necessarily involves traversing the entire collection up front as\nthe maximum cell width needs to be calculated for each column. You may not want to do this\nif the collection is very large.\n\nNote also the effect of `pack` is to fix the column widths as appropriate to the formatted cell\ncontents given the state of the underlying collection _at the point of packing_. If the underlying\ncollection changes between that point, and when the table is printed, then the columns will _not_ be\nresized yet again on printing. This is a consequence of the table always being essentially a\n\u0026ldquo;live view\u0026rdquo; on the underlying collection: formatted contents are never cached within the\ntable itself. There are [ways around this](#freezing-a-table), however, if this is not the desired\nbehaviour\u0026mdash;see [below](#freezing-a-table).\n\n\u003ca name=\"configuring-padding\"\u003e\u003c/a\u003e\n#### Configuring padding [\u0026#x2191;](#contents)\n\nThe single character of padding either side of each column is not counted in the column width.\nThe amount of this extra padding can be configured for the table as a whole, using the `column_padding`\noption passed to `Table.new`\u0026mdash;the default value of this option being `1`.\n\nPassing a single integer to this option causes the given amount of padding to be applied to each\nside of each column. For example:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?, column_padding: 0)\n```\n\n```\n\u003e puts table.pack\n+------+-----+-----+\n|itself|even?| odd?|\n+------+-----+-----+\n|     1|false| true|\n|     2| true|false|\n|     5|false| true|\n+------+-----+-----+\n```\n\nPassing an array of _two_ integers to this option configures the left and right padding for each\ncolumn, according to the first and second element of the array, respectively. For example:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?, column_padding: [0, 2])\n```\n\n```\n\u003e puts table.pack\n+--------+-------+-------+\n|itself  |even?  | odd?  |\n+--------+-------+-------+\n|     1  |false  | true  |\n|     2  | true  |false  |\n|     5  |false  | true  |\n+--------+-------+-------+\n```\n\nNote how the padding amount is completely unaffected by the call `pack`.\n\nPadding can also be configured on a column-by-column basis, using the `padding` option when calling\n`add_column`:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5], :itself, :even?)\ntable.add_column(:odd?, padding: 3)\n```\n\n```\n\u003e puts table.pack\n+--------+-------+-----------+\n| itself | even? |    odd?   |\n+--------+-------+-----------+\n|      1 | false |    true   |\n|      2 |  true |   false   |\n|      5 | false |    true   |\n+--------+-------+-----------+\n```\n\nThis column-level `padding` setting always overrides any table-level `column_padding` setting, for\nthe column in question.\n\n\u003ca name=\"overflow-handling\"\u003e\u003c/a\u003e\n#### Overflow handling [\u0026#x2191;](#contents)\n\nBy default, if cell contents exceed their column width, they are wrapped for as many rows as\nrequired:\n\n```ruby\ntable = Tabulo::Table.new(\n  [\"hello\", \"abcdefghijklmnopqrstuvwxyz\"],\n  :itself, :length\n)\n```\n\n```\n\u003e puts table\n+--------------+--------------+\n|    itself    |    length    |\n+--------------+--------------+\n| hello        |            5 |\n| abcdefghijkl |           26 |\n| mnopqrstuvwx |              |\n| yz           |              |\n+--------------+--------------+\n```\n\nWrapping behaviour is configured for the table as a whole using the `wrap_header_cells_to` option\nfor header cells and `wrap_body_cells_to` for body cells, both of which default to `nil`, meaning\nthat cells are wrapped to as many rows as required. Passing an `Integer` limits wrapping to the given\nnumber of rows, with content truncated from that point on. The `~` character is appended to the\noutputted cell content to show that truncation has occurred:\n\n```ruby\ntable = Tabulo::Table.new(\n  [\"hello\", \"abcdefghijklmnopqrstuvwxyz\"],\n  :itself, :length,\n  wrap_body_cells_to: 1\n)\n```\n\n```\n\u003e puts table\n+--------------+--------------+\n|    itself    |    length    |\n+--------------+--------------+\n| hello        |            5 |\n| abcdefghijkl~|           26 |\n+--------------+--------------+\n```\n\nThe character used to indicate truncation, which defaults to `~`, can be configured using the\n`truncation_indicator` option passed to `Table.new`.\n\n\u003ca name=\"preserve-words\"\u003e\u003c/a\u003e\n#### Wrapping at word boundaries [\u0026#x2191;](#contents)\n\nBy passing `:word` to the `wrap_preserve` option on either table initialization (for all columns),\nor when calling `add_column` (for an individual column), whole words can be preserved when wrapping:\n\n```ruby\nsentences = [\n  \"Words are preserved.\",\n  \"Excessively long words may still be split to fit into the configured column width.\",\n]\ntable = Tabulo::Table.new(sentences, :itself, :length, column_width: 10, wrap_preserve: :word)\n```\n\n```\n\u003e puts table\n+------------+------------+\n|   itself   |   length   |\n+------------+------------+\n| Words are  |         20 |\n| preserved. |            |\n| Excessivel |         82 |\n| y long     |            |\n| words may  |            |\n| still be   |            |\n| split to   |            |\n| fit into   |            |\n| the        |            |\n| configured |            |\n| column     |            |\n| width.     |            |\n+------------+------------+\n```\n\nWhen wrapping cell content, Tabulo will never insert hyphens itself, although it will recognize existing\nhyphens, m-dashes and n-dashes as word boundaries.\n\nThe `wrap_preserve` option defaults to the value `:rune`, meaning by default it will _not_ respect word\nboundaries when wrapping (although it will always preserve whole multibyte Unicode characters).\n\n\u003ca name=\"manual-wrapping\"\u003e\u003c/a\u003e\n#### Manual cell wrapping [\u0026#x2191;](#contents)\n\nYou can \u0026ldquo;manually\u0026rdquo; wrap the content of a title, header or body cell at a particular\npoint, simply by placing a newline character, at that point:\n\n```ruby\ntable = Tabulo::Table.new(1..3) do |t|\n  t.add_column(\"The number\\nitself\", \u0026:itself)\n  t.add_column(\"Even?\", \u0026:even?)\n  t.add_column(\"Odd?\", \u0026:odd?)\nend\n```\n\n```\n\u003e puts table\n+--------------+--------------+--------------+\n|  The number  |     Even?    |     Odd?     |\n|    itself    |              |              |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n+--------------+--------------+--------------+\n```\n\nTabulo will treat any of the character combinations `\"\\n\"`, `\"\\r\\n\"` or `\"\\r\"` equally, as a line break,\nregardless of the platform it\u0026#8217;s currently being run on. This ensures things are formatted as\nexpected if, for example, you are examining content that was produced on another platform from\nthe one you\u0026#8217;re running Tabulo on.\n\n\u003ca name=\"formatting-cell-values\"\u003e\u003c/a\u003e\n### Formatting cell values [\u0026#x2191;](#contents)\n\nWhile the callable passed to `add_column` determines the underyling, calculated value in each\ncell of the column, there is a separate concept, of a \u0026ldquo;formatter\u0026rdquo;, that determines how\nthat value will be visually displayed. By default, `.to_s` is called on the underlying cell value to\n\u0026ldquo;format\u0026rdquo; it; however, you can format it differently by passing another callable to the\n`formatter` option of `add_column`:\n\n```ruby\ntable = Tabulo::Table.new(1..3) do |t|\n  t.add_column(\"N\", \u0026:itself)\n  t.add_column(\"Reciprocal\", formatter: -\u003e (n) { \"%.2f\" % n }) do |n|\n    1.0 / n\n  end\nend\n```\n\n```\n\u003e puts table\n+--------------+--------------+\n|       N      |  Reciprocal  |\n+--------------+--------------+\n|            1 |         1.00 |\n|            2 |         0.50 |\n|            3 |         0.33 |\n+--------------+--------------+\n```\n\nNote the numbers in the \u0026ldquo;Reciprocal\u0026rdquo; column in this example are still right-aligned, even though\nthe callable passed to `formatter` returns a String. Default cell alignment is determined by the type\nof the underlying cell value, not the way it is formatted. This is usually the desired result.\n\nIf you want to set the default formatter for all columns of the table to something other than\n`#to_s`, use the `formatter` option when initializing the table:\n\n```ruby\ntable = Tabulo::Table.new(1..3, formatter: -\u003e (n) { \"%.2f\" % n }) do |t|\n  t.add_column(\"N\", \u0026:itself)\n  t.add_column(\"Reciprocal\") { |n| 1.0 / n }\n  t.add_column(\"Half\") { |n| n / 2.0 }\nend\n```\n\n```\n\u003e puts table\n+--------------+--------------+--------------+\n|       N      |  Reciprocal  |     Half     |\n+--------------+--------------+--------------+\n|         1.00 |         1.00 |         0.50 |\n|         2.00 |         0.50 |         1.00 |\n|         3.00 |         0.33 |         1.50 |\n+--------------+--------------+--------------+\n```\n\nFormatters set for individual columns on calling `#add_column` always override the default formatter for\nthe table.\n\nThe `formatter` callback also has an alternative, 2-parameter version. If `formatter` is passed\na 2-parameter callable, the second parameter will be given a `CellData` instance,\ncontaining additional information about the cell that may be useful in determining how to format\nit\u0026mdash;see the [documentation](https://www.rubydoc.info/gems/tabulo/3.0.3/Tabulo/CellData.html)\nfor details.\n\n\u003ca name=\"colours-and-styling\"\u003e\u003c/a\u003e\n### Colours and other styling [\u0026#x2191;](#contents)\n\n\u003ca name=\"styling-cell-content\"\u003e\u003c/a\u003e\n#### Styling cell content [\u0026#x2191;](#contents)\n\nIn most terminals, if you want to print text that is coloured, or has certain other styles such as\nunderlining, you need to use ANSI escape sequences, either directly, or by means of a library such\nas [Rainbow](http://github.com/sickill/rainbow) that uses them internally. Tabulo needs to properly\naccount for escape sequences when performing the width calculations required to render tables.\nThe `styler` option on the `add_column` method is intended to facilitate this.\n\nFor example, suppose you have a table to which you want to add a column that\ndisplays `true` in green if a given number is even, or else displays `false` in red.\nYou can achieve this as follows using raw ANSI escape codes:\n\n```ruby\ntable.add_column(\n  :even?,\n  styler: -\u003e (cell_value, s) { cell_value ? \"\\033[32m#{s}\\033[0m\" : \"\\033[31m#{s}\\033[0m\" }\n)\n```\n\nOr, if you are using the [rainbow](https://github.com/sickill/rainbow) gem for colouring, you\ncould do the following:\n\n```ruby\nrequire \"rainbow\"\n\n# ...\n\ntable.add_column(\n  :even?,\n  styler: -\u003e (cell_value, s) { cell_value ? Rainbow(s).green : Rainbow(s).red }\n)\n```\n\nThe `styler` option should be passed a callable that takes either 2, 3 or 4 parameters.\nThe first parameter represents the underlying value of the cell (in this case a boolean indicating whether the\nnumber is even). The second parameter represents the formatted string value of that cell, i.e. the cell\ncontent after any processing by the [formatter](#formatting-cell-values). The third and fourth\nparameters are optional, and contain further information about the cell and its contents that may be useful in\ndetermining how to style it. See the\n[documentation](https://www.rubydoc.info/gems/tabulo/3.0.3/Tabulo/Table#add_column-instance_method) for details.\n\nIf the content of a cell is wrapped over multiple lines, then the `styler` will be called once\nper line, so that each line of the cell will have the escape sequence applied to it separately\n(ensuring the styling doesn\u0026#8217;t bleed into neighbouring cells).\n\nIf the content of a cell has been [truncated](#overflow-handling), then whatever colours or other\nstyling apply to the cell content will also be applied the truncation indicator character.\n\n\u003ca name=\"styling-column-headers\"\u003e\u003c/a\u003e\n#### Styling column headers [\u0026#x2191;](#contents)\n\nIf you want to apply colours or other styling to the content of a column header, as opposed\nto cells in the table body, use the `header_styler` option, e.g.:\n\n```ruby\ntable.add_column(:even?, header_styler: -\u003e (s) { \"\\033[32m#{s}\\033[0m\" })\n```\n\nThe `header_styler` option accepts a 1-, 2- or 3-parameter callable. See the\n[documentation](https://www.rubydoc.info/gems/tabulo/3.0.3/Tabulo/Table#add_column-instance_method)\nfor details.\n\n\u003ca name=\"styling-title\"\u003e\u003c/a\u003e\n#### Styling the table title [\u0026#x2191;](#contents)\n\nTo apply colours or other styling to the table title, if present, use the `title_styler` option\nwhen initializing the table. This accepts a single-parameter callable:\n\n```ruby\ntable = Tabulo::Table.new(1..5, :itself, :even?, :odd?, title: \"Numbers\", title_styler: -\u003e (s) { \"\\033[32m#{s}\\033[0m\" })\n```\n\nThe `title_styler` option accepts a 1- or 2-parameter callable. See the\n[documentation](https://www.rubydoc.info/gems/tabulo/3.0.3/Tabulo/Table#initialize-instance_method)\nfor details.\n\n\u003ca name=\"styling-borders\"\u003e\u003c/a\u003e\n#### Styling borders [\u0026#x2191;](#contents)\n\nStyling can also be applied to borders and dividing lines, using the `border_styler` option when\ninitializing the table, e.g.:\n\n```ruby\ntable = Tabulo::Table.new(1..5, :itself, :even?, :odd?, border_styler: -\u003e (s) { \"\\033[32m#{s}\\033[0m\" })\n```\n\n\u003ca name=\"default-styles\"\u003e\u003c/a\u003e\n#### Setting default styles [\u0026#x2191;](#contents)\n\nBy default, no styling is applied to the headers or body content of a column unless configured to do\nso via the `header_styler` or `styler` option when calling `add_column` for that particular column.\nIt is possible to apply styling by default to _all_ columns in a table, however, as the table initializer\nalso accepts both a `header_styler` and a `styler` option. For example, if you want all the header text\nin the table to be green, you could do:\n\n```ruby\ntable = Tabulo::Table.new(1..5, :itself, :even?, :odd?, header_styler: -\u003e (s) { \"\\033[32m#{s}\\033[0m\" })\n```\n\nNow, all columns in the table will automatically have green header text, unless overridden by another\nheader styler being passed to `#add_column`.\n\n\u003ca name=\"repeating-headers\"\u003e\u003c/a\u003e\n### Repeating headers [\u0026#x2191;](#contents)\n\nBy default, headers are only shown once, at the top of the table (`header_frequency: :start`). If\n`header_frequency` is passed `nil`, headers are not shown at all; or, if passed an `Integer` N,\nheaders are shown at the top and then repeated every N rows. This can be handy when you\u0026#8217;re looking\nat table that\u0026#8217;s taller than your terminal.\n\nE.g.:\n\n```ruby\ntable = Tabulo::Table.new(1..10, :itself, :even?, header_frequency: 5)\n```\n\n```\n\u003e puts table\n+--------------+--------------+\n|    itself    |     even?    |\n+--------------+--------------+\n|            1 |     false    |\n|            2 |     true     |\n|            3 |     false    |\n|            4 |     true     |\n|            5 |     false    |\n+--------------+--------------+\n|    itself    |     even?    |\n+--------------+--------------+\n|            6 |     true     |\n|            7 |     false    |\n|            8 |     true     |\n|            9 |     false    |\n|           10 |     true     |\n+--------------+--------------+\n```\n\nNote that if the table has a [title](#title), it will not be repeated; only column headers are repeated.\n\nOne can achieve even finer-grained control over printing of headers within the table body by stepping\nthrough the table a row at a time (using `.each` or other methods of `Enumerable`) and calling the\nthe [`formatted_header`](https://www.rubydoc.info/gems/tabulo/Tabulo/Table#formatted_header-instance_method)\nmethod in combination with [`horizontal_rule`](https://www.rubydoc.info/gems/tabulo/Tabulo%2FTable:horizontal_rule)\nto produce headers at arbitrary points in the output.\n\n\u003ca name=\"enumerator\"\u003e\u003c/a\u003e\n### Using a Table Enumerator [\u0026#x2191;](#contents)\n\nBecause it\u0026#8217;s an `Enumerable`, a `Tabulo::Table` can also give you an `Enumerator`,\nwhich is useful when you want to step through rows one at a time. In a Rails console,\nfor example, you might do this:\n\n```\n\u003e e = Tabulo::Table.new(User.find_each) do |t|\n  t.add_column(:id)\n  t.add_column(:email, width: 24)\nend.to_enum  # \u003c-- make an Enumerator\n...\n\u003e puts e.next\n+--------------+--------------------------+\n|      id      |          email           |\n+--------------+--------------------------+\n|            1 | jane@example.com         |\n=\u003e nil\n\u003e puts e.next\n|            2 | betty@example.net        |\n=\u003e nil\n```\n\nNote the use of `.find_each`: we can start printing the table without having to load the entire\nunderlying collection. (This is negated if we [pack](#pack) the table, however, since\nin that case the entire collection must be traversed up front in order for column widths to be\ncalculated.)\n\n\u003ca name=\"accessing-cell-values\"\u003e\u003c/a\u003e\n### Accessing cell values [\u0026#x2191;](#contents)\n\nEach `Tabulo::Table` is an `Enumerable` of which each element is a `Tabulo::Row`. Each `Tabulo::Row`\nis itself an `Enumerable`, of `Tabulo::Cell`. The `Tabulo::Cell#value` method will return the\nunderlying value of the cell; while `Tabulo::Cell#formatted_content` will return its formatted content\nas a string.\n\nA `Tabulo::Row` can also\nbe converted to a `Hash` for keyed access. For example:\n\n```ruby\ntable = Tabulo::Table.new(1..3, :itself, :even?, :odd?)\n\ntable.each do |row|\n  row.each { |cell| puts cell.value } # 1, false, true...2, true, false...3, false, true\n  puts row.to_h[:even?].value         # false...true...false\nend\n```\n\nThe column label (being the first argument that was passed to `add_column`, converted if necessary\nto a `Symbol`), always forms the key for the purposes of this `Hash`:\n\n```ruby\ntable = Tabulo::Table.new(1..3) do |t|\n  t.add_column(\"Number\") { |n| n }\n  t.add_column(:doubled, header: \"Number X 2\") { |n| n * 2 }\nend\n\ntable.each do |row|\n  cells = row.to_h\n  puts cells[:Number].value  # 1...2...3...\n  puts cells[:doubled].value # 2...4...6...\nend\n```\n\n\u003ca name=\"accessing-sources\"\u003e\u003c/a\u003e\n### Accessing the underlying enumerable [\u0026#x2191;](#contents)\n\nThe underlying enumerable for a table can be retrieved by calling the `sources` getter:\n\n```ruby\ntable = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?)\n```\n\n```\n\u003e table.sources\n=\u003e [1, 2, 5]\n```\n\nThere is also a corresponding setter, meaning you can reuse the same table to tabulate\na different data set, without having to reconfigure the columns and other options from scratch:\n\n```ruby\ntable.sources = [50, 60]\n```\n\n```\n\u003e table.sources\n=\u003e [50, 60]\n```\n\nIn addition, the element of the underlying enumerable corresponding to a particular\nrow can be accessed by calling the `source` method on that row:\n\n```ruby\ntable.each do |row|\n  puts row.source # 50...60...\nend\n```\n\n\u003ca name=\"transposition\"\u003e\u003c/a\u003e\n### Transposing rows and columns [\u0026#x2191;](#contents)\n\nBy default, Tabulo generates a table in which each row corresponds to a _record_, i.e. an element of\nthe underlying enumerable, and each column to a _field_. However, there are times when one instead\nwants each row to represent a field, and each column a record. This is generally the case when there\nare a small number or records but a large number of fields. To produce such a table, we can first\ninitialize an ordinary table, specifying fields as columns, and then call `transpose`, which returns\na new table in which the rows and columns are swapped:\n\n```ruby\n\u003e puts Tabulo::Table.new(-1..1, :even?, :odd?, :zero?, :pred, :succ, :abs).transpose\n```\n```\n+-------+--------------+--------------+--------------+\n|       |      -1      |       0      |       1      |\n+-------+--------------+--------------+--------------+\n| even? |     false    |     true     |     false    |\n|  odd? |     true     |     false    |     true     |\n| zero? |     false    |     true     |     false    |\n|  pred |           -2 |           -1 |            0 |\n|  succ |            0 |            1 |            2 |\n|   abs |            1 |            0 |            1 |\n+-------+--------------+--------------+--------------+\n```\n\nBy default, a header row is added to the new table, showing the string value of the element\nrepresented in that column. This can be configured, however, along with other aspects of\n`transpose`\u0026#8217;s behaviour. For details, see the\n[documentation](https://www.rubydoc.info/gems/tabulo/3.0.3/Tabulo/Table#transpose-instance_method).\n\n\u003ca name=\"borders\"\u003e\u003c/a\u003e\n### Configuring borders [\u0026#x2191;](#contents)\n\nYou can configure the kind of border and divider characters that are used when the table is printed.\nThis is done using the `border` option passed to `Table.new`. The options are as follows.\n\n`:ascii`\u0026mdash;this is the default; the table is drawn entirely with characters in the ASCII set:\n\n```\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :ascii)\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n+--------------+--------------+--------------+\n\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :ascii, title: \"Numbers\")\n+--------------------------------------------+\n|                   Numbers                  |\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n+--------------+--------------+--------------+\n```\n\n`:modern`\u0026mdash;uses smoothly joined Unicode characters:\n\n```\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :modern)\n┌──────────────┬──────────────┬──────────────┐\n│    itself    │     even?    │     odd?     │\n├──────────────┼──────────────┼──────────────┤\n│            1 │     false    │     true     │\n│            2 │     true     │     false    │\n│            3 │     false    │     true     │\n└──────────────┴──────────────┴──────────────┘\n\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :modern, title: \"Numbers\")\n┌────────────────────────────────────────────┐\n│                   Numbers                  │\n├──────────────┬──────────────┬──────────────┤\n│    itself    │     even?    │     odd?     │\n├──────────────┼──────────────┼──────────────┤\n│            1 │     false    │     true     │\n│            2 │     true     │     false    │\n│            3 │     false    │     true     │\n└──────────────┴──────────────┴──────────────┘\n```\n\n_Note: The unicode characters used for the `:modern` border may not render properly\nwhen viewing this documentation on some browsers or devices. This doesn\u0026#8217;t reflect any brokenness\nin `tabulo` itself._\n\n`:markdown`\u0026mdash;renders a GitHub flavoured Markdown table:\n\n```\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :markdown)\n|    itself    |     even?    |     odd?     |\n|--------------|--------------|--------------|\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :markdown, title: \"Numbers\")\n|                   Numbers                  |\n|    itself    |     even?    |     odd?     |\n|--------------|--------------|--------------|\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n```\n\n_However_, note that when a table is rendered using the `:markdown` border type in combination with a\n(non-`nil`) `title`, the result will be _invalid Markdown_. This is because Markdown engines do not\ngenerally support adding a caption (i.e. title) element to tables.\n\n`:blank`\u0026mdash;no border or divider characters are printed:\n\n```\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :blank)\n    itself         even?         odd?     \n            1      false         true     \n            2      true          false    \n            3      false         true     \n\n\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :blank, title: \"Numbers\")\n                  Numbers                 \n    itself         even?         odd?     \n            1      false         true     \n            2      true          false    \n            3      false         true     \n```\n\n`:reduced_ascii`\u0026mdash;similar to `:ascii`, but without vertical lines:\n\n```\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :reduced_modern)\n-------------- -------------- --------------\n    itself          even?          odd?     \n-------------- -------------- --------------\n            1       false          true     \n            2       true           false    \n            3       false          true     \n-------------- -------------- --------------\n\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :reduced_modern, title: \"Numbers\")\n--------------------------------------------\n                   Numbers                  \n-------------- -------------- --------------\n    itself          even?          odd?     \n-------------- -------------- --------------\n            1       false          true     \n            2       true           false    \n            3       false          true     \n-------------- -------------- --------------\n```\n\n`:reduced_modern`\u0026mdash;similar to `:modern`, but without vertical lines:\n\n```\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :reduced_ascii)\n────────────── ────────────── ──────────────\n    itself          even?          odd?     \n────────────── ────────────── ──────────────\n            1       false          true     \n            2       true           false    \n            3       false          true     \n────────────── ────────────── ──────────────\n\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :reduced_ascii, title: \"Numbers\")\n────────────────────────────────────────────\n                   Numbers                  \n────────────── ────────────── ──────────────\n    itself          even?          odd?     \n────────────── ────────────── ──────────────\n            1       false          true     \n            2       true           false    \n            3       false          true     \n────────────── ────────────── ──────────────\n```\n\n_Note: The unicode characters used for the `:reduced_modern` border may not render properly\nwhen viewing this documentation on some browsers or devices. This doesn\u0026#8217;t reflect any brokenness\nin `tabulo` itself._\n\n`:classic`\u0026mdash;reproduces the default behaviour in Tabulo v1; this is like the `:ascii` option,\nbut without a bottom border:\n\n```\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :classic)\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :classic, title: \"Numbers\")\n+--------------------------------------------+\n|                   Numbers                  |\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n```\n\nNote that, by default, none of the border options includes lines drawn _between_ rows in the body of the table.\nThese are configured via a separate option: see [below](#dividers).\n\n\u003ca name=\"dividers\"\u003e\u003c/a\u003e\n### Row dividers [\u0026#x2191;](#contents)\n\nTo add lines between rows in the table body, use the `row_divider_frequency` option when initializing\nthe table. The default value for this option is `nil`, meaning there are no dividing lines between\nrows. But if this option passed is a positive integer N, then a dividing line is inserted before\nevery Nth row. For example:\n\n```\n\u003e puts Tabulo::Table.new(1..6, :itself, :even?, :odd?, row_divider_frequency: 2)\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n+--------------+--------------+--------------+\n|            3 |     false    |     true     |\n|            4 |     true     |     false    |\n+--------------+--------------+--------------+\n|            5 |     false    |     true     |\n|            6 |     true     |     false    |\n+--------------+--------------+--------------+\n```\n\nIf you want a line before every row, pass `1` to `row_divider_frequency`. For example:\n\n```\n\u003e puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, row_divider_frequency: 1)\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n+--------------+--------------+--------------+\n|            2 |     true     |     false    |\n+--------------+--------------+--------------+\n|            3 |     false    |     true     |\n+--------------+--------------+--------------+\n```\n\nIn addition to these options, it is also possible to print horizontal dividers at any chosen\npoint in the table output, by stepping through the table one row at a time\nand calling the [`horizontal_rule`](https://www.rubydoc.info/gems/tabulo/Tabulo%2FTable:horizontal_rule)\nmethod as required.\n\n\u003ca name=\"freezing-a-table\"\u003e\u003c/a\u003e\n### Using a table as a snapshot rather than as a dynamic view [\u0026#x2191;](#contents)\n\nThe nature of a `Tabulo::Table` is that of a dynamic view onto the underlying `sources` enumerable\nfrom which it was initialized (or which was subsequently assigned to its `sources` attribute). That\nis, if the contents of the `sources` enumerable change subsequent to initialization of or assignment to\n`sources`, then the table when printed will show the `sources` as they are at the time of printing,\nnot as they were at the time of initialization or assignment. For example:\n\n```ruby\narr = [1, 2]\ntable = Tabulo::Table.new(arr, :itself, :even?, :odd?)\n```\n\n```\n\u003e puts table\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n+--------------+--------------+--------------+\n```\n\n```ruby\narr \u003c\u003c 3\n```\n\n```\n\u003e puts table\n+--------------+--------------+--------------+\n|    itself    |     even?    |     odd?     |\n+--------------+--------------+--------------+\n|            1 |     false    |     true     |\n|            2 |     true     |     false    |\n|            3 |     false    |     true     |\n+--------------+--------------+--------------+\n```\n\nIn this example, even though no direct mutations have been made to `table`, the result\nof calling `puts table` has changed, in virtue of a mutation on the underyling enumerable `arr`.\n\nA similar behaviour can be seen when `sources` is an ActiveRecord query, and there\nare changes to the relevant database table(s) such that the result of the query changes. This is\nworth bearing in mind when calling [`pack`](#pack) on a table, since if the `sources` enumerable\nchanges between `pack`ing and printing, then the column widths calculated by the `pack` method\nmay no longer be \u0026ldquo;just right\u0026rdquo; given the changed `sources`.\n\nIf this is not the desired behaviour, there are ways around this. For example, if dealing with an\nActiveRecord relation, you can convert the query to a plain array before initializing the table:\n\n```ruby\nsources = User.all.to_a\ntable = Tabulo::Table.new(sources, :id, :first_name, :last_name)\ntable.pack\nputs table\n```\n\nPassing an `Array` rather than the ActiveRecord query directly means that if there are changes to\nthe content of the `users` database table, these will not be reflected in the rendered content of\nthe `Tabulo::Table` (unless some of the `Tabulo::Table` columns are based on callables that perform\nfurther database queries when called\u0026hellip;).\n\nNote that it is also possible simply to store the string value of a table for later use,\nrather than the table itself:\n\n```ruby\nrendered_table = Tabulo::Table.new(1..10, :itself, :even?, :odd?).pack.to_s\n```\n\n\u003ca name=\"motivation\"\u003e\u003c/a\u003e\n## Comparison with other libraries [\u0026#x2191;](#contents)\n\nThere are other libraries for generating plain text tables in Ruby. Popular among these are:\n\n* [terminal-table](https://github.com/tj/terminal-table)\n* [tty-table](https://github.com/piotrmurach/tty-table)\n* [table\\_print](https://github.com/arches/table_print)\n* [hirb](https://github.com/cldwalker/hirb)\n\n*DISCLAIMER: My comments regarding these other libraries are based only on my own, possibly flawed\nreading of the documentation for, and experimentation with, these libraries at the time of my\nwriting this. Their APIs, features or documentation may well change between when I write this, and\nwhen you read it. Please consult the libraries\u0026#8217; own documentation for yourself, rather than relying\non these comments.*\n\nWhile these libraries have their strengths, I have personally found that, for the common use case of\nprinting a table on the basis of some underlying enumerable collection (such as an ActiveRecord\nquery result), using these libraries feels more cumbersome than it could be.\n\nFor example, suppose we have called `User.all` from the Rails console, and want to print\na table showing the email, first name, last name and ID of each user,\nwith column headings. Also, we want the ID column to be right-aligned, because it\u0026#8217;s a number.\n\nIn **terminal-table**, we could achieve this as follows:\n\n```ruby\nrows = User.all.map { |u| [u.email, u.first_name, u.last_name, u.id] }\nheadings = [\"email\", \"first name\", \"last name\", \"id\"]\ntable = Terminal::Table.new(headings: headings, rows: rows)\ntable.align_column(3, :right)\nputs table\n```\n\nThe problem here is that there is no single source of knowledge about which columns\nappear, and in which order. If we want to add another column to the left of \u0026ldquo;email\u0026rdquo;,\nwe need to amend the rows array, and the headings array, and the index passed to `align_column`.\nWe bear the burden of keeping these three in sync. This is not be a big deal for small one-off\ntables, but for tables that have many columns, or that are constructed\ndynamically based on user input or other runtime factors determining the columns\nto be included, this can be a hassle and a source of brittleness.\n\n**tty-table** has a somewhat different API to `terminal-table`. It offers both a\n\u0026ldquo;row-based\u0026rdquo; and a \u0026ldquo;column-based\u0026rdquo; method of initializing a table. The row-based method\nis similar to `terminal-table`\u0026#8217;s in that it burdens the developer with syncing the\ncolumn ordering across multiple code locations. The \u0026ldquo;column-based\u0026rdquo; API for `tty-table`, on\nthe other hand, seems to avoid this problem. One way of using it is like this:\n\n```ruby\nusers = User.all\ntable = TTY::Table.new [\n  {\n    \"email\" =\u003e users.map(\u0026:email),\n    \"first name\" =\u003e users.map(\u0026:first_name),\n    \"last name\" =\u003e users.map(\u0026:last_name),\n    \"id\" =\u003e users.map(\u0026:id),\n  }\n]\nputs table\n```\n\nWhile this doesn\u0026#8217;t seem too bad, it does mean that the underlying collection (`users`) has to\nbe traversed multiple times, once for each column, which is inefficient, particularly\nif the underlying collection is large. In addition, it\u0026#8217;s not clear how to pass separate\nformatting information for each column when initializing in this way. (Perhaps there is a way to do\nthis, but if there is, it doesn\u0026#8217;t seem to be documented.) So it seems we still have to use\n`table.align_column(3, :right)`, which again burdens us with keeping the column index\npassed to `align_column` in sync.\n\nAs for **table\\_print**, this is a handy gem for quickly tabulating ActiveRecord\ncollections from the Rails console. `table_print` is similar to `tabulo` in that it has a\ncolumn-based API, so it doesn\u0026#8217;t suffer from the multiple-source-of-knowledge issue in regards to\ncolumn orderings. However, it lacks certain other useful features, such as the ability to repeat\nheaders every N rows, the automatic alignment of columns based on cell content (numbers right,\nstrings left), and a quick and easy way to automatically resize columns to accommodate cell content\nwithout overflowing the terminal. Also, as of the time of writing, `table_print`\u0026#8217;s last significant\ncommit (ignoring a deprecation warning fix in April 2018) was in March 2016.\n\nFinally, it is worth mentioning the **hirb** library. This is similar to `table_print`, in that\nit\u0026#8217;s\nwell suited to quickly displaying ActiveRecord collections from the Rails console. However, like\n`table_print`, there are certain useful features it\u0026#8217;s lacking; and using it outside the console\nenvironment seems cumbersome. Moreover, it seems no longer to be maintained. At the time of writing,\nits last commit was in March 2015.\n\n\u003ca name=\"contributing\"\u003e\u003c/a\u003e\n## Contributing [\u0026#x2191;](#contents)\n\nIssues and pull requests are welcome on GitHub at https://github.com/matt-harvey/tabulo.\n\nTo start working on Tabulo, `git clone` and `cd` into your fork of the repo, then run `bin/setup` to\ninstall dependencies.\n\n`bin/console` will give you an interactive prompt that will allow you to experiment; and\n`bundle exec rake spec` will run the test suite. For a list of other Rake tasks that are available in\nthe development environment, run `bundle exec rake -T`.\n\n\u003ca name=\"license\"\u003e\u003c/a\u003e\n## License [\u0026#x2191;](#contents)\n\nThe gem is available as open source under the terms of the [MIT\nLicense](http://opensource.org/licenses/MIT).\n\n[Gem Version]: https://rubygems.org/gems/tabulo\n[Documentation]: http://www.rubydoc.info/gems/tabulo\n[Build Status]: https://github.com/matt-harvey/tabulo/actions/workflows/tests.yml\n[Coverage Status]: https://coveralls.io/github/matt-harvey/tabulo\n[Awesome Ruby]: https://github.com/markets/awesome-ruby#cli-utilities\n\n[GV img]: https://img.shields.io/gem/v/tabulo.svg\n[DC img]: https://img.shields.io/badge/documentation-v3.0.3-blue.svg\n[BS img]: https://github.com/matt-harvey/tabulo/actions/workflows/tests.yml/badge.svg\n[CS img]: https://img.shields.io/coveralls/matt-harvey/tabulo.svg\n[AR img]: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\n","funding_links":[],"categories":["CLI Utilities","Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatt-harvey%2Ftabulo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatt-harvey%2Ftabulo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatt-harvey%2Ftabulo/lists"}