-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
scribe.ex
180 lines (141 loc) · 3.96 KB
/
scribe.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
defmodule Scribe do
@moduledoc """
Pretty-print tables of structs and maps
"""
alias Scribe.Table
@type data ::
[]
| [...]
| term
@typedoc ~S"""
Options for configuring table output.
- `:colorize` - When `false`, disables colored output. Defaults to `true`
- `:data` - Defines table headers
- `:style` - Style callback module. Defaults to `Scribe.Style.Default`
- `:width` - Defines table width. Defaults to `:infinite`
"""
@type format_opts :: [
colorize: boolean,
data: [...],
style: module,
width: integer
]
@doc ~S"""
Enables/disables auto-inspect override.
If true, Scribe will override `inspect/2` for maps and structs, printing
them as tables.
## Examples
iex> Scribe.auto_inspect(true)
:ok
"""
@spec auto_inspect(boolean) :: :ok
def auto_inspect(inspect?) do
Application.put_env(:scribe, :auto_inspect, inspect?)
end
@doc ~S"""
Returns true if Scribe is overriding `Inspect`.
## Examples
iex> Scribe.auto_inspect?
true
"""
def auto_inspect? do
compile_auto_inspect?() and
Application.get_env(:scribe, :auto_inspect, false)
end
@doc false
def compile_auto_inspect? do
Application.get_env(:scribe, :compile_auto_inspect, false)
end
@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, IO.device(), format_opts) :: :ok
def print(_results, dev \\ :stdio, opts \\ [])
def print([], _dev, _opts), do: :ok
def print(results, dev, opts) do
results
|> format(opts)
|> IO.puts(dev)
end
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(term, format_opts) :: term
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([] | [...] | term) :: 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()
|> process_headers()
end
end