• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

emqx / emqx / 13326843742

14 Feb 2025 10:00AM UTC coverage: 82.402%. First build
13326843742

Pull #14696

github

web-flow
Merge a3f73ba84 into 43916f96a
Pull Request #14696: fix(dsraft): avoid contacting lost servers during membership changes

191 of 205 new or added lines in 4 files covered. (93.17%)

57504 of 69785 relevant lines covered (82.4%)

15306.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.12
/apps/emqx_utils/src/emqx_utils_fmt.erl
1
%%--------------------------------------------------------------------
2
%% Copyright (c) 2025 EMQ Technologies Co., Ltd. All Rights Reserved.
3
%%
4
%% Licensed under the Apache License, Version 2.0 (the "License");
5
%% you may not use this file except in compliance with the License.
6
%% You may obtain a copy of the License at
7
%%
8
%%     http://www.apache.org/licenses/LICENSE-2.0
9
%%
10
%% Unless required by applicable law or agreed to in writing, software
11
%% distributed under the License is distributed on an "AS IS" BASIS,
12
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
%% See the License for the specific language governing permissions and
14
%% limitations under the License.
15
%%--------------------------------------------------------------------
16

17
-module(emqx_utils_fmt).
18

19
-compile(export_all).
20
-compile(nowarn_export_all).
21

22
-export([
23
    table/2,
24
    table/3
25
]).
26

27
%%
28

29
-type header_cell() :: unicode:chardata() | {unicode:chardata(), column_props()}.
30
-type table_cell() :: table_cell_simple() | table_sub_structure().
31
-type table_cell_simple() :: unicode:chardata() | atom() | number().
32
-type table_sub_structure() ::
33
    {subrows, [[table_cell()]]}
34
    | {subcolumns, [[table_cell_simple()]]}
35
    | {group, [[table_cell()]]}.
36

37
-type table_props() :: #{_ => _}.
38
-type column_props() :: #{align => left | right}.
39
-type cell_props() :: #{padc => char(), delimiter => string(), _ => _}.
40

41
-type formatted_cell() :: unicode:chardata() | {unicode:chardata(), cell_props()}.
42

