diff --git a/README.md b/README.md index ad754df..4c195f2 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,14 @@ See more in [Full Documentation](https://replaywizard.craftsman.lol/install.html ## Quickstart ```python -from replay_wizard.capturing import capture -capture('one') +import time +from quickstart.capturing import capture_actions +from quickstart.replay import replay_actions + +if __name__ == '__main__': + capture_actions() + time.sleep(5) + replay_actions() ``` ### More examples in [Full Documentation][documentation_path] @@ -125,9 +131,9 @@ This features will be built during 4 weeks. - Capture mouse actions - Save mouse actions - Replay mouse actions -- Capture keyboards actions -- Save keyboards actions -- Replay keyboards actions +- Capture keyboards actions (Done) +- Save keyboards actions (Done) +- Replay keyboards actions (Done) - Capture environment - Save environment - Replay environment diff --git a/docs/package/replay_wizard.capturing.rst b/docs/package/replay_wizard.capturing.rst new file mode 100644 index 0000000..59881fa --- /dev/null +++ b/docs/package/replay_wizard.capturing.rst @@ -0,0 +1,37 @@ +replay\_wizard.capturing package +================================ + +Submodules +---------- + +replay\_wizard.capturing.capture module +--------------------------------------- + +.. automodule:: replay_wizard.capturing.capture + :members: + :undoc-members: + :show-inheritance: + +replay\_wizard.capturing.errors module +-------------------------------------- + +.. automodule:: replay_wizard.capturing.errors + :members: + :undoc-members: + :show-inheritance: + +replay\_wizard.capturing.keyboard module +---------------------------------------- + +.. automodule:: replay_wizard.capturing.keyboard + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: replay_wizard.capturing + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/package/replay_wizard.models.rst b/docs/package/replay_wizard.models.rst new file mode 100644 index 0000000..c364901 --- /dev/null +++ b/docs/package/replay_wizard.models.rst @@ -0,0 +1,29 @@ +replay\_wizard.models package +============================= + +Submodules +---------- + +replay\_wizard.models.action module +----------------------------------- + +.. automodule:: replay_wizard.models.action + :members: + :undoc-members: + :show-inheritance: + +replay\_wizard.models.sequence module +------------------------------------- + +.. automodule:: replay_wizard.models.sequence + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: replay_wizard.models + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index e81ab97..44a013a 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,5 +1,15 @@ Quickstart ---------- -.. literalinclude:: ../quickstart/main.py - :pyobject: main +To capture actions +================== + +.. literalinclude:: ../quickstart/capturing.py + :pyobject: capture_actions + +To replay actions +================= + +.. literalinclude:: ../quickstart/replay.py + :pyobject: replay_actions + diff --git a/main.py b/main.py index 78bc0ee..10e8b8b 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,11 @@ """ Manual run module """ -from quickstart.main import main +import time +from quickstart.capturing import capture_actions +from quickstart.replay import replay_actions if __name__ == '__main__': - main() + capture_actions() + time.sleep(5) + replay_actions() diff --git a/quickstart/main.py b/quickstart/capturing.py similarity index 89% rename from quickstart/main.py rename to quickstart/capturing.py index 8941fa2..340a56a 100644 --- a/quickstart/main.py +++ b/quickstart/capturing.py @@ -3,7 +3,7 @@ """ -def main(): +def capture_actions(): """ ReplayWizard simple usage :return: diff --git a/quickstart/replay.py b/quickstart/replay.py new file mode 100644 index 0000000..74d4667 --- /dev/null +++ b/quickstart/replay.py @@ -0,0 +1,13 @@ +""" +Quickstart examples +""" + + +def replay_actions(): + """ + ReplayWizard simple usage + :return: + """ + from replay_wizard.replay import replay # pylint: disable=import-outside-toplevel + + replay('one') diff --git a/replay_wizard/models/sequence.py b/replay_wizard/models/sequence.py index 91f8a15..8a1458d 100644 --- a/replay_wizard/models/sequence.py +++ b/replay_wizard/models/sequence.py @@ -16,6 +16,9 @@ class Sequence(BaseModel): def __len__(self): return len(self.actions) + def __iter__(self): + return iter(self.actions) + def add(self, new_action: Action): """ Add action to sequence diff --git a/replay_wizard/package.py b/replay_wizard/package.py index 0b369ac..ebacbf2 100644 --- a/replay_wizard/package.py +++ b/replay_wizard/package.py @@ -2,5 +2,5 @@ Package info """ name = 'replay-wizard' -version = '0.0.2' -status = '2 - Pre-Alpha' +version = '0.1.0' +status = '3 - Alpha' diff --git a/replay_wizard/replay/__init__.py b/replay_wizard/replay/__init__.py new file mode 100644 index 0000000..1af3cee --- /dev/null +++ b/replay_wizard/replay/__init__.py @@ -0,0 +1,4 @@ +""" +Replay package +""" +from .core import replay diff --git a/replay_wizard/replay/core.py b/replay_wizard/replay/core.py new file mode 100644 index 0000000..b9d2a73 --- /dev/null +++ b/replay_wizard/replay/core.py @@ -0,0 +1,37 @@ +""" +Replay core module +""" +import json +from replay_wizard.models import Sequence, Action +from .keyboard import push_button + + +def replay(name, extension='sequence'): + """ + Replay sequence from file + + :param name: sequence name, without extension + :param extension: sequence file extension + """ + filename = f'{name}.{extension}' + with open(filename, 'r', encoding='utf-8') as f: + sequence_dict = json.load(f) + sequence = Sequence.model_validate(sequence_dict) + replay_sequence(sequence) + + +def replay_action(action: Action): + """ + Replay one Action + """ + push_button(action) + + +def replay_sequence(sequence: Sequence): + """ + Replay sequence + + :param sequence: sequence to replay + """ + for action in sequence: + replay_action(action) diff --git a/replay_wizard/replay/keyboard.py b/replay_wizard/replay/keyboard.py new file mode 100644 index 0000000..3171faf --- /dev/null +++ b/replay_wizard/replay/keyboard.py @@ -0,0 +1,21 @@ +""" +Keyboard replay functions +""" +import pynput +from pynput.keyboard import Key +from replay_wizard.models import Action + + +def push_button(action: Action): + """ + Push keyboard button + """ + value = action.value + action_type = action.action + try: + value = Key[value] + except KeyError: + pass + keyboard = pynput.keyboard.Controller() + push_function = getattr(keyboard, action_type) + push_function(value) diff --git a/sequence.testdata b/sequence.testdata new file mode 100644 index 0000000..c915abf --- /dev/null +++ b/sequence.testdata @@ -0,0 +1 @@ +{"name": "one", "actions": [{"subtype": "keyboard", "value": "m", "action": "press"}, {"subtype": "keyboard", "value": "m", "action": "release"}, {"subtype": "keyboard", "value": "o", "action": "press"}, {"subtype": "keyboard", "value": "o", "action": "release"}, {"subtype": "keyboard", "value": "u", "action": "press"}, {"subtype": "keyboard", "value": "u", "action": "release"}, {"subtype": "keyboard", "value": "r", "action": "press"}, {"subtype": "keyboard", "value": "r", "action": "release"}, {"subtype": "keyboard", "value": "backspace", "action": "press"}, {"subtype": "keyboard", "value": "backspace", "action": "release"}, {"subtype": "keyboard", "value": "s", "action": "press"}, {"subtype": "keyboard", "value": "s", "action": "release"}, {"subtype": "keyboard", "value": "t", "action": "press"}, {"subtype": "keyboard", "value": "h", "action": "press"}, {"subtype": "keyboard", "value": "t", "action": "release"}, {"subtype": "keyboard", "value": "h", "action": "release"}, {"subtype": "keyboard", "value": "i", "action": "press"}, {"subtype": "keyboard", "value": "i", "action": "release"}, {"subtype": "keyboard", "value": "s", "action": "press"}, {"subtype": "keyboard", "value": "space", "action": "press"}, {"subtype": "keyboard", "value": "s", "action": "release"}, {"subtype": "keyboard", "value": "space", "action": "release"}, {"subtype": "keyboard", "value": "i", "action": "press"}, {"subtype": "keyboard", "value": "i", "action": "release"}, {"subtype": "keyboard", "value": "s", "action": "press"}, {"subtype": "keyboard", "value": "space", "action": "press"}, {"subtype": "keyboard", "value": "s", "action": "release"}, {"subtype": "keyboard", "value": "space", "action": "release"}, {"subtype": "keyboard", "value": "t", "action": "press"}, {"subtype": "keyboard", "value": "t", "action": "release"}, {"subtype": "keyboard", "value": "backspace", "action": "press"}, {"subtype": "keyboard", "value": "backspace", "action": "release"}, {"subtype": "keyboard", "value": "r", "action": "press"}, {"subtype": "keyboard", "value": "r", "action": "release"}, {"subtype": "keyboard", "value": "e", "action": "press"}, {"subtype": "keyboard", "value": "e", "action": "release"}, {"subtype": "keyboard", "value": "p", "action": "press"}, {"subtype": "keyboard", "value": "p", "action": "release"}, {"subtype": "keyboard", "value": "l", "action": "press"}, {"subtype": "keyboard", "value": "l", "action": "release"}, {"subtype": "keyboard", "value": "a", "action": "press"}, {"subtype": "keyboard", "value": "a", "action": "release"}, {"subtype": "keyboard", "value": "y", "action": "press"}, {"subtype": "keyboard", "value": "y", "action": "release"}, {"subtype": "keyboard", "value": "enter", "action": "press"}, {"subtype": "keyboard", "value": "enter", "action": "release"}]} \ No newline at end of file diff --git a/testing/test_replay_wizard/conftest.py b/testing/test_replay_wizard/conftest.py index 32b621f..ca9ce22 100644 --- a/testing/test_replay_wizard/conftest.py +++ b/testing/test_replay_wizard/conftest.py @@ -1,10 +1,37 @@ """ Pytest fixtures """ -import pytest -from replay_wizard.models import Sequence +from pytest import fixture +from replay_wizard.models import Action, Subtypes, Sequence, ActionEnum -@pytest.fixture + +@fixture +def put_a_action(): + """ + Simple action fixture + """ + return Action( + subtype=Subtypes.KEYBOARD, + value='a', + timedelta=0.1, + ) + + +@fixture +def put_enter_action(): + """ + Simple action fixture + """ + action = Action( + subtype=Subtypes.KEYBOARD, + value='enter', + timedelta=0.1, + action=ActionEnum.RELEASE + ) + return action + + +@fixture def empty_sequence(): """ Empty sequence fixture @@ -13,3 +40,13 @@ def empty_sequence(): name='open youtube', actions=[] ) + + +@fixture +def one_action_sequence(empty_sequence, put_a_action): + """ + sequence with one action + """ + + empty_sequence.add(put_a_action) + return empty_sequence diff --git a/testing/test_replay_wizard/test_models/conftest.py b/testing/test_replay_wizard/test_models/conftest.py index 739fe39..3c454df 100644 --- a/testing/test_replay_wizard/test_models/conftest.py +++ b/testing/test_replay_wizard/test_models/conftest.py @@ -2,19 +2,6 @@ Pytest fixtures """ from pytest import fixture -from replay_wizard.models import Action, Subtypes - - -@fixture -def put_a_action(): - """ - Simple action fixture - """ - return Action( - subtype=Subtypes.KEYBOARD, - value='a', - timedelta=0.1, - ) @fixture diff --git a/testing/test_replay_wizard/test_models/test_sequence.py b/testing/test_replay_wizard/test_models/test_sequence.py index 154f3bc..4823096 100644 --- a/testing/test_replay_wizard/test_models/test_sequence.py +++ b/testing/test_replay_wizard/test_models/test_sequence.py @@ -60,3 +60,16 @@ def test_in(empty_sequence, put_a_action): assert (put_a_action in empty_sequence) is False empty_sequence.add(put_a_action) assert put_a_action in empty_sequence + + +def test_for(one_action_sequence, put_a_action): + """ + Test for method + """ + one_action_sequence.add(put_a_action) + one_action_sequence.add(put_a_action) + count = 0 + for action in one_action_sequence: + count += 1 + assert isinstance(action, Action) + assert count == len(one_action_sequence) diff --git a/replay_wizard/main.py b/testing/test_replay_wizard/test_replay/__init__.py similarity index 100% rename from replay_wizard/main.py rename to testing/test_replay_wizard/test_replay/__init__.py diff --git a/testing/test_replay_wizard/test_replay/conftest.py b/testing/test_replay_wizard/test_replay/conftest.py new file mode 100644 index 0000000..304ee99 --- /dev/null +++ b/testing/test_replay_wizard/test_replay/conftest.py @@ -0,0 +1,40 @@ +""" +Pytest fixtures +""" +from pytest import fixture + + +class MockKeyboardController: + """ + Mock class for pynput Controller + """ + + action_list = [] + + def press(self, key): + """ + Mocked press method + """ + self.action_list.append(('PRESS', key)) + + def release(self, key): + """ + Mocked release method + """ + self.action_list.append(('RELEASE', key)) + + @classmethod + def clear(cls): + """ + Clear action lint after testing + Because it's class property + """ + cls.action_list.clear() + + +@fixture +def mocked_keyboard_controller(): + """ + Mock keyboard kontroller from pynput + """ + return MockKeyboardController diff --git a/testing/test_replay_wizard/test_replay/test_core.py b/testing/test_replay_wizard/test_replay/test_core.py new file mode 100644 index 0000000..bc35fd4 --- /dev/null +++ b/testing/test_replay_wizard/test_replay/test_core.py @@ -0,0 +1,31 @@ +""" +Test core module +""" +from unittest import mock +from replay_wizard.replay.core import replay, replay_sequence, replay_action + + +def test_replay(mocked_keyboard_controller): + """ + Test for replay function + """ + with mock.patch('pynput.keyboard.Controller', mocked_keyboard_controller): + replay('sequence','testdata') + + +def test_replay_sequence(one_action_sequence, mocked_keyboard_controller): + """ + Test replay function + """ + with mock.patch('pynput.keyboard.Controller', mocked_keyboard_controller) as mock_controller: + replay_sequence(one_action_sequence) + mock_controller.clear() + + +def test_replay_action(put_a_action, mocked_keyboard_controller): + """ + Test replay one action + """ + with mock.patch('pynput.keyboard.Controller', mocked_keyboard_controller) as mock_controller: + replay_action(put_a_action) + mock_controller.clear() diff --git a/testing/test_replay_wizard/test_replay/test_keyboard.py b/testing/test_replay_wizard/test_replay/test_keyboard.py new file mode 100644 index 0000000..092b037 --- /dev/null +++ b/testing/test_replay_wizard/test_replay/test_keyboard.py @@ -0,0 +1,26 @@ +""" +Test keyboard module +""" +from unittest import mock +from pynput.keyboard import Key + +from replay_wizard.replay.keyboard import push_button + + +def test_push_button(put_a_action, put_enter_action, mocked_keyboard_controller): + """ + Test push button function + """ + with mock.patch('pynput.keyboard.Controller', mocked_keyboard_controller) as mock_controller: + assert len(mock_controller.action_list) == 0 + push_button(put_a_action) + assert len(mock_controller.action_list) == 1 + action_type, key = mock_controller.action_list[0] + assert action_type == 'PRESS' + assert key == 'a' + push_button(put_enter_action) + assert len(mock_controller.action_list) == 2 + action_type, key = mock_controller.action_list[1] + assert action_type == 'RELEASE' + assert key == Key.enter + mock_controller.clear()