Skip to content

Commit a173cbe

Browse files
dmygeroleg-jukovec
authored andcommitted
tt: add support set delimiter in console
The “\set delimiter [marker]” command is handled by the `tt` utility, simulating the behavior of a similar command for the Tarantool console. Closes #727
1 parent 22890dd commit a173cbe

File tree

7 files changed

+182
-6
lines changed

7 files changed

+182
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1919
cluster config (3.0) or cartridge orchestrator.
2020

2121
### Fixed
22+
- Command `\set delimiter [marker]` works correctly and don't hangs `tt` console.
2223

2324
- `tt log -f` crash on removing log directory.
2425

cli/connect/commands.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func (command argSetCmdDecorator) Aliases() []string {
160160
// Run checks that there is one allowed argument and runs the command.
161161
func (command argSetCmdDecorator) Run(console *Console,
162162
cmd string, args []string) (string, error) {
163-
if len(args) != 1 || !find(command.sorted, args[0]) {
163+
if len(command.sorted) > 0 && (len(args) != 1 || !find(command.sorted, args[0])) {
164164
return "", fmt.Errorf("the command expects one of: %s",
165165
strings.Join(command.sorted, ", "))
166166
}
@@ -361,6 +361,19 @@ func setTableColumnWidthMaxFunc(console *Console,
361361
return "", nil
362362
}
363363

364+
// setDelimiterMarker apply delimiter to the Console object.
365+
func setDelimiterMarker(console *Console, cmd string, args []string) (string, error) {
366+
switch len(args) {
367+
case 0:
368+
console.delimiter = ""
369+
case 1:
370+
console.delimiter = args[0]
371+
default:
372+
return "", fmt.Errorf("the command expects zero or single argument")
373+
}
374+
return "", nil
375+
}
376+
364377
// switchNextFormatFunc switches to a next output format.
365378
func switchNextFormatFunc(console *Console, cmd string, args []string) (string, error) {
366379
console.format = (1 + console.format) % formatter.FormatsAmount
@@ -453,6 +466,14 @@ var cmdInfos = []cmdInfo{
453466
),
454467
),
455468
},
469+
cmdInfo{
470+
Short: setDelimiter + " <marker>",
471+
Long: "set expression delimiter",
472+
Cmd: newArgSetCmdDecorator(
473+
newBaseCmd([]string{setDelimiter}, setDelimiterMarker),
474+
[]string{},
475+
),
476+
},
456477
cmdInfo{
457478
Short: setTableColumnWidthMaxShort + " <width>",
458479
Long: "set max column width for table/ttable",

cli/connect/console.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type Console struct {
6565
executor func(in string)
6666
completer func(in prompt.Document) []prompt.Suggest
6767
validators map[Language]ValidateCloser
68+
delimiter string
6869

6970
prompt *prompt.Prompt
7071
}
@@ -197,7 +198,7 @@ func getExecutor(console *Console) func(string) {
197198

198199
var completed bool
199200
validator := console.validators[console.language]
200-
console.input, completed = AddStmtPart(console.input, in, validator)
201+
console.input, completed = AddStmtPart(console.input, in, console.delimiter, validator)
201202
if !completed {
202203
console.livePrefixEnabled = true
203204
return

cli/connect/const.go

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const setTableDialect = "\\set table_format"
1919
// width for tables.
2020
const setTableColumnWidthMaxLong = "\\set table_column_width"
2121

22+
// setDelimiter set a custom expression delimiter for Tarantool console.
23+
const setDelimiter = "\\set delimiter"
24+
2225
// setTableColumnWidthMaxShort is a short command to set a maximum columnt
2326
// width for tables.
2427
const setTableColumnWidthMaxShort = "\\xw"

cli/connect/input.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package connect
33
import (
44
"fmt"
55
"strings"
6+
"unicode"
67

78
lua "github.com/yuin/gopher-lua"
89
)
@@ -86,9 +87,25 @@ func (v SQLValidator) Close() error {
8687
return nil
8788
}
8889

90+
// cleanupDelimiter checks if the statement ends with the string `delim`. If yes, it removes it.
91+
// Returns true if the delimiter has been removed.
92+
func cleanupDelimiter(stmt string, delim string) (string, bool) {
93+
if delim == "" {
94+
return stmt, true
95+
}
96+
no_space := strings.TrimRightFunc(stmt, func(r rune) bool {
97+
return unicode.IsSpace(r)
98+
})
99+
no_delim := strings.TrimSuffix(no_space, delim)
100+
if len(no_space) > len(no_delim) {
101+
return no_delim, true
102+
}
103+
return stmt, false
104+
}
105+
89106
// AddStmtPart adds a new part of the statement. It returns a result statement
90107
// and true if the statement is already completed.
91-
func AddStmtPart(stmt, part string, validator Validator) (string, bool) {
108+
func AddStmtPart(stmt, part, delim string, validator Validator) (string, bool) {
92109
if stmt == "" {
93110
trimmed := strings.TrimSpace(part)
94111
if trimmed != "" {
@@ -98,5 +115,7 @@ func AddStmtPart(stmt, part string, validator Validator) (string, bool) {
98115
stmt += "\n" + part
99116
}
100117

101-
return stmt, validator.Validate(stmt)
118+
var hasDelim bool
119+
stmt, hasDelim = cleanupDelimiter(stmt, delim)
120+
return stmt, hasDelim && validator.Validate(stmt)
102121
}

cli/connect/input_test.go

+66-2
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func TestAddStmtPart(t *testing.T) {
149149
}
150150
t.Run(name, func(t *testing.T) {
151151
validator.ret = c
152-
result, completed := AddStmtPart(stmt, part, validator)
152+
result, completed := AddStmtPart(stmt, part, "", validator)
153153
assert.Equal(t, expected, result)
154154
assert.Equal(t, c, completed)
155155
assert.Equal(t, expected, validator.in)
@@ -178,7 +178,71 @@ func TestAddStmtPart_luaValidator(t *testing.T) {
178178
stmt := ""
179179
for _, part := range parts {
180180
var completed bool
181-
stmt, completed = AddStmtPart(stmt, part.str, validator)
181+
stmt, completed = AddStmtPart(stmt, part.str, "", validator)
182+
183+
assert.Equal(t, part.expected, stmt)
184+
assert.Equal(t, part.completed, completed)
185+
}
186+
}
187+
188+
func TestAddStmtPart_luaValidator_Delimiter(t *testing.T) {
189+
validator := NewLuaValidator()
190+
defer validator.Close()
191+
192+
parts := []struct {
193+
str string
194+
expected string
195+
delim string
196+
completed bool
197+
}{
198+
{
199+
" ",
200+
"",
201+
"",
202+
true,
203+
},
204+
{
205+
"for i = 1,10 do ; ",
206+
"for i = 1,10 do ",
207+
";",
208+
false,
209+
},
210+
{
211+
" print(x)",
212+
"for i = 1,10 do \n print(x)",
213+
"",
214+
false,
215+
},
216+
{
217+
" local j = 5</br> ",
218+
"for i = 1,10 do \n print(x)\n local j = 5",
219+
"</br>",
220+
false,
221+
},
222+
{
223+
"",
224+
"for i = 1,10 do \n print(x)\n local j = 5\n",
225+
";",
226+
false,
227+
},
228+
{
229+
" ",
230+
"for i = 1,10 do \n print(x)\n local j = 5\n\n ",
231+
"",
232+
false,
233+
},
234+
{
235+
"end\t*** ",
236+
"for i = 1,10 do \n print(x)\n local j = 5\n\n \nend\t",
237+
"***",
238+
true,
239+
},
240+
}
241+
242+
stmt := ""
243+
for _, part := range parts {
244+
var completed bool
245+
stmt, completed = AddStmtPart(stmt, part.str, part.delim, validator)
182246

183247
assert.Equal(t, part.expected, stmt)
184248
assert.Equal(t, part.completed, completed)

test/integration/connect/test_connect.py

+67
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import shutil
55
import subprocess
66
import tempfile
7+
from pathlib import Path
78

89
import psutil
910
import pytest
@@ -233,6 +234,7 @@ def test_connect_and_get_commands_outputs(tt_cmd, tmpdir_with_cfg):
233234
\\set table_format <format> -- set table format default, jira or markdown
234235
\\set graphics <false/true> -- disables/enables pseudographics for table modes
235236
\\set table_column_width <width> -- set max column width for table/ttable
237+
\\set delimiter <marker> -- set expression delimiter
236238
\\xw <width> -- set max column width for table/ttable
237239
\\x -- switches output format cyclically
238240
\\x[l,t,T,y] -- set output format lua, table, ttable or yaml
@@ -253,6 +255,8 @@ def test_connect_and_get_commands_outputs(tt_cmd, tmpdir_with_cfg):
253255
commands["\\set graphics false"] = ""
254256
commands["\\set graphics true"] = ""
255257
commands["\\set table_column_width 1"] = ""
258+
commands["\\set delimiter ;"] = ""
259+
commands["\\set delimiter"] = ""
256260
commands["\\xw 1"] = ""
257261
commands["\\x"] = ""
258262
commands["\\xl"] = ""
@@ -331,6 +335,7 @@ def test_connect_and_get_commands_errors(tt_cmd, tmpdir_with_cfg):
331335
commands["\\set graphics arg"] = "⨯ the command expects one boolean"
332336
commands["\\set table_column_width"] = "⨯ the command expects one unsigned number"
333337
commands["\\set table_column_width arg"] = "⨯ the command expects one unsigned number"
338+
commands["\\set delimiter arg arg"] = "⨯ the command expects zero or single argument"
334339
commands["\\xw"] = "⨯ the command expects one unsigned number"
335340
commands["\\xw arg"] = "⨯ the command expects one unsigned number"
336341
commands["\\x arg"] = "⨯ the command does not expect arguments"
@@ -2595,3 +2600,65 @@ def test_connect_to_cluster_app(tt_cmd):
25952600
# Stop the Instance.
25962601
stop_app(tt_cmd, tmpdir, app_name)
25972602
shutil.rmtree(tmpdir)
2603+
2604+
2605+
@pytest.mark.parametrize(
2606+
"instance, opts, ready_file",
2607+
(
2608+
("test_app", None, Path(run_path, "test_app", control_socket)),
2609+
(
2610+
"localhost:3013",
2611+
{"-u": "test", "-p": "password"},
2612+
Path("ready"),
2613+
),
2614+
),
2615+
)
2616+
def test_set_delimiter(
2617+
tt_cmd, tmpdir_with_cfg, instance: str, opts: None | dict, ready_file: Path
2618+
):
2619+
input = """local a=1
2620+
a = a + 1
2621+
return a
2622+
"""
2623+
delimiter = "</br>"
2624+
tmpdir = Path(tmpdir_with_cfg)
2625+
2626+
# The test application file.
2627+
test_app_path = Path(__file__).parent / "test_localhost_app" / "test_app.lua"
2628+
# Copy test data into temporary directory.
2629+
copy_data(tmpdir, [test_app_path])
2630+
2631+
# Start an instance.
2632+
start_app(tt_cmd, tmpdir, "test_app")
2633+
# Check for start.
2634+
file = wait_file(tmpdir / "test_app" / ready_file.parent, ready_file.name, [])
2635+
assert file != ""
2636+
2637+
# Without delimiter should get an error.
2638+
ret, output = try_execute_on_instance(
2639+
tt_cmd,
2640+
tmpdir,
2641+
instance,
2642+
opts=opts,
2643+
stdin=input,
2644+
)
2645+
assert ret
2646+
assert "attempt to perform arithmetic on global" in output
2647+
2648+
# With delimiter expecting correct responses.
2649+
input = f"\\set delimiter {delimiter}\n{input}{delimiter}\n"
2650+
ret, output = try_execute_on_instance(
2651+
tt_cmd, tmpdir, instance, opts=opts, stdin=input
2652+
)
2653+
assert ret
2654+
assert (
2655+
output
2656+
== """---
2657+
- 2
2658+
...
2659+
2660+
"""
2661+
)
2662+
2663+
# Stop the Instance.
2664+
stop_app(tt_cmd, tmpdir, "test_app")

0 commit comments

Comments
 (0)