diff --git a/docs/source/config_reference/scripting/scripting_functions.rst b/docs/source/config_reference/scripting/scripting_functions.rst index e2cf4e41..b36573de 100644 --- a/docs/source/config_reference/scripting/scripting_functions.rst +++ b/docs/source/config_reference/scripting/scripting_functions.rst @@ -523,6 +523,38 @@ sub ---------------------------------------------------------------------------------------------------- +Print Functions +--------------- + +print +~~~~~ +:spec: ``print(message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer]) -> ReturnableArgument`` + +:description: + Print the ``message`` and return ``passthrough``. + Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error. + Defaults to info. + +print_if_false +~~~~~~~~~~~~~~ +:spec: ``print_if_false(message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer]) -> ReturnableArgument`` + +:description: + Print the ``message`` if ``passthrough`` evaluates to ``false``. Return ``passthrough``. + Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error. + Defaults to info. + +print_if_true +~~~~~~~~~~~~~ +:spec: ``print_if_true(message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer]) -> ReturnableArgument`` + +:description: + Print the ``message`` if ``passthrough`` evaluates to ``true``. Return ``passthrough``. + Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error. + Defaults to info. + +---------------------------------------------------------------------------------------------------- + Regex Functions --------------- diff --git a/src/ytdl_sub/script/functions/__init__.py b/src/ytdl_sub/script/functions/__init__.py index 5b98d24b..e43f7e28 100644 --- a/src/ytdl_sub/script/functions/__init__.py +++ b/src/ytdl_sub/script/functions/__init__.py @@ -9,6 +9,7 @@ from ytdl_sub.script.functions.error_functions import ErrorFunctions from ytdl_sub.script.functions.json_functions import JsonFunctions from ytdl_sub.script.functions.map_functions import MapFunctions from ytdl_sub.script.functions.numeric_functions import NumericFunctions +from ytdl_sub.script.functions.print_functions import PrintFunctions from ytdl_sub.script.functions.regex_functions import RegexFunctions from ytdl_sub.script.functions.string_functions import StringFunctions from ytdl_sub.script.types.resolvable import Resolvable @@ -26,6 +27,7 @@ class Functions( RegexFunctions, DateFunctions, JsonFunctions, + PrintFunctions, ): _custom_functions: Dict[str, Callable[..., Resolvable]] = {} diff --git a/src/ytdl_sub/script/functions/print_functions.py b/src/ytdl_sub/script/functions/print_functions.py new file mode 100644 index 00000000..a89bcd28 --- /dev/null +++ b/src/ytdl_sub/script/functions/print_functions.py @@ -0,0 +1,67 @@ +from typing import Optional + +from ytdl_sub.script.types.resolvable import AnyArgument +from ytdl_sub.script.types.resolvable import Integer +from ytdl_sub.script.types.resolvable import ReturnableArgument +from ytdl_sub.utils.logger import Logger + +logger = Logger.get(name="preset") + + +def _log(message: AnyArgument, level: Optional[Integer]) -> None: + if level is None: + logger.info(str(message)) + return + + level_value: int = level.native + if level_value < 0: + logger.debug(str(message)) + elif level_value == 0: + logger.info(str(message)) + elif level_value == 1: + logger.warning(str(message)) + else: # > 1 + logger.error(str(message)) + + +class PrintFunctions: + @staticmethod + def print( + message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer] = None + ) -> ReturnableArgument: + """ + :description: + Print the ``message`` and return ``passthrough``. + Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error. + Defaults to info. + """ + _log(message=message, level=level) + return passthrough + + @staticmethod + def print_if_true( + message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer] = None + ) -> ReturnableArgument: + """ + :description: + Print the ``message`` if ``passthrough`` evaluates to ``true``. Return ``passthrough``. + Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error. + Defaults to info. + """ + if passthrough.value: + _log(message=message, level=level) + return passthrough + + @staticmethod + def print_if_false( + message: AnyArgument, passthrough: ReturnableArgument, level: Optional[Integer] = None + ) -> ReturnableArgument: + """ + :description: + Print the ``message`` if ``passthrough`` evaluates to ``false``. Return ``passthrough``. + Optionally can pass level, where < 0 is debug, 0 is info, 1 is warning, > 1 is error. + Defaults to info. + """ + if not passthrough.value: + _log(message=message, level=level) + return passthrough diff --git a/tests/unit/script/functions/test_print_functions.py b/tests/unit/script/functions/test_print_functions.py new file mode 100644 index 00000000..a23f447d --- /dev/null +++ b/tests/unit/script/functions/test_print_functions.py @@ -0,0 +1,58 @@ +import logging +from typing import Any +from typing import Optional +from unittest.mock import patch + +import pytest +from unit.script.conftest import single_variable_output + + +class TestPrintFunctions: + @pytest.mark.parametrize( + "function_str, expected_print, expected_output", + [ + # print + ("{%print('hi mom', True)}", "hi mom", True), + ("{%print('this is great', [1, 2, 3])}", "this is great", [1, 2, 3]), + ("{%print([1, 2], [3, 4])}", "[1, 2]", [3, 4]), + # print_if_true + ("{%print_if_true('hi mom', True)}", "hi mom", True), + ("{%print_if_true('hi mom', False)}", None, False), + # print_if_false + ("{%print_if_false('hi mom', True)}", None, True), + ("{%print_if_false('hi mom', False)}", "hi mom", False), + ], + ) + def test_print_functions( + self, function_str: str, expected_print: Optional[str], expected_output: Any + ): + with patch.object(logging.Logger, "info") as mock_logger: + output = single_variable_output(function_str) + assert output == expected_output + + if expected_print is not None: + assert mock_logger.call_count == 1 + assert mock_logger.call_args.args[0] == expected_print + else: + assert mock_logger.call_count == 0 + + @pytest.mark.parametrize( + "function_str, expected_print, expected_output", + [ + # print + ("{%print('hi mom', True, LEVEL)}", "hi mom", True), + # print_if_true + ("{%print_if_true('hi mom', True, LEVEL)}", "hi mom", True), + # print_if_false + ("{%print_if_false('hi mom', False, LEVEL)}", "hi mom", False), + ], + ) + @pytest.mark.parametrize("level", [-1, 0, 1, 2]) + def test_levels(self, function_str: str, expected_print: str, expected_output: str, level: int): + level_mapping = {-1: "debug", 0: "info", 1: "warning", 2: "error"} + with patch.object(logging.Logger, level_mapping[level]) as mock_logger: + output = single_variable_output(function_str.replace("LEVEL", str(level))) + assert output == expected_output + + assert mock_logger.call_count == 1 + assert mock_logger.call_args.args[0] == expected_print