From 60117bf0b1b0a4b959e29c180572aca8212b8a33 Mon Sep 17 00:00:00 2001 From: Rob <5183487+Rexeh@users.noreply.github.com> Date: Sat, 30 Dec 2023 16:42:30 +0000 Subject: [PATCH] DCS World switched to ProfileCollection, initial working ProfileCollection model --- .github/FUNDING.yml | 12 -- .gitignore | 6 +- docs/{development => }/plugins.md | 6 +- docs/{index.md => setup.md} | 2 +- joystick_diagrams/adaptors/dcs/dcs_world.py | 49 ++++-- joystick_diagrams/classes/export.py | 2 +- joystick_diagrams/classes/input.py | 153 ------------------ joystick_diagrams/config.py | 3 - joystick_diagrams/input/__init__.py | 0 joystick_diagrams/input/device.py | 52 ++++++ joystick_diagrams/input/input.py | 56 +++++++ joystick_diagrams/input/profile.py | 27 ++++ joystick_diagrams/input/profile_collection.py | 44 +++++ pyproject.toml | 10 +- readme.md | 5 +- settings.json | 5 +- tests/input/test_input.py | 17 +- 17 files changed, 245 insertions(+), 204 deletions(-) delete mode 100644 .github/FUNDING.yml rename docs/{development => }/plugins.md (93%) rename docs/{index.md => setup.md} (96%) delete mode 100644 joystick_diagrams/classes/input.py create mode 100644 joystick_diagrams/input/__init__.py create mode 100644 joystick_diagrams/input/device.py create mode 100644 joystick_diagrams/input/input.py create mode 100644 joystick_diagrams/input/profile.py create mode 100644 joystick_diagrams/input/profile_collection.py diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 22e3a79..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: rexehuk -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WLLDYGQM5Z39W&source=url'] diff --git a/.gitignore b/.gitignore index a8d2462..26e93eb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,6 @@ poetry.lock tests/__pycache__ tests/*/__pycache__/ -# Folders -*__pycache__/ - # Folders *__pycache__/ /build/ @@ -26,6 +23,9 @@ temp/ /logs/* /src/joystick_diagrams/logs* *.egg-info/ +htmlcov +.mypy* +.pytest* # IDE *.code-workspace diff --git a/docs/development/plugins.md b/docs/plugins.md similarity index 93% rename from docs/development/plugins.md rename to docs/plugins.md index 864269e..32fe987 100644 --- a/docs/development/plugins.md +++ b/docs/plugins.md @@ -35,9 +35,9 @@ class ParserPlugin(PluginInterface): self.path = None self.settings = settings - def process(self) -> InputCollection(): - ## Parse things into an InputCollection() - ## Return + def process(self) -> ProfileCollection(): + ## Parse things into an ProfileCollection() + ## Return ProfileCollection() return None def set_path(self, path: Path) -> bool: diff --git a/docs/index.md b/docs/setup.md similarity index 96% rename from docs/index.md rename to docs/setup.md index 72b4a39..e0e07a7 100644 --- a/docs/index.md +++ b/docs/setup.md @@ -25,4 +25,4 @@ This will build the standalone binary package, as well as the installer package ## Developer Documentation -- Plugins [(LINK)](./development/plugins.md) \ No newline at end of file +- Plugins [(LINK)](./plugins.md) \ No newline at end of file diff --git a/joystick_diagrams/adaptors/dcs/dcs_world.py b/joystick_diagrams/adaptors/dcs/dcs_world.py index 1f1baa9..aa140ab 100644 --- a/joystick_diagrams/adaptors/dcs/dcs_world.py +++ b/joystick_diagrams/adaptors/dcs/dcs_world.py @@ -9,6 +9,8 @@ import joystick_diagrams.adaptors.dcs.dcs_world_lex # pylint: disable=unused-import import joystick_diagrams.adaptors.dcs.dcs_world_yacc # pylint: disable=unused-import import joystick_diagrams.adaptors.joystick_diagram_interface as jdi +from joystick_diagrams.input.device import Device_ +from joystick_diagrams.input.profile_collection import ProfileCollection _logger = logging.getLogger(__name__) @@ -99,7 +101,7 @@ def convert_button_format(self, button) -> str: _logger.warning(f"Button format not found for {split}") return f"{button}" - def process_profiles(self, profile_list: list | None = None) -> dict: + def process_profiles(self, profile_list: list | None = None) -> ProfileCollection: if isinstance(profile_list, list) and len(profile_list) > 0: self.profiles_to_process = profile_list else: @@ -109,20 +111,24 @@ def process_profiles(self, profile_list: list | None = None) -> dict: len(self.profiles_to_process) != 0 ), "DCS: There are no valid profiles to process" ## Replace with exception type + collection = ProfileCollection() + for profile in self.profiles_to_process: + prof = collection.create_profile(profile_name=profile) self.fq_path = os.path.join(self.path, "Config", "Input", profile, "joystick") self.profile_devices = os.listdir(os.path.join(self.fq_path)) self.joystick_listing = {} for item in self.profile_devices: - self.joystick_listing.update({item[:-48]: item}) - for joystick_device, joystick_file in self.joystick_listing.items(): - if os.path.isdir(os.path.join(self.fq_path, joystick_file)): + guid, name = item[-46:-11], item[:-48] + active_profile = prof.add_device(guid, name) + + if os.path.isdir(os.path.join(self.fq_path, item)): _logger.info("Skipping as Folder") else: try: - _logger.debug(f"Obtaining file data for {joystick_file}") + _logger.debug(f"Obtaining file data for {item}") file_data = ( - Path(os.path.join(self.fq_path, joystick_file)) + Path(os.path.join(self.fq_path, item)) .read_text(encoding="utf-8") .replace("local diff = ", "") .replace("return diff", "") @@ -130,7 +136,7 @@ def process_profiles(self, profile_list: list | None = None) -> dict: except FileNotFoundError as err: _logger.error( - f"DCS: File {joystick_file} no longer found - \ + f"DCS: File {item} no longer found - \ It has been moved/deleted from directory. {err}" ) raise @@ -141,10 +147,29 @@ def process_profiles(self, profile_list: list | None = None) -> dict: if parsed_config is None: break + self.assign_to_inputs(parsed_config, active_profile) + button_map = self.create_joystick_map(parsed_config) - self.update_joystick_dictionary(joystick_device, profile, False, button_map) - return self.joystick_dictionary + #self.update_joystick_dictionary(name, profile, False, button_map) + + return collection + # return self.joystick_dictionary + + def assign_to_inputs(self, config: dict, profile: Device_): + if "keyDiffs" in config.keys(): + for data in config["keyDiffs"].values(): + operation = data["name"] + if data.get("added"): + for binding in data["added"].values(): + profile.create_input(binding["key"], operation) + + if binding.get("reformers"): + reform_set = self.reformers_to_set(binding.get("reformers")) + profile.add_modifier_to_input(binding["key"], reform_set, operation) + + def reformers_to_set(self, reformers: dict) -> set: + return {x for x in reformers.values()} def parse_config(self, file: str): try: @@ -288,3 +313,9 @@ def p_error(t): # pylint: disable=invalid-name _logger.error(error) raise return data + + +if __name__ == "__main__": + instance = DCSWorldParser("C:\\Users\\RCox\\Saved Games\\DCS.openbeta") + data = instance.process_profiles() + print(data) diff --git a/joystick_diagrams/classes/export.py b/joystick_diagrams/classes/export.py index 07d0fd3..a6e0ace 100644 --- a/joystick_diagrams/classes/export.py +++ b/joystick_diagrams/classes/export.py @@ -21,7 +21,7 @@ def __init__(self, joystick_listing, parser_id="UNKNOWN"): # pylint disable=too self.file_name_divider = "_" self.joystick_listing = joystick_listing self.export_progress = None - self.no_bind_text = settings.noBindText | "" + self.no_bind_text = settings.noBindText self.executor = parser_id self.error_bucket = [] diff --git a/joystick_diagrams/classes/input.py b/joystick_diagrams/classes/input.py deleted file mode 100644 index 6acc4f5..0000000 --- a/joystick_diagrams/classes/input.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Joystick Diagrams Input Module - -This module contains access to the creation and tracking of devices and their inputs - -This module is designed to be used by Plugins, to share devices and inputs between them. - -This module operates on FIFO basis, and is designed to be used by a single thread. Only one instance of a device can exist at a time. -""" - -from dataclasses import dataclass -from enum import auto, Enum -import logging - -_logger = logging.getLogger(__name__) - - -@dataclass -class Command: - name: str - - -@dataclass -class Modifier: - modifiers: set - command: Command - - -class InputTypes(Enum): - BUTTON = auto() - AXIS = auto() - - -class Input: - # Rework input - # Better naming of the entities - # Change input to non conflciting class - - def __init__(self, identifier: str, style: InputTypes, command: Command, modifiers: list[Modifier] = []) -> None: - self.identifier = identifier - self.style = style - self.command = command - self.modifiers = modifiers - - def add_modifier(self, modifier: set, command: Command): - """ - Adds a modifier to an existing input, or amends an existing modifier - """ - existing = self._check_existing_modifier(modifier) - - _logger.info(f"Existing modifier check is {existing}") - - if existing is None: - _logger.info(f"Modifier {modifier} for input {self.identifier} not found so adding") - self.modifiers.append(Modifier(modifier, command)) - else: - _logger.info(f"Modifier {modifier} already exists for {self.identifier} and command has been overidden") - existing.command = command - - def _check_existing_modifier(self, modifier: set) -> Modifier | None: - _logger.debug(f"Checking for existing modifier {modifier}") - _logger.debug(f"Existing modifiers: {self.modifiers}") - - for x in self.modifiers: - if x.modifiers == modifier: - return x - return None - - -class LogicalDevice: - def __init__(self, guid: str, name: str): - self.guid = guid.strip().lower() - self.name = name.strip().lower() - self.inputs: list[Input] = [] - - def create_input(self, input_id: str, style: InputTypes, command: Command) -> None: - existing = self.__check_existing_input(input_id, style) - if not existing: - self.inputs.append(Input(input_id, style, command)) - else: - _logger.debug(f"Input {input_id} already exists and will be overwritten") - self.inputs[self.inputs.index(existing)].command = command - - def _get_input(self, input_id: str) -> Input | None: - """ - Returns a specific input from stored inputs - """ - for x in self.inputs: - if x.identifier == input_id: - return x - return None - - def get_device_inputs(self) -> list[Input]: - return self.inputs - - def add_modifier_to_input(self, input_id, modifier: set, command: Command) -> None: - """ - Adds a modifier to an input if it exists - """ - - _logger.debug(f"Adding modifier {modifier} to input {input_id}") - obj = self._get_input(input_id) - _logger.debug(f"Modifier input is: {obj}") - if obj is None: - _logger.warning(f"Modifier added to {input_id} but input does not exist") - else: - obj.add_modifier(modifier, command) - - def __check_existing_input(self, input_id: str, style: InputTypes) -> Input | None: - """ - One instance of an input can exist, based on the input ID and style - - Returns Input or None - """ - for x in self.inputs: - if x.identifier == input_id and x.style == style: - return x - return None - - -def clear_devices(): - """ - Clears all devices and inputs from the system. - """ - _devices.clear() - - -def get_device(guid: str) -> LogicalDevice: - return _devices[guid] - - -def get_all_devices() -> dict[str, LogicalDevice]: - return _devices - - -def add_device(guid: str, name: str) -> LogicalDevice: - if guid not in _devices: - _devices.update({guid: LogicalDevice(guid, name)}) - else: - _logger.warning(f"Device {guid} already exists and will not be re-added") - - return _devices[guid] - - -def add_input_modifier(guid: str, input_id: str, modifier: set, command: Command) -> None: - get_device(guid).add_modifier_to_input(input_id, modifier, command) - - -def add_inputs(guid: str, **kwargs) -> None: - _devices[guid].create_input(**kwargs) - - -_devices: dict[str, LogicalDevice] = {} diff --git a/joystick_diagrams/config.py b/joystick_diagrams/config.py index 8147f41..75fdd58 100644 --- a/joystick_diagrams/config.py +++ b/joystick_diagrams/config.py @@ -3,6 +3,3 @@ settings = Dynaconf( settings_files=["settings.json"], ) - -# `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. -# `settings_files` = Load these files in the order. diff --git a/joystick_diagrams/input/__init__.py b/joystick_diagrams/input/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/joystick_diagrams/input/device.py b/joystick_diagrams/input/device.py new file mode 100644 index 0000000..295a929 --- /dev/null +++ b/joystick_diagrams/input/device.py @@ -0,0 +1,52 @@ +import logging + +from joystick_diagrams.input.input import Input_ + +_logger = logging.getLogger("__name__") + + +class Device_: + def __init__(self, guid: str, name: str): + self.guid = guid.strip().lower() + self.name = name.strip().lower() + self.inputs: dict[str, Input_] = {} + + def create_input(self, input_id: str, command: str) -> None: + input = self.inputs.get(input_id) + + if input: + input.command = command + _logger.debug(f"Input {input_id} already exists and will be overwritten") + else: + self.inputs[input_id] = Input_(input_id, command) + + def add_modifier_to_input(self, input_id, modifier: set, command: str) -> None: + """ + Adds a modifier to an input if it exists + """ + + _logger.debug(f"Adding modifier {modifier} to input {input_id}") + input = self.inputs.get(input_id) + + if input is None: + _logger.warning(f"Modifier attempted to be added to {input_id} but input does not exist") + else: + input.add_modifier(modifier, command) + + +if __name__ == "__main__": + dev = Device_("guid1", "Potato") + + dev.create_input("button_1", "shoot") + + print(dev.inputs) + + dev.add_modifier_to_input("button_1", {"ctrl", "alt"}, "pickup") + dev.add_modifier_to_input("button_2", {"ctrl", "alt"}, "pickup") + dev.add_modifier_to_input("button_1", {"alt", "ctrl"}, "pickup") + dev.add_modifier_to_input("button_1", {"spacebar", "ctrl"}, "dunk") + + for i in dev.inputs.values(): + print(i.identifier) + print(i.command) + print(i.modifiers) diff --git a/joystick_diagrams/input/input.py b/joystick_diagrams/input/input.py new file mode 100644 index 0000000..e46e8b3 --- /dev/null +++ b/joystick_diagrams/input/input.py @@ -0,0 +1,56 @@ +""" +Joystick Diagrams Input Module + +This module contains access to the creation and tracking of devices and their inputs + +This module is designed to be used by Plugins, to share devices and inputs between them. + +This module operates on FIFO basis, and is designed to be used by a single thread. Only one instance of a device can exist at a time. +""" + +import logging +from dataclasses import dataclass +from enum import Enum, auto + +_logger = logging.getLogger(__name__) + + +@dataclass +class Modifier: + modifiers: set[str] + command: str + + +class Input_: + def __init__(self, identifier: str, command: str) -> None: + self.identifier = identifier.lower() + self.command = command.lower() + self.modifiers: list[Modifier] = [] + + def add_modifier(self, modifier: set, command: str): + """ + Adds a modifier to an existing input, or amends an existing modifier + """ + existing = self._check_existing_modifier(modifier) + + _logger.info(f"Existing modifier check is {existing}") + + if existing is None: + _logger.info(f"Modifier {modifier} for input {self.identifier} not found so adding") + self.modifiers.append(Modifier(modifier, command)) + else: + _logger.info(f"Modifier {modifier} already exists for {self.identifier} and command has been overidden") + existing.command = command + + def _check_existing_modifier(self, modifier: set) -> Modifier | None: + _logger.debug(f"Existing modifiers: {self.modifiers}") + for x in self.modifiers: + _logger.debug(f"Checking for existing modifier {modifier} in {x}") + if x.modifiers == modifier: + _logger.debug(f"Modifier already exists {x}") + return x + return None + + +if __name__ == "__main__": + pass diff --git a/joystick_diagrams/input/profile.py b/joystick_diagrams/input/profile.py new file mode 100644 index 0000000..a99f454 --- /dev/null +++ b/joystick_diagrams/input/profile.py @@ -0,0 +1,27 @@ +import logging +from typing import Optional + +from joystick_diagrams.input.device import Device_ + +_logger = logging.getLogger("__name__") + + +class Profile_: + def __init__(self, profile_name: str): + self.name: str = profile_name + self.devices: dict[str, Device_] = {} + + def __repr__(self) -> str: + return f"(Profile Object: {self.name})" + + def add_device(self, guid: str, name: str) -> Device_: + if self.get_device(guid) is None: + self.devices.update({guid: Device_(guid, name)}) + + else: + _logger.warning(f"Device {guid} already exists and will not be re-added") + + return self.get_device(guid) # type: ignore + + def get_device(self, guid: str) -> Device_ | None: + return self.devices.get(guid) diff --git a/joystick_diagrams/input/profile_collection.py b/joystick_diagrams/input/profile_collection.py new file mode 100644 index 0000000..be154ae --- /dev/null +++ b/joystick_diagrams/input/profile_collection.py @@ -0,0 +1,44 @@ +import logging + +from joystick_diagrams.input.profile import Profile_ + +_logger = logging.getLogger("__name__") + + +class ProfileCollection: + """Contains profiles for grouping devices and their input functionality""" + + def __init__(self): + self.profiles: dict[str, Profile_] = {} + + def create_profile(self, profile_name: str) -> Profile_: + profile_name = profile_name.lower() + + if self.get_profile(profile_name) is None: + self.profiles[profile_name] = Profile_(profile_name) + + return self.profiles.get(profile_name) # type: ignore + + def get_profile(self, profile_name) -> Profile_ | None: + return self.profiles.get(profile_name.lower()) + + +# Profile Collection +# -> Profile +# -> Device +# -> Input +# -> Command +# -> Modifiers +# -> Command + +if __name__ == "__main__": + collection = ProfileCollection() + + inst1 = collection.create_profile("test") + inst2 = collection.create_profile("abc") + + dev1 = inst1.add_device("guid1", "joystick_1") + dev2 = inst1.add_device("guid2", "joystick_2") + + if dev1 is not None: + print(dev1.inputs) diff --git a/pyproject.toml b/pyproject.toml index 6658eb0..d9992ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,9 @@ name = "joystick_diagrams" description = "" authors = ["Robert Cox"] version = "2.0" +homepage = "https://www.joystick-diagrams.com" +readme = "readme.md" +repository = "https://github.com/Rexeh/joystick-diagrams" [tool.poetry.dependencies] python = "^3.11" @@ -93,8 +96,7 @@ exclude = ''' | build | dist )/ - | foo.py # also separately exclude a file named foo.py in - # the root of the project + # the root of the project ) ''' @@ -112,4 +114,6 @@ output-format = "colorized" [tool.mypy] warn_return_any = false -warn_unused_configs = true \ No newline at end of file +warn_unused_configs = true +warn_annotation_unchecked = false +show_error_codes = true \ No newline at end of file diff --git a/readme.md b/readme.md index cae9b10..730f108 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,5 @@ -# Joystick Diagrams - Visualise your binds ![Discord](https://img.shields.io/discord/733253732355276800?label=Discord) +# Joystick Diagrams - Visualise your binds ![Discord](https://img.shields.io/discord/733253732355276800?label=Discord) [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/) + ![Joystick Diagrams](https://joystick-diagrams.com/img/main-hero.png) @@ -11,7 +12,7 @@ Are you tired of making your own PDF/Powerpoint diagrams of what you've configur # Development The project currently undergoing a v2 rework, allowing new features to be added easier. The latest stable release is 1.4.2. -For project setup and documentation see [Development](./docs/index.md) +For project setup and documentation see [Development](./docs/setup.md) # Support diff --git a/settings.json b/settings.json index 1fa4576..f3f9888 100644 --- a/settings.json +++ b/settings.json @@ -1 +1,4 @@ -{"username": "poo", "logLevel": "INFO"} \ No newline at end of file +{ + "noBindText": "No Bind", + "logLevel": "INFO" +} \ No newline at end of file diff --git a/tests/input/test_input.py b/tests/input/test_input.py index f1c8900..69fea75 100644 --- a/tests/input/test_input.py +++ b/tests/input/test_input.py @@ -3,17 +3,8 @@ # Check a device can have an device_input registered # Check a device can have an device_input overwritten import logging -from joystick_diagrams.classes.input import ( - add_device, - LogicalDevice, - InputTypes, - Command, - add_inputs, - add_input_modifier, - clear_devices, - get_all_devices, - get_device, -) + +from joystick_diagrams.input.device import Device_ LOGGER = logging.getLogger(__name__) @@ -125,13 +116,13 @@ def test_add_modify_existing_modifier(): device_input = next((x for x in device_inputs if x.identifier == input_data["id"]), None) assert device_input, None assert device_input.style, input_data["style"] - assert device_input.command.name, input_data["command"].name + assert device_input.command, input_data["command"] # Check that each device_input has the correct modifiers for modifier_data in modifiers: modifier = next((x for x in device_input.modifiers if x.modifiers == modifier_data["device_input"]), None) assert modifier, None - assert modifier.command.name, modifier_data["command"].name + assert modifier.command, modifier_data["command"] # Change a modifier device = get_device(guid)