From e435f62ff43ed105afc1ceda179e4f733f8259e5 Mon Sep 17 00:00:00 2001
From: Alexander Turenko <alexander.turenko@tarantool.org>
Date: Mon, 31 Jul 2023 16:02:38 +0300
Subject: [PATCH] config: add simple pretty-printer for tabular data

The next commit needs to output a lot of environment variables, their
types in the config schema, defaults and so on. It looks much more
readable when formatted in the tabular form.

This commits adds a simple tabular formatter for this purpose.

Part of #8862

NO_DOC=the new module is internal
NO_CHANGELOG=see NO_DOC
---
 src/box/CMakeLists.txt                |  1 +
 src/box/lua/config/utils/tabulate.lua | 68 +++++++++++++++++++++++++++
 src/box/lua/init.c                    |  7 ++-
 test/config-luatest/tabulate_test.lua | 44 +++++++++++++++++
 4 files changed, 119 insertions(+), 1 deletion(-)
 create mode 100644 src/box/lua/config/utils/tabulate.lua
 create mode 100644 test/config-luatest/tabulate_test.lua

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 234a8e81c8..d53506ba35 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -52,6 +52,7 @@ lua_source(lua_sources lua/config/source/env.lua          config_source_env_lua)
 lua_source(lua_sources lua/config/source/file.lua         config_source_file_lua)
 lua_source(lua_sources lua/config/utils/log.lua           config_utils_log_lua)
 lua_source(lua_sources lua/config/utils/schema.lua        config_utils_schema_lua)
+lua_source(lua_sources lua/config/utils/tabulate.lua      config_utils_tabulate_lua)
 
 if (ENABLE_CONFIG_EXTRAS)
     lua_source(lua_sources ${CONFIG_EXTRAS_DIR}/source/etcd.lua config_source_etcd_lua)
diff --git a/src/box/lua/config/utils/tabulate.lua b/src/box/lua/config/utils/tabulate.lua
new file mode 100644
index 0000000000..05c52e6c72
--- /dev/null
+++ b/src/box/lua/config/utils/tabulate.lua
@@ -0,0 +1,68 @@
+-- Very simple pretty-printer of tabular data.
+--
+-- Example:
+--
+-- tabulate.encode({
+--     {'a', 'b', 'c'},
+--     tabulate.SPACER,
+--     {'d', 'e', 'f'},
+--     {'g', 'h', 'i'},
+-- })
+--
+-- ->
+--
+-- | a | b | c |
+-- | - | - | - |
+-- | d | e | f |
+-- | g | h | i |
+
+local SPACER = {}
+
+-- Format data as a table.
+--
+-- Accepts an array of rows. Each row in an array of values. Each
+-- value is a string.
+local function encode(rows)
+    -- Calculate column widths and columns amount.
+    local column_widths = {}
+    for _i, row in ipairs(rows) do
+        for j, v in ipairs(row) do
+            assert(type(v) == 'string')
+            column_widths[j] = math.max(column_widths[j] or 0, #v)
+        end
+    end
+    local column_count = #column_widths
+
+    -- Use a table as a string buffer.
+    local acc = {}
+
+    -- Add all the values into the accumulator with proper spacing
+    -- around and appropriate separators.
+    for _i, row in ipairs(rows) do
+        if row == SPACER then
+            for j = 1, column_count do
+                local width = column_widths[j]
+                table.insert(acc, '| ')
+                table.insert(acc, ('-'):rep(width))
+                table.insert(acc, ' ')
+            end
+            table.insert(acc, '|\n')
+        else
+            for j = 1, column_count do
+                assert(row[j] ~= nil)
+                local width = column_widths[j]
+                table.insert(acc, '| ')
+                table.insert(acc, row[j]:ljust(width))
+                table.insert(acc, ' ')
+            end
+            table.insert(acc, '|\n')
+        end
+    end
+
+    return table.concat(acc)
+end
+
+return {
+    SPACER = SPACER,
+    encode = encode,
+}
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index b0a3b49c43..9be8ae05e9 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -150,7 +150,8 @@ extern char session_lua[],
 	config_source_env_lua[],
 	config_source_file_lua[],
 	config_utils_log_lua[],
-	config_utils_schema_lua[]
+	config_utils_schema_lua[],
+	config_utils_tabulate_lua[]
 #if ENABLE_CONFIG_EXTRAS
 	,
 	config_source_etcd_lua[],
@@ -322,6 +323,10 @@ static const char *lua_sources[] = {
 	"internal.config.utils.schema",
 	config_utils_schema_lua,
 
+	"config/utils/tabulate",
+	"internal.config.utils.tabulate",
+	config_utils_tabulate_lua,
+
 	"config/instance_config",
 	"internal.config.instance_config",
 	config_instance_config_lua,
diff --git a/test/config-luatest/tabulate_test.lua b/test/config-luatest/tabulate_test.lua
new file mode 100644
index 0000000000..7121f24c31
--- /dev/null
+++ b/test/config-luatest/tabulate_test.lua
@@ -0,0 +1,44 @@
+local tabulate = require('internal.config.utils.tabulate')
+local t = require('luatest')
+
+local g = t.group()
+
+g.test_basic = function()
+    local header = {
+        'Destination',
+        'Gateway',
+        'Genmask',
+        'Flags',
+        'Metric',
+        'Ref',
+        'Use',
+        'Iface',
+    }
+    local route_1 = {
+        '0.0.0.0',  -- Destination
+        '10.0.0.1', -- Gateway
+        '0.0.0.0',  -- Genmask
+        'UG',       -- Flags
+        '3005',     -- Metric
+        '0',        -- Ref
+        '0',        -- Use
+        'wlan0',    -- Iface
+    }
+    local route_2 = {
+        '10.0.0.0',      -- Destination
+        '0.0.0.0',       -- Gateway
+        '255.255.255.0', -- Genmask
+        'U',             -- Flags
+        '3005',          -- Metric
+        '0',             -- Ref
+        '0',             -- Use
+        'wlan0',         -- Iface
+    }
+    local res = tabulate.encode({header, tabulate.SPACER, route_1, route_2})
+    t.assert_equals(res, ([[
+| Destination | Gateway  | Genmask       | Flags | Metric | Ref | Use | Iface |
+| ----------- | -------- | ------------- | ----- | ------ | --- | --- | ----- |
+| 0.0.0.0     | 10.0.0.1 | 0.0.0.0       | UG    | 3005   | 0   | 0   | wlan0 |
+| 10.0.0.0    | 0.0.0.0  | 255.255.255.0 | U     | 3005   | 0   | 0   | wlan0 |
+]]):lstrip())
+end
-- 
GitLab