From 0cc287b4cf76ddbdfffda514ce3fc883a33b82ff Mon Sep 17 00:00:00 2001
From: Georgy Moshkin <gmoshkin@picodata.io>
Date: Fri, 4 Aug 2023 13:54:38 +0300
Subject: [PATCH] feat: make pico.raft_log() work over a remote session

---
 CHANGELOG.md            |  1 +
 src/luamod.rs           | 87 ++++++++++++++++++++++++++++-------------
 test/int/test_basics.py |  8 ++--
 3 files changed, 64 insertions(+), 32 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90d816fc68..396e7cc37a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,6 +51,7 @@ with the `YY.0M.MICRO` scheme.
 - Add `pico.raft_term()`
 - Add `pico.change_password()`
 - Add `pico.wait_ddl_finalize()`
+- Changed `pico.raft_log()` options.
 
 ## [23.06.0] - 2023-06-16
 
diff --git a/src/luamod.rs b/src/luamod.rs
index e5967d9a4d..e7eaa72a61 100644
--- a/src/luamod.rs
+++ b/src/luamod.rs
@@ -829,8 +829,8 @@ pub(crate) fn setup(args: &args::Run) {
     }
     #[derive(::tarantool::tlua::LuaRead)]
     struct RaftLogOpts {
-        return_string: Option<bool>,
         justify_contents: Option<Justify>,
+        max_width: Option<usize>,
     }
     luamod_set(
         &l,
@@ -839,24 +839,51 @@ pub(crate) fn setup(args: &args::Run) {
         pico.raft_log([opts])
         ====================================
 
-        Internal API.
+        Inspect raft log contents in human readable format. The contents are
+        returned as a table of strings similarly to how fselect works for
+        spaces.
 
-        If `return_string` is true, returns a string with formatted contents of
-        raft log. Otherwise, prints the formatted raft log contents to the
-        standard output.
+        opt.justify_contents can be used to control how contents are justified.
+
+        If opts.max_width is specified the output will not be wider than this
+        many printable characters. If not specified the terminal width is
+        used, unless it is called in context of a remote session.
+
+        Params:
+
+            1. opts (table)
+                - justify_contents (string), one of 'center' | 'left' | 'right', default: 'center'
+                - max_width (number), default for remote context: 80
+
+        Returns:
+
+            (table)
 
         Example:
 
-            pico.raft_log({justify_contents = 'center', return_string = false})
+            pico.raft_log({justify_contents = 'center', max_width = 100})
         "},
         tlua::function1(
-            |opts: Option<RaftLogOpts>| -> traft::Result<Option<String>> {
-                let mut return_string = false;
+            |opts: Option<RaftLogOpts>| -> traft::Result<Option<Vec<String>>> {
                 let mut justify_contents = Default::default();
+                let mut max_width = None;
                 if let Some(opts) = opts {
-                    return_string = opts.return_string.unwrap_or(false);
                     justify_contents = opts.justify_contents.unwrap_or_default();
+                    max_width = opts.max_width;
                 }
+                let remote_ctx =
+                    crate::tarantool::eval("return box.session.peer() ~= nil").unwrap();
+                let max_width = crate::unwrap_some_or!(
+                    max_width,
+                    if remote_ctx {
+                        80
+                    } else {
+                        // Need to subtract 4, because this is how many symbols
+                        // are prepended by the console when output format is yaml.
+                        crate::util::screen_size().1 as usize - 5
+                    }
+                );
+
                 let header = ["index", "term", "lc", "contents"];
                 let [index, term, lc, contents] = header;
                 let mut rows = vec![];
@@ -883,13 +910,8 @@ pub(crate) fn setup(args: &args::Run) {
                 let [iw, tw, lw, mut cw] = col_widths;
 
                 let total_width = 1 + header.len() + col_widths.iter().sum::<usize>();
-                let cols = if return_string {
-                    256
-                } else {
-                    crate::util::screen_size().1 as usize
-                };
-                if total_width > cols {
-                    match cw.checked_sub(total_width - cols) {
+                if total_width > max_width {
+                    match cw.checked_sub(total_width - max_width) {
                         Some(new_cw) if new_cw > 0 => cw = new_cw,
                         _ => {
                             return Err(traft::error::Error::other("screen too small"));
@@ -909,34 +931,42 @@ pub(crate) fn setup(args: &args::Run) {
                 let row_sep = |buf: &mut Vec<u8>| {
                     match justify_contents {
                         Justify::Left => {
-                            writeln!(buf, "+{0:-^iw$}+{0:-^tw$}+{0:-^lw$}+{0:-<cw$}+", "")
+                            // NOTE: here and later a special unicode character \u{200b} is used.
+                            // This is a ZERO WIDTH SPACE and it helps with the tarantool's console.
+                            // The way it works is that tarantool's console when printing the values
+                            // returned from a function will surround string values with quotes if
+                            // for instance they start with a '|' pipe character, which is our case.
+                            // Adding a space before '|' doesn't help but a ZERO WIDTH SPACE
+                            // for what ever reason does. So this is basically a crutch,
+                            // but if it's good enough for tarantool developers, it's good enough for us.
+                            writeln!(buf, "\u{200b}+{0:-^iw$}+{0:-^tw$}+{0:-^lw$}+{0:-<cw$}+", "")
                         }
                         Justify::Center => {
-                            writeln!(buf, "+{0:-^iw$}+{0:-^tw$}+{0:-^lw$}+{0:-^cw$}+", "")
+                            writeln!(buf, "\u{200b}+{0:-^iw$}+{0:-^tw$}+{0:-^lw$}+{0:-^cw$}+", "")
                         }
                         Justify::Right => {
-                            writeln!(buf, "+{0:-^iw$}+{0:-^tw$}+{0:-^lw$}+{0:->cw$}+", "")
+                            writeln!(buf, "\u{200b}+{0:-^iw$}+{0:-^tw$}+{0:-^lw$}+{0:->cw$}+", "")
                         }
                     }
                     .unwrap()
                 };
                 row_sep(&mut buf);
-                write!(buf, "|{index: ^iw$}|{term: ^tw$}|{lc: ^lw$}|").unwrap();
+                write!(buf, "\u{200b}|{index: ^iw$}|{term: ^tw$}|{lc: ^lw$}|").unwrap();
                 write_contents(&mut buf, contents).unwrap();
                 row_sep(&mut buf);
                 for [index, term, lc, contents] in rows {
                     if contents.len() <= cw {
-                        write!(buf, "|{index: ^iw$}|{term: ^tw$}|{lc: ^lw$}|").unwrap();
+                        write!(buf, "\u{200b}|{index: ^iw$}|{term: ^tw$}|{lc: ^lw$}|").unwrap();
                         write_contents(&mut buf, &contents).unwrap();
                     } else {
-                        write!(buf, "|{index: ^iw$}|{term: ^tw$}|{lc: ^lw$}|").unwrap();
+                        write!(buf, "\u{200b}|{index: ^iw$}|{term: ^tw$}|{lc: ^lw$}|").unwrap();
                         write_contents(&mut buf, &contents[..cw]).unwrap();
                         let mut rest = &contents[cw..];
                         while !rest.is_empty() {
                             let clamped_cw = usize::min(rest.len(), cw);
                             write!(
                                 buf,
-                                "|{blank: ^iw$}|{blank: ^tw$}|{blank: ^lw$}|",
+                                "\u{200b}|{blank: ^iw$}|{blank: ^tw$}|{blank: ^lw$}|",
                                 blank = "~",
                             )
                             .unwrap();
@@ -946,12 +976,13 @@ pub(crate) fn setup(args: &args::Run) {
                     }
                 }
                 row_sep(&mut buf);
-                if return_string {
-                    Ok(Some(String::from_utf8_lossy(&buf).into()))
-                } else {
-                    std::io::stdout().write_all(&buf).unwrap();
-                    Ok(None)
+
+                let s = String::from_utf8_lossy(&buf);
+                let mut res = vec![];
+                for line in s.lines() {
+                    res.push(line.into());
                 }
+                Ok(Some(res))
             },
         ),
     );
diff --git a/test/int/test_basics.py b/test/int/test_basics.py
index a2c24aac3d..280c55a873 100644
--- a/test/int/test_basics.py
+++ b/test/int/test_basics.py
@@ -193,13 +193,13 @@ def test_instance_info(instance: Instance):
 
 
 def test_raft_log(instance: Instance):
-    # fails due to screen size calculation
-    with pytest.raises(ReturnError) as _:
-        instance.call("pico.raft_log")
+    raft_log = instance.call("pico.raft_log", dict(max_width=256))
 
-    raft_log = instance.call("pico.raft_log", dict(return_string=True))
+    raft_log = str.join("\n", raft_log)
 
     def strip_spaces(s: str):
+        s = s.strip()
+        s = s.replace("\u200b", "")
         s = re.sub(r"[ ]*\|[ ]*", "|", s)
         s = re.sub(r"[-]*\+[-]*", "+", s)
         return s
-- 
GitLab