Skip to content

Commit

Permalink
Implement search string keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
robinmatz committed Oct 15, 2023
1 parent 3de6d4f commit 74258bb
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[flake8]
ignore = E501
ignore = E203, E501
12 changes: 12 additions & 0 deletions Mainframe3270/keywords/read_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ def read_all_screen(self) -> str:
"""
return self.mf.read_all_screen()

@keyword("Find String")
def find_string(self, search_string: str, ignore_case: bool = False):
"""Returns a list of tuples of ypos and xpos for the position where the `search_string` was found,
or an empty list if it was not found.
If `ignore_case` is set to `True`, then the search is done case-insensitively.
Example:
| ${indices} | Find String | Abc | # Returns something like [(1, 8)]
"""
return self.mf.find_string(search_string, ignore_case)

@keyword("Write")
def write(self, txt: str) -> None:
"""Send a string *and Enter* to the screen at the current cursor location.
Expand Down
22 changes: 22 additions & 0 deletions Mainframe3270/py3270.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import errno
import logging
import math
import re
import socket
import subprocess
import time
Expand Down Expand Up @@ -495,11 +497,22 @@ def search_string(self, string, ignore_case=False):
for ypos in range(self.model_dimensions["rows"]):
line = self.string_get(ypos + 1, 1, self.model_dimensions["columns"])
if ignore_case:
string = string.lower()
line = line.lower()
if string in line:
return True
return False

def find_string(self, search_string, ignore_case=False):
"""Returns a list of tuples of ypos and xpos for the position where the `search_string` was found,
or an empty list if it was not found."""
screen_content = self.read_all_screen().lower() if ignore_case else self.read_all_screen()
search_string = search_string.lower() if ignore_case else search_string
indices_object = re.finditer(re.escape(search_string), screen_content)
indices = [index.start() for index in indices_object]
# ypos and xpos should be returned 1-based
return [self._get_ypos_and_xpos_from_index(index + 1) for index in indices]

def read_all_screen(self):
"""
Read all the mainframe screen and return it in a single string.
Expand Down Expand Up @@ -545,3 +558,12 @@ def _check_limits(self, ypos, xpos):
raise Exception("You have exceeded the y-axis limit of the mainframe screen")
if xpos > self.model_dimensions["columns"]:
raise Exception("You have exceeded the x-axis limit of the mainframe screen")

def _get_ypos_and_xpos_from_index(self, index):
ypos = math.ceil(index / self.model_dimensions["columns"])
remainder = index % self.model_dimensions["columns"]
if remainder == 0:
xpos = self.model_dimensions["columns"]
else:
xpos = remainder
return (ypos, xpos)
16 changes: 16 additions & 0 deletions utest/Mainframe3270/keywords/test_read_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,19 @@ def test_write_bare_in_position(mocker: MockerFixture, under_test: ReadWriteKeyw
Emulator.move_to.assert_called_once_with(5, 5)
Emulator.exec_command.assert_called_once_with(b'String("abc")')
Emulator.send_enter.assert_not_called()


def test_find_string(mocker: MockerFixture, under_test: ReadWriteKeywords):
mocker.patch("Mainframe3270.py3270.Emulator.find_string", return_value=[(5, 10)])

assert under_test.find_string("abc") == [(5, 10)]

Emulator.find_string.assert_called_once_with("abc", False)


def test_find_string_ignore_case(mocker: MockerFixture, under_test: ReadWriteKeywords):
mocker.patch("Mainframe3270.py3270.Emulator.find_string", return_value=[(5, 10)])

assert under_test.find_string("abc", ignore_case=True) == [(5, 10)]

Emulator.find_string.assert_called_once_with("abc", True)
45 changes: 44 additions & 1 deletion utest/py3270/test_emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def test_search_string_ignoring_case(mocker: MockerFixture):
mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="ABC")
under_test = Emulator()

assert under_test.search_string("abc", True)
assert under_test.search_string("aBc", True)


@pytest.mark.usefixtures("mock_windows")
Expand Down Expand Up @@ -331,3 +331,46 @@ def test_check_limits_raises_Exception(model, ypos, xpos, expected_error):

with pytest.raises(Exception, match=expected_error):
under_test._check_limits(ypos, xpos)


@pytest.mark.usefixtures("mock_windows")
@pytest.mark.parametrize(
("model", "index", "expected"),
[
("2", 1, [(1, 2)]),
("2", 79, [(1, 80)]),
("2", 80, [(2, 1)]),
("2", 139, [(2, 60)]),
("5", 131, [(1, 132)]),
],
)
def test_find_string(mocker: MockerFixture, model, index, expected):
under_test = Emulator(model=model)
mocker.patch(
"Mainframe3270.py3270.Emulator.read_all_screen", return_value=_mock_return_all_screen(under_test, "abc", index)
)

assert under_test.find_string("abc") == expected


def test_find_string_ignore_case(mocker: MockerFixture):
under_test = Emulator()
mocker.patch(
"Mainframe3270.py3270.Emulator.read_all_screen", return_value=_mock_return_all_screen(under_test, "ABC", 5)
)

assert under_test.find_string("aBc", True) == [(1, 6)]


def test_find_string_without_result(mocker: MockerFixture):
under_test = Emulator()
mocker.patch(
"Mainframe3270.py3270.Emulator.read_all_screen", return_value=_mock_return_all_screen(under_test, "abc", 1)
)

assert under_test.find_string("does not exist") == []


def _mock_return_all_screen(emulator: Emulator, insert_string: str, at_index: int):
base_str = "a" * (emulator.model_dimensions["rows"] * emulator.model_dimensions["columns"])
return base_str[:at_index] + insert_string + base_str[at_index : -len(insert_string)]

0 comments on commit 74258bb

Please sign in to comment.