Skip to content

Commit

Permalink
Dynamically allocate selection commands
Browse files Browse the repository at this point in the history
I'm not going to lie, this is pretty cool. Last week, I made the fomod
selection logic into its own controller so we could re-use the UI. As
a consequence, we were no longer able to use `<index>` to change fomod
selections, and were stuck using the cumbersome `select <index>`.

It's also worth mentioning here that something like this will not be
allowed (obviously):

    def 0():
        # function named after a number?!

Make the fomod controller create a method named after an integer for
each index associated with a possible fomod selection. Executing the
command will toggle the associated fomod selection.

To top it all off, they get populated into the help menu with a
description named after the option they will change. This can get kind
of long depending on the mod, but it keeps things so that the UI doesn't
have to know anything about the controller, and the controller doesn't
have to know anything about the UI.
  • Loading branch information
cyberrumor committed Nov 3, 2023
1 parent 2bc9c61 commit 2c1bc3c
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 14 deletions.
36 changes: 25 additions & 11 deletions ammo/fomod_controller.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import os
import shutil
from functools import partial
from pathlib import Path
from xml.etree import ElementTree
from functools import reduce
Expand Down Expand Up @@ -29,6 +30,7 @@ def __init__(self, mod: Mod):
self.page = self.steps[self.visible_pages[self.page_index]]
self.selection = self.page["type"].lower()
self.do_exit = False
self._populate_index_commands()

def __str__(self) -> str:
num_pages = len(self.visible_pages)
Expand Down Expand Up @@ -69,8 +71,31 @@ def _post_exec(self) -> bool:

self.page = self.steps[self.visible_pages[self.page_index]]
self.selection = self.page["type"].lower()
self._populate_index_commands()
return False


def _populate_index_commands(self):
"""
Hack to get dynamically allocated methods which are
named after numbers, one for each selectable option.
"""
# Remove all attributes that are numbers
for i in list(self.__dict__.keys()):
try:
int(i)
del self.__dict__[i]
except ValueError:
pass
for i in range(len(self.page["plugins"])):
setattr(
self,
str(i),
lambda self, i=i: self._select(i)
)
self.__dict__[str(i)].__doc__ = f"Toggle {self.page['plugins'][i]['name']}"


def _normalize(self, destination: Path, dest_prefix: Path) -> Path:
"""
Prevent folders with the same name but different case
Expand Down Expand Up @@ -387,17 +412,6 @@ def _install_files(self, selected_nodes: list):

self.mod.has_data_dir = True

def select(self, index: int):
"""
Toggle state
"""
if index < 0 or index > len(self.page["plugins"]):
raise Warning(
f"Expected 0 through {len(self.page['plugins']) - 1} (inclusive)"
)

self._select(index)

def b(self):
"""
Return to the previous page
Expand Down
12 changes: 9 additions & 3 deletions ammo/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,14 @@ def populate_commands(self):
if not callable(attribute):
continue

# attribute is a bound method which is transient.
# Get the actual function associated with it.
func = attribute.__func__
if hasattr(attribute, "__func__"):
# attribute is a bound method (which is transient).
# Get the actual function associated with it instead
# of a descriptor.
func = attribute.__func__
else:
# lambdas
func = attribute

signature = inspect.signature(func)
type_hints = typing.get_type_hints(func)
Expand All @@ -111,6 +116,7 @@ def populate_commands(self):
args = []
for param in parameters:
required = False
description = ""
if param.default == param.empty:
# The argument did not have a default value set.

Expand Down

0 comments on commit 2c1bc3c

Please sign in to comment.