43
-define(table_header_props, #{padc => $-, delimiter => "."}).
44
-define(table_headcut_props, #{padc => $-}).
45
-define(group_header_props, #{padc => $-}).
46
-define(table_footer_props, #{padc => $-, delimiter => "`"}).
47

48
%%
49

50
%% @doc Format a set of rows accompanied by header into a printable, ASCII-delimited
51
%% table.
52
-spec table(Header :: [header_cell()], Rows :: [[table_cell()]]) ->
53
    unicode:chardata().
54
table(Header, Rows) ->
55
    table(Header, Rows, #{}).
22✔
56

57
-spec table(Header :: [header_cell()], Rows :: [[table_cell()]], table_props()) ->
58
    unicode:chardata().
59
table(Header, Rows, _Props) ->
60
    {FmtHeader, Columns0} = table_columns(Header),
22✔
61
    {FmtRows, Columns} = table_rows(Rows, Columns0),
22✔
62
    [
22✔
63
        table_tabulate_row([], Columns, ?table_header_props),
64
        table_tabulate_row(FmtHeader, Columns, #{}),
65
        table_tabulate_row([], Columns, ?table_headcut_props),
66
        [table_tabulate_row(FmtRow, Columns, #{}) || FmtRow <- FmtRows],
74✔
67
        table_tabulate_row([], Columns, ?table_footer_props)
68
    ].
69

70
%%
71

72
table_columns(Header) ->
73
    lists:unzip([table_column(C) || C <- Header]).
22✔
74

75
table_column({Title, Props}) ->
76
    Fmt = table_format_title(Title),
70✔
77
    Width = fmt_width(Fmt),
70✔
78
    Column = maps:merge(#{width => 1}, Props),
70✔
79
    {Fmt, table_column_resize(Width, Column)};
70✔
80
table_column(Title) ->
81
    table_column({Title, #{}}).
70✔
82

83
table_rows(Rows, Columns) ->
84
    lists:mapfoldl(fun table_row/2, Columns, Rows).
66✔
85

86
table_row(CellsIn, ColumnsIn) ->
87
    case table_row_expand(CellsIn) of
194✔
88
        {_Structure, RowsGroup} ->
89
            {FmtRows, Columns} = table_rows(RowsGroup, ColumnsIn),
44✔
90
            {{FmtRows}, Columns};
44✔
91
        Cells when is_list(Cells) ->
92
            lists:unzip(table_cells(Cells, ColumnsIn))
150✔
93
    end.
94

95
%% Expand complex row structures into potentially grouped together list of rows,
96
%% i.e. lists of lists of cells.
97
table_row_expand([{subrows, SubRowsIn}]) ->
98
    SubRows = [table_row_expand(SubRow) || SubRow <- SubRowsIn],
8✔
99
    {sub, table_flatten_rows(SubRows)};
8✔
100
table_row_expand([{subcolumns, SubColumns}]) ->
101
    SubRows = transpose(SubColumns, ""),
38✔
102
    {sub, SubRows};
38✔
103
table_row_expand([{group, Group}]) ->
104
    GroupRows = table_row_expand(Group),
40✔
105
    GroupHeader = [{repeat, {"", ?group_header_props}}],
40✔
106
    {group, [GroupHeader | table_flatten_rows(GroupRows)]};
40✔
107
table_row_expand([Cell | CellsIn]) when
108
    not is_tuple(Cell);
109
    element(1, Cell) =/= subrows,
110
    element(1, Cell) =/= subcolumns,
111
    element(1, Cell) =/= group
112
->
113
    case table_row_expand(CellsIn) of
510✔
114
        {sub, CellsSub} ->
115
            {sub, table_cells_expand(Cell, CellsSub)};
16✔
116
        {group, CellsGrouped} ->
117
            {group, table_cells_group(Cell, CellsGrouped)};
44✔
118
        Cells ->
119
            [Cell | Cells]
450✔
120
    end;
121
table_row_expand([]) ->
122
    [].
162✔
123

124
table_flatten_rows({_Section, Rows}) ->
125
    table_flatten_rows(Rows);
38✔
126
table_flatten_rows([{_Section, Rows} | Rest]) ->
127
    Rows ++ table_flatten_rows(Rest);
4✔
128
table_flatten_rows([Row | Rest]) ->
129
    [Row | table_flatten_rows(Rest)];
86✔
130
table_flatten_rows([]) ->
131
    [].
48✔
132

133
table_cells_group(GroupCell, [GroupHeader | SubRows]) ->
134
    SubRowsExpanded = [["" | Cells] || Cells <- SubRows],
44✔
135
    [[{GroupCell, ?group_header_props} | GroupHeader] | SubRowsExpanded].
44✔
136

137
table_cells_expand(LeftCell, [SubRow1 | SubRowsRest]) ->
138
    SubRow1Expanded = [LeftCell | SubRow1],
16✔
139
    SubRowsExpanded = [["" | SubCells] || SubCells <- SubRowsRest],
16✔
140
    [SubRow1Expanded | SubRowsExpanded];
16✔
141
table_cells_expand(LeftCell, []) ->
NEW
142
    [LeftCell].
×
143

144
%% 1. Formats table cells into text.
145
%% 2. Expands "repeated cells" into plain list of cells.
146
%% 3. Updates column props, for example resize and specify alignment if column
147
%%    cells contain numbers.
148
table_cells([{repeat, Cell}] = Cells, Columns = [_ | _]) ->
149
    table_cells([Cell | Cells], Columns);
82✔
150
table_cells([{repeat, _}], []) ->
151
    [];
40✔
152
table_cells([Cell | Cells], [Column | Columns]) ->
153
    Fmt = table_format_cell(Cell),
474✔
154
    [{Fmt, table_column_update(Fmt, Column)} | table_cells(Cells, Columns)];
474✔
155
table_cells([], [Column | Columns]) ->
156
    [{"-", Column} | table_cells([], Columns)];
8✔
157
table_cells([], []) ->
158
    [].
110✔
159

160
table_column_update(Fmt, Column0) ->
161
    Column1 = table_column_resize(fmt_width(Fmt), Column0),
474✔
162
    Column2 = table_column_realign(fmt_align(Fmt), Column1),
474✔
163
    Column2.
474✔
164

165
table_column_resize(Width, Column = #{width := W0}) ->
166
    Column#{width := max(Width, W0)}.
544✔
167

168
table_column_realign(default, Column) ->
NEW
169
    Column;
×
170
table_column_realign(left, Column) ->
171
    Column;
450✔
172
table_column_realign(right, Column) ->
173
    Column#{align => right}.
24✔
174

175
table_format_title(Title) ->
176
    Title.
70✔
177

178
%% Format cell into text + attach computed props if any.
179
table_format_cell(A) when is_atom(A) ->
180
    erlang:atom_to_binary(A);
26✔
181
table_format_cell(N) when is_integer(N) ->
182
    Fmt = integer_to_binary(N),
8✔
183
    {Fmt, #{align => right}};
8✔
184
table_format_cell(N) when is_number(N) ->
185
    Fmt = float_to_binary(N, [short]),
16✔
186
    {Fmt, #{align => right}};
16✔
187
table_format_cell({X, Props}) ->
188
    case table_format_cell(X) of
126✔
NEW
189
        {Fmt, CellProps} -> {Fmt, maps:merge(CellProps, Props)};
×
190
        Fmt -> {Fmt, Props}
126✔
191
    end;
192
table_format_cell(Text) ->
193
    Text.
424✔
194

195
-spec table_tabulate_row(Row | {[Row]}, [column_props()], cell_props()) ->
196
    unicode:chardata()
197
when
198
    Row :: [formatted_cell()].
199
table_tabulate_row({Rows}, Columns, Overrides) ->
200
    [table_tabulate_row(Fmts, Columns, Overrides) || Fmts <- Rows];
44✔
201
table_tabulate_row(Fmts, Columns, Overrides) ->
202
    Start = table_tabulate_row_start(Columns, Overrides),
238✔
203
    table_tabulate_row_end([Start | table_tabulate_cells(Fmts, Columns, Overrides)]).
238✔
204

205
table_tabulate_row_start([Column | _], Overrides) ->
206
    Delim = get_prop(delimiter, Column, Overrides, ":"),
238✔
207
    Delim.
238✔
208

209
table_tabulate_row_end(Acc) ->
210
    [Acc, $\n].
238✔
211

212
table_tabulate_cells([Fmt | Rest], [Column | Columns], Overrides) ->
213
    PadC = get_fmt_prop(padc, Fmt, Column, Overrides, $\s),
762✔
214
    Padding = get_fmt_prop(padding, Fmt, Column, Overrides, 1),
762✔
215
    Delim = get_prop(delimiter, Column, Overrides, ":"),
762✔
216
    Align = get_fmt_prop(align, Fmt, Column, Overrides, left),
762✔
217
    Width = maps:get(width, Column),
762✔
218
    case Align of
762✔
219
        left -> Dir = trailing;
700✔
220
        right -> Dir = leading
62✔
221
    end,
222
    Text = fmt_text(Fmt),
762✔
223
    PadS = lists:duplicate(Padding, PadC),
762✔
224
    Line = [PadS, string:pad(Text, Width, Dir, PadC), PadS, Delim],
762✔
225
    Line ++ table_tabulate_cells(Rest, Columns, Overrides);
762✔
226
table_tabulate_cells([], [], _Overrides) ->
227
    [];
238✔
228
table_tabulate_cells([], Columns, Overrides) ->
229
    table_tabulate_cells([<<>>], Columns, Overrides).
210✔
230

231
get_fmt_prop(Name, {_Fmt, Props1}, Props2, Props3, Default) ->
232
    case Props1 of
450✔
233
        #{Name := V} -> V;
150✔
234
        #{} -> get_prop(Name, Props2, Props3, Default)
300✔
235
    end;
236
get_fmt_prop(Name, _Fmt, Props2, Props3, Default) ->
237
    get_prop(Name, Props2, Props3, Default).
1,836✔
238

239
get_prop(Name, Props1, Props2, Default) ->
240
    case Props1 of
3,136✔
241
        #{Name := V} -> V;
38✔
242
        #{} -> get_prop(Name, Props2, Default)
3,098✔
243
    end.
244

245
get_prop(Name, Props, Default) ->
246
    maps:get(Name, Props, Default).
3,098✔
247

248
fmt_text({Fmt, _Props}) ->
249
    Fmt;
150✔
250
fmt_text(Fmt) ->
251
    Fmt.
612✔
252

253
fmt_align({_Fmt, Props}) ->
254
    maps:get(align, Props, left);
150✔
255
fmt_align(_Fmt) ->
256
    left.
324✔
257

258
fmt_width({Fmt, _Props}) ->
259
    fmt_width(Fmt);
150✔
260
fmt_width(Fmt) ->
261
    string:length(Fmt).
544✔
262

263
%% @doc Transpose list of lists into list of list of their respective elements.
264
transpose(Ls, Pad) ->
265
    case lists:any(fun(L) -> L =/= [] end, Ls) of
106✔
266
        true ->
267
            Heads = [transpose_head(L, Pad) || L <- Ls],
68✔
268
            Tails = [transpose_tail(L) || L <- Ls],
68✔
269
            [Heads | transpose(Tails, Pad)];
68✔
270
        false ->
271
            []
38✔
272
    end.
273

274
transpose_head([H | _], _Pad) -> H;
80✔
275
transpose_head([], Pad) -> Pad.
48✔
276

277
transpose_tail([_ | T]) -> T;
80✔
278
transpose_tail([]) -> [].
48✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc