Hex version API docs CircleCI Coverage Status REUSE status

Tablet renders tabular data as text for output to the console or any where else. Give it data in either of the following common tabular data shapes:

# List of matching maps (atom or string keys)
data = [
  %{"id" => 1, "name" => "Puck"},
  %{"id" => 2, "name" => "Nick Bottom"}
]

# List of matching key-value lists
data = [
  [{"id", 1}, {"name", "Puck"}],
  [{"id", 2}, {"name", "Nick Bottom"}]
]

Then call Tablet.puts/2:

Tablet.puts(data)
#=> id  name
#=> 1   Puck
#=> 2   Nick Bottom

While this shows a table with minimal styling, it's possible to create fancier tables with colors, borders and more.

Here are some of Tablet's features:

  • Kino.DataTable-inspired API for ease of switching between Livebook and console output
  • Small. No runtime dependencies. Intentionally minimal feature scope.
  • Emoji and CJK character width calculations w/o external dependencies
  • Multi-column wrapping for tables with many rows and few columns
  • Built-in styles and optional callback interface for customization
  • Supports IO.ANSI.ansidata throughout for color, italics, and more in cells and styling
  • Supports usage_rules for helping LLMs make beautiful tables

Run in Livebook

If you're comparing tabular data rendering libraries, Tablet sacrifices flexibility in how individual cells can be rendered for small size. Color selection, text alignment, border characters, etc. are all grouped into the style. If you're happy with the built-in styles, rendering may be a one-liner. See table_rex for another option that has more flexibility in cell layout.

Example

Here's a more involved example:

iex> data = [
...>   %{planet: "Mercury", orbital_period: 88},
...>   %{planet: "Venus", orbital_period: 224.701},
...>   %{planet: "Earth", orbital_period: 365.256},
...>   %{planet: "Mars", orbital_period: 686.971}
...> ]
iex> formatter = fn
...>   :__header__, :planet -> {:ok, "Planet"}
...>   :__header__, :orbital_period -> {:ok, "Orbital Period"}
...>   :orbital_period, value -> {:ok, "\#{value} days"}
...>   _, _ -> :default
...> end
iex> Tablet.render(data, keys: [:planet, :orbital_period], formatter: formatter)
...>    |> IO.ANSI.format(false)
...>    |> IO.chardata_to_string()
"Planet   Orbital Period\n" <>
"Mercury  88 days       \n" <>
"Venus    224.701 days  \n" <>
"Earth    365.256 days  \n" <>
"Mars     686.971 days  \n"

Note that normally you'd call IO.ANSI.format/2 without passing false to get colorized output and also call IO.puts/2 to write to a terminal.

Data formatting and column headers

Tablet naively converts data values and constructs column headers to IO.ANSI.ansidata/0. This may not be what you want. To customize this, pass a 2-arity function using the :formatter option. That function takes the key and value as arguments and should return {:ok, ansidata}. The special key :__header__ is passed when constructing header row. Return :default to use the default conversion.

Styling

Various table output styles are supported by passing an atom or 1-arity function to the :style parameter.

See the Style Gallery for the built-in styles.

If the built-in styles don't suffice, it is possible for you to add your own by creating a function of the type Tablet.style_function/0. Due to the desire to minimize the main Tablet code as much as possible, only a few helper functions are available. See the built-in styles for code examples.

Ansidata

Tablet takes advantage of IO.ANSI.ansidata/0 everywhere. This makes it easy to apply styling, colorization, and other transformations. However, it can be hard to read. It's highly recommended to either call simplify/1 to simplify the output for review or to call IO.ANSI.format/2 and then IO.puts/2 to print it.

In a nutshell, IO.ANSI.ansidata/0 lets you create lists of strings to print and intermix atoms like :red or :blue to indicate where ANSI escape sequences should be inserted if supported. Tablet actually doesn't know what any of the atoms means and passes them through. Elixir's IO.ANSI module does all of the work. If fact, if you find IO.ANSI too limited, then you could use an alternative like bunt and include atoms like :chartreuse which its formatter will understand.

Notes and acknowledgments

  1. The implementation is no longer simple. Multi-line cell support really put it over the edge, but I still hope to figure out how to simplify it again.

  2. Thanks to Claude 3.7 for creating tons of example uses of Tablet so I could see how it looked. It drove me to usage-rules.md due to its epic butchering of ansidata.

  3. Thanks to the Rust tabled project for showing what's possible.