Skip to content

Commit

Permalink
console: configure continuation
Browse files Browse the repository at this point in the history
Continuation marker can be set up with `\set continuation` command.
Works on both server and client side in any language.

Closes tarantool#4317
Requires tarantool#7357

@TarantoolBot document
Title: introduce line carrying slash

Now we can use multiline commands with lines ending by configuring
continuation symbol. Works only when there is no set delimiter.
There is also an alias `\set c on|off`, that does the same.
Consider the example where the marker is set, used and removed:
```
tarantool> \set continuation on
---
- true
...

tarantool> a = 10\
         > + 12
---
...

tarantool> \set continuation off
---
- true
...

tarantool> a = 10\
---
- error: '[string "a = 10\"]:1: unexpected symbol near ''\'''
...

tarantool>

```
  • Loading branch information
Lord-KA committed Mar 6, 2023
1 parent 56ec2cc commit 4ce4fb3
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## feature/console

* Now multiline commands can be used by setting up continuation marker
with `\set continuation` command.
82 changes: 71 additions & 11 deletions src/box/lua/console.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ local output_eos = { ["yaml"] = '\n...\n', ["lua"] = ';' }

local default_local_eos = ''

-- Each but the last string of multiline command should end with this string.
local continuation_symbol = '\\'

output_handlers["yaml"] = function(status, _opts, ...)
local err, ok, res
-- Using pcall, because serializer can raise an exception
Expand Down Expand Up @@ -294,6 +297,14 @@ local function set_output(storage, value)
return true
end

local function set_continuation(storage, value)
if value ~= 'on' and value ~= 'off' then
return error('usage: \\set continuation on|off')
end
storage.continuation_on = value == 'on'
return true
end

local param_handlers = {
language = set_language,
lang = set_language,
Expand All @@ -302,7 +313,8 @@ local param_handlers = {
o = set_output,
delimiter = set_delimiter,
delim = set_delimiter,
d = set_delimiter
d = set_delimiter,
continuation = set_continuation,
}

local function set_param(storage, _func, param, value)
Expand Down Expand Up @@ -497,6 +509,14 @@ local text_connection_mt = {
end
end
end
if operators[items[1]] == set_param and
param_handlers[items[2]] == set_continuation then

local continuation = items[3]
if (continuation == 'on' or continuation == 'off') then
self.continuation_on = continuation == 'on'
end
end
return text
end,
--
Expand Down Expand Up @@ -573,7 +593,8 @@ local function wrap_text_socket(connection, url, print_f)
print_f = print_f,
eos = current_eos(),
fmt = current_output()["fmt"],
local_eos = default_local_eos
local_eos = default_local_eos,
continuation_on = false,
}, text_connection_mt)
--
-- Prepare the connection: setup EOS symbol
Expand Down Expand Up @@ -649,19 +670,29 @@ local function local_read(self)
break
end
if delim == "" then
local lang = self.language
if not lang or lang == 'lua' then
-- stop once a complete Lua statement is entered
if local_check_lua(buf) then
break
end
local continuation_on
if self.remote ~= nil then
continuation_on = self.remote.continuation_on
else
break
continuation_on = self.continuation_on
end
local lang = self.language or 'lua'
-- Interpret continuation_symbol as a line continuation marker.
if continuation_on and line:endswith(continuation_symbol) then
buf = buf:sub(1, -1 - #continuation_symbol)
goto continue
end
-- Continue reading if Lua input seems incomplete according to
-- internal heuristic.
if lang == 'lua' and not local_check_lua(buf) then
goto continue
end
break
elseif #buf >= #delim and buf:sub(#buf - #delim + 1) == delim then
buf = buf:sub(0, #buf - #delim)
break
end
::continue::
buf = buf.."\n"
prompt = string.rep(' ', #self.prompt)
end
Expand Down Expand Up @@ -689,9 +720,12 @@ local function local_print(self, output)
end

--
-- Read command from connected client console.listen()
-- Read a line or a chunk from connected client console.listen().
--
local function client_read(self)
-- The value is returned without delimiter (if any) and trailing
-- newline character.
--
local function client_read_line(self)
--
-- Byte sequences that come over the network and come from
-- the local client console may have a different terminal
Expand All @@ -712,6 +746,31 @@ local function client_read(self)
return buf:sub(1, -#self.delimiter-2)
end

--
-- Read a command from connected client console.listen().
--
local function client_read(self)
local buf = ''
local continuation_on = self.continuation_on
while true do
local line = client_read_line(self)
if line == nil then
-- EOF or error.
--
-- Note: Even if `buf` is not empty, skip unfinished
-- command.
return nil
end
buf = buf .. line
if continuation_on and line:endswith(continuation_symbol) then
buf = buf:sub(1, -1 - #continuation_symbol)
else
break
end
end
return buf
end

--
-- Print result to connected client from console.listen()
--
Expand All @@ -733,6 +792,7 @@ local repl_mt = {
completion = internal.completion_handler;
ac = true;
local_eos = default_local_eos;
continuation_on = false;
};
}

Expand Down
63 changes: 63 additions & 0 deletions test/app-luatest/gh_4317_console_backslash_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
local t = require('luatest')
local g = t.group()

local result_str = [[tarantool> \set continuation on
---
- true
...
tarantool> local a = 0\
> for i = 1, 10 do
> a = a + i
> end\
> print(a)
55
---
...
tarantool> ]]

local TARANTOOL_PATH = arg[-1]

g.test_using_backslash_on_local_console = function()
local tarantool_command = [[\\set continuation on\nlocal a = 0\\\n]] ..
[[for i = 1, 10 do\na = a + i\nend\\\n]] ..
[[print(a)\n]]

local cmd = "printf '%s' | %s -i 2>/dev/null"
cmd = (cmd):format(tarantool_command, TARANTOOL_PATH)
local fh = io.popen(cmd, 'r')

-- XXX: Readline on CentOS 7 produces \e[?1034h escape sequence before
-- tarantool> prompt, remove it.
local result = fh:read('*a'):gsub('\x1b%[%?1034h', '')

fh:close()
t.assert_equals(result, result_str)
end

g.test_using_backslash_on_remote_console = function()
local console = require('console')
local socket = require('socket')
local log = require('log')

-- Suppress console log messages.
log.level(4)
local CONSOLE_SOCKET = '/tmp/tarantool-test-gh-4317.sock'
local EOL = '\n...\n'
console.listen(CONSOLE_SOCKET)
socket.tcp_connect('unix/', CONSOLE_SOCKET)
local tarantool_command = '\\set continuation on\nlocal a = 0 \\\n' ..
'for i = 1, 10 do\\\n a = a + i \\\n end \\\n' ..
'return a'

local client = socket.tcp_connect('unix/', CONSOLE_SOCKET)
client:write(('%s\n'):format(tarantool_command))
-- Read 'true' after '\set continuation on' and then read result.
client:read(EOL)
local result = client:read(EOL)
t.assert_str_contains(result, '- 55')
t.assert_not_str_contains(result, 'error')
client:close()
os.remove(CONSOLE_SOCKET)
end

0 comments on commit 4ce4fb3

Please sign in to comment.