Pretty is a very lightweight cross-platform C++20 library for pretty formatting and pretty printing tabular data to the standard output of the program.
Tags: C++ pretty table, print table in C++, table formatting, tabulated output, console output, pretty formatting.
The library was tested with Visual C++/Visual Studio Version 17.5.1:
-
Copy
unicode.ixx
,pretty-impl.ixx
,pretty.ixx
to your project, include them as Existing Items. -
Compile the files as module interface files. In Visual Studio, go to file Properties,
Property Pages
>C/C++
>Advanced
, setCompile As
toCompile as C++ Module Code (/interface )
for all the three files. -
Go to project Properties,
Property Pages
>General
, setC++ Language Standard
toStandard ISO C++20 Standard (/std:c++20)
. -
Go to project Properties,
Property Pages
>C/C++
>Command Line
. InAdditional Options
, add/utf-8
-
You are ready to use the API.
The library was tested with clang version 15.0.7:
-
Copy
unicode.ixx
,pretty-impl.ixx
,pretty.ixx
to your project directory. -
Go to your project directory:
cd <your_project_dir>
-
The following command prebuilds the library modules:
clang++-15 -std=c++20 -Wall -O3 -fprebuilt-module-path=. -xc++-module unicode.ixx pretty-impl.ixx pretty.ixx --precompile
-
The following command builds a console test program for the library:
clang++-15 -std=c++20 -Wall -O3 -s -fprebuilt-module-path=. main.cpp unicode.pcm pretty-impl.pcm pretty.pcm
main.cpp
in the command above is taken from the library and contains tests. -
You are ready to use the API.
GCC C++ versions 11,12,13 were all tried. Versions 12 and 13 were unable to build the project due to internal compiler errors. Version 11 required to introduce some weird changes to the source code and to stop using some unsupported features like module:private
, after which GCC managed to build an executable. The executable crashed with a segmentation fault at startup, without printing anything. This section will be updated when GCC catches up.
CMake support for C++20 modules is very experimental and is not ready for production.
The library provides a temporary CMake-compatible solution without C++20 modules, pretty.cpp
and pretty.h
. The support for these files will be dropped in the future in favor of modules.
pretty/Impl/CMakeLists.txt
builds the library as an object file.
pretty/tests/CMakeLists.txt
builds tests.
Successfully tested with
- Clang 15.0.7, Linux x64
- GCC 12.2.1, Linux x64
- Visual Studio 17.5.3, Windows 10 x64
If you want to use CMake with Visual Studio, use the built-in Visual Studio support for CMake
On Linux, an easy way to use CMake is via Visual Studio Code
with C/C++ Extension Pack
and CMake Tools
extensions. A C++ compiler and ninja
need to be installed separately in a way specific to your Linux distribution.
All the detailed examples from this section can be found in main.cpp
.
Import module pretty
:
import pretty;
and you are ready to use the API.
Instantiate pretty::Table
and call its functions. pretty::Table
can grow at runtime by appending new rows and/or columns in any order. It is possible to add some rows first, then some columns to the rows, then again rows:
const auto table = pretty::Table()
.addRow("r1")
.addRow("r2")
.addCol({ "c1", "c2" })
.addRow("r3", "r3");
The code above produces the following table:
┌────┬────┐
│ r1 │ c1 │
│ r2 │ c2 │
│ r3 │ r3 │
└────┴────┘
pretty::Table
supports only regular tables - the number of columns is the same in each row.
When adding new rows, it is important to make sure that the number of columns in appended row[s] matches the current number of columns in the table.
When adding new columns, it is important to make sure that the number of rows in appended column[s] matches the current number of rows in the table.
addRow(…)
is overloaded and accepts either std::vector<std::string>
or a variadic pack of arguments convertible to std::string
.
addCol(…)
accepts only std::vector<std::string>
.
addColumns(ColN numCols)
and addRows(RowN numRows)
allow to add the given number of empty columns and rows, respectively. The added cells are empty and need to be populated programmatically by calling
setText(RowN row, ColN col, std::convertible_to<std::string> auto&& text)
:
auto table = pretty::Table()
.addColumns(3)
.addRows(2);
auto format = [](pretty::RowN r, pretty::ColN c)
{
return std::to_string(r.val()) + "," + std::to_string(c.val());
};
pretty::RowN r{};
for (; r < table.numRows(); ++r)
for (pretty::ColN c{}; c < table.numColumns(); ++c)
table.setText(r, c, format(r, c));
The above code produces:
┌─────┬─────┬─────┐
│ 0,0 │ 0,1 │ 0,2 │
│ 1,0 │ 1,1 │ 1,2 │
└─────┴─────┴─────┘
Now let's add 3 additional rows to the previous table:
table.addRows(3);
for (;r < table.numRows(); ++r)
for (pretty::ColN c{};c < table.numColumns(); ++c)
table.setText(r, c, format(r, c));
which produces:
┌─────┬─────┬─────┐
│ 0,0 │ 0,1 │ 0,2 │
│ 1,0 │ 1,1 │ 1,2 │
│ 2,0 │ 2,1 │ 2,2 │
│ 3,0 │ 3,1 │ 3,2 │
│ 4,0 │ 4,1 │ 4,2 │
└─────┴─────┴─────┘
Instantiate pretty::Printer
and configure it as required:
pretty::Printer print;
Basic
print.frame(pretty::FrameStyle::Basic);
+----------------+-----------+
| Country | UN Region |
+----------------+-----------+
| World | - |
| United States | Americas |
| China | Asia |
| Japan | Asia |
| Germany | Europe |
| India | Asia |
| United Kingdom | Europe |
| France | Europe |
+----------------+-----------+
Line
print.frame(pretty::FrameStyle::Line);
┌────────────────┬───────────┐
│ Country │ UN Region │
├────────────────┼───────────┤
│ World │ - │
│ United States │ Americas │
│ China │ Asia │
│ Japan │ Asia │
│ Germany │ Europe │
│ India │ Asia │
│ United Kingdom │ Europe │
│ France │ Europe │
└────────────────┴───────────┘
LineRounded
print.frame(pretty::FrameStyle::LineRounded);
╭────────────────┬───────────╮
│ Country │ UN Region │
├────────────────┼───────────┤
│ World │ - │
│ United States │ Americas │
│ China │ Asia │
│ Japan │ Asia │
│ Germany │ Europe │
│ India │ Asia │
│ United Kingdom │ Europe │
│ France │ Europe │
╰────────────────┴───────────╯
ThickLine
print.frame(pretty::FrameStyle::ThickLine);
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Country ┃ UN Region ┃
┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━┫
┃ World ┃ - ┃
┃ United States ┃ Americas ┃
┃ China ┃ Asia ┃
┃ Japan ┃ Asia ┃
┃ Germany ┃ Europe ┃
┃ India ┃ Asia ┃
┃ United Kingdom ┃ Europe ┃
┃ France ┃ Europe ┃
┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━┛
DoubleLine
print.frame(pretty::FrameStyle::DoubleLine);
╔════════════════╦═══════════╗
║ Country ║ UN Region ║
╠════════════════╬═══════════╣
║ World ║ - ║
║ United States ║ Americas ║
║ China ║ Asia ║
║ Japan ║ Asia ║
║ Germany ║ Europe ║
║ India ║ Asia ║
║ United Kingdom ║ Europe ║
║ France ║ Europe ║
╚════════════════╩═══════════╝
Minimal
print.frame(pretty::FrameStyle::Minimal);
Country UN Region
---------------- -----------
World -
United States Americas
China Asia
Japan Asia
Germany Europe
India Asia
United Kingdom Europe
France Europe
padding(n)
allows to set the horizontal distance from the text to the cell border:
print.padding(0);
┌──────────────┬─────────┐
│Country │UN Region│
├──────────────┼─────────┤
│World │- │
│United States │Americas │
│China │Asia │
│Japan │Asia │
│Germany │Europe │
│India │Asia │
│United Kingdom│Europe │
│France │Europe │
└──────────────┴─────────┘
headerSeparator(bool show)
shows or hides horizontal separator line for the first row ("header"). Otherwise, the header row does not differ from other rows:
print.headerSeparator(false);
┌──────────────┬─────────┐
│Country │UN Region│
│World │- │
│United States │Americas │
│China │Asia │
│Japan │Asia │
│Germany │Europe │
│India │Asia │
│United Kingdom│Europe │
│France │Europe │
└──────────────┴─────────┘
Once pretty::Printer
is configured, call [[nodiscard]]std::string toString(const Table& t) const
to obtain a string with a pretty-formatted table.
If you need to print to the standard output, the operator<<
is overloaded for pretty::Printer
:
pretty::Printer print;
print.frame(pretty::FrameStyle::Basic);
cout << print(table);
pretty::Printer
also overloads the operator()
to allow the concise syntax for data loading like in the code above.
On Windows, printing to the standard output requires enabling UTF8 for the console, which is done by the overloaded operator<<
. Otherwise, you may just use toString(...)
and then manually print the obtained string to whatever stream/file you need.
The library is fully Unicode-aware. The data in all cells is of type std::string
which is expected to be encoded in UTF8. When formatting the table, pretty::Printer
takes into account the visible/display/column width of cell text, so tables are correctly formatted even with hieroglyphs and emojis:
For correct Unicode rendering it is important that:
- The used OS and terminal application support Unicode
- The selected terminal font supports Unicode and all the codepoints that you need to print
On Linux, hieroglyphic and emoji codepoints are rendered much better.
On Windows, using Windows Terminal is highly recommended, as cmd.exe
does not support rendering some codepoints even after enabling UTF8 and selecting a Unicode font, see https://stackoverflow.com/a/67510140. Windows Terminal renders many emojis correctly
, but not all:
Report issues at Windows Terminal