diff --git a/ammo/mod_controller.py b/ammo/mod_controller.py index e36c379..cdfeac2 100755 --- a/ammo/mod_controller.py +++ b/ammo/mod_controller.py @@ -19,14 +19,20 @@ class ModController(Controller): - def __init__(self, downloads_dir: Path, game: Game, *args, **kwargs): + """ + ModController is responsible for managing mods. It exposes + methods to the UI that allow the user to easily manage mods. + Private methods here are private simply so the UI doesn't + display them. + """ + def __init__(self, downloads_dir: Path, game: Game, *keywords): self.downloads_dir: Path = downloads_dir self.game: Game = game self.changes: bool = False self.downloads: list[Download] = [] self.mods: list[Mod] = [] self.plugins: list[Plugin] = [] - self.keywords = [*args] + self.keywords = [*keywords] # Create required directories for testing. Harmless if exists. Path.mkdir(self.game.ammo_mods_dir, parents=True, exist_ok=True) diff --git a/ammo/ui.py b/ammo/ui.py index 60975db..c36503a 100755 --- a/ammo/ui.py +++ b/ammo/ui.py @@ -20,8 +20,14 @@ class Controller(ABC): methods, as they are consumed by the UI. The UI performs validation and type casting based on type - hinting and inspection, so type hints for public methods - are required. + hinting and doc inspection, so type hints for public methods + are required. Doc strings for public methods should typically + fit on one line. + + A recoverable error from any public methods should be raised + as a Warning(). This will cause the UI to display the warning + text and prompt the user to [Enter] before the next frame + is drawn. """ @abstractmethod def _prompt(self) -> str: @@ -38,6 +44,16 @@ def _post_exec(self) -> bool: """ return False + @abstractmethod + def __str__(self) -> str: + """ + Between every command, the screen will be cleared, and + controller will be printed before the user is presented + with the prompt. This should return a 'frame' of your + interface. + """ + return str(self) + class UI: """ @@ -55,6 +71,20 @@ def __init__(self, controller: Controller): # get a map of commands to functions and the amount of args they expect self.command = {} + # Default 'help', may be overridden. + self.command["help"] = { + "func": self.help, + "args": [], + "doc": str(self.help.__doc__).strip(), + } + + # Default 'exit', may be overridden. + self.command["exit"] = { + "func": self.exit, + "args": [], + "doc": str(self.exit.__doc__).strip(), + } + for name, func in inspect.getmembers( self.controller.__class__, predicate=inspect.isfunction ): @@ -106,17 +136,6 @@ def __init__(self, controller: Controller): "instance": self.controller, } - self.command["help"] = { - "func": self.help, - "args": [], - "doc": str(self.help.__doc__).strip(), - } - - self.command["exit"] = { - "func": self.exit, - "args": [], - "doc": str(self.exit.__doc__).strip(), - } def help(self): """ @@ -143,14 +162,17 @@ def help(self): def exit(self): """ - Quit. Prompts if there are changes. + Quit. """ - if self.controller.changes: - if input("There are unapplied changes. Quit? [y/n]: ").lower() != "y": - return True sys.exit(0) + def cast_to_type(self, arg, target_type): + """ + This method is responsible for casting user input into + the type that the command expects. If target_type is + type[str|int], it may not be cast correctly. + """ if target_type is int: try: return int(arg)