from __future__ import unicode_literals
import sys
from collections import OrderedDict, namedtuple
try:
from configparser import ConfigParser # Python 3
except ImportError:
from ConfigParser import ConfigParser # Python 2
try:
from builtins import input as raw_input # Python 3
except ImportError:
from __builtin__ import raw_input # Python 2
Action = namedtuple("Action", "name do")
class Menu:
def __init__(self, title, actions, parent=None):
self.title = title
self.actions = actions
self.parent = parent
@classmethod
def from_config(cls, game, config, title, parent=None):
if title not in game.menus: # create menu
actions = OrderedDict()
self = game.menus[title] = cls(title, actions, parent)
for opt, value in config.items(title): # Python 3.2+:
# config[title].items()
if value in config.sections(): # submenu
do = cls.from_config(game, config, value, self)
else: # concrete choice
def make_do(action, menu):
return lambda: getattr(game, 'do_' + action)(menu)
do = make_do(value, self)
actions[opt] = Action(value, do)
return game.menus[title]
def __call__(self):
# show menu
print(self.title + ":")
for opt, action in self.actions.items():
print("\t{}: {}".format(opt, action.name))
# take user input
prompt = 'Choose [{}]: '.format(', '.join(self.actions))
action_name = read_choice(choices=self.actions, prompt=prompt)
if not sys.stdin.isatty():
print(action_name) # echo the choice in non-interactive mode
# act on it
return self.actions[action_name].do
class Game:
def __init__(self):
self.menus = {}
self.options = {}
def do_start(self, menu):
print("start game with options: {}".format(self.options))
# ... play game
return menu # return to menu
def do_exit(self, menu):
print("bye")
return None # do nothing (the end)
def __getattr__(self, name): # called for non-existing game attributes
if not name.startswith('do_'):
raise AttributeError(name)
# game.do_something(menu) sets corresponding option to 'something'
def set_option(menu):
self.options[menu.title] = name[len('do_'):]
return menu.parent # return to parent menu
return set_option
def read_choice(choices, prompt):
c = None
while c not in choices:
c = raw_input(prompt)
return c
def main():
# read menu provided in ini-format
config = ConfigParser()
config_lines = []
for line in iter(sys.stdin.readline, ''): # consume config in
# ini-format
# line-by-line to allow
# accepting user input
# later from stdin
# (avoid read-ahead)
if line.rstrip() == '---':
break # end of config (the rest is user input)
config_lines.append(line)
# Python 3.3:
# config.read_string(''.join(config_lines))
# Python < 3.3:
from io import StringIO
config.readfp(StringIO(''.join(config_lines)))
# create game
game = Game()
menu = Menu.from_config(game, config, 'Main menu')
# run it
while menu is not None: # use trampoline to avoid recursion limit
menu = menu()
main()