Skip to content

Commit

Permalink
tt: add support set delimiter in console
Browse files Browse the repository at this point in the history
The “\set delimiter [marker]” command is handled by the `tt` utility, simulating the behavior of a similar command for the Tarantool console.

Closes #727
  • Loading branch information
dmyger authored and oleg-jukovec committed Sep 25, 2024
1 parent 22890dd commit a173cbe
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
cluster config (3.0) or cartridge orchestrator.

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

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

Expand Down
23 changes: 22 additions & 1 deletion cli/connect/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (command argSetCmdDecorator) Aliases() []string {
// Run checks that there is one allowed argument and runs the command.
func (command argSetCmdDecorator) Run(console *Console,
cmd string, args []string) (string, error) {
if len(args) != 1 || !find(command.sorted, args[0]) {
if len(command.sorted) > 0 && (len(args) != 1 || !find(command.sorted, args[0])) {
return "", fmt.Errorf("the command expects one of: %s",
strings.Join(command.sorted, ", "))
}
Expand Down Expand Up @@ -361,6 +361,19 @@ func setTableColumnWidthMaxFunc(console *Console,
return "", nil
}

// setDelimiterMarker apply delimiter to the Console object.
func setDelimiterMarker(console *Console, cmd string, args []string) (string, error) {
switch len(args) {
case 0:
console.delimiter = ""
case 1:
console.delimiter = args[0]
default:
return "", fmt.Errorf("the command expects zero or single argument")
}
return "", nil
}

// switchNextFormatFunc switches to a next output format.
func switchNextFormatFunc(console *Console, cmd string, args []string) (string, error) {
console.format = (1 + console.format) % formatter.FormatsAmount
Expand Down Expand Up @@ -453,6 +466,14 @@ var cmdInfos = []cmdInfo{
),
),
},
cmdInfo{
Short: setDelimiter + " <marker>",
Long: "set expression delimiter",
Cmd: newArgSetCmdDecorator(
newBaseCmd([]string{setDelimiter}, setDelimiterMarker),
[]string{},
),
},
cmdInfo{
Short: setTableColumnWidthMaxShort + " <width>",
Long: "set max column width for table/ttable",
Expand Down
3 changes: 2 additions & 1 deletion cli/connect/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type Console struct {
executor func(in string)
completer func(in prompt.Document) []prompt.Suggest
validators map[Language]ValidateCloser
delimiter string

prompt *prompt.Prompt
}
Expand Down Expand Up @@ -197,7 +198,7 @@ func getExecutor(console *Console) func(string) {

var completed bool
validator := console.validators[console.language]
console.input, completed = AddStmtPart(console.input, in, validator)
console.input, completed = AddStmtPart(console.input, in, console.delimiter, validator)
if !completed {
console.livePrefixEnabled = true
return
Expand Down
3 changes: 3 additions & 0 deletions cli/connect/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const setTableDialect = "\\set table_format"
// width for tables.
const setTableColumnWidthMaxLong = "\\set table_column_width"

// setDelimiter set a custom expression delimiter for Tarantool console.
const setDelimiter = "\\set delimiter"

// setTableColumnWidthMaxShort is a short command to set a maximum columnt
// width for tables.
const setTableColumnWidthMaxShort = "\\xw"
Expand Down
23 changes: 21 additions & 2 deletions cli/connect/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package connect
import (
"fmt"
"strings"
"unicode"

lua "github.com/yuin/gopher-lua"
)
Expand Down Expand Up @@ -86,9 +87,25 @@ func (v SQLValidator) Close() error {
return nil
}

// cleanupDelimiter checks if the statement ends with the string `delim`. If yes, it removes it.
// Returns true if the delimiter has been removed.
func cleanupDelimiter(stmt string, delim string) (string, bool) {
if delim == "" {
return stmt, true
}
no_space := strings.TrimRightFunc(stmt, func(r rune) bool {
return unicode.IsSpace(r)
})
no_delim := strings.TrimSuffix(no_space, delim)
if len(no_space) > len(no_delim) {
return no_delim, true
}
return stmt, false
}

// AddStmtPart adds a new part of the statement. It returns a result statement
// and true if the statement is already completed.
func AddStmtPart(stmt, part string, validator Validator) (string, bool) {
func AddStmtPart(stmt, part, delim string, validator Validator) (string, bool) {
if stmt == "" {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
Expand All @@ -98,5 +115,7 @@ func AddStmtPart(stmt, part string, validator Validator) (string, bool) {
stmt += "\n" + part
}

return stmt, validator.Validate(stmt)
var hasDelim bool
stmt, hasDelim = cleanupDelimiter(stmt, delim)
return stmt, hasDelim && validator.Validate(stmt)
}
68 changes: 66 additions & 2 deletions cli/connect/input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func TestAddStmtPart(t *testing.T) {
}
t.Run(name, func(t *testing.T) {
validator.ret = c
result, completed := AddStmtPart(stmt, part, validator)
result, completed := AddStmtPart(stmt, part, "", validator)
assert.Equal(t, expected, result)
assert.Equal(t, c, completed)
assert.Equal(t, expected, validator.in)
Expand Down Expand Up @@ -178,7 +178,71 @@ func TestAddStmtPart_luaValidator(t *testing.T) {
stmt := ""
for _, part := range parts {
var completed bool
stmt, completed = AddStmtPart(stmt, part.str, validator)
stmt, completed = AddStmtPart(stmt, part.str, "", validator)

assert.Equal(t, part.expected, stmt)
assert.Equal(t, part.completed, completed)
}
}

func TestAddStmtPart_luaValidator_Delimiter(t *testing.T) {
validator := NewLuaValidator()
defer validator.Close()

parts := []struct {
str string
expected string
delim string
completed bool
}{
{
" ",
"",
"",
true,
},
{
"for i = 1,10 do ; ",
"for i = 1,10 do ",
";",
false,
},
{
" print(x)",
"for i = 1,10 do \n print(x)",
"",
false,
},
{
" local j = 5</br> ",
"for i = 1,10 do \n print(x)\n local j = 5",
"</br>",
false,
},
{
"",
"for i = 1,10 do \n print(x)\n local j = 5\n",
";",
false,
},
{
" ",
"for i = 1,10 do \n print(x)\n local j = 5\n\n ",
"",
false,
},
{
"end\t*** ",
"for i = 1,10 do \n print(x)\n local j = 5\n\n \nend\t",
"***",
true,
},
}

stmt := ""
for _, part := range parts {
var completed bool
stmt, completed = AddStmtPart(stmt, part.str, part.delim, validator)

assert.Equal(t, part.expected, stmt)
assert.Equal(t, part.completed, completed)
Expand Down
67 changes: 67 additions & 0 deletions test/integration/connect/test_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import shutil
import subprocess
import tempfile
from pathlib import Path

import psutil
import pytest
Expand Down Expand Up @@ -233,6 +234,7 @@ def test_connect_and_get_commands_outputs(tt_cmd, tmpdir_with_cfg):
\\set table_format <format> -- set table format default, jira or markdown
\\set graphics <false/true> -- disables/enables pseudographics for table modes
\\set table_column_width <width> -- set max column width for table/ttable
\\set delimiter <marker> -- set expression delimiter
\\xw <width> -- set max column width for table/ttable
\\x -- switches output format cyclically
\\x[l,t,T,y] -- set output format lua, table, ttable or yaml
Expand All @@ -253,6 +255,8 @@ def test_connect_and_get_commands_outputs(tt_cmd, tmpdir_with_cfg):
commands["\\set graphics false"] = ""
commands["\\set graphics true"] = ""
commands["\\set table_column_width 1"] = ""
commands["\\set delimiter ;"] = ""
commands["\\set delimiter"] = ""
commands["\\xw 1"] = ""
commands["\\x"] = ""
commands["\\xl"] = ""
Expand Down Expand Up @@ -331,6 +335,7 @@ def test_connect_and_get_commands_errors(tt_cmd, tmpdir_with_cfg):
commands["\\set graphics arg"] = "⨯ the command expects one boolean"
commands["\\set table_column_width"] = "⨯ the command expects one unsigned number"
commands["\\set table_column_width arg"] = "⨯ the command expects one unsigned number"
commands["\\set delimiter arg arg"] = "⨯ the command expects zero or single argument"
commands["\\xw"] = "⨯ the command expects one unsigned number"
commands["\\xw arg"] = "⨯ the command expects one unsigned number"
commands["\\x arg"] = "⨯ the command does not expect arguments"
Expand Down Expand Up @@ -2595,3 +2600,65 @@ def test_connect_to_cluster_app(tt_cmd):
# Stop the Instance.
stop_app(tt_cmd, tmpdir, app_name)
shutil.rmtree(tmpdir)


@pytest.mark.parametrize(
"instance, opts, ready_file",
(
("test_app", None, Path(run_path, "test_app", control_socket)),
(
"localhost:3013",
{"-u": "test", "-p": "password"},
Path("ready"),
),
),
)
def test_set_delimiter(
tt_cmd, tmpdir_with_cfg, instance: str, opts: None | dict, ready_file: Path
):
input = """local a=1
a = a + 1
return a
"""
delimiter = "</br>"
tmpdir = Path(tmpdir_with_cfg)

# The test application file.
test_app_path = Path(__file__).parent / "test_localhost_app" / "test_app.lua"
# Copy test data into temporary directory.
copy_data(tmpdir, [test_app_path])

# Start an instance.
start_app(tt_cmd, tmpdir, "test_app")
# Check for start.
file = wait_file(tmpdir / "test_app" / ready_file.parent, ready_file.name, [])
assert file != ""

# Without delimiter should get an error.
ret, output = try_execute_on_instance(
tt_cmd,
tmpdir,
instance,
opts=opts,
stdin=input,
)
assert ret
assert "attempt to perform arithmetic on global" in output

# With delimiter expecting correct responses.
input = f"\\set delimiter {delimiter}\n{input}{delimiter}\n"
ret, output = try_execute_on_instance(
tt_cmd, tmpdir, instance, opts=opts, stdin=input
)
assert ret
assert (
output
== """---
- 2
...
"""
)

# Stop the Instance.
stop_app(tt_cmd, tmpdir, "test_app")

0 comments on commit a173cbe

Please sign in to comment.