Skip to content

Instantly share code, notes, and snippets.

@TimelessP
Last active November 13, 2021 13:09
Show Gist options
  • Save TimelessP/f776245792ef0e1613a28e0a3b6219e0 to your computer and use it in GitHub Desktop.
Save TimelessP/f776245792ef0e1613a28e0a3b6219e0 to your computer and use it in GitHub Desktop.
minimalmenu2.py is more productive
"""
minimalmenu2.py by https://gist.github.com/TimelessP
(MIT Licence)
The primary requirement for minimalmenu2.py is to make it quick and easy to add new menu items.
Example usage:
```python
from minimalmenu2 import register_menu_action, menu
@register_menu_action("Main Menu/Sub-Menu/Print Hello")
def action_print_hello():
print("hello")
menu()
```
Which would result in the following menu system:
```
Main Menu
---------
1. Sub-Menu
2. Exit
? 1
```
```
Sub Menu
--------
1. Print Hello
2. Exit
?
```
"""
import functools
import os
from dataclasses import dataclass
from typing import Callable, Dict, Optional, List
menu_data: Dict[str, Callable] = {} # {full menu path: action}
def register_menu_action(menu_path):
def decorator_register(func):
menu_data[menu_path] = func
@functools.wraps(func)
def menu_action(*args, **kwargs):
print(f"{menu_path} before {func, args, kwargs}")
result = None
try:
result = func(menu_path, *args, **kwargs)
except KeyboardInterrupt:
print("")
print(f"{menu_path} after {func, args, kwargs} -> {result}")
return result
return menu_action
return decorator_register
@dataclass
class Choice:
title: str
full_menu_path: str
is_action: bool
action: Optional[Callable]
is_exit: bool = False
def __hash__(self):
return hash((self.title, self.full_menu_path, self.is_action, self.action, self.is_exit))
def prepare_choices(path) -> List[Choice]:
choices: List[Choice] = []
for choice_key in menu_data:
if choice_key.startswith(path):
parts = choice_key.split(path, 1)[1].split("/")
title = parts[0]
is_action = False if len(parts) > 1 else True
action = menu_data[choice_key] if is_action else None
choices.append(Choice(title, path + title, is_action, action, False))
choices.append(Choice("Exit", path, False, None, is_exit=True))
# Trick to remove duplicates from the list using a Dict. We don't use a Set because its order changes.
choices = list(dict.fromkeys(choices))
print_choices(path, choices)
return choices
def print_choices(path: str, choices: List[Choice]):
title = path.split("/")[-2]
print(f"{os.linesep}{title}{os.linesep}{'-' * len(title)}")
for index, item in enumerate(choices):
print(f"{index + 1}. {item.title}")
def perform_action(action):
try:
action()
except (KeyboardInterrupt,):
print("")
def menu(path: Optional[str] = None):
while True:
choices = prepare_choices(path if path else tuple(menu_data)[0].split("/")[0] + "/")
try:
choice = int(input("? ")) - 1
if choice >= len(choices) or choice < 0:
print("Invalid choice.")
raise IndexError()
choice_item = list(choices)[choice]
if choice_item.is_exit:
break
elif choice_item.is_action:
perform_action(choice_item.action)
else: # It's a sub-menu.
menu(choice_item.full_menu_path + "/")
except (ValueError, IndexError, EOFError, KeyboardInterrupt) as e:
if isinstance(e, (EOFError, KeyboardInterrupt)):
print("")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment