exit_commands = ['exit', 'abort', 'quit', 'bye', 'eat flaming death', 'q'] help_commands = ['help', '?'] context_commands = ['what', '??'] local_help_commands = ['help!', '???'] class Menu(): def __init__(self, name, items=[], prompt='> ', return_index=True, exit_msg=None, exit_confirm_msg=None, exit_disallowed_msg=None, help_text=None, uses_db=False): self.name = name self.items = items self.prompt = prompt self.return_index = return_index self.exit_msg = exit_msg self.exit_confirm_msg = exit_confirm_msg self.exit_disallowed_msg = exit_disallowed_msg self.help_text = help_text self.context = None self.header_format = '[%s]' self.uses_db = uses_db def exit_menu(self): if self.exit_disallowed_msg != None: print self.exit_disallowed_msg return if self.exit_confirm_msg != None: if not self.confirm(self.exit_confirm_msg, default=True): return raise ExitMenu() def at_exit(self): if self.exit_msg: print self.exit_msg def set_context(self, string, display=True): self.context = string if self.context != None and display: print self.context def add_to_context(self, string): self.context += string def printc(self, string): print string if self.context == None: self.context = string else: self.context += '\n' + string def show_context(self): print self.header_format % self.name if self.context != None: print self.context def item_is_submenu(self, i): return isinstance(self.items[i], Menu) def item_name(self, i): if self.item_is_submenu(i): return self.items[i].name elif isinstance(self.items[i], tuple): return self.items[i][1] else: return self.items[i] def item_value(self, i): if isinstance(self.items[i], tuple): return self.items[i][0] if self.return_index: return i return self.items[i] def input_str(self, prompt=None, regex=None, length_range=(None,None), empty_string_is_none=False): if regex != None: while True: result = self.input_str(prompt, length_range=length_range, empty_string_is_none=empty_string_is_none) if result == None or re.match(regex+'$', result): return result else: print 'Value must match regular expression "%s"' % regex if length_range != (None,None): while True: result = self.input_str(prompt, empty_string_is_none=empty_string_is_none) if result == None: length = 0 else: length = len(result) if ((length_range[0] and length < length_range[0]) or (length_range[1] and length > length_range[1])): if length_range[0] and length_range[1]: print 'Value must have length in range [%d,%d]' % length_range elif length_range[0]: print 'Value must have length at least %d' % length_range[0] else: print 'Value must have length at most %d' % length_range[1] else: return result if prompt == None: prompt = self.prompt while True: try: result = unicode(raw_input(safe_str(prompt)), conf.input_encoding) except EOFError: print 'quit' self.exit_menu() continue if result in exit_commands: self.exit_menu() continue if result in help_commands: self.general_help() continue if result in local_help_commands: self.local_help() continue if result in context_commands: self.show_context() continue # if result in faq_commands: # FAQMenu().execute() # continue # if result in restart_commands: # if self.confirm('Restart Dibbler?'): # restart() # continue if empty_string_is_none and result == '': return None return result def input_int(self, prompt=None, allowed_range=(None,None)): if prompt == None: prompt = self.prompt while True: result = self.input_str(prompt) try: value = int(result) if ((allowed_range[0] and value < allowed_range[0]) or (allowed_range[1] and value > allowed_range[1])): if allowed_range[0] and allowed_range[1]: print 'Value must be in range [%d,%d]' % allowed_range elif allowed_range[0]: print 'Value must be at least %d' % allowed_range[0] else: print 'Value must be at most %d' % allowed_range[1] else: return value except ValueError: print 'Please enter an integer' # def input_user(self, prompt=None): # user = None # while user == None: # user = self.retrieve_user(self.input_str(prompt)) # return user # def retrieve_user(self, search_str): # return self.search_ui(search_user, search_str, 'user') # def input_product(self, prompt=None): # product = None # while product == None: # product = self.retrieve_product(self.input_str(prompt)) # return product # def retrieve_product(self, search_str): # return self.search_ui(search_product, search_str, 'product') # def input_thing(self, prompt=None, permitted_things=('user','product'), # add_nonexisting=(), empty_input_permitted=False): # result = None # while result == None: # search_str = self.input_str(prompt) # if search_str == '' and empty_input_permitted: # return None # result = self.search_for_thing(search_str, permitted_things, add_nonexisting) # return result # def search_for_thing(self, search_str, permitted_things=('user','product'), # add_nonexisting=()): # search_fun = {'user': search_user, # 'product': search_product} # results = {} # result_values = {} # for thing in permitted_things: # results[thing] = search_fun[thing](search_str, self.session) # result_values[thing] = self.search_result_value(results[thing]) # selected_thing = argmax(result_values) # if results[selected_thing] == []: # thing_for_type = {'card': 'user', 'username': 'user', # 'bar_code': 'product'} # type_guess = guess_data_type(search_str) # if type_guess != None and thing_for_type[type_guess] in add_nonexisting: # return self.search_add(search_str) # print 'No match found for "%s".' % search_str # return None # return self.search_ui2(search_str, results[selected_thing], selected_thing) def search_result_value(self, result): if result == None: return 0 if not isinstance(result, list): return 3 if len(result) == 0: return 0 if len(result) == 1: return 2 return 1 def search_add(self, string): type_guess = guess_data_type(string) if type_guess == 'username': print '"%s" looks like a username, but no such user exists.' % string if self.confirm('Create user %s?' % string): user = User(string, None) self.session.add(user) return user return None if type_guess == 'card': selector = Selector('"%s" looks like a card number, but no user with that card number exists.' % string, [('create', 'Create user with card number %s' % string), ('set', 'Set card number of an existing user to %s' % string)]) selection = selector.execute() if selection == 'create': username = self.input_str('Username for new user (should be same as PVV username)> ', User.name_re, (1,10)) user = User(username, string) self.session.add(user) return user if selection == 'set': user = self.input_user('User to set card number for> ') old_card = user.card user.card = string print 'Card number of %s set to %s (was %s)' % (user.name, string, old_card) return user return None if type_guess == 'bar_code': print '"%s" looks like the bar code for a product, but no such product exists.' % string return None def search_ui(self, search_fun, search_str, thing): result = search_fun(search_str, self.session) return self.search_ui2(search_str, result, thing) def search_ui2(self, search_str, result, thing): if not isinstance(result, list): return result if len(result) == 0: print 'No %ss matching "%s"' % (thing, search_str) return None if len(result) == 1: msg = 'One %s matching "%s": %s. Use this?' %\ (thing, search_str, unicode(result[0])) if self.confirm(msg, default=True): return result[0] return None limit = 9 if len(result) > limit: select_header = '%d %ss matching "%s"; showing first %d' % \ (len(result), thing, search_str, limit) select_items = result[:limit] else: select_header = '%d %ss matching "%s"' % \ (len(result), thing, search_str) select_items = result selector = Selector(select_header, items=select_items, return_index=False) return selector.execute() def confirm(self, prompt, default=None): return ConfirmMenu(prompt, default).execute() def print_header(self): print print self.header_format % self.name def pause(self): self.input_str('.') def general_help(self): print ''' DIBBLER HELP The following commands are recognized (almost) everywhere: help, ? -- display this help what, ?? -- redisplay the current context help!, ??? -- display context-specific help (if any) faq -- display frequently asked questions (with answers) exit, quit, etc. -- exit from the current menu When prompted for a user, you can type (parts of) the user name or card number. When prompted for a product, you can type (parts of) the product name or barcode. About payment and "credit": When paying for something, use either Dibbler or the good old money box -- never both at the same time. Dibbler keeps track of a "credit" for each user, which is the amount of money PVVVV owes the user. This value decreases with the appropriate amount when you register a purchase, and you may increase it by putting money in the box and using the "Adjust credit" menu. ''' def local_help(self): if self.help_text == None: print 'no help here' else: print print 'Help for %s:' % (self.header_format%self.name) print self.help_text def execute(self): self.set_context(None) try: if self.uses_db: self.session = Session() else: self.session = None return self._execute() except ExitMenu: self.at_exit() return None finally: if self.session != None: self.session.close() self.session = None def _execute(self): line_format = '%' + str(len(str(len(self.items)))) + 'd ) %s' while True: self.print_header() self.set_context(None) if len(self.items)==0: self.printc('(empty menu)') self.pause() return None for i in range(len(self.items)): self.printc(line_format % (i+1, self.item_name(i))) item_i = self.input_int(self.prompt, (1,len(self.items)))-1 if self.item_is_submenu(item_i): self.items[item_i].execute() else: return self.item_value(item_i) class Selector(Menu): def __init__(self, name, items=[], prompt='select> ', return_index=True, exit_msg=None, exit_confirm_msg=None, help_text=None): Menu.__init__(self, name, items, prompt, return_index, exit_msg) self.header_format = '%s' def print_header(self): print self.header_format % self.name def local_help(self): if self.help_text == None: print 'This is a selection menu. Enter one of the listed numbers, or' print '\'exit\' to go out and do something else.' else: print print 'Help for selector (%s):' % self.name print self.help_text class ConfirmMenu(Menu): def __init__(self, prompt='confirm?', default=None): Menu.__init__(self, 'question', prompt=prompt, exit_disallowed_msg='Please answer yes or no') self.default=default def _execute(self): options = {True: '[y]/n', False: 'y/[n]', None: 'y/n'}[self.default] while True: result = self.input_str('%s (%s) ' % (self.prompt, options)) result = result.lower() if result in ['y','yes']: return True if result in ['n','no']: return False if self.default != None and result == '': return self.default print 'Please answer yes or no' class MessageMenu(Menu): def __init__(self, name, message, pause_after_message=True): Menu.__init__(self, name) self.message = message.strip() self.pause_after_message = pause_after_message def _execute(self): self.print_header() print print self.message if self.pause_after_message: self.pause()