Packages
io_ansi_table
1.0.21
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/spec.ex
defmodule IO.ANSI.Table.Spec do
@moduledoc """
A table spec struct and functions for the IO ANSI Table app.
The table spec struct contains the following fields:
* initial fields:
- `spec_name`,
- `headers`,
- `align_specs`,
- `bell`,
- `count`,
- `header_fixes`,
- `margins`,
- `max_width`,
- `sort_specs`,
- `sort_symbols`,
- `style`
* derived fields:
- `align_attrs`,
- `headings`,
- `left_margin`,
- `sort_attrs`
* data dependent fields:
- `column_widths`
- `rows`
"""
use PersistConfig
alias __MODULE__
alias IO.ANSI.Plus, as: ANSI
alias IO.ANSI.Table.{Column, Header, LineType, Row, Style}
@default_bell get_env(:default_bell)
@default_count get_env(:default_count)
@default_margins get_env(:default_margins)
@default_max_width get_env(:default_max_width)
@default_sort_symbols get_env(:default_sort_symbols)
@default_style get_env(:default_style)
@enforce_keys [
# initial fields
:spec_name,
:headers,
:align_specs,
:bell,
:count,
:header_fixes,
:margins,
:max_width,
:sort_specs,
:sort_symbols,
:style
]
defstruct [
# initial fields
:spec_name,
:headers,
:align_specs,
:bell,
:count,
:header_fixes,
:margins,
:max_width,
:sort_specs,
:sort_symbols,
:style,
# derived fields
align_attrs: [],
headings: [],
left_margin: "",
sort_attrs: [],
# data dependent fields
column_widths: [],
rows: []
]
@typedoc "A table spec struct for the IO ANSI Table app"
@type t :: %Spec{
# initial fields
spec_name: String.t(),
headers: [Header.t(), ...],
align_specs: [Header.align_spec()],
bell: boolean,
count: integer,
header_fixes: %{optional(String.t() | Regex.t()) => String.t()},
margins: Keyword.t(),
max_width: pos_integer,
sort_specs: [Header.sort_spec()],
sort_symbols: [Header.sort_symbol()],
style: Style.t(),
# derived fields
align_attrs: [Header.align_attr()],
headings: [String.t()],
left_margin: String.t(),
sort_attrs: [Header.sort_attr()],
# data dependent fields
column_widths: [Column.width()],
rows: [Row.t()]
}
@doc """
Creates a table spec struct from `headers` and `options`.
"""
@spec new([Header.t(), ...], Keyword.t()) :: t
def new([_ | _] = headers, options \\ []) when is_list(options) do
%Spec{
spec_name: spec_name(options),
headers: headers,
align_specs: Keyword.get(options, :align_specs, []),
bell: Keyword.get(options, :bell, @default_bell),
count: Keyword.get(options, :count, @default_count),
header_fixes: Keyword.get(options, :header_fixes, %{}),
margins: Keyword.merge(@default_margins, options[:margins] || []),
max_width: Keyword.get(options, :max_width, @default_max_width),
sort_specs: Keyword.get(options, :sort_specs, []),
sort_symbols:
Keyword.merge(@default_sort_symbols, options[:sort_symbols] || []),
style: Keyword.get(options, :style, @default_style)
}
end
@doc """
Updates the derived fields of `spec`.
"""
@spec develop(t) :: t
def develop(%Spec{} = spec) do
spec
|> Spec.AlignAttrs.derive_and_put()
|> Spec.Headings.derive_and_put()
|> Spec.LeftMargin.derive_and_put()
|> Spec.SortAttrs.derive_and_put()
end
@doc """
Gets the table spec server name in `options`.
Defaults to the current app name expressed as a string.
## Examples
iex> alias IO.ANSI.Table.Spec
iex> Spec.spec_name(style: :light, count: 9, spec_name: "github_issues")
"github_issues"
iex> alias IO.ANSI.Table.Spec
iex> Spec.spec_name([])
"io_ansi_table"
"""
@spec spec_name(Keyword.t()) :: String.t()
def spec_name(options) when is_list(options) do
case options[:spec_name] do
nil ->
case :application.get_application() do
{:ok, app} -> app
# Thanks to `use PersistConfig`...
:undefined -> @app
end
|> to_string()
spec_name when is_binary(spec_name) ->
spec_name
end
end
@doc """
Writes data from `maps` to `:stdio` per table `spec` and `options`.
"""
@spec write_table(t, [Access.container()], Keyword.t()) :: :ok
def write_table(%Spec{} = spec, maps, options \\ [])
when is_list(maps) and is_list(options) do
spec
|> put(:bell, options[:bell])
|> put(:count, options[:count])
|> put(:max_width, options[:max_width])
|> put(:style, options[:style])
|> Spec.Rows.derive_and_put(maps)
|> Spec.ColumnWidths.derive_and_put()
|> write_table()
end
## Private functions
@spec put(t, atom, Keyword.value()) :: t
defp put(spec, _key, nil), do: spec
defp put(spec, key, value), do: Map.put(spec, key, value)
@spec top_margin(t) :: String.t()
defp top_margin(%Spec{margins: margins} = _spec) do
case margins[:top] do
# Move the cursor up N lines: \e[<N>A...
n when n <= -1 -> ANSI.cursor_up(-n)
n -> String.duplicate("\n", n)
end
end
@spec bottom_margin(t) :: String.t()
defp bottom_margin(%Spec{margins: margins} = _spec) do
case margins[:bottom] do
n when n >= 0 -> String.duplicate("\n", n)
_ -> ""
end
end
@spec write_table(t) :: :ok
defp write_table(spec) do
top_margin(spec) |> IO.write()
Style.line_types(spec.style) |> Enum.each(&LineType.write_lines(&1, spec))
bottom_margin(spec) |> IO.write()
IO.write(if spec.bell, do: "\a", else: "")
end
end