changed CHANGELOG.md
 
@@ -1,5 +1,18 @@
1
1
# Changelog
2
2
3
+ ## v3.2.0 (2023-06-28)
4
+
5
+ New features:
6
+
7
+ * CJK/Unicode character support at the cell level. Non-ascii characters
8
+ should no longer break rendering.
9
+
10
+ * We now have row-level support for colored backgrounds and foregrounds,
11
+ see some [examples](https://github.com/djm/table_rex/pull/44).
12
+
13
+ * Tables can now have no rows if they wish; that constraint has been relaxed
14
+ & tables will render fine without any rows (e.g if they just have a header).
15
+
3
16
## v3.1.1 (2021-01-30)
4
17
5
18
Bugfix: Fixes a multiline text rendering crash when inputs had differing
changed README.md
 
@@ -6,7 +6,7 @@
6
6
7
7
**An Elixir app which generates text-based tables for display**
8
8
9
- <img src="https://raw.githubusercontent.com/djm/table_rex/master/assets/examples.gif" width="500" alt="Layout Examples" />
9
+ <img src="https://raw.githubusercontent.com/djm/table_rex/main/assets/examples.gif" width="500" alt="Layout Examples" />
10
10
11
11
12
12
#### Features
 
@@ -21,8 +21,9 @@ The data structures support:
21
21
* column & cell level text alignment: left, center, right.
22
22
* table titles & column headers.
23
23
* sorting based on raw input data (rather than just strings).
24
- * column, header & cell level <img src="http://i.imgur.com/LCfvYYM.png" width="44" /> support.
24
+ * column, header & cell level <img src="http://i.imgur.com/LCfvYYM.png" width="44" /> support, including backgrounds.
25
25
* automatic but defineable column & cell level padding.
26
+ * CJK & Unicode support in the cells.
26
27
* styling the table with various vertical & horizontal framing.
27
28
* styling the table with custom separator symbols.
28
29
* multi-line cell support.
 
@@ -60,7 +61,7 @@ The package is [available on Hex](https://hex.pm/packages/table_rex), therefore:
60
61
61
62
```elixir
62
63
def deps do
63
- [{:table_rex, "~> 3.1.1"}]
64
+ [{:table_rex, "~> 3.2.0"}]
64
65
end
65
66
```
66
67
 
@@ -305,10 +306,10 @@ Table.new(rows, header)
305
306
306
307
We have an extensive test suite which helps showcase project usage. For example:
307
308
the
308
- [quick render functions](https://github.com/djm/table_rex/blob/master/test/table_rex_test.exs),
309
- [table manipulation API](https://github.com/djm/table_rex/blob/master/test/table_rex/table_test.exs)
309
+ [quick render functions](https://github.com/djm/table_rex/blob/main/test/table_rex_test.exs),
310
+ [table manipulation API](https://github.com/djm/table_rex/blob/main/test/table_rex/table_test.exs)
310
311
or
311
- [the text renderer module](https://github.com/djm/table_rex/blob/master/test/table_rex/renderer/text_test.exs).
312
+ [the text renderer module](https://github.com/djm/table_rex/blob/main/test/table_rex/renderer/text_test.exs).
312
313
313
314
To run the test suite, from the project directory, do:
changed hex_metadata.config
 
@@ -15,4 +15,4 @@
15
15
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/djm/table_rex">>}]}.
16
16
{<<"name">>,<<"table_rex">>}.
17
17
{<<"requirements">>,[]}.
18
- {<<"version">>,<<"3.1.1">>}.
18
+ {<<"version">>,<<"3.2.0">>}.
changed lib/table_rex.ex
 
@@ -25,7 +25,6 @@ defmodule TableRex do
25
25
def quick_render!(rows, header \\ [], title \\ nil) when is_list(rows) and is_list(header) do
26
26
case quick_render(rows, header, title) do
27
27
{:ok, rendered} -> rendered
28
- {:error, reason} -> raise TableRex.Error, message: reason
29
28
end
30
29
end
31
30
end
changed lib/table_rex/column.ex
 
@@ -5,7 +5,7 @@ defmodule TableRex.Column do
5
5
The align field can be one of :left, :center or :right.
6
6
"""
7
7
8
- defstruct align: :left, padding: 1, color: nil
8
+ defstruct align: :left, padding: 1, color: nil, width_calc: &String.length/1
9
9
10
10
@type t :: %__MODULE__{}
11
11
end
changed lib/table_rex/renderer/text.ex
 
@@ -295,23 +295,29 @@ defmodule TableRex.Renderer.Text do
295
295
cell_align = Map.get(cell, :align) || Table.get_column_meta(table, col_index, :align)
296
296
cell_color = Map.get(cell, :color) || Table.get_column_meta(table, col_index, :color)
297
297
298
- do_render_cell(cell.rendered_value, col_width, col_padding, align: cell_align)
298
+ cell_width_calc =
299
+ Map.get(cell, :width_calc, nil) || Table.get_column_meta(table, col_index, :width_calc)
300
+
301
+ do_render_cell(cell.rendered_value, col_width, col_padding,
302
+ align: cell_align,
303
+ width_calc: cell_width_calc
304
+ )
299
305
|> format_with_color(cell.rendered_value, cell_color)
300
306
end
301
307
302
308
defp do_render_cell(value, inner_width) do
303
- do_render_cell(value, inner_width, 0, align: :center)
309
+ do_render_cell(value, inner_width, 0, align: :center, width_calc: &String.length/1)
304
310
end
305
311
306
- defp do_render_cell(value, inner_width, _padding, align: :center) do
307
- value_len = String.length(strip_ansi_color_codes(value))
312
+ defp do_render_cell(value, inner_width, _padding, align: :center, width_calc: width_calc) do
313
+ value_len = width_calc.(strip_ansi_color_codes(value))
308
314
post_value = ((inner_width - value_len) / 2) |> round
309
315
pre_value = inner_width - (post_value + value_len)
310
316
String.duplicate(" ", pre_value) <> value <> String.duplicate(" ", post_value)
311
317
end
312
318
313
- defp do_render_cell(value, inner_width, padding, align: align) do
314
- value_len = String.length(strip_ansi_color_codes(value))
319
+ defp do_render_cell(value, inner_width, padding, align: align, width_calc: width_calc) do
320
+ value_len = width_calc.(strip_ansi_color_codes(value))
315
321
alt_side_padding = inner_width - value_len - padding
316
322
317
323
{pre_value, post_value} =
 
@@ -389,20 +395,22 @@ defmodule TableRex.Renderer.Text do
389
395
row_index
390
396
) do
391
397
padding = Table.get_column_meta(table, col_index, :padding)
392
- {width, height} = content_dimensions(cell.rendered_value, padding)
398
+ width_calc = Table.get_column_meta(table, col_index, :width_calc)
399
+ {width, height} = content_dimensions(cell.rendered_value, padding, width_calc)
393
400
col_widths = Map.update(col_widths, col_index, width, &Enum.max([&1, width]))
394
401
row_heights = Map.update(row_heights, row_index, height, &Enum.max([&1, height]))
395
402
{col_widths, row_heights}
396
403
end
397
404
398
- defp content_dimensions(value, padding) when is_binary(value) and is_number(padding) do
405
+ defp content_dimensions(value, padding, width_calc)
406
+ when is_binary(value) and is_number(padding) do
399
407
lines =
400
408
value
401
409
|> strip_ansi_color_codes()
402
410
|> String.split("\n")
403
411
404
412
height = Enum.count(lines)
405
- width = lines |> Enum.map(&String.length/1) |> Enum.max()
413
+ width = lines |> Enum.map(width_calc) |> Enum.max()
406
414
{width + padding * 2, height}
407
415
end
changed lib/table_rex/table.ex
 
@@ -243,13 +243,6 @@ defmodule TableRex.Table do
243
243
|> Map.fetch!(key)
244
244
end
245
245
246
- @doc """
247
- Returns a boolean detailing if the passed table has any row data set.
248
- """
249
- @spec has_rows?(Table.t()) :: boolean
250
- def has_rows?(%Table{rows: []}), do: false
251
- def has_rows?(%Table{rows: rows}) when is_list(rows), do: true
252
-
253
246
@doc """
254
247
Returns a boolean detailing if the passed table has a header row set.
255
248
"""
 
@@ -272,12 +265,7 @@ defmodule TableRex.Table do
272
265
def render(%Table{} = table, opts \\ []) when is_list(opts) do
273
266
{renderer, opts} = Keyword.pop(opts, :renderer, @default_renderer)
274
267
opts = opts |> Enum.into(renderer.default_options)
275
-
276
- if Table.has_rows?(table) do
277
- renderer.render(table, opts)
278
- else
279
- {:error, "Table must have at least one row before being rendered"}
280
- end
268
+ renderer.render(table, opts)
281
269
end
282
270
283
271
@doc """
 
@@ -294,4 +282,21 @@ defmodule TableRex.Table do
294
282
{:error, reason} -> raise TableRex.Error, message: reason
295
283
end
296
284
end
285
+
286
+ def row_colors(result, colors) do
287
+ %{result | rows: map_colors(Map.get(result, :rows), colors)}
288
+ end
289
+
290
+ defp map_colors(rows, colors) do
291
+ n = Enum.count(colors)
292
+
293
+ rows
294
+ |> Enum.with_index()
295
+ |> Enum.map(fn {list, i} ->
296
+ Enum.map(list, fn x ->
297
+ color = Enum.at(colors, rem(i, n))
298
+ %{x | color: color}
299
+ end)
300
+ end)
301
+ end
297
302
end
changed mix.exs
 
@@ -2,12 +2,13 @@ defmodule TableRex.Mixfile do
2
2
use Mix.Project
3
3
4
4
@source_url "https://github.com/djm/table_rex"
5
- @version "3.1.1"
5
+ @version "3.2.0"
6
6
7
7
def project do
8
8
[
9
9
app: :table_rex,
10
10
name: "table_rex",
11
+ source_url: @source_url,
11
12
description: "Generate configurable text-based tables for display (ASCII & more)",
12
13
version: @version,
13
14
elixir: "~> 1.7",
 
@@ -20,13 +21,14 @@ defmodule TableRex.Mixfile do
20
21
end
21
22
22
23
def application do
23
- [applications: [:logger]]
24
+ [extra_applications: [:logger]]
24
25
end
25
26
26
27
defp deps do
27
28
[
28
29
{:earmark, ">= 0.0.0", only: :docs},
29
- {:ex_doc, ">= 0.0.0", only: :docs}
30
+ {:ex_doc, ">= 0.0.0", only: :docs},
31
+ {:unicode, ">= 0.0.0", only: :test}
30
32
]
31
33
end