1:- encoding(utf8).
    2:- module(cli_table, [
    3    cli_table/1,
    4    cli_table/2
    5  ]).    6
    7cli_table(Rows) :-
    8  cli_table(Rows, []).
    9
   10cli_table(Rows, Options) :-
   11  transpose(Rows, Cols),
   12  add_headers_to_cols(Options, Cols, Cols_),
   13  maplist(get_col_width, Cols_, ColWidths),
   14  default_options(Default_Options),
   15  merge_options(Options, Default_Options, Options_),
   16  cli_table(Rows, Options_, ColWidths).
   17
   18cli_table(Rows, Options, ColWidths) :-
   19  print_header(Options, ColWidths),
   20  forall(
   21    member(Row, Rows),
   22    print_row(Options, Row, ColWidths)
   23  ),
   24  print_footer(Options, ColWidths).
   25
   26default_options(Default_Options) :-
   27  Default_Options = [
   28    top('═'), top_left('╔'), top_mid('╤'), top_right('╗'),
   29    bottom('═'), bottom_left('╚'), bottom_mid('╧'), bottom_right('╝'),
   30    space(' '), left('║'), mid('│'), right('║'),
   31    mid_space('─'), mid_left('╟'), mid_mid('┼'), mid_right('╢')
   32  ].
   33
   34add_headers_to_cols(Options, Cols, Cols_With_Headers) :-
   35  ( option(head(Header), Options) ->
   36    maplist(add_header_to_col, Cols, Header, Cols_With_Headers)
   37  ; Cols_With_Headers = Cols ).
   38
   39add_header_to_col(Col, Header, [Header|Col]).
   40
   41print_header(Options, ColWidths) :-
   42  print_char_row(Options, ColWidths, [top, top_left, top_mid, top_right]),
   43  ( option(head(Header), Options) ->
   44    print_row(Options, Header, ColWidths),
   45    print_char_row(Options, ColWidths, [mid_space, mid_left, mid_mid, mid_right])
   46  ; true ).
   47
   48print_footer(Options, ColWidths) :-
   49  print_char_row(Options, ColWidths, [bottom, bottom_left, bottom_mid, bottom_right]).
   50
   51print_char_row(Options, ColWidths, Identifiers) :-
   52  maplist(option_char(Options), Identifiers, [Space, Left, Mid, Right]),
   53  option(space(OriginalSpace), Options),
   54  atom_length(OriginalSpace, OriginalSpace_Length),
   55  apply_length(OriginalSpace_Length, Space, Space_),
   56  merge_options([space(Space_), left(Left), mid(Mid), right(Right)], Options, Char_Row_Options),
   57  maplist(mock_cell(Space), ColWidths, CellMocks),
   58  print_row(Char_Row_Options, CellMocks, ColWidths).
   59
   60apply_length(Length, Space, Space_With_Length) :-
   61  atom_length(Space, 0), !,
   62  apply_length(Length, ' ', Space_With_Length).
   63apply_length(Length, Space, Space_With_Length) :-
   64  atomic_list_concat(['~`', Space, 't~', Length, '|'], Format),
   65  format(atom(Space_With_Length), Format, []).
   66
   67option_char(Options, Option, Char) :-
   68  KV =.. [Option, Char],
   69  option(KV, Options).
   70
   71print_row(Options, [First|Cells], [FirstWidth|ColWidths]) :-
   72  print_first_cell(Options, First, FirstWidth),
   73  maplist(print_cell(Options), Cells, ColWidths),
   74  option(right(Char), Options),
   75  write(Char),
   76  nl.
   77
   78print_first_cell(Options, Cell, ColWidth) :-
   79  option(left(Char), Options),
   80  merge_options([mid(Char)], Options, Options_For_First),
   81  print_cell(Options_For_First, Cell, ColWidth).
   82
   83print_cell(Options, Cell, ColWidth) :-
   84  option(space(Space), Options),
   85  option(mid(Middle), Options),
   86  Width is ColWidth,
   87  Center = '~t~w~t',
   88  atomic_list_concat([Center, '~', Width, '|'], Format),
   89  format(atom(Entry), Format, [ Cell ]),
   90  atomic_list_concat([Middle, Space, Entry, Space], Result),
   91  write(Result).
   92
   93get_col_width(Cells, MaxWidth) :-
   94  maplist(get_cell_width, Cells, CellWidths),
   95  max_list(CellWidths, MaxWidth).
   96
   97get_cell_width(Cell, Width) :-
   98  atom_length(Cell, Width).
   99
  100mock_cell(_Char, 0, '') :- !.
  101mock_cell(Char, N, A) :-
  102  N_ is N-1,
  103  mock_cell(Char, N_, A_),
  104  atomic_concat(Char, A_, A).
  105
  106transpose([], []).
  107transpose([L|Ls], Ts) :-
  108  maplist(same_length(L), Ls),
  109  foldl(transpose_, L, Ts, [L|Ls], _).
  110
  111transpose_(_, Fs, Lists0, Lists) :-
  112  maplist(list_first_rest, Lists0, Fs, Lists).
  113
  114list_first_rest([L|Ls], L, Ls)