Packages
io_ansi_table
0.2.0
1.0.40
1.0.39
1.0.38
1.0.37
1.0.36
1.0.34
1.0.33
1.0.32
1.0.31
1.0.30
1.0.29
1.0.28
1.0.27
1.0.26
1.0.25
1.0.24
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.17
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.4.32
0.4.31
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Writes data to "stdio" in a table with borders and colors. Can choose a table style to change the look of the table.
Current section
Files
Jump to
Current section
Files
lib/io/ansi/table/formatter.ex
# Based on the book "Programming Elixir" by Dave Thomas.
defmodule IO.ANSI.Table.Formatter do
@moduledoc """
Prints a table to STDOUT applying a table style to a list of key-value
collections.
"""
alias IO.ANSI.Table.{Config, Formatter.Helper, Style}
@type collection :: map | Keyword.t
@type collection_key :: String.t | atom | charlist
@type column :: [String.t]
@type column_width :: non_neg_integer
@type row :: [String.t]
Mix.Project.config[:config_path] |> Mix.Config.read! |> Mix.Config.persist
@external_resource Path.expand Mix.Project.config[:config_path]
@app Mix.Project.config[:app]
@max_width_range Application.get_env @app, :max_width_range
@lower_max_width @max_width_range.first
@upper_max_width @max_width_range.last
@doc """
Takes a list of key-value `collections`, the (maximum) number of
`collections` to format, whether to ring the `bell`, a table `style` and
up to six `options`:
- `headers` - to identify each column (keys)
- `key headers` - to sort the `collections` on
- `header fixes` - to alter the `headers`
- `align attrs` - to align column elements
- `margins` - to position the table
- `max width` - to cap column widths
Prints a table to STDOUT of the values in each selected `collection`.
The columns are identified by successive `headers` in order.
We calculate the width of each column to fit the longest element
in that column, also considering the `header` itself. However, the
`max width` option prevails.
If the number of `collections` given is positive, we format
the first _n_ `collections` in the list, once sorted. If negative,
the last _n_ ones.
## Parameters
- `collections` - list of collections (maps or keywords)
- `count` - number of collections to format (integer)
- `bell?` - ring the bell? (boolean)
- `style` - table style (atom)
- `options` - up to six options as described (keyword)
## Options
- `:headers` - defaults to config for `:headers` (list)
- `:key_headers` - defaults to config for `:key_headers` (list)
- `:header_fixes` - defaults to config for `:header_fixes` (map)
- `:align_attrs` - defaults to config for `:align_attrs` (map)
- `:margins` - defaults to config for `:margins` (keyword)
- `:max_width` - defaults to config for `:max_width` (non neg integer)
## Table styles
#{Style.texts " - `&style`&filler - ¬e\n"}
## Examples
alias IO.ANSI.Table.Formatter
people = [
%{name: "Mike", likes: "ski, arts", date_of_birth: "1992-04-15"},
%{name: "Mary", likes: "reading" , date_of_birth: "1985-07-11"},
%{name: "Ray" , likes: "cycling" , date_of_birth: "1977-08-28"}
]
Formatter.print_table(
people, 3, true, :dark,
headers: [:name, :date_of_birth, :likes],
key_headers: [:date_of_birth],
header_fixes: %{~r[ of ]i => " of "},
align_attrs: %{date_of_birth: :right},
margins: [top: 2, bottom: 2, left: 2]
)
## 
iex> alias ExUnit.CaptureIO
iex> alias IO.ANSI.Table.Formatter
iex> people = [
...> %{name: "Mike", likes: "ski, arts", date_of_birth: "1992-04-15"},
...> %{name: "Mary", likes: "reading" , date_of_birth: "1985-07-11"},
...> %{name: "Ray" , likes: "cycling" , date_of_birth: "1977-08-28"}
...> ]
iex> CaptureIO.capture_io fn ->
...> Formatter.print_table(
...> people, 3, false, :dashed,
...> headers: [:name, :date_of_birth, :likes],
...> key_headers: [:date_of_birth],
...> header_fixes: %{~r[ of ]i => " of "},
...> align_attrs: %{date_of_birth: :right},
...> margins: [top: 0, bottom: 0, left: 0]
...> )
...> end
\"""
+------+---------------+-----------+
| Name | Date of Birth | Likes |
+------+---------------+-----------+
| Ray | 1977-08-28 | cycling |
| Mary | 1985-07-11 | reading |
| Mike | 1992-04-15 | ski, arts |
+------+---------------+-----------+
\"""
"""
@spec print_table([collection], integer, boolean, Style.t, Keyword.t) ::
:ok
def print_table(collections, count, bell?, style, options \\ []) do
headers = Keyword.get(options, :headers, Config.headers)
key_headers = Keyword.get(options, :key_headers, Config.key_headers)
header_fixes = Keyword.get(options, :header_fixes, Config.header_fixes)
align_attrs = Keyword.get(options, :align_attrs, Config.align_attrs)
margins = Config.margins Keyword.get(options, :margins)
max_width = Keyword.get(options, :max_width, Config.max_width)
collections =
collections
|> Stream.map(&take &1, headers) # optional
|> Enum.sort_by(&key_for &1, key_headers)
|> Enum.take(count)
column_widths =
[Map.new(headers, &{&1, titlecase(&1, header_fixes)}) | collections]
|> columns(headers)
|> column_widths(max_width) # => max widths of column values or headers
rows = rows(collections, headers)
Helper.print_table(
rows, headers, key_headers,
header_fixes, align_attrs, margins, column_widths, style, bell?
)
end
@spec take(collection, [collection_key]) :: collection
defp take(collection, headers) when is_map(collection) do
Map.take collection, headers
end
defp take(collection, headers) when is_list(collection) do
Keyword.take collection, headers
end
@doc """
Takes a key-value `collection` and a list of `keys`.
Returns a sort key for the `collection`.
## Examples
iex> alias IO.ANSI.Table.Formatter
iex> collection = [one: '1', two: "2", three: 3.0, four: 4]
iex> keys = [:three, :one, :four]
iex> Formatter.key_for(collection, keys)
"3.014"
"""
@spec key_for(collection, [collection_key]) :: String.t
def key_for(collection, keys) do
Enum.map_join keys, &collection[&1]
end
@doc """
Takes a list of key-value `collections` and a list of `keys`.
Returns a list of columns, each column being a list of string values
selected from each `collection` on a given `key` and repeatedly on
successive `keys` in order.
## Examples
iex> alias IO.ANSI.Table.Formatter
iex> list = [
...> %{a: 1, b: 2, c: 3},
...> %{a: 4, b: 5, c: 6}
...> ]
iex> Formatter.columns list, [:c, :a]
[["3", "6"], ["1", "4"]]
iex> alias IO.ANSI.Table.Formatter
iex> list = [
...> %{:one => "1", '2' => 2.0, "3" => :three},
...> %{:one => '4', '2' => "5", "3" => 000006}
...> ]
iex> Formatter.columns list, ["3", :one, '2']
[["three", "6"], ["1", "4"], ["2.0", "5"]]
"""
@spec columns([collection], [collection_key]) :: [column]
def columns(collections, keys) do
for key <- keys do
for collection <- collections, do: to_string collection[key]
end
end
@doc """
Takes a list of key-value `collections` and a list of `keys`.
Returns a list of rows, each row being a list of string values
orderly selected on successive `keys` from a given `collection` and
repeatedly for each `collection` in turn.
## Examples
iex> alias IO.ANSI.Table.Formatter
iex> list = [
...> %{a: 1, b: 2, c: 3},
...> %{a: 4, b: 5, c: 6}
...> ]
iex> Formatter.rows list, [:c, :a]
[["3", "1"], ["6", "4"]]
iex> alias IO.ANSI.Table.Formatter
iex> list = [
...> %{:one => "1", '2' => 2.0, "3" => :three},
...> %{:one => '4', '2' => "5", "3" => 000006}
...> ]
iex> Formatter.rows list, ["3", :one, '2']
[["three", "1", "2.0"], ["6", "4", "5"]]
"""
@spec rows([collection], [collection_key]) :: [row]
def rows(collections, keys) do
for collection <- collections do
for key <- keys, do: to_string collection[key]
end
end
@doc """
Given a list of `columns` (string sublists), returns a list containing
the width of each `column` capped by `maximum width`.
## Examples
iex> alias IO.ANSI.Table.Formatter
iex> data = [["cat", "wombat", "elk"], ["mongoose", "ant", "gnu"]]
iex> Formatter.column_widths(data)
[6, 8]
iex> alias IO.ANSI.Table.Formatter
iex> data = [["cat", "wombat", "elk"], ["mongoose", "ant", "gnu"]]
iex> Formatter.column_widths(data, 7)
[6, 7]
"""
@spec column_widths([column], non_neg_integer) :: [column_width]
def column_widths(columns, max_width \\ @upper_max_width)
def column_widths(columns, max_width) when
is_integer(max_width) and max_width >= @lower_max_width
do
for column <- columns do
column |> Enum.map(&String.length/1) |> Enum.max |> min(max_width)
end
end
def column_widths(columns, _max_width) do
column_widths(columns, @lower_max_width)
end
@doc ~S"""
Uppercases the first letter of every "word" of a `title` (must be
convertible to a string).
Any underscore is considered a space and consecutive
whitespace characters are treated as a single occurrence.
Leading and trailing whitespace characters are removed.
If a map of `fixes` is given, all occurrences of each `fix` key in the
`title` will be replaced with the corresponding `fix` value.
## Examples
iex> alias IO.ANSI.Table.Formatter
iex> Formatter.titlecase " son of a gun ", %{
...> ~r[ of ]i => " of ",
...> ~r[ a ]i => " a "
...> }
"Son of a Gun"
iex> alias IO.ANSI.Table.Formatter
iex> Formatter.titlecase "_date___of_birth_", %{
...> ~r[ of ]i => " of "
...> }
"Date of Birth"
iex> alias IO.ANSI.Table.Formatter
iex> Formatter.titlecase :" _an_ _oDD case_ "
"An ODD Case"
"""
@spec titlecase(collection_key, map) :: String.t
def titlecase(title, fixes \\ %{}) do
import Enum, only: [map_join: 3, reduce: 3]
import String, only: [first: 1, replace: 3, slice: 2, split: 3, upcase: 1]
title =
title
|> to_string
|> split(~r/(_| )+/, trim: true)
|> map_join(" ", &(upcase(first &1) <> slice &1, 1..-1))
reduce fixes, title, &replace(&2, elem(&1, 0), elem(&1, 1))
end
end