Tablet
View SourceTablet 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
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
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.
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.Thanks to the Rust tabled project for showing what's possible.