Current section
Files
Jump to
Current section
Files
lib/scribe.ex
defmodule Scribe do
@moduledoc ~S"""
Pretty-print tables of structs and maps
## Usage
A common use case is printing results from an Ecto query.
# %User{id: nil, email: nil}
iex(1)> User |> limit(5) |> Repo.all() |> Scribe.print()
+-------------+----------------------------+------+
| :__struct__ | :email | :id |
+-------------+----------------------------+------+
| User | "myles_fisher@beahan.com" | 5171 |
| User | "dawson_bartell@lynch.org" | 4528 |
| User | "hassan1972@langworth.com" | 1480 |
| User | "kiera.schulist@koch.com" | 2084 |
| User | "cynthia1970@mann.name" | 6599 |
+-------------+----------------------------+------+
## Pagination
Scribe uses [pane](https://github.com/codedge-llc/pane) to paginate large tables.
Use with `Scribe.console/2`.
# %User{id: nil, email: nil, first_name: nil, last_name: nil}
iex(1)> User |> limit(5) |> Repo.all |> Scribe.console
+-------------+------------------------+-------------+-------+------------+
| :__struct__ | :email | :first_name | :id | :last_name |
+-------------+------------------------+-------------+-------+------------+
| User | "celestine_satterfield | "Gene" | 9061 | "Krajcik" |
| User | "lynn1978@bednar.org" | "Maeve" | 9865 | "Gerlach" |
| User | "melisa1975@hilll.biz" | "Theodora" | 2262 | "Wunsch" |
| User | "furman.grady@ryan.org | "Oswaldo" | 4977 | "Simonis" |
| User | "caesar_hirthe@reynold | "Arjun" | 3907 | "Prohaska" |
+-------------+------------------------+-------------+-------+------------+
[1 of 1] (j)next (k)prev (q)quit
## Printing Custom Tables
`Scribe.print/2` takes a list of of columns on the `:data` options key to
customize output. You can use either the atom key or customize the header
with `{"Custom Title", :key}`.
# %User{id: nil, email: nil, first_name: nil, last_name: nil}
User
|> limit(5)
|> Repo.all
|> Scribe.print(data: [{"ID", :id}, :first_name, :last_name])
+------+--------------+-------------+
| "ID" | :first_name | :last_name |
+------+--------------+-------------+
| 9061 | "Gene" | "Krajcik" |
| 9865 | "Maeve" | "Gerlach" |
| 2262 | "Theodora" | "Wunsch" |
| 4977 | "Oswaldo" | "Simonis" |
| 3907 | "Arjun" | "Prohaska" |
+------+--------------+-------------+
### Function Columns
You can specify functions that take the given row's struct or map as its only argument.
# %User{id: nil, email: nil, first_name: nil, last_name: nil}
results =
User
|> limit(5)
|> Repo.all
|> Scribe.print(data: [{"ID", :id}, {"Full Name", fn(x) -> "#{x.last_name}, #{x.first_name}" end}])
+--------------------------+----------------------------------------------+
| "ID" | "Full Name" |
+--------------------------+----------------------------------------------+
| 9061 | "Krajcik, Gene" |
| 9865 | "Gerlach, Maeve" |
| 2262 | "Wunsch, Theodora" |
| 4977 | "Simonis, Oswaldo" |
| 3907 | "Prohaska, Arjun" |
+--------------------------+----------------------------------------------+
## Styling Options
### Width
Pass a `width` option to define table width.
iex> Scribe.print(data, width: 80)
+-------------------+-----------------------------------------------------+
| :id | :key |
+-------------------+-----------------------------------------------------+
| 910 | "B1786AC67B4DEB19" |
| 313 | "30CB8A2DE4750070" |
| 25 | "D0859205FC7E7298" |
| 647 | "8F0060AD0BD6AB04" |
| 253 | "65509A684D619182" |
+-------------------+-----------------------------------------------------+
### Disable Colors
iex> Scribe.print(data, colorize: false)
### Text Alignment
Pass an `alignment` option of `:left`, `:center`, or `:right` for text alignment.
Defaults to `:left`.
iex> Scribe.print(data, alignment: :center)
+------------------------------+------------+--------+
| :body | :current | :id |
+------------------------------+------------+--------+
| "A rather short string." | true | 1234 |
| "A rather short string." | false | 2222 |
| "A rather short string." | true | 4444 |
+------------------------------+------------+--------+
iex> Scribe.print(data, alignment: :right)
+------------------------------+------------+--------+
| :body | :current | :id |
+------------------------------+------------+--------+
| "A rather short string." | true | 1234 |
| "A rather short string." | false | 2222 |
| "A rather short string." | true | 4444 |
+------------------------------+------------+--------+
### Styles
Scribe supports five styling formats natively, with support for custom adapters.
See more in `Scribe.Style`.
"""
alias Scribe.Table
@typedoc ~S"""
Printable data. Can be either a struct, map, or list of structs/maps.
"""
@type data :: [map] | [struct] | map | struct
@typedoc ~S"""
Options for configuring table output.
- `:alignment` - Define text alignment in cells. Defaults to `:left`.
- `:colorize` - When `false`, disables colored output. Defaults to `true`
- `:data` - Defines table headers
- `:device` - Where to print (defaults to STDOUT)
- `:style` - Style callback module. Defaults to `Scribe.Style.Default`
- `:width` - Defines table width. Defaults to `:infinite`
"""
@type format_opts :: [
alignment: atom,
colorize: boolean,
data: [...],
style: module,
width: integer
]
@doc ~S"""
Prints a table from given data.
## Examples
iex> print([])
:ok
iex> Scribe.print(%{key: :value, test: 1234}, colorize: false)
+----------+---------+
| :key | :test |
+----------+---------+
| :value | 1234 |
+----------+---------+
:ok
"""
@spec print(data, format_opts) :: :ok
def print(_results, opts \\ [])
def print([], _opts), do: :ok
def print(results, opts) do
dev = opts |> Keyword.get(:device, :stdio)
results = results |> format(opts)
dev |> IO.puts(results)
end
@doc ~S"""
Paginates data and starts a pseudo-interactive console.
"""
@spec console(data, format_opts) :: no_return
def console(results, opts \\ []) do
results
|> format(opts)
|> Pane.console()
end
@doc ~S"""
Prints a table from given data and returns the data.
Useful for inspecting pipe chains.
## Examples
iex> Scribe.inspect([])
[]
iex> Scribe.inspect(%{key: :value, test: 1234}, colorize: false)
+----------+---------+
| :key | :test |
+----------+---------+
| :value | 1234 |
+----------+---------+
%{test: 1234, key: :value}
"""
@spec inspect(data, format_opts) :: data
def inspect(results, opts \\ []) do
print(results, opts)
results
end
@doc ~S"""
Formats data into a printable table string.
## Examples
iex> format([])
:ok
iex> format(%{test: 1234}, colorize: false)
"+---------+\n| :test |\n+---------+\n| 1234 |\n+---------+\n"
"""
@spec format(data) :: String.t() | :ok
def format(_results, opts \\ [])
def format([], _opts), do: :ok
def format(results, opts) when not is_list(results) do
format([results], opts)
end
def format(results, opts) do
keys = fetch_keys(results, opts[:data])
headers = map_string_values(keys)
data = Enum.map(results, &map_string_values(&1, keys))
table = [headers | data]
Table.format(table, Enum.count(table), Enum.count(keys), opts)
end
defp map_string_values(keys), do: Enum.map(keys, &string_value(&1))
defp map_string_values(row, keys), do: Enum.map(keys, &string_value(row, &1))
defp string_value(%{name: name, key: _key}) do
name
end
defp string_value(map, %{name: _name, key: key}) when is_function(key) do
map |> key.()
end
defp string_value(map, %{name: _name, key: key}) do
map |> Map.get(key)
end
defp fetch_keys([first | _rest], nil), do: fetch_keys(first)
defp fetch_keys(_list, opts), do: process_headers(opts)
defp process_headers(opts) do
for opt <- opts do
case opt do
{name, key} -> %{name: name, key: key}
key -> %{name: key, key: key}
end
end
end
defp fetch_keys(map) do
map
|> Map.keys()
|> Enum.sort()
|> process_headers()
end
end