diff --git a/README.md b/README.md index 86d1b1e..e8371cd 100644 --- a/README.md +++ b/README.md @@ -135,21 +135,23 @@ pre-commit install ### Plugin architecture ```mermaid classDiagram -TCodeRunner "1" o--> "1" IConfigManager : aggregates +TCodeRunner "1" o--> "1" IConfig : aggregates TCodeRunner "1" o--> "1" ICommandsExecutor : aggregates TCodeRunner "1" o--> "1" IMessagePrinter : aggregates TCodeRunner "1" o--> "1" TBasicEditorServiceForCodeRunner : aggregates TCodeRunner "1" o--> "1" TBasicCommandDispatcherStrategySelector : aggregates -IConfigManager "1" o--> "1" IConfigGetter : aggregates -IConfigManager "1" o--> "1" TBasicConfigValidator : aggregates +TBasicConfig ..|> IConfig : implements +TBasicConfig "1" o--> "n" TConfigField : aggregates +TConfigField "1" o--> "1" IConfigValueGetter : aggregates +TConfigField "1" o--> "1" IValidator : aggregates TBasicEditorServiceForCodeRunner "1" o--> "1" IEditor : aggregates -TBasicEditorServiceForCodeRunner "1" o--> "1" IConfigManager : aggregates -ICommandsExecutor "1" o--> "1" IConfigManager : aggregates +TBasicEditorServiceForCodeRunner "1" o--> "1" IConfig : aggregates +ICommandsExecutor "1" o--> "1" IConfig : aggregates TBasicCommandDispatcherStrategySelector "1" o--> "1" TShebangCommandBuildersDispatcher : aggregates TBasicCommandDispatcherStrategySelector "1" o--> "1" TGlobCommandBuildersDispatcher : aggregates TBasicCommandDispatcherStrategySelector "1" o--> "1" TFileExtCommandBuildersDispatcher : aggregates TBasicCommandDispatcherStrategySelector "1" o--> "1" TFileTypeCommandBuildersDispatcher : aggregates -TBasicCommandDispatcherStrategySelector "1" o--> "1" IConfigManager : aggregates +TBasicCommandDispatcherStrategySelector "1" o--> "1" IConfig : aggregates TShebangCommandBuildersDispatcher ..|> ICommandBuildersDispatcher : implements TShebangCommandBuildersDispatcher "1" o--> "1" IFileInfoExtractor : aggregates TGlobCommandBuildersDispatcher ..|> ICommandBuildersDispatcher : implements @@ -166,7 +168,7 @@ TBaseFileInfoExtractor ..|> IFileInfoExtractor : implements class TCodeRunner { - # config_manager: IConfigManager + # config: IConfig # editor_service: TBasicEditorServiceForCodeRunner # command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector # commands_executor: ICommandsExecutor @@ -180,40 +182,48 @@ class TCodeRunner { } - -class IConfigManager { +class IConfig { <> - + get_dispatchers_order() list[str] + + get_by_file_ext() Dict[str, str] + + get_by_file_type() Dict[str, str] + + get_by_glob() Dict[str, str] + + get_dispatchers_order() List[EDispatchersTypes] + + get_coderunner_tempfile_prefix() str + get_executor() str + get_ignore_selection() bool + get_respect_shebang() bool - + get_save_all_files() bool - + get_save_file() bool + + get_remove_coderunner_tempfiles_on_exit() bool + + get_save_all_files_before_run() bool + + get_save_file_before_run() bool } -class IConfigGetter { - <> - + get_dispatchers_order() Any - + get_executor() Any - + get_ignore_selection() Any - + get_respect_shebang() Any - + get_save_all_files() Any - + get_save_file() Any +class TConfigField { + # name: str + # getter: IConfigValueGetter + # validator: IValidator + # allowed_values_description: str + + get() ValueType +} + + +class IConfigValueGetter { + <> + + __call__() Any } -class TBasicConfigValidator { - + validate_bool() bool - + validate_str() str - + validate_dispatcher() Dict[str, str] - + validate_dispatchers_order() List[EDispatchersTypes] +class IValidator { + <> + + __call__(value: Any) ValueType + } class TBasicEditorServiceForCodeRunner { # editor: IEditor - # config_manager: IConfigManager + # config:IConfig + # file_info_extractor: IFileInfoExtractor Creates context which will delete file if it's temporary + get_file_for_run() Context[str] Runs save_file or save_all_files if the command_builder is found, @@ -240,13 +250,14 @@ class ICommandsExecutor { class IMessagePrinter { + <> + info(text: str) None + error(text: str) None } class TBasicCommandDispatcherStrategySelector { - # config_manager: IConfigManager + # config: IConfig # shebang_dispatcher: TShebangCommandBuildersDispatcher # file_type_dispatcher: TFileExtCommandBuildersDispatcher # file_ext_dispatcher: TFileTypeCommandBuildersDispatcher diff --git a/python_coderunner/pyproject.toml b/python_coderunner/pyproject.toml index 8f17204..bfcca57 100644 --- a/python_coderunner/pyproject.toml +++ b/python_coderunner/pyproject.toml @@ -81,6 +81,7 @@ disable = [ "too-few-public-methods", "too-many-arguments", "too-many-positional-arguments", + "too-many-instance-attributes", "raise-missing-from", "line-too-long", ] diff --git a/python_coderunner/src/coderunner/coderunner.py b/python_coderunner/src/coderunner/coderunner.py index 910ce2e..696fc77 100644 --- a/python_coderunner/src/coderunner/coderunner.py +++ b/python_coderunner/src/coderunner/coderunner.py @@ -5,7 +5,7 @@ TBasicCommandDispatcherStrategySelector, ) from ..commands_executor import ICommandsExecutor -from ..config_manager import TBasicConfigManager +from ..config import IConfig from ..editor_service_for_coderunner import TBasicEditorServiceForCodeRunner from ..message_printer import IMessagePrinter @@ -14,13 +14,13 @@ class TCodeRunner: def __init__( self, *, - config_manager: TBasicConfigManager, + config: IConfig, editor_service: TBasicEditorServiceForCodeRunner, command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector, commands_executor: ICommandsExecutor, message_printer: IMessagePrinter, ): - self._config_manager: TBasicConfigManager = config_manager + self._config: IConfig = config self._editor_service: TBasicEditorServiceForCodeRunner = editor_service self._command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector = ( command_dispatcher_strategy_selector @@ -53,5 +53,5 @@ def remove_coderunner_tempfiles(self) -> None: self._editor_service.remove_coderunner_tempfiles() def on_exit(self) -> None: - if self._config_manager.get_remove_coderunner_tempfiles_on_exit(): + if self._config.get_remove_coderunner_tempfiles_on_exit(): self.remove_coderunner_tempfiles() diff --git a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py index f74a100..ecbc2f8 100644 --- a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py +++ b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py @@ -14,10 +14,25 @@ TBasicCommandDispatcherStrategySelector, ) from ..commands_executor import TVimCommandsExecutor -from ..config_manager import ( - TBasicConfigValidator, - TVimConfigGetter, - TVimConfigManager, +from ..config import EDispatchersTypes, TConfigField, TVimConfig +from ..config.getter import ( + TVimByFileExtConfigValueGetter, + TVimByFileTypeConfigValueGetter, + TVimByGlobConfigValueGetter, + TVimCoderunnerTempfilePrefixConfigValueGetter, + TVimDispatchersOrderConfigValueGetter, + TVimExecutorConfigValueGetter, + TVimIgnoreSelectionConfigValueGetter, + TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter, + TVimRespectShebangConfigValueGetter, + TVimSaveAllFilesBeforeRunConfigValueGetter, + TVimSaveFileBeforeRunConfigValueGetter, +) +from ..config.validator import ( + TBoolValidator, + TDispatchersOrderValidator, + TDispatchersValidator, + TStrValidator, ) from ..editor import TVimEditor from ..editor_service_for_coderunner import TBasicEditorServiceForCodeRunner @@ -29,7 +44,7 @@ class TVimCodeRunnerFactory(ICodeRunnerFactory): def create(self) -> Optional[TCodeRunner]: - config_manager: TVimConfigManager = TVimConfigManager(TVimConfigGetter(), TBasicConfigValidator()) + config: TVimConfig = self._create_config() message_printer: TVimMessagePrinter = TVimMessagePrinter() try: @@ -38,18 +53,16 @@ def create(self) -> Optional[TCodeRunner]: editor: TVimEditor = TVimEditor() editor_service_for_coderunner: TBasicEditorServiceForCodeRunner = TBasicEditorServiceForCodeRunner( - config_manager, editor, file_info_extractor + config, editor, file_info_extractor ) command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector = ( - self._create_command_dispatcher_strategy_selector( - config_manager, file_info_extractor, project_info_extractor - ) + self._create_command_dispatcher_strategy_selector(config, file_info_extractor, project_info_extractor) ) - commands_executor: TVimCommandsExecutor = TVimCommandsExecutor(config_manager) + commands_executor: TVimCommandsExecutor = TVimCommandsExecutor(config) return TCodeRunner( - config_manager=config_manager, + config=config, editor_service=editor_service_for_coderunner, command_dispatcher_strategy_selector=command_dispatcher_strategy_selector, commands_executor=commands_executor, @@ -61,9 +74,79 @@ def create(self) -> Optional[TCodeRunner]: return None + def _create_config(self) -> TVimConfig: + return TVimConfig( + by_file_ext_field=TConfigField( + name="g:coderunner_by_file_ext", + getter=TVimByFileExtConfigValueGetter(), + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + by_file_type_field=TConfigField( + name="g:coderunner_by_file_type", + getter=TVimByFileTypeConfigValueGetter(), + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + by_glob_field=TConfigField( + name="g:coderunner_by_glob", + getter=TVimByGlobConfigValueGetter(), + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + dispatchers_order_field=TConfigField( + name="g:coderunner_runners_order", + getter=TVimDispatchersOrderConfigValueGetter(), + validator=TDispatchersOrderValidator(), + allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), + ), + coderunner_tempfile_prefix_field=TConfigField( + name="g:coderunner_tempfile_prefix", + getter=TVimCoderunnerTempfilePrefixConfigValueGetter(), + validator=TStrValidator(), + allowed_values_description="str value", + ), + executor_field=TConfigField( + name="g:coderunner_executor", + getter=TVimExecutorConfigValueGetter(), + validator=TStrValidator(), + allowed_values_description="str value", + ), + ignore_selection_field=TConfigField( + name="g:coderunner_ignore_selection", + getter=TVimIgnoreSelectionConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + respect_shebang_field=TConfigField( + name="g:coderunner_respect_shebang", + getter=TVimRespectShebangConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + remove_coderunner_tempfiles_on_exit_field=TConfigField( + name="g:coderunner_remove_coderunner_tempfiles_on_exit", + getter=TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + save_all_files_before_run_field=TConfigField( + name="g:coderunner_save_all_files_before_run", + getter=TVimSaveAllFilesBeforeRunConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + save_file_before_run_field=TConfigField( + name="g:coderunner_save_file_before_run", + getter=TVimSaveFileBeforeRunConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + ) + def _create_command_dispatcher_strategy_selector( self, - config_manager: TVimConfigManager, + config: TVimConfig, file_info_extractor: TVimFileInfoExtractor, project_info_extractor: TVimProjectInfoExtractor, ) -> TBasicCommandDispatcherStrategySelector: @@ -71,62 +154,58 @@ def _create_command_dispatcher_strategy_selector( file_info_extractor ) file_ext_command_builders_dispatcher: TFileExtCommandBuildersDispatcher = ( - self._create_file_ext_command_builders_dispatcher( - config_manager, file_info_extractor, project_info_extractor - ) + self._create_file_ext_command_builders_dispatcher(config, file_info_extractor, project_info_extractor) ) file_type_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher = ( - self._create_file_type_command_builders_dispatcher( - config_manager, file_info_extractor, project_info_extractor - ) + self._create_file_type_command_builders_dispatcher(config, file_info_extractor, project_info_extractor) ) glob_command_builders_dispatcher: TGlobCommandBuildersDispatcher = ( - self._create_glob_command_builders_dispatcher(config_manager, file_info_extractor, project_info_extractor) + self._create_glob_command_builders_dispatcher(config, file_info_extractor, project_info_extractor) ) return TBasicCommandDispatcherStrategySelector( + config=config, shebang_command_builders_dispatcher=shebang_command_builders_dispatcher, glob_command_builders_dispatcher=glob_command_builders_dispatcher, file_ext_command_builders_dispatcher=file_ext_command_builders_dispatcher, file_type_command_builders_dispatcher=file_type_command_builders_dispatcher, - config_manager=config_manager, ) def _create_file_ext_command_builders_dispatcher( self, - config_manager: TVimConfigManager, + config: TVimConfig, file_info_extractor: TVimFileInfoExtractor, project_info_extractor: TVimProjectInfoExtractor, ) -> TFileExtCommandBuildersDispatcher: return TFileExtCommandBuildersDispatcher( { key: TInterpolatorCommandBuilder(val, project_info_extractor, file_info_extractor) - for key, val in config_manager.get_by_file_ext().items() + for key, val in config.get_by_file_ext().items() }, file_info_extractor, ) def _create_file_type_command_builders_dispatcher( self, - config_manager: TVimConfigManager, + config: TVimConfig, file_info_extractor: TVimFileInfoExtractor, project_info_extractor: TVimProjectInfoExtractor, ) -> TFileTypeCommandBuildersDispatcher: return TFileTypeCommandBuildersDispatcher( { key: TInterpolatorCommandBuilder(val, project_info_extractor, file_info_extractor) - for key, val in config_manager.get_by_file_type().items() + for key, val in config.get_by_file_type().items() }, file_info_extractor, ) def _create_glob_command_builders_dispatcher( self, - config_manager: TVimConfigManager, + config: TVimConfig, file_info_extractor: TVimFileInfoExtractor, project_info_extractor: TVimProjectInfoExtractor, ) -> TGlobCommandBuildersDispatcher: - dict_with_commands: Dict[str, str] = config_manager.get_by_glob() + dict_with_commands: Dict[str, str] = config.get_by_glob() return TGlobCommandBuildersDispatcher( tuple( ( diff --git a/python_coderunner/src/command_builder/interpolator_command_builder.py b/python_coderunner/src/command_builder/interpolator_command_builder.py index 3740ab7..4843bef 100644 --- a/python_coderunner/src/command_builder/interpolator_command_builder.py +++ b/python_coderunner/src/command_builder/interpolator_command_builder.py @@ -7,14 +7,14 @@ class TInterpolatorCommandBuilder(ICommandBuilder): - WORKSPACE_ROOT_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$workspaceRoot") - FULL_FILE_NAME_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$fullFileName") - FILE_NAME_WITHOUT_EXT_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$fileNameWithoutExt") - FILE_NAME_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$fileName") - FILE_EXT: ClassVar[re.Pattern] = re.compile(r"\$fileExt") - DRIVE_LETTER_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$driveLetter") - DIR_WITHOUT_TRAILING_SLASH_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$dirWithoutTrailingSlash") - DIR_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$dir") + WORKSPACE_ROOT_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$workspaceRoot") + FULL_FILE_NAME_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fullFileName") + FILE_NAME_WITHOUT_EXT_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fileNameWithoutExt") + FILE_NAME_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fileName") + FILE_EXT: ClassVar[re.Pattern[str]] = re.compile(r"\$fileExt") + DRIVE_LETTER_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$driveLetter") + DIR_WITHOUT_TRAILING_SLASH_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$dirWithoutTrailingSlash") + DIR_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$dir") def __init__( self, diff --git a/python_coderunner/src/command_builders_dispatcher/glob_command_builders_dispatcher.py b/python_coderunner/src/command_builders_dispatcher/glob_command_builders_dispatcher.py index f092abf..509fea0 100644 --- a/python_coderunner/src/command_builders_dispatcher/glob_command_builders_dispatcher.py +++ b/python_coderunner/src/command_builders_dispatcher/glob_command_builders_dispatcher.py @@ -6,8 +6,8 @@ class TGlobCommandBuildersDispatcher(ICommandBuildersDispatcher): - def __init__(self, glob_to_builder: Tuple[Tuple[re.Pattern, ICommandBuilder], ...]): - self._glob_to_builder: Final[Tuple[Tuple[re.Pattern, ICommandBuilder], ...]] = glob_to_builder + def __init__(self, glob_to_builder: Tuple[Tuple[re.Pattern[str], ICommandBuilder], ...]): + self._glob_to_builder: Final[Tuple[Tuple[re.Pattern[str], ICommandBuilder], ...]] = glob_to_builder def dispatch(self, file_path_abs: str) -> Optional[ICommandBuilder]: for pattern, builder in self._glob_to_builder: diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py index 89f2d2e..071aba0 100644 --- a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py +++ b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py @@ -7,20 +7,20 @@ TGlobCommandBuildersDispatcher, TShebangCommandBuildersDispatcher, ) -from ..config_manager import EDispatchersTypes, TBasicConfigManager +from ..config import EDispatchersTypes, IConfig class TBasicCommandDispatcherStrategySelector: def __init__( self, *, - config_manager: TBasicConfigManager, + config: IConfig, shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher, glob_command_builders_dispatcher: TGlobCommandBuildersDispatcher, file_ext_command_builders_dispatcher: TFileExtCommandBuildersDispatcher, file_type_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher, ): - self._config_manager: TBasicConfigManager = config_manager + self._config: IConfig = config self._shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher = ( shebang_command_builders_dispatcher ) @@ -48,12 +48,12 @@ def dispatch(self, file_path_abs: str) -> Optional[ICommandBuilder]: command_builder: Optional[ICommandBuilder] = None if ( - self._config_manager.get_respect_shebang() + self._config.get_respect_shebang() and (command_builder := self.dispatch_by_shebang(file_path_abs)) is not None ): return command_builder - for dispatcher in self._config_manager.get_dispatchers_order(): + for dispatcher in self._config.get_dispatchers_order(): if ( dispatcher == EDispatchersTypes.BY_FILE_EXT and (command_builder := self.dispatch_by_file_ext(file_path_abs)) is not None diff --git a/python_coderunner/src/commands_executor/vim_commands_executor.py b/python_coderunner/src/commands_executor/vim_commands_executor.py index 10d796d..b339cf3 100644 --- a/python_coderunner/src/commands_executor/vim_commands_executor.py +++ b/python_coderunner/src/commands_executor/vim_commands_executor.py @@ -1,13 +1,13 @@ import vim -from ..config_manager import TBasicConfigManager +from ..config import IConfig from .inteface import ICommandsExecutor class TVimCommandsExecutor(ICommandsExecutor): - def __init__(self, config_manager: TBasicConfigManager): - self._config_manager: TBasicConfigManager = config_manager + def __init__(self, config: IConfig): + self._config: IConfig = config def execute(self, command: str) -> None: - executor_command: str = self._config_manager.get_executor() + executor_command: str = self._config.get_executor() vim.command(f"{executor_command} {command}") diff --git a/python_coderunner/src/config/__init__.py b/python_coderunner/src/config/__init__.py new file mode 100644 index 0000000..a7c16fd --- /dev/null +++ b/python_coderunner/src/config/__init__.py @@ -0,0 +1,5 @@ +from .basic import TBasicConfig +from .config_field import TConfigField +from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError +from .interface import EDispatchersTypes, IConfig +from .vim_config import TVimConfig diff --git a/python_coderunner/src/config/basic.py b/python_coderunner/src/config/basic.py new file mode 100644 index 0000000..a7f85f5 --- /dev/null +++ b/python_coderunner/src/config/basic.py @@ -0,0 +1,78 @@ +from typing import Dict, List, TypeVar + +from .config_field import TConfigField +from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError +from .interface import EDispatchersTypes, IConfig + +ValueType = TypeVar("ValueType") + + +class TBasicConfig(IConfig): + def __init__( + self, + by_file_ext_field: TConfigField[Dict[str, str]], + by_file_type_field: TConfigField[Dict[str, str]], + by_glob_field: TConfigField[Dict[str, str]], + dispatchers_order_field: TConfigField[List[EDispatchersTypes]], + coderunner_tempfile_prefix_field: TConfigField[str], + executor_field: TConfigField[str], + ignore_selection_field: TConfigField[bool], + respect_shebang_field: TConfigField[bool], + remove_coderunner_tempfiles_on_exit_field: TConfigField[bool], + save_all_files_before_run_field: TConfigField[bool], + save_file_before_run_field: TConfigField[bool], + ): + self._by_file_ext_field: TConfigField[Dict[str, str]] = by_file_ext_field + self._by_file_type_field: TConfigField[Dict[str, str]] = by_file_type_field + self._by_glob_field: TConfigField[Dict[str, str]] = by_glob_field + self._dispatchers_order_field: TConfigField[List[EDispatchersTypes]] = dispatchers_order_field + self._coderunner_tempfile_prefix_field: TConfigField[str] = coderunner_tempfile_prefix_field + self._executor_field: TConfigField[str] = executor_field + self._ignore_selection_field: TConfigField[bool] = ignore_selection_field + self._respect_shebang_field: TConfigField[bool] = respect_shebang_field + self._remove_coderunner_tempfiles_on_exit_field: TConfigField[bool] = remove_coderunner_tempfiles_on_exit_field + self._save_all_files_before_run_field: TConfigField[bool] = save_all_files_before_run_field + self._save_file_before_run_field: TConfigField[bool] = save_file_before_run_field + + def _get_field_value(self, field: TConfigField[ValueType]) -> ValueType: + """ + Get field value, converting ConfigField exceptions to ValueError. + Preserves exception chain with 'raise from'. + """ + try: + return field.get() + except (ConfigFieldNotFoundError, ConfigFieldValidationError) as e: + raise ValueError(str(e)) from e + + def get_by_file_ext(self) -> Dict[str, str]: + return self._get_field_value(self._by_file_ext_field) + + def get_by_file_type(self) -> Dict[str, str]: + return self._get_field_value(self._by_file_type_field) + + def get_by_glob(self) -> Dict[str, str]: + return self._get_field_value(self._by_glob_field) + + def get_dispatchers_order(self) -> List[EDispatchersTypes]: + return self._get_field_value(self._dispatchers_order_field) + + def get_coderunner_tempfile_prefix(self) -> str: + return self._get_field_value(self._coderunner_tempfile_prefix_field) + + def get_executor(self) -> str: + return self._get_field_value(self._executor_field) + + def get_ignore_selection(self) -> bool: + return self._get_field_value(self._ignore_selection_field) + + def get_respect_shebang(self) -> bool: + return self._get_field_value(self._respect_shebang_field) + + def get_remove_coderunner_tempfiles_on_exit(self) -> bool: + return self._get_field_value(self._remove_coderunner_tempfiles_on_exit_field) + + def get_save_all_files_before_run(self) -> bool: + return self._get_field_value(self._save_all_files_before_run_field) + + def get_save_file_before_run(self) -> bool: + return self._get_field_value(self._save_file_before_run_field) diff --git a/python_coderunner/src/config/config_field.py b/python_coderunner/src/config/config_field.py new file mode 100644 index 0000000..5508a6a8 --- /dev/null +++ b/python_coderunner/src/config/config_field.py @@ -0,0 +1,40 @@ +from typing import Any, Generic, TypeVar + +from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError +from .getter import IConfigValueGetter, UndefinedValueError +from .validator import IValidator, ValidationError + +ValueType = TypeVar("ValueType") + + +class TConfigField(Generic[ValueType]): + """ + Self-describing field object that encapsulates: + - Getting value (getter) + - Validating value (validator) + - Field metadata (name, allowed_values) + - Error handling + """ + + def __init__( + self, + name: str, + getter: IConfigValueGetter, + validator: IValidator[ValueType], + allowed_values_description: str, + ): + self._name: str = name + self._getter: IConfigValueGetter = getter + self._validator: IValidator[ValueType] = validator + self._allowed_values_description: str = allowed_values_description + + def get(self) -> ValueType: + try: + raw_value: Any = self._getter() + except UndefinedValueError as e: + raise ConfigFieldNotFoundError.from_undefined_value_error(e, self._allowed_values_description) + + try: + return self._validator(raw_value) + except ValidationError as e: + raise ConfigFieldValidationError.from_validation_error(e, self._name, self._allowed_values_description) diff --git a/python_coderunner/src/config/exceptions.py b/python_coderunner/src/config/exceptions.py new file mode 100644 index 0000000..fe16ac8 --- /dev/null +++ b/python_coderunner/src/config/exceptions.py @@ -0,0 +1,16 @@ +from typing import Self + +from .getter import UndefinedValueError +from .validator import ValidationError + + +class ConfigFieldNotFoundError(UndefinedValueError): + @classmethod + def from_undefined_value_error(cls, e: UndefinedValueError, allowed_values: str) -> Self: + return cls(f"{e} Allowed values: {allowed_values}.") + + +class ConfigFieldValidationError(ValidationError): + @classmethod + def from_validation_error(cls, e: ValidationError, field_name: str, allowed_values: str) -> Self: + return cls(f"Invalid value of {field_name}. {e} Allowed values: {allowed_values}.") diff --git a/python_coderunner/src/config/getter/__init__.py b/python_coderunner/src/config/getter/__init__.py new file mode 100644 index 0000000..1a6c403 --- /dev/null +++ b/python_coderunner/src/config/getter/__init__.py @@ -0,0 +1,15 @@ +from .exceptions import UndefinedValueError +from .interface import IConfigValueGetter +from .vim_config_value_getter import ( + TVimByFileExtConfigValueGetter, + TVimByFileTypeConfigValueGetter, + TVimByGlobConfigValueGetter, + TVimCoderunnerTempfilePrefixConfigValueGetter, + TVimDispatchersOrderConfigValueGetter, + TVimExecutorConfigValueGetter, + TVimIgnoreSelectionConfigValueGetter, + TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter, + TVimRespectShebangConfigValueGetter, + TVimSaveAllFilesBeforeRunConfigValueGetter, + TVimSaveFileBeforeRunConfigValueGetter, +) diff --git a/python_coderunner/src/config/getter/exceptions.py b/python_coderunner/src/config/getter/exceptions.py new file mode 100644 index 0000000..0f3fdc6 --- /dev/null +++ b/python_coderunner/src/config/getter/exceptions.py @@ -0,0 +1,2 @@ +class UndefinedValueError(ValueError): + """Config value is not defined""" diff --git a/python_coderunner/src/config/getter/interface.py b/python_coderunner/src/config/getter/interface.py new file mode 100644 index 0000000..8456121 --- /dev/null +++ b/python_coderunner/src/config/getter/interface.py @@ -0,0 +1,3 @@ +from typing import Any, Callable, TypeAlias + +IConfigValueGetter: TypeAlias = Callable[[], Any] diff --git a/python_coderunner/src/config/getter/vim_config_value_getter.py b/python_coderunner/src/config/getter/vim_config_value_getter.py new file mode 100644 index 0000000..4f4a95c --- /dev/null +++ b/python_coderunner/src/config/getter/vim_config_value_getter.py @@ -0,0 +1,70 @@ +from typing import Any + +import vim + +from .exceptions import UndefinedValueError + + +class TBaseVimConfigValueGetter: + """Base class for getting Vim config values""" + + def _get_vim_var(self, var_name: str) -> Any: + try: + return vim.eval(var_name) + except vim.error: + raise UndefinedValueError(f"Vim variable {var_name} is not defined. Please set it in your vimrc.") + + +class TVimByFileExtConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_by_file_ext") + + +class TVimByFileTypeConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_by_file_type") + + +class TVimByGlobConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_by_glob") + + +class TVimCoderunnerTempfilePrefixConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_tempfile_prefix") + + +class TVimDispatchersOrderConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_runners_order") + + +class TVimExecutorConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_executor") + + +class TVimIgnoreSelectionConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_ignore_selection") + + +class TVimRespectShebangConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_respect_shebang") + + +class TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_remove_coderunner_tempfiles_on_exit") + + +class TVimSaveAllFilesBeforeRunConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_save_all_files_before_run") + + +class TVimSaveFileBeforeRunConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_save_file_before_run") diff --git a/python_coderunner/src/config/interface.py b/python_coderunner/src/config/interface.py new file mode 100644 index 0000000..d94a9a5 --- /dev/null +++ b/python_coderunner/src/config/interface.py @@ -0,0 +1,68 @@ +from abc import ABC, abstractmethod +from enum import StrEnum +from typing import Dict, List + + +class EDispatchersTypes(StrEnum): + BY_FILE_EXT = "by_file_ext" + BY_FILE_TYPE = "by_file_type" + BY_GLOB = "by_glob" + + +class IConfig(ABC): + """Configuration interface""" + + @abstractmethod + def get_by_file_ext(self) -> Dict[str, str]: + """Gets config for file extension-based dispatching""" + raise NotImplementedError + + @abstractmethod + def get_by_file_type(self) -> Dict[str, str]: + """Gets config for file type-based dispatching""" + raise NotImplementedError + + @abstractmethod + def get_by_glob(self) -> Dict[str, str]: + """Gets config for glob pattern-based dispatching""" + raise NotImplementedError + + @abstractmethod + def get_dispatchers_order(self) -> List[EDispatchersTypes]: + """Gets the priority order of dispatchers""" + raise NotImplementedError + + @abstractmethod + def get_coderunner_tempfile_prefix(self) -> str: + """Gets the prefix for coderunner temporary files""" + raise NotImplementedError + + @abstractmethod + def get_executor(self) -> str: + """Gets the command executor""" + raise NotImplementedError + + @abstractmethod + def get_ignore_selection(self) -> bool: + """Gets the flag for ignoring selection""" + raise NotImplementedError + + @abstractmethod + def get_respect_shebang(self) -> bool: + """Gets the flag for respecting shebang""" + raise NotImplementedError + + @abstractmethod + def get_remove_coderunner_tempfiles_on_exit(self) -> bool: + """Gets the flag for removing temporary files on exit""" + raise NotImplementedError + + @abstractmethod + def get_save_all_files_before_run(self) -> bool: + """Gets the flag for saving all files before run""" + raise NotImplementedError + + @abstractmethod + def get_save_file_before_run(self) -> bool: + """Gets the flag for saving current file before run""" + raise NotImplementedError diff --git a/python_coderunner/src/config/validator/__init__.py b/python_coderunner/src/config/validator/__init__.py new file mode 100644 index 0000000..355ba05 --- /dev/null +++ b/python_coderunner/src/config/validator/__init__.py @@ -0,0 +1,6 @@ +from .bool_validator import TBoolValidator +from .dispatchers_order_validator import TDispatchersOrderValidator +from .dispatchers_validator import TDispatchersValidator +from .exceptions import ValidationError +from .interface import IValidator +from .str_validator import TStrValidator diff --git a/python_coderunner/src/config/validator/bool_validator.py b/python_coderunner/src/config/validator/bool_validator.py new file mode 100644 index 0000000..be5c6a4 --- /dev/null +++ b/python_coderunner/src/config/validator/bool_validator.py @@ -0,0 +1,13 @@ +from typing import Any + +from .exceptions import ValidationError +from .interface import IValidator + + +class TBoolValidator(IValidator[bool]): + def __call__(self, value: Any) -> bool: + if isinstance(value, bool): + return value + if str(value).strip() in ("0", "1"): + return bool(int(value)) + raise ValidationError(f"Invalid bool value: {value}.") diff --git a/python_coderunner/src/config/validator/dispatchers_order_validator.py b/python_coderunner/src/config/validator/dispatchers_order_validator.py new file mode 100644 index 0000000..25e4b5e --- /dev/null +++ b/python_coderunner/src/config/validator/dispatchers_order_validator.py @@ -0,0 +1,19 @@ +from typing import Any, List, Set + +from ..interface import EDispatchersTypes +from .exceptions import ValidationError +from .interface import IValidator + + +class TDispatchersOrderValidator(IValidator[List[EDispatchersTypes]]): + def __init__(self) -> None: + self.allowed_dispatcher_types: Set[EDispatchersTypes] = set(EDispatchersTypes) + + def __call__(self, value: Any) -> List[EDispatchersTypes]: + if not isinstance(value, list): + raise ValidationError(f"Invalid dispatcher order container type: {type(value)}.") + + if invalid_items := [v for v in value if v not in self.allowed_dispatcher_types]: + raise ValidationError(f"Invalid dispatcher types values: {', '.join(map(str, invalid_items))}.") + + return value diff --git a/python_coderunner/src/config/validator/dispatchers_validator.py b/python_coderunner/src/config/validator/dispatchers_validator.py new file mode 100644 index 0000000..4992f6e --- /dev/null +++ b/python_coderunner/src/config/validator/dispatchers_validator.py @@ -0,0 +1,17 @@ +from typing import Any, Dict + +from .exceptions import ValidationError +from .interface import IValidator + + +class TDispatchersValidator(IValidator[Dict[str, str]]): + def __call__(self, value: Any) -> Dict[str, str]: + if not isinstance(value, dict): + raise ValidationError(f"Invalid dispatcher container type: {type(value)}.") + for key, val in value.items(): + if not isinstance(key, str): + raise ValidationError(f"Invalid type in dispatcher dict key: {type(key)}.") + if not isinstance(val, str): + raise ValidationError(f"Invalid type in dispatcher dict value: {type(val)}.") + + return value diff --git a/python_coderunner/src/config/validator/exceptions.py b/python_coderunner/src/config/validator/exceptions.py new file mode 100644 index 0000000..b60a2e3 --- /dev/null +++ b/python_coderunner/src/config/validator/exceptions.py @@ -0,0 +1,2 @@ +class ValidationError(Exception): + """Raised when value validation fails""" diff --git a/python_coderunner/src/config/validator/interface.py b/python_coderunner/src/config/validator/interface.py new file mode 100644 index 0000000..e30e4ef --- /dev/null +++ b/python_coderunner/src/config/validator/interface.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod +from typing import Any, Generic, TypeVar + +ValueType = TypeVar("ValueType") + + +class IValidator(ABC, Generic[ValueType]): + @abstractmethod + def __call__(self, value: Any) -> ValueType: + """ + Validates value and returns typed result. + Raises ValidationError on validation failure. + """ + raise NotImplementedError diff --git a/python_coderunner/src/config/validator/str_validator.py b/python_coderunner/src/config/validator/str_validator.py new file mode 100644 index 0000000..750bc6b --- /dev/null +++ b/python_coderunner/src/config/validator/str_validator.py @@ -0,0 +1,11 @@ +from typing import Any + +from .exceptions import ValidationError +from .interface import IValidator + + +class TStrValidator(IValidator[str]): + def __call__(self, value: Any) -> str: + if isinstance(value, str): + return value + raise ValidationError(f"Invalid str type: {type(value)}.") diff --git a/python_coderunner/src/config/vim_config.py b/python_coderunner/src/config/vim_config.py new file mode 100644 index 0000000..d8bbe23 --- /dev/null +++ b/python_coderunner/src/config/vim_config.py @@ -0,0 +1,5 @@ +from .basic import TBasicConfig + + +class TVimConfig(TBasicConfig): + """Config for Vim, created through factory""" diff --git a/python_coderunner/src/config_manager/__init__.py b/python_coderunner/src/config_manager/__init__.py deleted file mode 100644 index bb5dadb..0000000 --- a/python_coderunner/src/config_manager/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .basic import ( - EDispatchersTypes, - IConfigGetter, - TBasicConfigManager, - TBasicConfigValidator, - UndefinedValueError, - ValidationError, -) -from .vim_config_manager import TVimConfigGetter, TVimConfigManager diff --git a/python_coderunner/src/config_manager/basic.py b/python_coderunner/src/config_manager/basic.py deleted file mode 100644 index dd128a2..0000000 --- a/python_coderunner/src/config_manager/basic.py +++ /dev/null @@ -1,288 +0,0 @@ -from abc import ABC, abstractmethod -from enum import StrEnum -from typing import Any, ClassVar, Dict, List - - -class EDispatchersTypes(StrEnum): - BY_FILE_EXT = "by_file_ext" - BY_FILE_TYPE = "by_file_type" - BY_GLOB = "by_glob" - - -class UndefinedValueError(ValueError): - pass - - -class IConfigGetter(ABC): - @abstractmethod - def get_by_file_ext(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_by_file_type(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_by_glob(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_coderunner_tempfile_prefix(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_dispatchers_order(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_executor(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_ignore_selection(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_respect_shebang(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_remove_coderunner_tempfiles_on_exit(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_save_all_files_before_run(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_save_file_before_run(self) -> Any: - raise NotImplementedError - - -class ValidationError(ValueError): - pass - - -class TBasicConfigValidator: - def validate_bool(self, value: Any) -> bool: - if isinstance(value, bool): - return value - if str(value).strip() in ("0", "1"): - return bool(int(value)) - raise ValidationError(f"Invalid bool value: {value}.") - - def validate_str(self, value: Any) -> str: - if isinstance(value, str): - return value - raise ValidationError(f"Invalid str type: {type(value)}.") - - def validate_dispatcher(self, value: Any) -> Dict[str, str]: - if not isinstance(value, dict): - raise ValidationError(f"Invalid dispatcher container type: {type(value)}.") - for key, val in value.items(): - if not isinstance(key, str): - raise ValidationError(f"Invalid type in dispatcher dict key: {type(key)}.") - if not isinstance(val, str): - raise ValidationError(f"Invalid type in dispatcher dict value: {type(val)}.") - return value - - def validate_dispatchers_order(self, value: Any) -> List[EDispatchersTypes]: - if not isinstance(value, list): - raise ValidationError(f"Invalid dispatcher order container type: {type(value)}.") - - if invalid_items := [v for v in value if v not in set(EDispatchersTypes)]: - raise ValidationError(f"Invalid dispatcher types values: {', '.join(map(str, invalid_items))}.") - return value - - -class TBasicConfigManager: - by_file_ext_alias: ClassVar[str] = "by_file_ext" - by_file_ext_allowed_values: ClassVar[str] = "Dict[str, str] value" - by_file_type_alias: ClassVar[str] = "by_file_type" - by_file_type_allowed_values: ClassVar[str] = "Dict[str, str] value" - by_glob_alias: ClassVar[str] = "by_glob" - by_glob_allowed_values: ClassVar[str] = "Dict[str, str] value" - - dispatchers_order_alias: ClassVar[str] = "runners_order" - dispatchers_order_allowed_values: ClassVar[str] = ", ".join( - dispatcher_type.value for dispatcher_type in EDispatchersTypes - ) - - coderunner_tempfile_prefix_alias: ClassVar[str] = "coderunner_tempfile_prefix" - coderunner_tempfile_prefix_allowed_values: ClassVar[str] = "str value" - executor_alias: ClassVar[str] = "executor" - executor_allowed_values: ClassVar[str] = "str value" - - bool_allowed_values: ClassVar[str] = "0 or 1" - ignore_selection_alias: ClassVar[str] = "ignore_selection" - ignore_selection_allowed_values: ClassVar[str] = bool_allowed_values - respect_shebang_alias: ClassVar[str] = "respect_shebang" - respect_shebang_allowed_values: ClassVar[str] = bool_allowed_values - remove_coderunner_tempfiles_on_exit_alias: ClassVar[str] = "coderunner_remove_coderunner_tempfiles_on_exit" - remove_coderunner_tempfiles_on_exit_allowed_values: ClassVar[str] = bool_allowed_values - save_all_files_before_run_alias: ClassVar[str] = "save_all_files_before_run" - save_all_files_before_run_allowed_values: ClassVar[str] = bool_allowed_values - save_file_before_run_alias: ClassVar[str] = "save_file_before_run" - save_file_before_run_allowed_values: ClassVar[str] = bool_allowed_values - - def __init__(self, config_getter: IConfigGetter, config_validator: TBasicConfigValidator): - self._config_getter: IConfigGetter = config_getter - self._config_validator: TBasicConfigValidator = config_validator - - def get_by_file_ext(self) -> Dict[str, str]: - try: - raw_value: Any = self._config_getter.get_by_file_ext() - validated_value: Dict[str, str] = self._config_validator.validate_dispatcher(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.by_file_ext_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message(e, self.by_file_ext_alias, self.by_file_ext_allowed_values) - ) - - def get_by_file_type(self) -> Dict[str, str]: - try: - raw_value: Any = self._config_getter.get_by_file_type() - validated_value: Dict[str, str] = self._config_validator.validate_dispatcher(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.by_file_type_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message(e, self.by_file_type_alias, self.by_file_type_allowed_values) - ) - - def get_by_glob(self) -> Dict[str, str]: - try: - raw_value: Any = self._config_getter.get_by_glob() - validated_value: Dict[str, str] = self._config_validator.validate_dispatcher(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.by_glob_allowed_values)) - except ValidationError as e: - raise ValueError(self._format_validation_error_message(e, self.by_glob_alias, self.by_glob_allowed_values)) - - def get_dispatchers_order(self) -> List[EDispatchersTypes]: - try: - raw_value: Any = self._config_getter.get_dispatchers_order() - validated_value: List[EDispatchersTypes] = self._config_validator.validate_dispatchers_order(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.dispatchers_order_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.dispatchers_order_alias, self.dispatchers_order_allowed_values - ) - ) - - def get_coderunner_tempfile_prefix(self) -> str: - try: - raw_value: Any = self._config_getter.get_coderunner_tempfile_prefix() - validated_value: str = self._config_validator.validate_str(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError( - self._format_undefined_value_error_message(e, self.coderunner_tempfile_prefix_allowed_values) - ) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.coderunner_tempfile_prefix_alias, self.coderunner_tempfile_prefix_allowed_values - ) - ) - - def get_executor(self) -> str: - try: - raw_value: Any = self._config_getter.get_executor() - validated_value: str = self._config_validator.validate_str(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.executor_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message(e, self.executor_alias, self.executor_allowed_values) - ) - - def get_ignore_selection(self) -> bool: - try: - raw_value: Any = self._config_getter.get_ignore_selection() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.ignore_selection_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.ignore_selection_alias, self.ignore_selection_allowed_values - ) - ) - - def get_respect_shebang(self) -> bool: - try: - raw_value: Any = self._config_getter.get_respect_shebang() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.respect_shebang_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.respect_shebang_alias, self.respect_shebang_allowed_values - ) - ) - - def get_remove_coderunner_tempfiles_on_exit(self) -> bool: - try: - raw_value: Any = self._config_getter.get_remove_coderunner_tempfiles_on_exit() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError( - self._format_undefined_value_error_message(e, self.remove_coderunner_tempfiles_on_exit_allowed_values) - ) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, - self.remove_coderunner_tempfiles_on_exit_alias, - self.remove_coderunner_tempfiles_on_exit_allowed_values, - ) - ) - - def get_save_all_files_before_run(self) -> bool: - try: - raw_value: Any = self._config_getter.get_save_all_files_before_run() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError( - self._format_undefined_value_error_message(e, self.save_all_files_before_run_allowed_values) - ) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.save_all_files_before_run_alias, self.save_all_files_before_run_allowed_values - ) - ) - - def get_save_file_before_run(self) -> bool: - try: - raw_value: Any = self._config_getter.get_save_file_before_run() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.save_file_before_run_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.save_file_before_run_alias, self.save_file_before_run_allowed_values - ) - ) - - def _format_undefined_value_error_message(self, e: UndefinedValueError, allowed_values: str) -> str: - return f"{e} Allowed values: {allowed_values}." - - def _format_validation_error_message(self, e: ValidationError, var_alias: str, allowed_values: str) -> str: - return f"Invalid value of the {var_alias} variable. {e} Allowed values: {allowed_values}." diff --git a/python_coderunner/src/config_manager/vim_config_manager.py b/python_coderunner/src/config_manager/vim_config_manager.py deleted file mode 100644 index c094d44..0000000 --- a/python_coderunner/src/config_manager/vim_config_manager.py +++ /dev/null @@ -1,67 +0,0 @@ -from typing import Any, ClassVar - -import vim - -from .basic import ( - IConfigGetter, - TBasicConfigManager, - UndefinedValueError, -) - - -class TVimConfigGetter(IConfigGetter): - def get_by_file_ext(self) -> Any: - return self._get_vim_var("g:coderunner_by_file_ext") - - def get_by_file_type(self) -> Any: - return self._get_vim_var("g:coderunner_by_file_type") - - def get_by_glob(self) -> Any: - return self._get_vim_var("g:coderunner_by_glob") - - def get_coderunner_tempfile_prefix(self) -> Any: - return self._get_vim_var("g:coderunner_tempfile_prefix") - - def get_dispatchers_order(self) -> Any: - return self._get_vim_var("g:coderunner_runners_order") - - def get_executor(self) -> Any: - return self._get_vim_var("g:coderunner_executor") - - def get_ignore_selection(self) -> Any: - return self._get_vim_var("g:coderunner_ignore_selection") - - def get_respect_shebang(self) -> Any: - return self._get_vim_var("g:coderunner_respect_shebang") - - def get_remove_coderunner_tempfiles_on_exit(self) -> Any: - return self._get_vim_var("g:coderunner_remove_coderunner_tempfiles_on_exit") - - def get_save_all_files_before_run(self) -> Any: - return self._get_vim_var("g:coderunner_save_all_files_before_run") - - def get_save_file_before_run(self) -> Any: - return self._get_vim_var("g:coderunner_save_file_before_run") - - def _get_vim_var(self, var_name: str) -> Any: - try: - return vim.eval(var_name) - except vim.error: - raise UndefinedValueError(f"Vim variable {var_name} is not defined. Please set it in your vimrc.") - - -class TVimConfigManager(TBasicConfigManager): - by_file_ext_alias: ClassVar[str] = "g:coderunner_by_file_ext" - by_file_type_alias: ClassVar[str] = "g:coderunner_by_file_type" - by_glob_alias: ClassVar[str] = "g:coderunner_by_glob" - - dispatchers_order_alias: ClassVar[str] = "g:coderunner_runners_order" - - coderunner_tempfile_prefix_alias: ClassVar[str] = "g:coderunner_tempfile_prefix" - executor_alias: ClassVar[str] = "g:coderunner_executor" - - ignore_selection_alias: ClassVar[str] = "g:coderunner_ignore_selection" - respect_shebang_alias: ClassVar[str] = "g:coderunner_respect_shebang" - remove_coderunner_tempfiles_on_exit_alias: ClassVar[str] = "g:coderunner_remove_coderunner_tempfiles_on_exit" - save_all_files_before_run_alias: ClassVar[str] = "g:coderunner_save_all_files_before_run" - save_file_before_run_alias: ClassVar[str] = "g:coderunner_save_file_before_run" diff --git a/python_coderunner/src/editor_service_for_coderunner/basic.py b/python_coderunner/src/editor_service_for_coderunner/basic.py index b0d5c6b..5397bbf 100644 --- a/python_coderunner/src/editor_service_for_coderunner/basic.py +++ b/python_coderunner/src/editor_service_for_coderunner/basic.py @@ -3,14 +3,14 @@ from tempfile import NamedTemporaryFile from typing import Generator, List -from ..config_manager import TBasicConfigManager +from ..config import IConfig from ..editor import IEditor from ..file_info_extractor import IFileInfoExtractor class TBasicEditorServiceForCodeRunner: - def __init__(self, config_manager: TBasicConfigManager, editor: IEditor, file_info_extractor: IFileInfoExtractor): - self._config_manager: TBasicConfigManager = config_manager + def __init__(self, config: IConfig, editor: IEditor, file_info_extractor: IFileInfoExtractor): + self._config: IConfig = config self._editor: IEditor = editor self._file_info_extractor: IFileInfoExtractor = file_info_extractor self._temp_files: List[str] = [] @@ -19,15 +19,12 @@ def __init__(self, config_manager: TBasicConfigManager, editor: IEditor, file_in def get_file_for_run(self) -> Generator[str, None, None]: file_path_abs: str = self._editor.get_current_file_name() - if ( - not self._config_manager.get_ignore_selection() - and (selected_text := self._editor.get_selected_text()) is not None - ): + if not self._config.get_ignore_selection() and (selected_text := self._editor.get_selected_text()) is not None: with NamedTemporaryFile( "w", encoding="utf-8", dir=self._file_info_extractor.get_dir(file_path_abs), - prefix=self._config_manager.get_coderunner_tempfile_prefix(), + prefix=self._config.get_coderunner_tempfile_prefix(), suffix=self._file_info_extractor.get_file_ext(file_path_abs), delete=False, ) as temp_file: @@ -45,10 +42,10 @@ def get_file_for_run(self) -> Generator[str, None, None]: yield file_path_abs def prepare_for_run(self) -> None: - if self._config_manager.get_save_all_files_before_run(): + if self._config.get_save_all_files_before_run(): self._editor.save_all_files() return - if self._config_manager.get_save_file_before_run(): + if self._config.get_save_file_before_run(): self._editor.save_file() return diff --git a/python_coderunner/src/project_info_extractor/interface.py b/python_coderunner/src/project_info_extractor/interface.py index 4966813..be01ca6 100644 --- a/python_coderunner/src/project_info_extractor/interface.py +++ b/python_coderunner/src/project_info_extractor/interface.py @@ -7,7 +7,7 @@ class IProjectInfoExtractor(ABC): def __init__(self, file_info_extractor: IFileInfoExtractor): - self._file_info_extractor = file_info_extractor + self._file_info_extractor: IFileInfoExtractor = file_info_extractor @abstractmethod def get_workspace_root(self) -> str: diff --git a/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py b/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py index 3b5677f..ab68501 100644 --- a/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py +++ b/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py @@ -6,7 +6,7 @@ class TestInterpolatorCommandBuilder: def test_build( self, fixture_project_info_extractor: IProjectInfoExtractor, fixture_file_info_extractor: IFileInfoExtractor - ): + ) -> None: comparator: TestInterpolatorCommandBuilder.TComparator = TestInterpolatorCommandBuilder.TComparator( fixture_project_info_extractor, fixture_file_info_extractor ) @@ -30,7 +30,7 @@ def __init__(self, project_info_extractor: IProjectInfoExtractor, file_info_extr self._project_info_extractor: IProjectInfoExtractor = project_info_extractor self._file_info_extractor: IFileInfoExtractor = file_info_extractor - def __call__(self, pattern: str, file_path_abs: str, expected_result: str): + def __call__(self, pattern: str, file_path_abs: str, expected_result: str) -> None: builder: TInterpolatorCommandBuilder = TInterpolatorCommandBuilder( pattern, self._project_info_extractor, self._file_info_extractor ) diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_file_ext_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_file_ext_command_builders_dispatcher.py index cc14da4..25f12ce 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_file_ext_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_file_ext_command_builders_dispatcher.py @@ -22,7 +22,7 @@ def test_file_ext_command_builders_dispatcher( fixture_file_ext_command_builders_dispatcher: TFileExtCommandBuildersDispatcher, file_path: str, expected_build_result: Optional[str], -): +) -> None: dispatch_result: Optional[ICommandBuilder] = fixture_file_ext_command_builders_dispatcher.dispatch(file_path) if expected_build_result is not None: diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_file_type_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_file_type_command_builders_dispatcher.py index f51ffeb..a18c676 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_file_type_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_file_type_command_builders_dispatcher.py @@ -22,7 +22,7 @@ def test_file_type_command_builders_dispatcher( fixture_file_type_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher, file_path: str, expected_build_result: Optional[str], -): +) -> None: dispatch_result: Optional[ICommandBuilder] = fixture_file_type_command_builders_dispatcher.dispatch(file_path) if expected_build_result is not None: diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_glob_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_glob_command_builders_dispatcher.py index a063187..a38be74 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_glob_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_glob_command_builders_dispatcher.py @@ -25,7 +25,7 @@ def test_glob_command_builders_dispatcher( fixture_glob_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher, file_path: str, expected_build_result: Optional[str], -): +) -> None: dispatch_result: Optional[ICommandBuilder] = fixture_glob_command_builders_dispatcher.dispatch(file_path) if expected_build_result is not None: diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py index 69f5688..927674c 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py @@ -1,8 +1,10 @@ +from pathlib import Path from typing import Optional import pytest -from src.command_builder import TConcatenatorCommandBuilder +from src.command_builder import ICommandBuilder +from src.command_builders_dispatcher import TShebangCommandBuildersDispatcher @pytest.mark.parametrize( @@ -22,17 +24,20 @@ ], ) def test_shebang_command_builders_dispatcher( - fixture_shebang_command_builders_dispatcher, content, expected_result, tmp_path -): - file_path_abs = tmp_path / "test_file" + fixture_shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher, + content: Optional[bytes], + expected_result: Optional[str], + tmp_path: Path, +) -> None: + file_path_abs: Path = tmp_path / "test_file" if content is None: assert fixture_shebang_command_builders_dispatcher.dispatch(str(file_path_abs)) is None return file_path_abs.write_bytes(content) - dispatch_result: Optional[TConcatenatorCommandBuilder] = fixture_shebang_command_builders_dispatcher.dispatch( - file_path_abs + dispatch_result: Optional[ICommandBuilder] = fixture_shebang_command_builders_dispatcher.dispatch( + str(file_path_abs) ) if expected_result is None: diff --git a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py index bcaa82e..81003db 100644 --- a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py +++ b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py @@ -1,3 +1,4 @@ +from pathlib import Path from typing import List, Optional from unittest.mock import MagicMock @@ -13,7 +14,7 @@ from src.command_dispatcher_strategy_selector import ( TBasicCommandDispatcherStrategySelector, ) -from src.config_manager import EDispatchersTypes, TBasicConfigManager +from src.config import EDispatchersTypes, TBasicConfig @pytest.mark.parametrize( @@ -73,19 +74,19 @@ def test_basic_command_dispatcher_strategy_selector( order: List[EDispatchersTypes], respect_shebang: bool, expected: Optional[str], - tmp_path, -): + tmp_path: Path, +) -> None: file_path_abs = tmp_path / file_path file_path_abs.write_bytes(content) - config_manager: TBasicConfigManager = MagicMock( + config: TBasicConfig = MagicMock( get_dispatchers_order=MagicMock(return_value=order), get_respect_shebang=MagicMock(return_value=respect_shebang) ) - selector = TBasicCommandDispatcherStrategySelector( + selector: TBasicCommandDispatcherStrategySelector = TBasicCommandDispatcherStrategySelector( shebang_command_builders_dispatcher=fixture_shebang_command_builders_dispatcher, glob_command_builders_dispatcher=fixture_glob_command_builders_dispatcher, file_ext_command_builders_dispatcher=fixture_file_ext_command_builders_dispatcher, file_type_command_builders_dispatcher=fixture_file_type_command_builders_dispatcher, - config_manager=config_manager, + config=config, ) dispatch_result: Optional[ICommandBuilder] = selector.dispatch(str(file_path_abs)) diff --git a/python_coderunner/tests/unit/config/__init__.py b/python_coderunner/tests/unit/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_coderunner/tests/unit/config_manager/test_basic_config_validator.py b/python_coderunner/tests/unit/config/test_validator.py similarity index 68% rename from python_coderunner/tests/unit/config_manager/test_basic_config_validator.py rename to python_coderunner/tests/unit/config/test_validator.py index 3e06847..204368e 100644 --- a/python_coderunner/tests/unit/config_manager/test_basic_config_validator.py +++ b/python_coderunner/tests/unit/config/test_validator.py @@ -1,15 +1,19 @@ from contextlib import nullcontext as does_not_raise +from typing import Any, ContextManager import pytest -from src.config_manager import ( - EDispatchersTypes, - TBasicConfigValidator, +from src.config import EDispatchersTypes +from src.config.validator import ( + TBoolValidator, + TDispatchersOrderValidator, + TDispatchersValidator, + TStrValidator, ValidationError, ) -class TestConfigValidator: +class TestBoolValidator: @pytest.mark.parametrize( ("content", "expected", "expectation"), ( @@ -25,16 +29,18 @@ class TestConfigValidator: ) def test_validate_bool( self, - fixture_config_validator: TBasicConfigValidator, - content, - expected, - expectation, - ): + content: Any, + expected: Any, + expectation: ContextManager[Any], + ) -> None: + validator = TBoolValidator() with expectation: - result = fixture_config_validator.validate_bool(content) - if expected is not None: - assert result == expected + result = validator(content) + if expected is not None: + assert result == expected + +class TestStrValidator: @pytest.mark.parametrize( ("content", "expected", "expectation"), ( @@ -50,16 +56,19 @@ def test_validate_bool( ) def test_validate_str( self, - fixture_config_validator: TBasicConfigValidator, - content, - expected, - expectation, - ): + content: Any, + expected: Any, + expectation: ContextManager[Any], + ) -> None: + """Test string validation with various inputs.""" + validator = TStrValidator() with expectation: - result = fixture_config_validator.validate_str(content) + result = validator(content) if expected is not None: assert result == expected + +class TestDispatchersValidator: @pytest.mark.parametrize( ("content", "expected", "expectation"), ( @@ -78,16 +87,18 @@ def test_validate_str( ) def test_validate_dispatcher( self, - fixture_config_validator: TBasicConfigValidator, - content, - expected, - expectation, - ): + content: Any, + expected: Any, + expectation: ContextManager[Any], + ) -> None: + validator = TDispatchersValidator() with expectation: - result = fixture_config_validator.validate_dispatcher(content) + result = validator(content) if expected is not None: assert result == expected + +class TestDispatchersOrderValidator: @pytest.mark.parametrize( ("content", "expected", "expectation"), ( @@ -104,12 +115,12 @@ def test_validate_dispatcher( ) def test_validate_dispatchers_order( self, - fixture_config_validator: TBasicConfigValidator, - content, - expected, - expectation, - ): + content: Any, + expected: Any, + expectation: ContextManager[Any], + ) -> None: + validator = TDispatchersOrderValidator() with expectation: - result: list[EDispatchersTypes] = fixture_config_validator.validate_dispatchers_order(content) - if expected is not None: - assert result == expected + result = validator(content) + if expected is not None: + assert result == expected diff --git a/python_coderunner/tests/unit/conftest.py b/python_coderunner/tests/unit/conftest.py index 2a50973..70a6685 100644 --- a/python_coderunner/tests/unit/conftest.py +++ b/python_coderunner/tests/unit/conftest.py @@ -4,6 +4,7 @@ import tempfile import unittest from typing import Dict, Generator, Tuple +from unittest import mock from unittest.mock import MagicMock import pytest @@ -17,13 +18,27 @@ TGlobCommandBuildersDispatcher, TShebangCommandBuildersDispatcher, ) -from src.config_manager import ( - IConfigGetter, - TBasicConfigManager, - TBasicConfigValidator, - TVimConfigGetter, - TVimConfigManager, +from src.config import ( + EDispatchersTypes, + IConfig, + TConfigField, + TVimConfig, ) +from src.config.getter import ( + IConfigValueGetter, + TVimByFileExtConfigValueGetter, + TVimByFileTypeConfigValueGetter, + TVimByGlobConfigValueGetter, + TVimCoderunnerTempfilePrefixConfigValueGetter, + TVimDispatchersOrderConfigValueGetter, + TVimExecutorConfigValueGetter, + TVimIgnoreSelectionConfigValueGetter, + TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter, + TVimRespectShebangConfigValueGetter, + TVimSaveAllFilesBeforeRunConfigValueGetter, + TVimSaveFileBeforeRunConfigValueGetter, +) +from src.config.validator import TBoolValidator, TDispatchersOrderValidator, TDispatchersValidator, TStrValidator from src.file_info_extractor import ( IFileInfoExtractor, TVimFileInfoExtractor, @@ -34,36 +49,81 @@ ) -@pytest.fixture(params=(lazy_fixture("fixture_vim_config_manager"),)) -def fixture_config_manager(request: pytest.FixtureRequest) -> TBasicConfigManager: - return request.param - - -@pytest.fixture -def fixture_vim_config_manager( - fixture_config_getter: IConfigGetter, fixture_config_validator: TBasicConfigValidator -) -> TBasicConfigManager: - return TVimConfigManager(fixture_config_getter, fixture_config_validator) - - -@pytest.fixture(params=(lazy_fixture("fixture_vim_config_getter"),)) -def fixture_config_getter(request: pytest.FixtureRequest) -> IConfigGetter: +@pytest.fixture(params=(lazy_fixture("fixture_vim_config"),)) +def fixture_config(request: pytest.FixtureRequest) -> IConfig: return request.param @pytest.fixture -def fixture_vim_config_getter() -> IConfigGetter: - return TVimConfigGetter() - - -@pytest.fixture(params=(lazy_fixture("fixture_basic_config_validator"),)) -def fixture_config_validator(request: pytest.FixtureRequest) -> TBasicConfigValidator: - return request.param - - -@pytest.fixture -def fixture_basic_config_validator() -> TBasicConfigValidator: - return TBasicConfigValidator() +def fixture_vim_config() -> IConfig: + return TVimConfig( + by_file_ext_field=TConfigField( + name="g:coderunner_by_file_ext", + getter=TVimByFileExtConfigValueGetter(), + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + by_file_type_field=TConfigField( + name="g:coderunner_by_file_type", + getter=TVimByFileTypeConfigValueGetter(), + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + by_glob_field=TConfigField( + name="g:coderunner_by_glob", + getter=TVimByGlobConfigValueGetter(), + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + dispatchers_order_field=TConfigField( + name="g:coderunner_runners_order", + getter=TVimDispatchersOrderConfigValueGetter(), + validator=TDispatchersOrderValidator(), + allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), + ), + coderunner_tempfile_prefix_field=TConfigField( + name="g:coderunner_tempfile_prefix", + getter=TVimCoderunnerTempfilePrefixConfigValueGetter(), + validator=TStrValidator(), + allowed_values_description="str value", + ), + executor_field=TConfigField( + name="g:coderunner_executor", + getter=TVimExecutorConfigValueGetter(), + validator=TStrValidator(), + allowed_values_description="str value", + ), + ignore_selection_field=TConfigField( + name="g:coderunner_ignore_selection", + getter=TVimIgnoreSelectionConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + respect_shebang_field=TConfigField( + name="g:coderunner_respect_shebang", + getter=TVimRespectShebangConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + remove_coderunner_tempfiles_on_exit_field=TConfigField( + name="g:coderunner_remove_coderunner_tempfiles_on_exit", + getter=TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + save_all_files_before_run_field=TConfigField( + name="g:coderunner_save_all_files_before_run", + getter=TVimSaveAllFilesBeforeRunConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + save_file_before_run_field=TConfigField( + name="g:coderunner_save_file_before_run", + getter=TVimSaveFileBeforeRunConfigValueGetter(), + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + ) @pytest.fixture @@ -141,7 +201,7 @@ def fixture_vim_project_info_extractor( ) -> Generator[IProjectInfoExtractor, None, None]: with tempfile.TemporaryDirectory() as temp_dir: extractor: TVimProjectInfoExtractor = TVimProjectInfoExtractor(fixture_file_info_extractor) - with unittest.mock.patch.object( + with mock.patch.object( extractor, "get_workspace_root", return_value=temp_dir, @@ -164,7 +224,7 @@ def fixture_vim_file_info_extractor() -> Generator[IFileInfoExtractor, None, Non ".ts": "typescript", ".js": "javascript", } - with unittest.mock.patch.object( + with mock.patch.object( extractor, "get_file_type", side_effect=lambda file_path_abs: ext_to_lang.get(extractor.get_file_ext(file_path_abs)), diff --git a/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py b/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py index dfcf3f8..6f6262b 100644 --- a/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py +++ b/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py @@ -1,5 +1,6 @@ import os import tempfile +from typing import Optional from unittest.mock import patch import pytest @@ -8,7 +9,7 @@ class TestFileInfoExtractorInterface: - def test_get_dir(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_dir(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_dir("/home/user/project/file.py") == "/home/user/project/" assert fixture_file_info_extractor.get_dir("/home/file.txt") == "/home/" assert fixture_file_info_extractor.get_dir("/home/") == "/home/" @@ -16,42 +17,42 @@ def test_get_dir(self, fixture_file_info_extractor: IFileInfoExtractor): assert fixture_file_info_extractor.get_dir("/file.txt") == "/" assert fixture_file_info_extractor.get_dir("/") == "/" - def test_get_dir_without_trailing_slash(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_dir_without_trailing_slash(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_dir_without_trailing_slash("/file.py") == "" assert ( fixture_file_info_extractor.get_dir_without_trailing_slash("/home/user/project/file.py") == "/home/user/project" ) - def test_get_file_name(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_file_name(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_file_name("/home/user/project/file.py") == "file.py" - def test_get_file_name_without_ext(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_file_name_without_ext(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_file_name_without_ext("/home/user/project/file") == "file" assert fixture_file_info_extractor.get_file_name_without_ext("/home/user/project/file.py") == "file" assert fixture_file_info_extractor.get_file_name_without_ext("/home/user/project/file.tar.gz") == "file.tar" assert fixture_file_info_extractor.get_file_name_without_ext("/home/user/project/.zig.zon") == ".zig" - def test_get_file_ext(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_file_ext(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_file_ext("/home/user/project/.zig.zon") == ".zon" assert fixture_file_info_extractor.get_file_ext("/home/user/project/file") == "" assert fixture_file_info_extractor.get_file_ext("/home/user/project/file.py") == ".py" assert fixture_file_info_extractor.get_file_ext("/home/user/project/file.cPp") == ".cpp" - def test_get_file_type(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_file_type(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_file_type("/home/user/project/file.py") == "python" assert fixture_file_info_extractor.get_file_type("/home/user/project/file.rs") == "rust" assert fixture_file_info_extractor.get_file_type("/home/user/project/file.cpp") == "cpp" assert fixture_file_info_extractor.get_file_type("/home/user/project/file.unknownext") is None assert fixture_file_info_extractor.get_file_type("/home/user/project/file.PY") == "python" - def test_get_drive_letter_windows(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_drive_letter_windows(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: # On Unix, drive will be '', on Windows, 'C:' path = "C:\\Users\\user\\file.py" expected = os.path.splitdrive(path)[0] assert fixture_file_info_extractor.get_drive_letter(path) == expected - def test_get_drive_letter_unix(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_drive_letter_unix(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: path = "/home/user/file.py" assert fixture_file_info_extractor.get_drive_letter(path) == "" @@ -71,23 +72,25 @@ def test_get_drive_letter_unix(self, fixture_file_info_extractor: IFileInfoExtra pytest.param("#!/bin/bash -x\ndef f():\n\treturn0", "/bin/bash -x", id="shebang with multiple lines"), ], ) - def test_get_shebang(self, fixture_file_info_extractor: IFileInfoExtractor, content, expected): + def test_get_shebang( + self, fixture_file_info_extractor: IFileInfoExtractor, content: Optional[str], expected: Optional[str] + ) -> None: if content is None: assert fixture_file_info_extractor.get_shebang("/bad/path") is expected return with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file: temp_file.write(content) - file_path = temp_file.name + file_path: str = temp_file.name try: assert fixture_file_info_extractor.get_shebang(file_path) == expected finally: os.unlink(file_path) - def test_get_shebang_io_error_handling(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_shebang_io_error_handling(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: with patch("builtins.open", side_effect=IOError("Permission denied")): assert fixture_file_info_extractor.get_shebang("/some/file") is None - def test_get_shebang_unicode_decode_error_handling(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_shebang_unicode_decode_error_handling(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: with patch("builtins.open", side_effect=UnicodeDecodeError("utf-8", b"", 0, 1, "Invalid byte")): assert fixture_file_info_extractor.get_shebang("/some/file") is None diff --git a/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py b/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py index db6b954..73b0b41 100644 --- a/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py +++ b/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py @@ -5,10 +5,10 @@ class TestProjectInfoExtractorInterface: - def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IProjectInfoExtractor): - workspace_root = Path(fixture_project_info_extractor.get_workspace_root()) + def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IProjectInfoExtractor) -> None: + workspace_root: Path = Path(fixture_project_info_extractor.get_workspace_root()) - test_files = ( + test_files: tuple[Path, ...] = ( workspace_root / "file_0.py", workspace_root / "file_0.txt", workspace_root / "file_1.py", @@ -21,7 +21,7 @@ def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IPro file_path.touch(exist_ok=True) exts: Set[str] = {".py", ".md"} - result = {Path(p) for p in fixture_project_info_extractor.get_all_files_filter_by_exts(exts)} + result: Set[Path] = {Path(p) for p in fixture_project_info_extractor.get_all_files_filter_by_exts(exts)} assert result == { workspace_root / "file_0.py", workspace_root / "file_1.py", @@ -29,10 +29,10 @@ def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IPro workspace_root / "dir_0" / "py.py", } - def test_get_all_files_filter_by_file_type(self, fixture_project_info_extractor: IProjectInfoExtractor): - workspace_root = Path(fixture_project_info_extractor.get_workspace_root()) + def test_get_all_files_filter_by_file_type(self, fixture_project_info_extractor: IProjectInfoExtractor) -> None: + workspace_root: Path = Path(fixture_project_info_extractor.get_workspace_root()) - test_files = ( + test_files: tuple[Path, ...] = ( workspace_root / "file_0.py", workspace_root / "file_0.cpp", workspace_root / "file_0.txt", @@ -46,7 +46,9 @@ def test_get_all_files_filter_by_file_type(self, fixture_project_info_extractor: file_path.touch(exist_ok=True) file_types: Set[str] = {"python", "cpp", "cobol"} - result = {Path(p) for p in fixture_project_info_extractor.get_all_files_filter_by_file_type(file_types)} + result: Set[Path] = { + Path(p) for p in fixture_project_info_extractor.get_all_files_filter_by_file_type(file_types) + } assert result == { workspace_root / "file_0.py", workspace_root / "file_0.cpp",