diff --git a/.flake8 b/.flake8 index b1576c3..5b5a5ef 100644 --- a/.flake8 +++ b/.flake8 @@ -9,6 +9,7 @@ ignore=WPS100, # Found wrong module name WPS111, # Found too short name WPS115, # Found upper-case constant in a class. Disabled due to false positive results in enums. WPS201, # Found module with too many imports + WPS202, # Found too many module members WPS210, # Found too many local variables WPS221, # Found line with high Jones Complexity WPS237, # Found a too complex `f` string diff --git a/README.md b/README.md index 44ae6b0..3f6c9e4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ pip install git+https://github.com/GirZ0n/anderson.git Run the tool with the several arguments: ```bash -anderson [--debug] +anderson [--debug] ``` where: - `` – Executable to record. @@ -49,6 +49,7 @@ The `interaction_config` contains the following arguments: - `keystroke_delay` – Delay between each keystroke (in milliseconds). By default, `150`. - `keystroke_std` – Standard deviation for the `keystroke_delay` (in milliseconds). By default, `60`. - `action_delay` – Delay between each action (in milliseconds). By default, `80`. +- `timeout` – Number of seconds to wait for the `expect` action to complete. The `gif_config` contains the following fields: - `gifs` – List of arguments for each of the GIFs you want to generate. @@ -71,9 +72,13 @@ The `gifs` list item consists of the following arguments: - `line_height` – Line height. By default, `1.4`. - `speed` – Playback speed. By default, `1`. - `no_loop` – Disable animation loop. By default, `false`. +- `wait_before_loop` – Number of second to wait before a new loop. By default, `3`. There are several kinds of actions that can be present in the `scenario`: -- `enter` – Enter some string in the terminal. +- `write` – Write some string in the terminal and press enter. +- `send` – Write some string in the terminal (without pressing enter). +- `press` -- Press some character. +- `ctrl` -- Press Ctrl + some character. - `expect` – Wait for some string in the terminal. - `delay` – Overwrite the `keystroke_delay` argument. **Note**: the overwriting happens globally, therefore if you need to change this argument for some part of the scenario, don't forget to revert to the default value. diff --git a/anderson/config/action.py b/anderson/config/action.py index c4377f0..e32e458 100644 --- a/anderson/config/action.py +++ b/anderson/config/action.py @@ -17,17 +17,26 @@ def to_bash_command(self) -> str: raise NotImplementedError -class EnterAction(Action): - """Action that enters some symbols.""" +class WriteAction(Action): + """Action that writes some symbols and presses enter.""" - enter: str + write: str def to_bash_command(self) -> str: - return self.enter + return self.write + + +class SendAction(Action): + """Action that writes some symbols (without pressing enter).""" + + send: str + + def to_bash_command(self) -> str: + return f'#$ send {self.send}' class ExpectAction(Action): - """Action that waits for some symbols.""" + """Action that expects some symbols to be printed.""" expect: str @@ -53,4 +62,22 @@ def to_bash_command(self) -> str: return f'#$ delay {self.delay}' -ActionType = Union[EnterAction, WaitAction, ExpectAction, DelayAction] +class ControlAction(Action): + """Action that sends control character.""" + + ctrl: str + + def to_bash_command(self) -> str: + return f'#$ sendcontrol {self.ctrl}' + + +class PressAction(Action): + """Action that press some key.""" + + press: str + + def to_bash_command(self) -> str: + return f'#$ sendcharacter {self.press}' + + +ActionType = Union[WriteAction, WaitAction, ExpectAction, DelayAction, ControlAction, SendAction, PressAction] diff --git a/anderson/config/model.py b/anderson/config/model.py index a9d1077..bc6c56a 100644 --- a/anderson/config/model.py +++ b/anderson/config/model.py @@ -16,6 +16,7 @@ class InteractionConfig(BaseModel): keystroke_delay: NonNegativeInt = 150 keystroke_std: NonNegativeInt = 60 action_delay: NonNegativeInt = 80 + timeout: NonNegativeInt = 30 class Gif(BaseModel): @@ -27,6 +28,7 @@ class Gif(BaseModel): line_height: PositiveFloat = 1.4 speed: PositiveFloat = 1.0 no_loop: bool = False + wait_before_loop: NonNegativeInt = 3 @validator('font_family', pre=True) def check_font_family_list(cls, value: Any) -> Any: # noqa: N805 diff --git a/anderson/main.py b/anderson/main.py index 8296e2a..a0af58d 100644 --- a/anderson/main.py +++ b/anderson/main.py @@ -75,6 +75,8 @@ def create_gif_generation_command(cast_file: Path, output_dir: Path, gif: Gif) - str(gif.line_height), '--speed', str(gif.speed), + '--last-frame-duration', + str(gif.wait_before_loop), ] if gif.no_loop: @@ -120,6 +122,7 @@ def main() -> int: wait=interaction_config.action_delay, delay=interaction_config.keystroke_delay, standart_deviation=interaction_config.keystroke_std, + timeout=interaction_config.timeout, ).execute() args.output.mkdir(parents=True, exist_ok=True) diff --git a/examples/kotlin_calculator/Main.kt b/examples/kotlin_calculator/Main.kt new file mode 100644 index 0000000..a05d8cd --- /dev/null +++ b/examples/kotlin_calculator/Main.kt @@ -0,0 +1,57 @@ +import java.util.function.BinaryOperator + +enum class IntOperator(val symbol: String) : BinaryOperator { + PLUS("+") { + override fun apply(t: Int, u: Int) = t + u + }, + MINUS("-") { + override fun apply(t: Int, u: Int) = t - u + }, + MULTIPLY("*") { + override fun apply(t: Int, u: Int) = t * u + }, + DIVIDE("/") { + override fun apply(t: Int, u: Int) = t / u + }; + + companion object { + fun possibleSymbols() = IntOperator.values().map { it.symbol } + + fun bySymbol(symbol: String) = IntOperator.values().firstOrNull { it.symbol == symbol } + } +} + + +fun readInput(errorMessage: String, inputConverter: (String) -> T?): T { + do { + val input: T? = inputConverter(readln()) + input?.run { return this } + println(errorMessage) + } while (true) +} + + +fun main() { + println("Welcome to a simple Kotlin calculator!\n") + + do { + print("Please enter the operator (possible values: ${IntOperator.possibleSymbols().joinToString(", ")}): ") + val operator = readInput( + "Incorrect operator. Possible values: ${IntOperator.possibleSymbols().joinToString(", ")}.", + ) { IntOperator.bySymbol(it) } + + println() + + print("Please enter the left operand: ") + val leftOperand = readInput("Incorrect operand. It must be an integer.") { it.toIntOrNull() } + + println() + + print("Please enter the right operand: ") + val rightOperand = readInput("Incorrect operand. It must be an integer.") { it.toIntOrNull() } + + println() + + println("$leftOperand ${operator.symbol} $rightOperand = ${operator.apply(leftOperand, rightOperand)}\n") + } while (true) +} diff --git a/examples/kotlin_calculator/README.md b/examples/kotlin_calculator/README.md new file mode 100644 index 0000000..b4e26ba --- /dev/null +++ b/examples/kotlin_calculator/README.md @@ -0,0 +1,19 @@ +# Kotlin calculator + +This is a simple Kotlin calculator. + +Run the following command to generate GIFs: + +```bash +anderson "kotlinc Main.kt -include-runtime -d /tmp/Main.jar && java -jar /tmp/Main.jar" ./gifs ./config.yaml +``` + +**Note**: You need to have the `JetBrains Mono` font in your system in order for the GIFs to be generated. +You can download the font [here](https://www.jetbrains.com/lp/mono/). +You can also change the font in the config to any other font that is installed in your system. + +In the `gifs` folder you will get the following gifs: + +| `light.gif` | `dark.gif` | +|-------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| ![light.gif](../../test/resources/anderson/kotlin_calculator/linux/light.gif) | ![dark.gif](../../test/resources/anderson/kotlin_calculator/linux/dark.gif) | diff --git a/examples/kotlin_calculator/config.yaml b/examples/kotlin_calculator/config.yaml new file mode 100644 index 0000000..58da237 --- /dev/null +++ b/examples/kotlin_calculator/config.yaml @@ -0,0 +1,71 @@ +terminal_config: + rows: 45 + +interaction_config: + action_delay: 1000 + +gif_config: + common: + font_size: 32 + font_family: JetBrains Mono + + gifs: + - name: 'dark' + theme: 'monokai' + speed: 2 + + - name: 'light' + theme: 'solarized_light' + speed: 0.5 + +scenario: + - expect: ": " + - write: abracadabra + + - expect: \n + - write: + + + - expect: ": " + - write: ahalai + + - expect: \n + - write: mahalai + + - expect: \n + - write: 69 + + - expect: ": " + - write: avada kedavra + + - expect: \n + - write: 42 + + - expect: ": " + - write: "-" + + - expect: ": " + - write: 69 + + - expect: ": " + - write: 42 + + - expect: ": " + - write: "*" + + - expect: ": " + - write: 69 + + - expect: ": " + - write: 42 + + - expect: ": " + - write: / + + - expect: ": " + - write: 69 + + - expect: ": " + - write: 42 + + - expect: ": " + - ctrl: c diff --git a/examples/python_bot/README.md b/examples/python_bot/README.md index 207f8e4..09cf5e9 100644 --- a/examples/python_bot/README.md +++ b/examples/python_bot/README.md @@ -13,6 +13,6 @@ You can also change the font in the config to any other font that is installed i In the `gifs` folder you will get the following gifs: - `light.gif`: - ![light.gif](gifs%2Flight.gif) + ![light.gif](../../test/resources/anderson/python_bot/linux/light.gif) - `dark.gif`: - ![dark.gif](gifs%2Fdark.gif) \ No newline at end of file + ![dark.gif](../../test/resources/anderson/python_bot/linux/dark.gif) \ No newline at end of file diff --git a/examples/python_bot/config.yaml b/examples/python_bot/config.yaml index 5ec7942..837161d 100644 --- a/examples/python_bot/config.yaml +++ b/examples/python_bot/config.yaml @@ -14,16 +14,16 @@ scenario: - expect: \n - wait: 1500 - # Enter "Ilya" - - enter: "Ilya" + # Write "Ilya" + - write: "Ilya" # Wait for the second message and wait 1.5 seconds to read the message - expect: \n - wait: 1500 - # Enter "21" sloooooowly + # Write "21" sloooooowly - delay: 2000 - - enter: "21" + - write: "21" # Restore default delay - delay: 50 diff --git a/examples/python_bot/gifs/dark.gif b/examples/python_bot/gifs/dark.gif deleted file mode 100644 index 277ef89..0000000 Binary files a/examples/python_bot/gifs/dark.gif and /dev/null differ diff --git a/examples/python_bot/gifs/light.gif b/examples/python_bot/gifs/light.gif deleted file mode 100644 index 5d33a0a..0000000 Binary files a/examples/python_bot/gifs/light.gif and /dev/null differ diff --git a/requirements.txt b/requirements.txt index 54408f4..8e2e64c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -git+https://github.com/PierreMarchand20/asciinema_automation.git@85ee2fdfc3acaa8ce1f6323ecc19d7881416e3a6#egg=asciinema-automation +asciinema-automation==0.1.3 pyyaml==6.0 pydantic==1.10.5 diff --git a/setup.py b/setup.py index 0db5390..6c0c5cb 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,8 @@ from anderson.utils import AGG_PATH -VERSION = '0.2.0' -AGG_VERSION = '1.3.0' +VERSION = '0.3.0' +AGG_VERSION = '1.4.2' REQUIREMENTS_FILE = Path(__file__).parent / 'requirements.txt' diff --git a/test/__init__.py b/test/__init__.py index 3778e63..fb62b00 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,3 +1,7 @@ from anderson.utils import PROJECT_ROOT EXAMPLES_FOLDER = PROJECT_ROOT / 'examples' + +RESOURCES_FOLDER = PROJECT_ROOT / 'test' / 'resources' + +ANDERSON_TEST_DATA_FOLDER = RESOURCES_FOLDER / 'anderson' diff --git a/test/resources/anderson/kotlin_calculator/darwin/dark.gif b/test/resources/anderson/kotlin_calculator/darwin/dark.gif new file mode 100644 index 0000000..b9df11e Binary files /dev/null and b/test/resources/anderson/kotlin_calculator/darwin/dark.gif differ diff --git a/test/resources/anderson/kotlin_calculator/darwin/light.gif b/test/resources/anderson/kotlin_calculator/darwin/light.gif new file mode 100644 index 0000000..c452d4e Binary files /dev/null and b/test/resources/anderson/kotlin_calculator/darwin/light.gif differ diff --git a/test/resources/anderson/kotlin_calculator/linux/dark.gif b/test/resources/anderson/kotlin_calculator/linux/dark.gif new file mode 100644 index 0000000..3470e55 Binary files /dev/null and b/test/resources/anderson/kotlin_calculator/linux/dark.gif differ diff --git a/test/resources/anderson/kotlin_calculator/linux/light.gif b/test/resources/anderson/kotlin_calculator/linux/light.gif new file mode 100644 index 0000000..5ec9d4f Binary files /dev/null and b/test/resources/anderson/kotlin_calculator/linux/light.gif differ diff --git a/test/resources/anderson/python_bot/darwin/dark.gif b/test/resources/anderson/python_bot/darwin/dark.gif new file mode 100644 index 0000000..2ede688 Binary files /dev/null and b/test/resources/anderson/python_bot/darwin/dark.gif differ diff --git a/test/resources/anderson/python_bot/darwin/light.gif b/test/resources/anderson/python_bot/darwin/light.gif new file mode 100644 index 0000000..45d3750 Binary files /dev/null and b/test/resources/anderson/python_bot/darwin/light.gif differ diff --git a/test/resources/anderson/python_bot/linux/dark.gif b/test/resources/anderson/python_bot/linux/dark.gif new file mode 100644 index 0000000..9800e77 Binary files /dev/null and b/test/resources/anderson/python_bot/linux/dark.gif differ diff --git a/test/resources/anderson/python_bot/linux/light.gif b/test/resources/anderson/python_bot/linux/light.gif new file mode 100644 index 0000000..5efca99 Binary files /dev/null and b/test/resources/anderson/python_bot/linux/light.gif differ diff --git a/test/test_anderson.py b/test/test_anderson.py index 8030cbb..ef1750d 100644 --- a/test/test_anderson.py +++ b/test/test_anderson.py @@ -1,9 +1,10 @@ import os +import platform from pathlib import Path from anderson.utils import run_in_subprocess -from test import EXAMPLES_FOLDER +from test import ANDERSON_TEST_DATA_FOLDER, EXAMPLES_FOLDER from test.utils import LocalCommandBuilder -from tempfile import TemporaryDirectory +from tempfile import TemporaryDirectory, gettempdir import pytest from PIL import Image from PIL.ImageSequence import Iterator @@ -13,8 +14,15 @@ ( f'python3 {EXAMPLES_FOLDER / "python_bot" / "main.py"}', EXAMPLES_FOLDER / 'python_bot' / 'config.yaml', - EXAMPLES_FOLDER / 'python_bot' / 'gifs', - ) + ANDERSON_TEST_DATA_FOLDER / 'python_bot', + ), + # TODO: why this test doesn't work? + # ( + # f'kotlinc {EXAMPLES_FOLDER / "kotlin_calculator" / "Main.kt"} ' + # f'-include-runtime -d {gettempdir()}/Main.jar && java -jar {gettempdir()}/Main.jar', + # EXAMPLES_FOLDER / 'kotlin_calculator' / 'config.yaml', + # ANDERSON_TEST_DATA_FOLDER / 'kotlin_calculator', + # ), ] @@ -24,6 +32,8 @@ def test_gif_generation(executable: str, config: Path, expected_output: Path): command_builder = LocalCommandBuilder(executable, actual_output, config) run_in_subprocess(command_builder.build()) + expected_output = expected_output / platform.system().lower() + expected_gifs = { file: expected_output / file for file in os.listdir(expected_output)